feature: remove sidebar & redesign top navbar

This commit is contained in:
Jordan Knott 2020-06-23 15:20:53 -05:00
parent fd7c006b73
commit 57382de9d0
78 changed files with 4124 additions and 465 deletions

27
.tmuxinator.yml Normal file
View File

@ -0,0 +1,27 @@
name: citadel
root: .
on_project_start: docker start test-db
windows:
- services:
root: ./
panes:
- api:
- cd api
- go run cmd/citadel/main.go
- yarn:
- cd web
- yarn start
- web/editor:
root: ./web
panes:
- vim src/index.tsx
- api/editor:
root: ./api
panes:
- vim cmd/citadel/main.go
- database:
root: ./api
panes:
- docker container exec -it test-db psql -U postgres citadel

View File

@ -1,10 +1,10 @@
package main
import (
_ "github.com/lib/pq"
"fmt"
_ "github.com/lib/pq"
"net/http"
"time"
"github.com/jmoiron/sqlx"
"github.com/jordanknott/project-citadel/api/router"
@ -20,6 +20,10 @@ func main() {
if err != nil {
log.Panic(err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
defer db.Close()
fmt.Println("starting graphql server on http://localhost:3333")
fmt.Println("starting graphql playground on http://localhost:3333/__graphql")

View File

@ -1,16 +1,16 @@
package main
import (
"context"
"fmt"
"io/ioutil"
// "context"
// "fmt"
// "io/ioutil"
"github.com/jordan-wright/email"
_ "github.com/lib/pq"
"github.com/jmoiron/sqlx"
"github.com/jordanknott/project-citadel/api/pg"
"github.com/BurntSushi/toml"
"net/smtp"
// "github.com/jmoiron/sqlx"
// "github.com/jordanknott/project-citadel/api/pg"
// "github.com/BurntSushi/toml"
// "github.com/jordanknott/project-citadel/api/router"
// "time"
)
@ -26,6 +26,14 @@ type colors struct {
}
func main() {
e := email.NewEmail()
e.From = "Jordan Knott <no-reply@citadel.com>"
e.To = []string{"jordan@jordanthedev.com"}
e.Subject = "Jordan Knott (@jordanthedev) invited you to join the team \"Paradox\" on Citadel"
e.Text = []byte("Text Body is, of course, supported!")
e.HTML = []byte("<h1>Fancy HTML is supported, too!</h1>")
e.Send("localhost:1025", smtp.PlainAuth("", "test@gmail.com", "password123", "localhost"))
// dur := time.Hour * 24 * 7 * 30
// token, err := router.NewAccessTokenCustomExpiration("21345076-6423-4a00-a6bd-cd9f830e2764", dur)
// if err != nil {
@ -33,23 +41,23 @@ func main() {
// }
// fmt.Println(token)
fmt.Println("seeding database...")
// fmt.Println("seeding database...")
dat, err := ioutil.ReadFile("data/colors.toml")
if err != nil {
panic(err)
}
// dat, err := ioutil.ReadFile("data/colors.toml")
// if err != nil {
// panic(err)
// }
var labelColors colors
_, err = toml.Decode(string(dat), &labelColors)
if err != nil {
panic(err)
}
db, err := sqlx.Connect("postgres", "user=postgres password=test host=0.0.0.0 dbname=citadel sslmode=disable")
repository := pg.NewRepository(db)
for _, color := range labelColors.Color {
fmt.Printf("%v\n", color)
repository.CreateLabelColor(context.Background(), pg.CreateLabelColorParams{color.Name, color.Color, float64(color.Position)})
}
// var labelColors colors
// _, err = toml.Decode(string(dat), &labelColors)
// if err != nil {
// panic(err)
// }
// db, err := sqlx.Connect("postgres", "user=postgres password=test host=0.0.0.0 dbname=citadel sslmode=disable")
// repository := pg.NewRepository(db)
// for _, color := range labelColors.Color {
// fmt.Printf("%v\n", color)
// repository.CreateLabelColor(context.Background(), pg.CreateLabelColorParams{color.Name, color.Color, float64(color.Position)})
// }
}

8
api/docker-compose.yml Normal file
View File

@ -0,0 +1,8 @@
version: "3"
services:
mailhog:
image: mailhog/mailhog:latest
restart: always
ports:
- 1025:1025
- 8025:8025

View File

@ -5,17 +5,25 @@ go 1.13
require (
github.com/99designs/gqlgen v0.11.3
github.com/BurntSushi/toml v0.3.1
github.com/boyter/scc v2.12.0+incompatible // indirect
github.com/dbaggerman/cuba v0.3.2 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-chi/chi v3.3.2+incompatible
github.com/go-chi/cors v1.0.0
github.com/google/martian v2.1.0+incompatible
github.com/google/uuid v1.1.1
github.com/jmoiron/sqlx v1.2.0
github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e
github.com/lib/pq v1.0.0
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f // indirect
github.com/pelletier/go-toml v1.8.0
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/urfave/cli v1.20.0 // indirect
github.com/vektah/gqlparser/v2 v2.0.1
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/text v0.3.3 // indirect
)

View File

@ -1,29 +1,64 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/99designs/gqlgen v0.11.1 h1:QoSL8/AAJ2T3UOeQbdnBR32JcG4pO08+P/g5jdbFkUg=
github.com/99designs/gqlgen v0.11.1/go.mod h1:vjFOyBZ7NwDl+GdSD4PFn7BQn5Fy7ohJwXn7Vk8zz+c=
github.com/99designs/gqlgen v0.11.3 h1:oFSxl1DFS9X///uHV3y6CEfpcXWrDUxVblR4Xib2bs4=
github.com/99designs/gqlgen v0.11.3/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/boyter/scc v2.12.0+incompatible h1:Sxhi1Ry3gdreoaF0xEITQSdjR+pJT0cXHXhlDw5FHT0=
github.com/boyter/scc v2.12.0+incompatible/go.mod h1:VB5w4e0dahmIiKnpZ7LRh/sjauoY0BmCWjIzZcShNY0=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dbaggerman/cuba v0.3.2 h1:6ZbQX3FNvkocR222YyoAIZ8wi4avrb7JcJkPvVI2AS4=
github.com/dbaggerman/cuba v0.3.2/go.mod h1:t9Oo05XRZGcjaVqsA/gFeNAOm7DYZYNhI17unI5FlwY=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v3.3.2+incompatible h1:uQNcQN3NsV1j4ANsPh42P4ew4t6rnRbJb8frvpp31qQ=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
@ -32,12 +67,28 @@ github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e h1:OGunVjqY7y4U4laftpEHv+mvZBlr7UGimJXKEGQtg48=
github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e/go.mod h1:Fy2gCFfZhay8jplf/Csj6cyH/oshQTkLQYZbKkcV+SY=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -46,6 +97,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@ -53,16 +105,39 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f h1:0HN0GKijN4mr9SmYoW/Ni3ozuNeHiSxo2s7drhv7obY=
github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -71,14 +146,30 @@ github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJ
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
@ -87,15 +178,36 @@ github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWp
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -104,18 +216,35 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtD
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=

File diff suppressed because it is too large Load Diff

View File

@ -59,6 +59,10 @@ type DeleteProjectPayload struct {
Project *pg.Project `json:"project"`
}
type DeleteTaskChecklist struct {
TaskChecklistID uuid.UUID `json:"taskChecklistID"`
}
type DeleteTaskChecklistItem struct {
TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"`
}
@ -68,6 +72,11 @@ type DeleteTaskChecklistItemPayload struct {
TaskChecklistItem *pg.TaskChecklistItem `json:"taskChecklistItem"`
}
type DeleteTaskChecklistPayload struct {
Ok bool `json:"ok"`
TaskChecklist *pg.TaskChecklist `json:"taskChecklist"`
}
type DeleteTaskGroupInput struct {
TaskGroupID uuid.UUID `json:"taskGroupID"`
}
@ -86,6 +95,25 @@ type DeleteTaskPayload struct {
TaskID string `json:"taskID"`
}
type DeleteTeam struct {
TeamID uuid.UUID `json:"teamID"`
}
type DeleteTeamPayload struct {
Ok bool `json:"ok"`
Team *pg.Team `json:"team"`
Projects []pg.Project `json:"projects"`
}
type DeleteUserAccount struct {
UserID uuid.UUID `json:"userID"`
}
type DeleteUserAccountPayload struct {
Ok bool `json:"ok"`
UserAccount *pg.UserAccount `json:"userAccount"`
}
type FindProject struct {
ProjectID string `json:"projectId"`
}
@ -94,6 +122,10 @@ type FindTask struct {
TaskID uuid.UUID `json:"taskID"`
}
type FindTeam struct {
TeamID uuid.UUID `json:"teamID"`
}
type FindUser struct {
UserID string `json:"userId"`
}
@ -167,7 +199,7 @@ type ProjectMember struct {
}
type ProjectsFilter struct {
TeamID *string `json:"teamID"`
TeamID *uuid.UUID `json:"teamID"`
}
type RemoveTaskLabelInput struct {
@ -229,6 +261,11 @@ type UpdateTaskChecklistItemName struct {
Name string `json:"name"`
}
type UpdateTaskChecklistName struct {
TaskChecklistID uuid.UUID `json:"taskChecklistID"`
Name string `json:"name"`
}
type UpdateTaskDescriptionInput struct {
TaskID uuid.UUID `json:"taskID"`
Description string `json:"description"`

View File

@ -103,7 +103,7 @@ type Task {
}
input ProjectsFilter {
teamID: String
teamID: UUID
}
input FindUser {
@ -123,6 +123,10 @@ type Organization {
name: String!
}
input FindTeam {
teamID: UUID!
}
type Query {
organizations: [Organization!]!
users: [UserAccount!]!
@ -130,6 +134,7 @@ type Query {
findProject(input: FindProject!): Project!
findTask(input: FindTask!): Task!
projects(input: ProjectsFilter): [Project!]!
findTeam(input: FindTeam!): Team!
teams: [Team!]!
labelColors: [LabelColor!]!
taskGroups: [TaskGroup!]!
@ -356,11 +361,43 @@ type DeleteProjectPayload {
project: Project!
}
input DeleteTeam {
teamID: UUID!
}
type DeleteTeamPayload {
ok: Boolean!
team: Team!
projects: [Project!]!
}
input DeleteUserAccount {
userID: UUID!
}
type DeleteUserAccountPayload {
ok: Boolean!
userAccount: UserAccount!
}
input UpdateTaskChecklistName {
taskChecklistID: UUID!
name: String!
}
input DeleteTaskChecklist {
taskChecklistID: UUID!
}
type DeleteTaskChecklistPayload {
ok: Boolean!
taskChecklist: TaskChecklist!
}
type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken!
createUserAccount(input: NewUserAccount!): UserAccount!
deleteUserAccount(input: DeleteUserAccount!): DeleteUserAccountPayload!
deleteTeam(input: DeleteTeam!): DeleteTeamPayload!
createTeam(input: NewTeam!): Team!
clearProfileAvatar: UserAccount!
@ -386,6 +423,8 @@ type Mutation {
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
createTaskChecklist(input: CreateTaskChecklist!): TaskChecklist!
deleteTaskChecklist(input: DeleteTaskChecklist!): DeleteTaskChecklistPayload!
updateTaskChecklistName(input: UpdateTaskChecklistName!): TaskChecklist!
createTaskChecklistItem(input: CreateTaskChecklistItem!): TaskChecklistItem!
updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!): TaskChecklistItem!
setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!): TaskChecklistItem!

View File

@ -13,6 +13,7 @@ import (
"github.com/jordanknott/project-citadel/api/pg"
log "github.com/sirupsen/logrus"
"github.com/vektah/gqlparser/v2/gqlerror"
"golang.org/x/crypto/bcrypt"
)
func (r *labelColorResolver) ID(ctx context.Context, obj *pg.LabelColor) (uuid.UUID, error) {
@ -29,17 +30,54 @@ func (r *mutationResolver) CreateRefreshToken(ctx context.Context, input NewRefr
func (r *mutationResolver) CreateUserAccount(ctx context.Context, input NewUserAccount) (*pg.UserAccount, error) {
createdAt := time.Now().UTC()
hashedPwd, err := bcrypt.GenerateFromPassword([]byte(input.Password), 14)
if err != nil {
return &pg.UserAccount{}, err
}
userAccount, err := r.Repository.CreateUserAccount(ctx, pg.CreateUserAccountParams{
FullName: input.FullName,
Initials: input.Initials,
Email: input.Email,
Username: input.Username,
CreatedAt: createdAt,
PasswordHash: input.Password,
PasswordHash: string(hashedPwd),
})
return &userAccount, err
}
func (r *mutationResolver) DeleteUserAccount(ctx context.Context, input DeleteUserAccount) (*DeleteUserAccountPayload, error) {
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
if err != nil {
return &DeleteUserAccountPayload{Ok: false}, err
}
err = r.Repository.DeleteUserAccountByID(ctx, input.UserID)
if err != nil {
return &DeleteUserAccountPayload{Ok: false}, err
}
return &DeleteUserAccountPayload{UserAccount: &user, Ok: true}, nil
}
func (r *mutationResolver) DeleteTeam(ctx context.Context, input DeleteTeam) (*DeleteTeamPayload, error) {
team, err := r.Repository.GetTeamByID(ctx, input.TeamID)
if err != nil {
log.Error(err)
return &DeleteTeamPayload{Ok: false}, err
}
projects, err := r.Repository.GetAllProjectsForTeam(ctx, input.TeamID)
if err != nil {
log.Error(err)
return &DeleteTeamPayload{Ok: false}, err
}
err = r.Repository.DeleteTeamByID(ctx, input.TeamID)
if err != nil {
log.Error(err)
return &DeleteTeamPayload{Ok: false}, err
}
return &DeleteTeamPayload{Ok: true, Team: &team, Projects: projects}, nil
}
func (r *mutationResolver) CreateTeam(ctx context.Context, input NewTeam) (*pg.Team, error) {
createdAt := time.Now().UTC()
team, err := r.Repository.CreateTeam(ctx, pg.CreateTeamParams{input.OrganizationID, createdAt, input.Name})
@ -277,6 +315,26 @@ func (r *mutationResolver) CreateTaskChecklist(ctx context.Context, input Create
return &taskChecklist, nil
}
func (r *mutationResolver) DeleteTaskChecklist(ctx context.Context, input DeleteTaskChecklist) (*DeleteTaskChecklistPayload, error) {
taskChecklist, err := r.Repository.GetTaskChecklistByID(ctx, input.TaskChecklistID)
if err != nil {
return &DeleteTaskChecklistPayload{Ok: false}, err
}
err = r.Repository.DeleteTaskChecklistByID(ctx, input.TaskChecklistID)
if err != nil {
return &DeleteTaskChecklistPayload{Ok: false}, err
}
return &DeleteTaskChecklistPayload{Ok: true, TaskChecklist: &taskChecklist}, nil
}
func (r *mutationResolver) UpdateTaskChecklistName(ctx context.Context, input UpdateTaskChecklistName) (*pg.TaskChecklist, error) {
checklist, err := r.Repository.UpdateTaskChecklistName(ctx, pg.UpdateTaskChecklistNameParams{TaskChecklistID: input.TaskChecklistID, Name: input.Name})
if err != nil {
return &pg.TaskChecklist{}, err
}
return &checklist, nil
}
func (r *mutationResolver) CreateTaskChecklistItem(ctx context.Context, input CreateTaskChecklistItem) (*pg.TaskChecklistItem, error) {
createdAt := time.Now().UTC()
taskChecklistItem, err := r.Repository.CreateTaskChecklistItem(ctx, pg.CreateTaskChecklistItemParams{
@ -475,6 +533,9 @@ func (r *projectResolver) TaskGroups(ctx context.Context, obj *pg.Project) ([]pg
func (r *projectResolver) Members(ctx context.Context, obj *pg.Project) ([]ProjectMember, error) {
user, err := r.Repository.GetUserAccountByID(ctx, obj.Owner)
members := []ProjectMember{}
if err == sql.ErrNoRows {
return members, nil
}
if err != nil {
return members, err
}
@ -561,15 +622,19 @@ func (r *queryResolver) FindTask(ctx context.Context, input FindTask) (*pg.Task,
func (r *queryResolver) Projects(ctx context.Context, input *ProjectsFilter) ([]pg.Project, error) {
if input != nil {
teamID, err := uuid.Parse(*input.TeamID)
if err != nil {
return []pg.Project{}, err
}
return r.Repository.GetAllProjectsForTeam(ctx, teamID)
return r.Repository.GetAllProjectsForTeam(ctx, *input.TeamID)
}
return r.Repository.GetAllProjects(ctx)
}
func (r *queryResolver) FindTeam(ctx context.Context, input FindTeam) (*pg.Team, error) {
team, err := r.Repository.GetTeamByID(ctx, input.TeamID)
if err != nil {
return &pg.Team{}, err
}
return &team, nil
}
func (r *queryResolver) Teams(ctx context.Context) ([]pg.Team, error) {
return r.Repository.GetAllTeams(ctx)
}
@ -727,6 +792,9 @@ func (r *teamResolver) ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error)
func (r *teamResolver) Members(ctx context.Context, obj *pg.Team) ([]ProjectMember, error) {
teamMembers, err := r.Repository.GetTeamMembersForTeamID(ctx, obj.TeamID)
var projectMembers []ProjectMember
if err == sql.ErrNoRows {
return projectMembers, nil
}
if err != nil {
return projectMembers, err
}

View File

@ -0,0 +1,6 @@
ALTER TABLE project DROP CONSTRAINT project_team_id_fkey;
ALTER TABLE project
ADD CONSTRAINT project_team_id_fkey
FOREIGN KEY (team_id)
REFERENCES team(team_id)
ON DELETE CASCADE;

View File

@ -0,0 +1,6 @@
ALTER TABLE task_assigned DROP CONSTRAINT task_assigned_user_id_fkey;
ALTER TABLE task_assigned
ADD CONSTRAINT task_assigned_user_id_fkey
FOREIGN KEY (user_id)
REFERENCES user_account(user_id)
ON DELETE CASCADE;

View File

@ -0,0 +1,5 @@
ALTER TABLE refresh_token
ADD CONSTRAINT refresh_token_user_id_fkey
FOREIGN KEY (user_id)
REFERENCES user_account(user_id)
ON DELETE CASCADE;

View File

@ -17,7 +17,11 @@ type Repository interface {
GetAllTeams(ctx context.Context) ([]Team, error)
DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error
DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error
GetTaskChecklistByID(ctx context.Context, taskChecklistID uuid.UUID) (TaskChecklist, error)
DeleteTaskChecklistByID(ctx context.Context, taskChecklistID uuid.UUID) error
UpdateTaskChecklistName(ctx context.Context, arg UpdateTaskChecklistNameParams) (TaskChecklist, error)
CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error)
GetAllProjects(ctx context.Context) ([]Project, error)
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)

View File

@ -30,6 +30,7 @@ type Querier interface {
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error
DeleteTaskChecklistByID(ctx context.Context, taskChecklistID uuid.UUID) error
DeleteTaskChecklistItem(ctx context.Context, taskChecklistItemID uuid.UUID) error
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error
@ -37,6 +38,7 @@ type Querier interface {
DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error
DeleteTeamMemberByUserID(ctx context.Context, userID uuid.UUID) error
DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error
GetAllOrganizations(ctx context.Context) ([]Organization, error)
GetAllProjects(ctx context.Context) ([]Project, error)
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
@ -52,6 +54,7 @@ type Querier interface {
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error)
GetTaskChecklistByID(ctx context.Context, taskChecklistID uuid.UUID) (TaskChecklist, error)
GetTaskChecklistItemByID(ctx context.Context, taskChecklistItemID uuid.UUID) (TaskChecklistItem, error)
GetTaskChecklistItemsForTaskChecklist(ctx context.Context, taskChecklistID uuid.UUID) ([]TaskChecklistItem, error)
GetTaskChecklistsForTask(ctx context.Context, taskID uuid.UUID) ([]TaskChecklist, error)
@ -74,6 +77,7 @@ type Querier interface {
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error)
UpdateTaskChecklistName(ctx context.Context, arg UpdateTaskChecklistNameParams) (TaskChecklist, error)
UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescriptionParams) (Task, error)
UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error)
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)

View File

@ -72,6 +72,15 @@ func (q *Queries) CreateTaskChecklistItem(ctx context.Context, arg CreateTaskChe
return i, err
}
const deleteTaskChecklistByID = `-- name: DeleteTaskChecklistByID :exec
DELETE FROM task_checklist WHERE task_checklist_id = $1
`
func (q *Queries) DeleteTaskChecklistByID(ctx context.Context, taskChecklistID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteTaskChecklistByID, taskChecklistID)
return err
}
const deleteTaskChecklistItem = `-- name: DeleteTaskChecklistItem :exec
DELETE FROM task_checklist_item WHERE task_checklist_item_id = $1
`
@ -81,6 +90,23 @@ func (q *Queries) DeleteTaskChecklistItem(ctx context.Context, taskChecklistItem
return err
}
const getTaskChecklistByID = `-- name: GetTaskChecklistByID :one
SELECT task_checklist_id, task_id, created_at, name, position FROM task_checklist WHERE task_checklist_id = $1
`
func (q *Queries) GetTaskChecklistByID(ctx context.Context, taskChecklistID uuid.UUID) (TaskChecklist, error) {
row := q.db.QueryRowContext(ctx, getTaskChecklistByID, taskChecklistID)
var i TaskChecklist
err := row.Scan(
&i.TaskChecklistID,
&i.TaskID,
&i.CreatedAt,
&i.Name,
&i.Position,
)
return i, err
}
const getTaskChecklistItemByID = `-- name: GetTaskChecklistItemByID :one
SELECT task_checklist_item_id, task_checklist_id, created_at, complete, name, position, due_date FROM task_checklist_item WHERE task_checklist_item_id = $1
`
@ -217,3 +243,26 @@ func (q *Queries) UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTas
)
return i, err
}
const updateTaskChecklistName = `-- name: UpdateTaskChecklistName :one
UPDATE task_checklist SET name = $2 WHERE task_checklist_id = $1
RETURNING task_checklist_id, task_id, created_at, name, position
`
type UpdateTaskChecklistNameParams struct {
TaskChecklistID uuid.UUID `json:"task_checklist_id"`
Name string `json:"name"`
}
func (q *Queries) UpdateTaskChecklistName(ctx context.Context, arg UpdateTaskChecklistNameParams) (TaskChecklist, error) {
row := q.db.QueryRowContext(ctx, updateTaskChecklistName, arg.TaskChecklistID, arg.Name)
var i TaskChecklist
err := row.Scan(
&i.TaskChecklistID,
&i.TaskID,
&i.CreatedAt,
&i.Name,
&i.Position,
)
return i, err
}

View File

@ -49,6 +49,15 @@ func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountPa
return i, err
}
const deleteUserAccountByID = `-- name: DeleteUserAccountByID :exec
DELETE FROM user_account WHERE user_id = $1
`
func (q *Queries) DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteUserAccountByID, userID)
return err
}
const getAllUserAccounts = `-- name: GetAllUserAccounts :many
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url FROM user_account
`

View File

@ -5,6 +5,16 @@ INSERT INTO task_checklist (task_id, created_at, name, position) VALUES ($1, $2,
-- name: GetTaskChecklistsForTask :many
SELECT * FROM task_checklist WHERE task_id = $1;
-- name: UpdateTaskChecklistName :one
UPDATE task_checklist SET name = $2 WHERE task_checklist_id = $1
RETURNING *;
-- name: DeleteTaskChecklistByID :exec
DELETE FROM task_checklist WHERE task_checklist_id = $1;
-- name: GetTaskChecklistByID :one
SELECT * FROM task_checklist WHERE task_checklist_id = $1;
-- name: CreateTaskChecklistItem :one
INSERT INTO task_checklist_item (task_checklist_id, created_at, name, position, complete, due_date) VALUES ($1, $2, $3, $4, false, null)
RETURNING *;

View File

@ -14,3 +14,6 @@ INSERT INTO user_account(full_name, initials, email, username, created_at, passw
-- name: UpdateUserAccountProfileAvatarURL :one
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
RETURNING *;
-- name: DeleteUserAccountByID :exec
DELETE FROM user_account WHERE user_id = $1;

View File

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@apollo/client": "^3.0.0-rc.8",
"@apollo/react-common": "^3.1.4",
"@apollo/react-hooks": "^3.1.3",
"@fortawesome/fontawesome-svg-core": "^1.2.27",

View File

@ -0,0 +1,471 @@
{
"header": {
"reportVersion": 1,
"event": "Allocation failed - JavaScript heap out of memory",
"trigger": "FatalError",
"filename": "report.20200621.183857.68808.0.001.json",
"dumpEventTime": "2020-06-21T18:38:57Z",
"dumpEventTimeStamp": "1592782737343",
"processId": 68808,
"cwd": "/home/jordan/Projects/project-citadel/web",
"commandLine": [
"/usr/bin/node",
"/home/jordan/.config/coc/extensions/node_modules/coc-tsserver/bin/tsserverForkStart",
"/home/jordan/Projects/project-citadel/web/node_modules/typescript/lib/tsserver.js",
"--allowLocalPluginLoads",
"--useInferredProjectPerProjectRoot",
"--cancellationPipeName",
"/tmp/coc-nvim-tscancellation-49e6b7aafa04f6c486f4.sock*",
"--npmLocation",
"\"/usr/bin/npm\"",
"--noGetErrOnBackgroundUpdate",
"--validateDefaultNpmLocation"
],
"nodejsVersion": "v13.8.0",
"glibcVersionRuntime": "2.30",
"glibcVersionCompiler": "2.30",
"wordSize": 64,
"arch": "x64",
"platform": "linux",
"componentVersions": {
"node": "13.8.0",
"v8": "7.9.317.25-node.28",
"uv": "1.34.1",
"zlib": "1.2.11",
"brotli": "1.0.7",
"ares": "1.15.0",
"modules": "79",
"nghttp2": "1.39.2",
"napi": "5",
"llhttp": "2.0.4",
"openssl": "1.1.1d",
"cldr": "36.0",
"icu": "65.1",
"tz": "2019c",
"unicode": "12.1"
},
"release": {
"name": "node",
"headersUrl": "https://nodejs.org/download/release/v13.8.0/node-v13.8.0-headers.tar.gz",
"sourceUrl": "https://nodejs.org/download/release/v13.8.0/node-v13.8.0.tar.gz"
},
"osName": "Linux",
"osRelease": "4.19.101-1-lts",
"osVersion": "#1 SMP Sat, 01 Feb 2020 16:35:36 +0000",
"osMachine": "x86_64",
"cpus": [
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2646,
"user": 48577700,
"nice": 14200,
"sys": 8014100,
"idle": 166454900,
"irq": 735500
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2646,
"user": 48745100,
"nice": 19200,
"sys": 7840700,
"idle": 42737200,
"irq": 528800
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2647,
"user": 48543900,
"nice": 15800,
"sys": 7804900,
"idle": 42914200,
"irq": 530200
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2726,
"user": 48996200,
"nice": 19900,
"sys": 7808300,
"idle": 42957300,
"irq": 390800
}
],
"networkInterfaces": [
{
"name": "lo",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "127.0.0.1",
"netmask": "255.0.0.0",
"family": "IPv4"
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "192.168.43.5",
"netmask": "255.255.255.0",
"family": "IPv4"
},
{
"name": "docker0",
"internal": false,
"mac": "02:42:13:a9:89:9d",
"address": "172.17.0.1",
"netmask": "255.255.0.0",
"family": "IPv4"
},
{
"name": "br-e929893879ec",
"internal": false,
"mac": "02:42:48:f4:23:30",
"address": "172.19.0.1",
"netmask": "255.255.0.0",
"family": "IPv4"
},
{
"name": "lo",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "::1",
"netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
"family": "IPv6",
"scopeid": 0
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "2600:100b:b000:9bf1:7eb0:c2ff:fefe:9386",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 0
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "fe80::7eb0:c2ff:fefe:9386",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 3
},
{
"name": "docker0",
"internal": false,
"mac": "02:42:13:a9:89:9d",
"address": "fe80::42:13ff:fea9:899d",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 5
},
{
"name": "veth79cfd83",
"internal": false,
"mac": "a2:e9:86:a8:58:bb",
"address": "fe80::a0e9:86ff:fea8:58bb",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 7
},
{
"name": "br-e929893879ec",
"internal": false,
"mac": "02:42:48:f4:23:30",
"address": "fe80::42:48ff:fef4:2330",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 8
},
{
"name": "vethe27cc3e",
"internal": false,
"mac": "c6:bd:d7:3b:6c:96",
"address": "fe80::c4bd:d7ff:fe3b:6c96",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 10
}
],
"host": "archlinux"
},
"javascriptStack": {
"message": "No stack.",
"stack": [
"Unavailable."
]
},
"nativeStack": [
{
"pc": "0x0000557aee1dae8a",
"symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, v8::Local<v8::String>) [/usr/bin/node]"
},
{
"pc": "0x0000557aee09bd48",
"symbol": "node::OnFatalError(char const*, char const*) [/usr/bin/node]"
},
{
"pc": "0x0000557aee20f382",
"symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/bin/node]"
},
{
"pc": "0x0000557aee20f5e8",
"symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/bin/node]"
},
{
"pc": "0x0000557aee399906",
"symbol": " [/usr/bin/node]"
},
{
"pc": "0x0000557aee399a49",
"symbol": " [/usr/bin/node]"
},
{
"pc": "0x0000557aee3ac22d",
"symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/usr/bin/node]"
},
{
"pc": "0x0000557aee3acf58",
"symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/usr/bin/node]"
},
{
"pc": "0x0000557aee3af48c",
"symbol": "v8::internal::Heap::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]"
},
{
"pc": "0x0000557aee3af4f4",
"symbol": "v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]"
},
{
"pc": "0x0000557aee37499b",
"symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/usr/bin/node]"
},
{
"pc": "0x0000557aee6a5540",
"symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]"
},
{
"pc": "0x0000557aee9febd9",
"symbol": " [/usr/bin/node]"
}
],
"javascriptHeap": {
"totalMemory": 2152407040,
"totalCommittedMemory": 2150546232,
"usedMemory": 2137632424,
"availableMemory": 47662336,
"memoryLimit": 2197815296,
"heapSpaces": {
"read_only_space": {
"memorySize": 262144,
"committedMemory": 33328,
"capacity": 33040,
"used": 33040,
"available": 0
},
"new_space": {
"memorySize": 2097152,
"committedMemory": 1029144,
"capacity": 1047424,
"used": 21136,
"available": 1026288
},
"old_space": {
"memorySize": 2058231808,
"committedMemory": 2057888048,
"capacity": 2048399928,
"used": 2048219560,
"available": 180368
},
"code_space": {
"memorySize": 9080832,
"committedMemory": 8864320,
"capacity": 7755264,
"used": 7755264,
"available": 0
},
"map_space": {
"memorySize": 1052672,
"committedMemory": 1048960,
"capacity": 740080,
"used": 740080,
"available": 0
},
"large_object_space": {
"memorySize": 81305600,
"committedMemory": 81305600,
"capacity": 80548528,
"used": 80548528,
"available": 0
},
"code_large_object_space": {
"memorySize": 376832,
"committedMemory": 376832,
"capacity": 314816,
"used": 314816,
"available": 0
},
"new_large_object_space": {
"memorySize": 0,
"committedMemory": 0,
"capacity": 1047424,
"used": 0,
"available": 1047424
}
}
},
"resourceUsage": {
"userCpuSeconds": 3484.11,
"kernelCpuSeconds": 64.7409,
"cpuConsumptionPercent": 35.606,
"maxRss": 2330890240,
"pageFaults": {
"IORequired": 31,
"IONotRequired": 9729381
},
"fsActivity": {
"reads": 53144,
"writes": 16
}
},
"uvthreadResourceUsage": {
"userCpuSeconds": 2308.89,
"kernelCpuSeconds": 31.2857,
"cpuConsumptionPercent": 23.4792,
"fsActivity": {
"reads": 53248,
"writes": 16
}
},
"libuv": [
],
"environmentVariables": {
"COLORTERM": "truecolor",
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
"DESKTOP_SESSION": "awesome",
"DISPLAY": ":0.0",
"EDITOR": "nano",
"FZF_DEFAULT_COMMAND": "ag --hidden --ignore .git -g \"\"",
"GDMSESSION": "awesome",
"GO111MODULE": "on",
"GOBIN": "/home/jordan/go/bin",
"GREP_COLOR": "37;45",
"GREP_COLORS": "mt=37;45",
"GTK_MODULES": "canberra-gtk-module",
"HOME": "/home/jordan",
"LANG": "C",
"LESS": "-F -g -i -M -R -S -w -X -z-4",
"LESS_TERMCAP_mb": "\u001b[01;31m",
"LESS_TERMCAP_md": "\u001b[01;31m",
"LESS_TERMCAP_me": "\u001b[0m",
"LESS_TERMCAP_se": "\u001b[0m",
"LESS_TERMCAP_so": "\u001b[00;47;30m",
"LESS_TERMCAP_ue": "\u001b[0m",
"LESS_TERMCAP_us": "\u001b[01;32m",
"LOGNAME": "jordan",
"LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:",
"MAIL": "/var/spool/mail/jordan",
"OLDPWD": "/home/jordan/Projects/project-citadel/web",
"PAGER": "less",
"PATH": "/home/jordan/.local/bin:/usr/local/bin:/usr/local/sbin:/home/nightwolf/Programs/cmake/bin:/home/nightwolf/Programs/idea-IU-163.13906.18/bin:/home/nightwolf/Programs/wpcli:/home/nightwolf/neovim/bin:/home/nightwolf/Programs/Postman:/home/nightwolf/Programs/Android_SDK/tools/bin:/home/nightwolf/Development/PhantomJS/bin:/home/nightwolf/Programs/node/bin:/home/nightwolf/pyenv/bin:/home/nightwolf/Programs/vv:/usr/bin:/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/local/go/bin:/home/jordan/go/bin:/home/jordan/.garden/bin:~/Programs/node/bin:~/.utilities:/home/jordan/.fzf/bin:/home/jordan/.gem/ruby/2.6.0/bin:/home/jordan/.garden/bin:~/Programs/node/bin:~/.utilities:/home/jordan/.gem/ruby/2.6.0/bin",
"PWD": "/home/jordan/Projects/project-citadel/web",
"SHELL": "/usr/bin/zsh",
"SHLVL": "2",
"SSH_AGENT_PID": "773",
"SSH_AUTH_SOCK": "/tmp/ssh-agent.sock.1000",
"TERM": "xterm-256color",
"TMUX": "/tmp/tmux-1000/default,13203,1",
"TMUX_PANE": "%2",
"USER": "jordan",
"VIRTUALENVWRAPPER_PYTHON": "/usr/bin/python3",
"VIRTUAL_ENV_DISABLE_PROMPT": "12",
"VISUAL": "nano",
"VTE_VERSION": "5602",
"WINDOWID": "6291459",
"WORKON_HOME": "/home/jordan/.virtualenvs",
"XAUTHORITY": "/home/jordan/.Xauthority",
"XDG_GREETER_DATA_DIR": "/var/lib/lightdm-data/jordan",
"XDG_RUNTIME_DIR": "/run/user/1000",
"XDG_SEAT": "seat0",
"XDG_SEAT_PATH": "/org/freedesktop/DisplayManager/Seat0",
"XDG_SESSION_CLASS": "user",
"XDG_SESSION_DESKTOP": "awesome",
"XDG_SESSION_ID": "1",
"XDG_SESSION_PATH": "/org/freedesktop/DisplayManager/Session0",
"XDG_SESSION_TYPE": "x11",
"XDG_VTNR": "7",
"_": "/usr/bin/nvim",
"is_vim": "ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'",
"tmux_version": "$(tmux -V | sed -En \"s/^tmux ([0-9]+(.[0-9]+)?).*/\\1/p\")",
"LC_MESSAGES": "",
"VIMRUNTIME": "/usr/share/nvim/runtime",
"NVIM_LISTEN_ADDRESS": "/tmp/nvimaHxiZq/0",
"MYVIMRC": "/home/jordan/.config/nvim/init.vim",
"COC_VIMCONFIG": "/home/jordan/.config/nvim",
"COC_DATA_HOME": "/home/jordan/.config/coc",
"TSS_LOG": "-level verbose -file /tmp/coc-nvim-tsc.log",
"NODE_PATH": "/home/jordan/Projects/project-citadel/web/node_modules"
},
"userLimits": {
"core_file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"data_seg_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_locked_memory_bytes": {
"soft": 65536,
"hard": 65536
},
"max_memory_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"open_files": {
"soft": 524288,
"hard": 524288
},
"stack_size_bytes": {
"soft": 8388608,
"hard": "unlimited"
},
"cpu_time_seconds": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_user_processes": {
"soft": 31138,
"hard": 31138
},
"virtual_memory_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
}
},
"sharedObjects": [
"linux-vdso.so.1",
"/usr/lib/libz.so.1",
"/usr/lib/libcares.so.2",
"/usr/lib/libnghttp2.so.14",
"/usr/lib/libcrypto.so.1.1",
"/usr/lib/libssl.so.1.1",
"/usr/lib/libicui18n.so.65",
"/usr/lib/libicuuc.so.65",
"/usr/lib/libdl.so.2",
"/usr/lib/libstdc++.so.6",
"/usr/lib/libm.so.6",
"/usr/lib/libgcc_s.so.1",
"/usr/lib/libpthread.so.0",
"/usr/lib/libc.so.6",
"/usr/lib/libicudata.so.65",
"/lib64/ld-linux-x86-64.so.2"
]
}

158
web/src/Admin/index.tsx Normal file
View File

@ -0,0 +1,158 @@
import React, { useEffect } from 'react';
import Admin from 'shared/components/Admin';
import GlobalTopNavbar from 'App/TopNavbar';
import { useUsersQuery, useCreateUserAccountMutation, UsersDocument } from 'shared/generated/graphql';
import Input from 'shared/components/Input';
import styled from 'styled-components';
import Button from 'shared/components/Button';
import { useForm } from 'react-hook-form';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import produce from 'immer';
type CreateUserData = {
email: string;
username: string;
fullName: string;
initials: string;
password: string;
};
const CreateUserForm = styled.form`
display: flex;
flex-direction: column;
`;
const CreateUserButton = styled(Button)`
margin-top: 8px;
padding: 6px 12px;
width: 100%;
`;
const AddUserInput = styled(Input)`
margin-bottom: 8px;
`;
const InputError = styled.span`
color: rgba(${props => props.theme.colors.danger});
font-size: 12px;
`;
type AddUserPopupProps = {
onAddUser: (user: CreateUserData) => void;
};
const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
const { register, handleSubmit, errors } = useForm<CreateUserData>();
const createUser = (data: CreateUserData) => {
onAddUser(data);
};
return (
<CreateUserForm onSubmit={handleSubmit(createUser)}>
<AddUserInput
floatingLabel
width="100%"
label="Full Name"
id="fullName"
name="fullName"
variant="alternate"
ref={register({ required: 'Full name is required' })}
/>
{errors.fullName && <InputError>{errors.fullName.message}</InputError>}
<AddUserInput
floatingLabel
width="100%"
label="Email"
id="email"
name="email"
variant="alternate"
ref={register({ required: 'Email is required' })}
/>
{errors.email && <InputError>{errors.email.message}</InputError>}
<AddUserInput
floatingLabel
width="100%"
label="Username"
id="username"
name="username"
variant="alternate"
ref={register({ required: 'Username is required' })}
/>
{errors.username && <InputError>{errors.username.message}</InputError>}
<AddUserInput
floatingLabel
width="100%"
label="Initials"
id="initials"
name="initials"
variant="alternate"
ref={register({ required: 'Initials is required' })}
/>
{errors.initials && <InputError>{errors.initials.message}</InputError>}
<AddUserInput
floatingLabel
width="100%"
label="Password"
id="password"
name="password"
variant="alternate"
ref={register({ required: 'Password is required' })}
/>
{errors.password && <InputError>{errors.password.message}</InputError>}
<CreateUserButton type="submit">Create</CreateUserButton>
</CreateUserForm>
);
};
const AdminRoute = () => {
useEffect(() => {
document.title = 'Citadel | Admin';
}, []);
const { loading, data } = useUsersQuery();
const { showPopup, hidePopup } = usePopup();
const [createUser] = useCreateUserAccountMutation({
update: (client, createData) => {
const cacheData: any = client.readQuery({
query: UsersDocument,
});
console.log(cacheData);
console.log(createData);
const newData = produce(cacheData, (draftState: any) => {
draftState.users = [...draftState.users, { ...createData.data.createUserAccount }];
});
client.writeQuery({
query: UsersDocument,
data: {
...newData,
},
});
},
});
if (loading) {
return <GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />;
}
if (data) {
return (
<>
<GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />
<Admin
initialTab={1}
users={data.users.map((user: any) => ({ ...user, role: 'TBD' }))}
onInviteUser={() => {}}
onAddUser={$target => {
showPopup(
$target,
<Popup tab={0} title="Add member" onClose={() => hidePopup()}>
<AddUserPopup
onAddUser={user => {
createUser({ variables: { ...user } });
hidePopup();
}}
/>
</Popup>,
);
}}
/>
</>
);
}
return <span>error</span>;
};
export default AdminRoute;

View File

@ -15,7 +15,7 @@ const GlobalNavbar = () => {
<ButtonContainer>
<Link to="/">
<ActionButton name="Home">
<Home size={28} color="#c2c6dc" />
<Home width={28} height={28} />
</ActionButton>
</Link>
<Link to="/projects">

View File

@ -3,14 +3,16 @@ import { Router, Switch, Route } from 'react-router-dom';
import * as H from 'history';
import Dashboard from 'Dashboard';
import Admin from 'Admin';
import Projects from 'Projects';
import Project from 'Projects/Project';
import Teams from 'Teams';
import Login from 'Auth';
import Profile from 'Profile';
import styled from 'styled-components';
const MainContent = styled.div`
padding: 0 0 0 80px;
padding: 0 0 0 0;
background: #262c49;
height: 100%;
display: flex;
@ -28,7 +30,9 @@ const Routes = ({ history }: RoutesProps) => (
<Route exact path="/" component={Dashboard} />
<Route exact path="/projects" component={Projects} />
<Route path="/projects/:projectID" component={Project} />
<Route path="/teams/:teamID" component={Teams} />
<Route path="/profile" component={Profile} />
<Route path="/admin" component={Admin} />
</MainContent>
</Switch>
);

View File

@ -1,24 +1,172 @@
import React, { useState, useContext } from 'react';
import React, { useState, useContext, useEffect } from 'react';
import TopNavbar from 'shared/components/TopNavbar';
import styled from 'styled-components/macro';
import DropdownMenu, { ProfileMenu } from 'shared/components/DropdownMenu';
import ProjectSettings, { DeleteProject } from 'shared/components/ProjectSettings';
import ProjectSettings, { DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings';
import { useHistory } from 'react-router';
import UserIDContext from 'App/context';
import { useMeQuery, useDeleteProjectMutation, GetProjectsDocument } from 'shared/generated/graphql';
import {
useMeQuery,
useDeleteProjectMutation,
useGetProjectsQuery,
GetProjectsDocument,
} from 'shared/generated/graphql';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { History } from 'history';
import produce from 'immer';
import { Link } from 'react-router-dom';
type GlobalTopNavbarProps = {
projectID: string | null;
name: string | null;
projectMembers?: null | Array<TaskUser>;
onSaveProjectName?: (projectName: string) => void;
const TeamContainer = styled.div`
display: flex;
flex-direction: column;
`;
const TeamTitle = styled.h3`
font-size: 14px;
font-weight: 700;
`;
const TeamProjects = styled.div`
display: flex;
flex-direction: column;
margin-top: 8px;
margin-bottom: 4px;
`;
const TeamProjectLink = styled(Link)`
display: flex;
font-weight: 700;
height: 36px;
overflow: hidden;
padding: 0;
position: relative;
text-decoration: none;
user-select: none;
`;
const TeamProjectBackground = styled.div<{ color: string }>`
background-image: url(null);
background-color: ${props => props.color};
background-size: cover;
background-position: 50%;
position: absolute;
width: 100%;
height: 36px;
opacity: 1;
border-radius: 3px;
&:before {
background: rgba(${props => props.theme.colors.bg.secondary});
bottom: 0;
content: '';
left: 0;
opacity: 0.88;
position: absolute;
right: 0;
top: 0;
}
`;
const TeamProjectAvatar = styled.div<{ color: string }>`
background-image: url(null);
background-color: ${props => props.color};
display: inline-block;
flex: 0 0 auto;
background-size: cover;
border-radius: 3px 0 0 3px;
height: 36px;
width: 36px;
position: relative;
opacity: 0.7;
`;
const TeamProjectContent = styled.div`
display: flex;
position: relative;
flex: 1;
width: 100%;
padding: 9px 0 9px 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const TeamProjectTitle = styled.div`
font-weight: 700;
display: block;
padding-right: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const TeamProjectContainer = styled.div`
box-sizing: border-box;
border-radius: 3px;
position: relative;
margin: 0 4px 4px 0;
min-width: 0;
&:hover ${TeamProjectTitle} {
color: rgba(${props => props.theme.colors.text.secondary});
}
&:hover ${TeamProjectAvatar} {
opacity: 1;
}
&:hover ${TeamProjectBackground}:before {
opacity: 0.78;
}
`;
const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
const ProjectFinder = () => {
const { loading, data } = useGetProjectsQuery();
if (loading) {
return <span>loading</span>;
}
if (data) {
const { projects, teams, organizations } = data;
const projectTeams = teams.map(team => {
return {
id: team.id,
name: team.name,
projects: projects.filter(project => project.team.id === team.id),
};
});
return (
<>
{projectTeams.map(team => (
<TeamContainer>
<TeamTitle>{team.name}</TeamTitle>
<TeamProjects>
{team.projects.map((project, idx) => (
<TeamProjectContainer>
<TeamProjectLink to={`/projects/${project.id}`}>
<TeamProjectBackground color={colors[idx % 5]} />
<TeamProjectAvatar color={colors[idx % 5]} />
<TeamProjectContent>
<TeamProjectTitle>{project.name}</TeamProjectTitle>
</TeamProjectContent>
</TeamProjectLink>
</TeamProjectContainer>
))}
</TeamProjects>
</TeamContainer>
))}
</>
);
}
return <span>error</span>;
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ projectID, name, projectMembers, onSaveProjectName }) => {
const { loading, data } = useMeQuery();
const { showPopup, hidePopup, setTab } = usePopup();
const history = useHistory();
const { userID, setUserID } = useContext(UserIDContext);
type ProjectPopupProps = {
history: History<History.PoorMansUnknown>;
name: string;
projectID: string;
};
export const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, projectID }) => {
const { hidePopup, setTab } = usePopup();
const [deleteProject] = useDeleteProjectMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
@ -42,6 +190,62 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ projectID, name, proj
});
},
});
return (
<>
<Popup title={null} tab={0}>
<ProjectSettings
onDeleteProject={() => {
setTab(1);
}}
/>
</Popup>
<Popup title={`Delete the "${name}" project?`} tab={1}>
<DeleteConfirm
description={DELETE_INFO.DELETE_PROJECTS.description}
deletedItems={DELETE_INFO.DELETE_PROJECTS.deletedItems}
onConfirmDelete={() => {
if (projectID) {
deleteProject({ variables: { projectID } });
hidePopup();
history.push('/projects');
}
}}
/>
</Popup>
</>
);
};
type GlobalTopNavbarProps = {
nameOnly?: boolean;
projectID: string | null;
name: string | null;
initialTab?: number;
popupContent?: JSX.Element;
menuType?: Array<string>;
projectMembers?: null | Array<TaskUser>;
onSaveProjectName?: (projectName: string) => void;
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
initialTab,
menuType,
projectID,
name,
popupContent,
projectMembers,
onSaveProjectName,
nameOnly,
}) => {
console.log(popupContent);
const { loading, data } = useMeQuery();
const { showPopup, hidePopup, setTab } = usePopup();
const history = useHistory();
const [currentTab, setCurrentTab] = useState(initialTab);
useEffect(() => {
setCurrentTab(initialTab);
}, [initialTab]);
const { userID, setUserID } = useContext(UserIDContext);
const onLogout = () => {
fetch('http://localhost:3333/auth/logout', {
method: 'POST',
@ -61,42 +265,26 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ projectID, name, proj
<Popup title={null} tab={0}>
<ProfileMenu
onLogout={onLogout}
onAdminConsole={() => {
history.push('/admin');
hidePopup();
}}
onProfile={() => {
history.push('/profile');
hidePopup();
}}
/>
</Popup>,
185,
195,
);
};
const onOpenSettings = ($target: React.RefObject<HTMLElement>) => {
showPopup(
$target,
<>
<Popup title={null} tab={0}>
<ProjectSettings
onDeleteProject={() => {
setTab(1, 325);
}}
/>
</Popup>
<Popup title={`Delete the "${name}" project?`} tab={1}>
<DeleteProject
name={name ?? ''}
onDeleteProject={() => {
if (projectID) {
deleteProject({ variables: { projectID } });
hidePopup();
history.push('/projects');
}
}}
/>
</Popup>
</>,
185,
);
console.log('maybe firing popup');
if (popupContent) {
console.log('showing popup');
showPopup($target, popupContent, 185);
}
};
if (!userID) {
@ -105,12 +293,25 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ projectID, name, proj
return (
<>
<TopNavbar
projectName={name}
name={name}
menuType={menuType}
onOpenProjectFinder={$target => {
showPopup(
$target,
<Popup tab={0} title={null}>
<ProjectFinder />
</Popup>,
);
}}
currentTab={currentTab}
user={data ? data.me : null}
onNotificationClick={() => {}}
onDashboardClick={() => {
history.push('/');
}}
projectMembers={projectMembers}
onProfileClick={onProfileClick}
onSaveProjectName={onSaveProjectName}
onSaveName={onSaveProjectName}
onOpenSettings={onOpenSettings}
/>
</>

View File

@ -41,20 +41,19 @@ const App = () => {
<>
<UserIDContext.Provider value={{ userID, setUserID }}>
<ThemeProvider theme={theme}>
<PopupProvider>
<NormalizeStyles />
<BaseStyles />
<Router history={history}>
<NormalizeStyles />
<BaseStyles />
<Router history={history}>
<PopupProvider>
{loading ? (
<div>loading</div>
) : (
<>
<Navbar />
<Routes history={history} />
</>
)}
</Router>
</PopupProvider>
</PopupProvider>
</Router>
</ThemeProvider>
</UserIDContext.Provider>
</>

View File

@ -5,6 +5,9 @@ import PopupMenu, { Popup, usePopup } from 'shared/components/PopupMenu';
import MemberManager from 'shared/components/MemberManager';
import { useRouteMatch, useHistory } from 'react-router';
import {
useDeleteTaskChecklistMutation,
useUpdateTaskChecklistNameMutation,
useCreateTaskChecklistMutation,
useFindTaskQuery,
useUpdateTaskDueDateMutation,
useSetTaskCompleteMutation,
@ -20,6 +23,82 @@ import UserIDContext from 'App/context';
import MiniProfile from 'shared/components/MiniProfile';
import DueDateManager from 'shared/components/DueDateManager';
import produce from 'immer';
import styled from 'styled-components';
import Button from 'shared/components/Button';
import Input from 'shared/components/Input';
import { useForm } from 'react-hook-form';
const calculateChecklistBadge = (draftState: any) => {
const total = draftState.checklists.reduce((prev: any, next: any) => {
return (
prev +
next.items.reduce((innerPrev: any, _item: any) => {
return innerPrev + 1;
}, 0)
);
}, 0);
const complete = draftState.checklists.reduce(
(prev: any, next: any) =>
prev +
next.items.reduce((innerPrev: any, item: any) => {
return innerPrev + (item.complete ? 1 : 0);
}, 0),
0,
);
return { total, complete };
};
const DeleteChecklistButton = styled(Button)`
width: 100%;
padding: 6px 12px;
margin-top: 8px;
`;
type CreateChecklistData = {
name: string;
};
const CreateChecklistForm = styled.form`
display: flex;
flex-direction: column;
`;
const CreateChecklistButton = styled(Button)`
margin-top: 8px;
padding: 6px 12px;
width: 100%;
`;
const CreateChecklistInput = styled(Input)`
margin-bottom: 8px;
`;
const InputError = styled.span`
color: rgba(${props => props.theme.colors.danger});
font-size: 12px;
`;
type CreateChecklistPopupProps = {
onCreateChecklist: (data: CreateChecklistData) => void;
};
const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChecklist }) => {
const { register, handleSubmit, errors } = useForm<CreateChecklistData>();
const createUser = (data: CreateChecklistData) => {
onCreateChecklist(data);
};
console.log(errors);
return (
<CreateChecklistForm onSubmit={handleSubmit(createUser)}>
<CreateChecklistInput
floatingLabel
width="100%"
label="Name"
id="name"
name="name"
variant="alternate"
ref={register({ required: 'Checklist name is required' })}
/>
<CreateChecklistButton type="submit">Create</CreateChecklistButton>
</CreateChecklistForm>
);
};
type DetailsProps = {
taskID: string;
@ -50,8 +129,93 @@ const Details: React.FC<DetailsProps> = ({
const match = useRouteMatch();
const [currentMemberTask, setCurrentMemberTask] = useState('');
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation();
const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation({
update: client => {
const cacheData: any = client.readQuery({
query: FindTaskDocument,
variables: { taskID },
});
const newData = produce(cacheData.findTask, (draftState: any) => {
const { complete, total } = calculateChecklistBadge(draftState);
if (!draftState.badges) {
draftState.badges = {
checklist: {},
};
}
draftState.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
});
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
data: {
findTask: newData,
},
});
},
});
const [deleteTaskChecklist] = useDeleteTaskChecklistMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
query: FindTaskDocument,
variables: { taskID },
});
console.log(deleteData);
const newData = produce(cacheData.findTask, (draftState: any) => {
draftState.checklists = cacheData.findTask.checklists.filter(
(checklist: any) => checklist.id !== deleteData.data.deleteTaskChecklist.taskChecklist.id,
);
const { complete, total } = calculateChecklistBadge(draftState);
if (!draftState.badges) {
draftState.badges = {
checklist: {},
};
}
draftState.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
if (complete === 0 && total === 0) {
draftState.badges.checklist = null;
}
});
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
data: {
findTask: newData,
},
});
},
});
const [updateTaskChecklistItemName] = useUpdateTaskChecklistItemNameMutation();
const [createTaskChecklist] = useCreateTaskChecklistMutation({
update: (client, createData) => {
const cacheData: any = client.readQuery({
query: FindTaskDocument,
variables: { taskID },
});
console.log(createData);
const newData = {
findTask: {
...cacheData.findTask,
checklists: [...cacheData.findTask.checklists, { ...createData.data.createTaskChecklist }],
},
};
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
data: newData,
});
},
});
const [updateTaskChecklistName] = useUpdateTaskChecklistNameMutation();
const [deleteTaskChecklistItem] = useDeleteTaskChecklistItemMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
@ -69,6 +233,17 @@ const Details: React.FC<DetailsProps> = ({
draftState.checklists[idx].items = cacheData.findTask.checklists[idx].items.filter(
(item: any) => item.id !== deleteData.data.deleteTaskChecklistItem.taskChecklistItem.id,
);
const { complete, total } = calculateChecklistBadge(draftState);
if (!draftState.badges) {
draftState.badges = {
checklist: {},
};
}
draftState.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}
});
client.writeQuery({
@ -97,8 +272,23 @@ const Details: React.FC<DetailsProps> = ({
...cacheData.findTask.checklists[idx].items,
{ ...newTaskItem.data.createTaskChecklistItem },
];
console.log(draftState.checklists.map((item: any) => item.items));
const { complete, total } = calculateChecklistBadge(draftState);
if (!draftState.badges) {
draftState.badges = {
checklist: {},
};
}
draftState.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
console.log(draftState.badges.checklist);
}
});
console.log(newData);
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
@ -156,11 +346,24 @@ const Details: React.FC<DetailsProps> = ({
updateTaskChecklistItemName({ variables: { taskChecklistItemID: itemID, name: itemName } });
}}
onCloseModal={() => history.push(projectURL)}
onChangeChecklistName={(checklistID, newName) => {
updateTaskChecklistName({ variables: { taskChecklistID: checklistID, name: newName } });
}}
onDeleteItem={itemID => {
deleteTaskChecklistItem({ variables: { taskChecklistItemID: itemID } });
}}
onToggleChecklistItem={(itemID, complete) => {
setTaskChecklistItemComplete({ variables: { taskChecklistItemID: itemID, complete } });
setTaskChecklistItemComplete({
variables: { taskChecklistItemID: itemID, complete },
optimisticResponse: {
__typename: 'Mutation',
setTaskChecklistItemComplete: {
__typename: 'TaskChecklistItem',
id: itemID,
complete,
},
},
});
}}
onAddItem={(taskChecklistID, name, position) => {
createTaskChecklistItem({ variables: { taskChecklistID, name, position } });
@ -202,6 +405,57 @@ const Details: React.FC<DetailsProps> = ({
);
}}
onOpenAddLabelPopup={onOpenAddLabelPopup}
onOpenAddChecklistPopup={(_task, $target) => {
showPopup(
$target,
<Popup
title={'Add checklist'}
tab={0}
onClose={() => {
hidePopup();
}}
>
<CreateChecklistPopup
onCreateChecklist={checklistData => {
let position = 65535;
console.log(data.findTask.checklists);
if (data.findTask.checklists) {
const [lastChecklist] = data.findTask.checklists.slice(-1);
console.log(`lastCheclist ${lastChecklist}`);
if (lastChecklist) {
position = lastChecklist.position * 2 + 1;
}
}
createTaskChecklist({
variables: {
taskID: data.findTask.id,
name: checklistData.name,
position,
},
});
hidePopup();
}}
/>
</Popup>,
);
}}
onDeleteChecklist={($target, checklistID) => {
showPopup(
$target,
<Popup tab={0} title="Delete checklist?" onClose={() => hidePopup()}>
<p>Deleting a checklist is permanent and there is no way to get it back.</p>
<DeleteChecklistButton
color="danger"
onClick={() => {
deleteTaskChecklist({ variables: { taskChecklistID: checklistID } });
hidePopup();
}}
>
Delete Checklist
</DeleteChecklistButton>
</Popup>,
);
}}
onOpenDueDatePopop={(task, $targetRef) => {
showPopup(
$targetRef,

View File

@ -1,7 +1,9 @@
import React, { useState, useRef, useContext, useEffect } from 'react';
import GlobalTopNavbar from 'App/TopNavbar';
import { MENU_TYPES } from 'shared/components/TopNavbar';
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
import styled from 'styled-components/macro';
import { Bolt, ToggleOn, Tags } from 'shared/icons';
import { DataProxy } from '@apollo/client';
import { Bolt, ToggleOn, Tags, CheckCircle, Sort, Filter } from 'shared/icons';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom';
import {
@ -26,6 +28,7 @@ import {
useCreateProjectLabelMutation,
useUnassignTaskMutation,
useUpdateTaskDueDateMutation,
FindProjectQuery,
} from 'shared/generated/graphql';
import TaskAssignee from 'shared/components/TaskAssignee';
@ -42,11 +45,35 @@ import produce from 'immer';
import MiniProfile from 'shared/components/MiniProfile';
import Details from './Details';
import { useApolloClient } from '@apollo/react-hooks';
import { DocumentNode } from 'graphql';
import UserIDContext from 'App/context';
import DueDateManager from 'shared/components/DueDateManager';
type UpdateCacheFn<T> = (cache: T) => T;
function updateApolloCache<T>(client: DataProxy, document: DocumentNode, update: UpdateCacheFn<T>, variables?: object) {
let queryArgs: DataProxy.Query<any>;
if (variables) {
queryArgs = {
query: document,
variables,
};
} else {
queryArgs = {
query: document,
};
}
const cache: T | null = client.readQuery(queryArgs);
if (cache) {
const newCache = update(cache);
client.writeQuery({
...queryArgs,
data: newCache,
});
}
}
const getCacheData = (client: any, projectID: string) => {
const cacheData: any = client.readQuery({
const cacheData: FindProjectQuery = client.readQuery({
query: FindProjectDocument,
variables: {
projectId: projectID,
@ -220,7 +247,7 @@ const initialQuickCardEditorState: QuickCardEditorState = {
const ProjectBar = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
justify-content: space-between;
height: 40px;
padding: 0 12px;
`;
@ -302,13 +329,17 @@ const Project = () => {
const [createTaskGroup] = useCreateTaskGroupMutation({
onCompleted: newTaskGroupData => {},
update: (client, newTaskGroupData) => {
const cacheData = getCacheData(client, projectID);
const newData = {
...cacheData.findProject,
taskGroups: [...cacheData.findProject.taskGroups, { ...newTaskGroupData.data.createTaskGroup, tasks: [] }],
};
writeCacheData(client, projectID, cacheData, newData);
updateApolloCache<FindProjectQuery>(
client,
FindProjectDocument,
cache => {
console.log(cache);
return produce(cache, draftCache => {
draftCache.findProject.taskGroups.push({ ...newTaskGroupData.data.createTaskGroup, tasks: [] });
});
},
{ projectId: projectID },
);
},
});
@ -316,7 +347,7 @@ const Project = () => {
onCompleted: newTaskData => {},
update: (client, newTaskData) => {
const cacheData = getCacheData(client, projectID);
const newTaskGroups = produce(cacheData.findProject.taskGroups, (draftState: any) => {
const newTaskGroups = produce(cacheData.findProject.taskGroups, draftState => {
const targetIndex = draftState.findIndex(
(taskGroup: any) => taskGroup.id === newTaskData.data.createTask.taskGroup.id,
);
@ -388,14 +419,18 @@ const Project = () => {
createTask: {
__typename: 'Task',
id: '' + Math.round(Math.random() * -1000000),
name: name,
name,
complete: false,
taskGroup: {
__typename: 'TaskGroup',
id: taskGroup.id,
name: taskGroup.name,
position: taskGroup.position,
},
position: position,
badges: {
checklist: null,
},
position,
dueDate: null,
description: null,
labels: [],
@ -509,11 +544,28 @@ const Project = () => {
onSaveProjectName={projectName => {
updateProjectName({ variables: { projectID, name: projectName } });
}}
popupContent={<ProjectPopup history={history} name={data.findProject.name} projectID={projectID} />}
menuType={MENU_TYPES.PROJECT_MENU}
initialTab={0}
projectMembers={data.findProject.members}
projectID={projectID}
name={data.findProject.name}
/>
<ProjectBar>
<ProjectActions>
<ProjectAction>
<CheckCircle width={13} height={13} />
<ProjectActionText>All Tasks</ProjectActionText>
</ProjectAction>
<ProjectAction>
<Filter width={13} height={13} />
<ProjectActionText>Filter</ProjectActionText>
</ProjectAction>
<ProjectAction>
<Sort width={13} height={13} />
<ProjectActionText>Sort</ProjectActionText>
</ProjectAction>
</ProjectActions>
<ProjectActions>
<ProjectAction
ref={$labelsRef}

View File

@ -137,6 +137,7 @@ const ProjectTileName = styled.div<{ centered?: boolean }>`
`;
const Wrapper = styled.div`
position: relative;
display: flex;
flex-direction: row;
align-items: flex-start;
@ -146,10 +147,24 @@ const Wrapper = styled.div`
const ProjectSectionTitleWrapper = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
height: 32px;
margin-bottom: 24px;
padding: 8px 0;
position: relative;
margin-top: 16px;
`;
const SectionActions = styled.div`
display: flex;
align-items: center;
`;
const SectionAction = styled(Button)`
padding: 6px 12px;
`;
const SectionActionLink = styled(Link)`
margin-right: 8px;
`;
const ProjectSectionTitle = styled.h3`
@ -171,13 +186,19 @@ const ProjectGrid = styled.div`
`;
const AddTeamButton = styled(Button)`
padding: 6px 12px;
float: right;
position: absolute;
top: 6px;
right: 12px;
`;
type ShowNewProject = {
open: boolean;
initialTeamID: null | string;
};
const ProjectLink = styled(Link)``;
const Projects = () => {
const { showPopup } = usePopup();
const { showPopup, hidePopup } = usePopup();
const { loading, data } = useGetProjectsQuery();
useEffect(() => {
document.title = 'Citadel';
@ -202,9 +223,24 @@ const Projects = () => {
});
},
});
const [showNewProject, setShowNewProject] = useState(false);
const [showNewProject, setShowNewProject] = useState<ShowNewProject>({ open: false, initialTeamID: null });
const { userID, setUserID } = useContext(UserIDContext);
const [createTeam] = useCreateTeamMutation();
const [createTeam] = useCreateTeamMutation({
update: (client, createData) => {
const cacheData: any = client.readQuery({
query: GetProjectsDocument,
});
const newData = {
...cacheData,
teams: [...cacheData.teams, { ...createData.data.createTeam }],
};
client.writeQuery({
query: GetProjectsDocument,
data: newData,
});
},
});
if (loading) {
return (
<>
@ -234,11 +270,18 @@ const Projects = () => {
onClick={$target => {
showPopup(
$target,
<Popup title="Create team" tab={0}>
<Popup
title="Create team"
tab={0}
onClose={() => {
hidePopup();
}}
>
<CreateTeamForm
onCreateTeam={teamName => {
if (organizationID) {
createTeam({ variables: { name: teamName, organizationID } });
hidePopup();
}
}}
/>
@ -253,6 +296,17 @@ const Projects = () => {
<div key={team.id}>
<ProjectSectionTitleWrapper>
<ProjectSectionTitle>{team.name}</ProjectSectionTitle>
<SectionActions>
<SectionActionLink to={`/teams/${team.id}`}>
<SectionAction variant="outline">Projects</SectionAction>
</SectionActionLink>
<SectionActionLink to="/">
<SectionAction variant="outline">Members</SectionAction>
</SectionActionLink>
<SectionActionLink to="/">
<SectionAction variant="outline">Settings</SectionAction>
</SectionActionLink>
</SectionActions>
</ProjectSectionTitleWrapper>
<ProjectList>
{team.projects.map((project, idx) => (
@ -268,7 +322,7 @@ const Projects = () => {
<ProjectListItem>
<ProjectAddTile
onClick={() => {
setShowNewProject(true);
setShowNewProject({ open: true, initialTeamID: team.id });
}}
>
<ProjectTileFade />
@ -281,16 +335,17 @@ const Projects = () => {
</div>
);
})}
{showNewProject && (
{showNewProject.open && (
<NewProject
initialTeamID={showNewProject.initialTeamID}
onCreateProject={(name, teamID) => {
if (userID) {
createProject({ variables: { teamID, name, userID } });
setShowNewProject(false);
setShowNewProject({ open: false, initialTeamID: null });
}
}}
onClose={() => {
setShowNewProject(false);
setShowNewProject({ open: false, initialTeamID: null });
}}
teams={teams}
/>

227
web/src/Teams/index.tsx Normal file
View File

@ -0,0 +1,227 @@
import React, { useState, useContext, useEffect } from 'react';
import styled from 'styled-components/macro';
import { MENU_TYPES } from 'shared/components/TopNavbar';
import GlobalTopNavbar from 'App/TopNavbar';
import { useGetTeamQuery, useDeleteTeamMutation, GetProjectsDocument } from 'shared/generated/graphql';
import { useParams, useHistory } from 'react-router';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { History } from 'history';
import produce from 'immer';
import { TeamSettings, DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings';
import { Link } from 'react-router-dom';
const ProjectAddTile = styled.div`
background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4);
background-size: cover;
background-position: 50%;
color: #fff;
line-height: 20px;
padding: 8px;
position: relative;
text-decoration: none;
border-radius: 3px;
display: block;
`;
const ProjectTile = styled(Link)<{ color: string }>`
background-color: ${props => props.color};
background-size: cover;
background-position: 50%;
color: #fff;
line-height: 20px;
padding: 8px;
position: relative;
text-decoration: none;
border-radius: 3px;
display: block;
`;
const ProjectTileFade = styled.div`
background-color: rgba(0, 0, 0, 0.15);
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
`;
const ProjectListItem = styled.li`
width: 23.5%;
padding: 0;
margin: 0 2% 2% 0;
box-sizing: border-box;
position: relative;
cursor: pointer;
&:hover ${ProjectTileFade} {
background-color: rgba(0, 0, 0, 0.25);
}
`;
const ProjectList = styled.ul`
display: flex;
flex-wrap: wrap;
& ${ProjectListItem}:nth-of-type(4n) {
margin-right: 0;
}
`;
const ProjectTileDetails = styled.div`
display: flex;
height: 80px;
position: relative;
flex-direction: column;
justify-content: space-between;
`;
const ProjectAddTileDetails = styled.div`
display: flex;
height: 80px;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
`;
const ProjectTileName = styled.div<{ centered?: boolean }>`
flex: 0 0 auto;
font-size: 16px;
font-weight: 700;
display: inline-block;
overflow: hidden;
max-height: 40px;
width: 100%;
word-wrap: break-word;
${props => props.centered && 'text-align: center;'}
`;
const Wrapper = styled.div`
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: center;
`;
type TeamPopupProps = {
history: History<History.PoorMansUnknown>;
name: string;
teamID: string;
};
export const TeamPopup: React.FC<TeamPopupProps> = ({ history, name, teamID }) => {
const { hidePopup, setTab } = usePopup();
const [deleteTeam] = useDeleteTeamMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
query: GetProjectsDocument,
});
console.log(cacheData);
console.log(deleteData);
const newData = produce(cacheData, (draftState: any) => {
draftState.teams = draftState.teams.filter((team: any) => team.id !== deleteData.data.deleteTeam.team.id);
draftState.projects = draftState.projects.filter(
(project: any) => project.team.id !== deleteData.data.deleteTeam.team.id,
);
});
client.writeQuery({
query: GetProjectsDocument,
data: {
...newData,
},
});
},
});
return (
<>
<Popup title={null} tab={0}>
<TeamSettings
onDeleteTeam={() => {
setTab(1, 340);
}}
/>
</Popup>
<Popup title={`Delete the "${name}" team?`} tab={1} onClose={() => hidePopup()}>
<DeleteConfirm
description={DELETE_INFO.DELETE_TEAMS.description}
deletedItems={DELETE_INFO.DELETE_TEAMS.deletedItems}
onConfirmDelete={() => {
if (teamID) {
deleteTeam({ variables: { teamID } });
hidePopup();
history.push('/projects');
}
}}
/>
</Popup>
</>
);
};
const ProjectsContainer = styled.div`
margin: 40px 16px 0;
width: 100%;
max-width: 825px;
min-width: 288px;
`;
type TeamsRouteProps = {
teamID: string;
};
const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
const Projects = () => {
const { teamID } = useParams<TeamsRouteProps>();
const history = useHistory();
const { loading, data } = useGetTeamQuery({ variables: { teamID } });
useEffect(() => {
document.title = 'Citadel | Teams';
}, []);
if (loading) {
return (
<>
<span>loading</span>
</>
);
}
if (data) {
return (
<>
<GlobalTopNavbar
menuType={MENU_TYPES.TEAM_MENU}
initialTab={0}
popupContent={<TeamPopup history={history} name={data.findTeam.name} teamID={teamID} />}
onSaveProjectName={() => {}}
projectID={null}
name={data.findTeam.name}
/>
<Wrapper>
<ProjectsContainer>
<ProjectList>
{data.projects.map((project, idx) => (
<ProjectListItem key={project.id}>
<ProjectTile color={colors[idx % 5]} to={`/projects/${project.id}`}>
<ProjectTileFade />
<ProjectTileDetails>
<ProjectTileName>{project.name}</ProjectTileName>
</ProjectTileDetails>
</ProjectTile>
</ProjectListItem>
))}
</ProjectList>
</ProjectsContainer>
</Wrapper>
</>
);
}
return <div>Error!</div>;
};
export default Projects;

View File

@ -17,6 +17,15 @@ type ContextMenuEvent = {
taskGroupID: string;
};
type User = {
id: string;
fullName: string;
username: string;
email: string;
role: string;
profileIcon: ProfileIcon;
};
type TaskUser = {
id: string;
fullName: string;

10
web/src/projects.d.ts vendored
View File

@ -47,10 +47,20 @@ type TaskChecklistItem = {
dueDate?: null | string;
};
type ChecklistBadge = {
complete: number;
total: number;
};
type TaskBadges = {
checklist?: ChecklistBadge | null;
};
type Task = {
id: string;
taskGroup: InnerTaskGroup;
name: string;
badges?: TaskBadges;
position: number;
dueDate?: string;
complete?: boolean;

View File

@ -1,7 +1,10 @@
import React, { useRef } from 'react';
import Admin from '.';
import { theme } from 'App/ThemeStyles';
import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles';
import { ThemeProvider } from 'styled-components';
import { action } from '@storybook/addon-actions';
export default {
component: Admin,
@ -19,7 +22,27 @@ export const Default = () => {
<>
<NormalizeStyles />
<BaseStyles />
<Admin />
<ThemeProvider theme={theme}>
<Admin
onInviteUser={action('invite user')}
initialTab={1}
users={[
{
id: '1',
username: 'jordanthedev',
email: 'jordan@jordanthedev.com',
role: 'Admin',
fullName: 'Jordan Knott',
profileIcon: {
bgColor: '#fff',
initials: 'JK',
url: null,
},
},
]}
onAddUser={action('add user')}
/>
</ThemeProvider>
</>
);
};

View File

@ -1,34 +1,28 @@
import React, { useState, useRef } from 'react';
import styled from 'styled-components';
import { User, Plus } from 'shared/icons';
import { User, Plus, Lock, Pencil, Trash } from 'shared/icons';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-material.css';
import Button from 'shared/components/Button';
const NewUserButton = styled.button`
outline: none;
border: none;
cursor: pointer;
line-height: 20px;
padding: 0.75rem;
background-color: transparent;
display: flex;
align-items: center;
justify-content: center;
color: rgba(115, 103, 240);
font-size: 14px;
border-radius: 0.5rem;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image: initial;
border-color: rgba(115, 103, 240);
span {
padding-left: 0.5rem;
}
const NewUserButton = styled(Button)`
padding: 6px 12px;
margin-right: 12px;
`;
const InviteUserButton = styled(Button)`
padding: 6px 12px;
margin-right: 8px;
`;
const MemberActions = styled.div`
display: flex;
justify-content: flex-end;
align-items: center;
`;
const GridTable = styled.div`
height: 620px;
`;
@ -93,8 +87,29 @@ const Header = styled.div`
min-height: 112px;
`;
const ActionButtonsContainer = styled.div`
display: flex;
align-items: center;
`;
const EditUserIcon = styled(Pencil)`
margin-right: 8px;
`;
const LockUserIcon = styled(Lock)`
margin-right: 8px;
`;
const DeleteUserIcon = styled(Trash)``;
const ActionButtons = () => {
return <span>Hello!</span>;
return (
<>
<EditUserIcon width={16} height={16} />
<LockUserIcon width={16} height={16} />
<DeleteUserIcon width={16} height={16} />
</>
);
};
const data = {
defaultColDef: {
@ -103,16 +118,14 @@ const data = {
},
columnDefs: [
{
minWidth: 125,
width: 125,
minWidth: 55,
width: 55,
headerCheckboxSelection: true,
checkboxSelection: true,
headerName: 'ID',
field: 'id',
},
{ minWidth: 210, headerName: 'Username', editable: true, field: 'username' },
{ minWidth: 225, headerName: 'Email', field: 'email' },
{ minWidth: 200, headerName: 'Name', editable: true, field: 'full_name' },
{ minWidth: 200, headerName: 'Name', editable: true, field: 'fullName' },
{ minWidth: 200, headerName: 'Role', editable: true, field: 'role' },
{
minWidth: 200,
@ -123,14 +136,13 @@ const data = {
frameworkComponents: {
actionButtons: ActionButtons,
},
rowData: [
{ id: '1', full_name: 'Jordan Knott', username: 'jordan', email: 'jordan@jordanthedev.com', role: 'Admin' },
{ id: '2', full_name: 'Jordan Test', username: 'jordantest', email: 'jordan@jordanthedev.com', role: 'Admin' },
{ id: '3', full_name: 'Jordan Other', username: 'alphatest1050', email: 'jordan@jordanthedev.com', role: 'Admin' },
{ id: '5', full_name: 'Jordan French', username: 'other', email: 'jordan@jordanthedev.com', role: 'Admin' },
],
};
const ListTable = () => {
type ListTableProps = {
users: Array<User>;
};
const ListTable: React.FC<ListTableProps> = ({ users }) => {
return (
<Root>
<div className="ag-theme-material" style={{ height: '296px', width: '100%' }}>
@ -138,7 +150,7 @@ const ListTable = () => {
rowSelection="multiple"
defaultColDef={data.defaultColDef}
columnDefs={data.columnDefs}
rowData={data.rowData}
rowData={users}
frameworkComponents={data.frameworkComponents}
onFirstDataRendered={params => {
params.api.sizeColumnsToFit();
@ -146,7 +158,7 @@ const ListTable = () => {
onGridSizeChanged={params => {
params.api.sizeColumnsToFit();
}}
></AgGridReact>
/>
</div>
</Root>
);
@ -184,6 +196,7 @@ const TabNavContent = styled.ul`
const TabNavItem = styled.li`
padding: 0.35rem 0.3rem;
height: 48px;
display: block;
position: relative;
`;
@ -282,9 +295,16 @@ const NavItem: React.FC<NavItemProps> = ({ active, name, tab, onClick }) => {
);
};
const Admin = () => {
const [currentTop, setTop] = useState(0);
const [currentTab, setTab] = useState(0);
type AdminProps = {
initialTab: number;
onAddUser: ($target: React.RefObject<HTMLElement>) => void;
onInviteUser: ($target: React.RefObject<HTMLElement>) => void;
users: Array<User>;
};
const Admin: React.FC<AdminProps> = ({ initialTab, onAddUser, onInviteUser, users }) => {
const [currentTop, setTop] = useState(initialTab * 48);
const [currentTab, setTab] = useState(initialTab);
const $tabNav = useRef<HTMLDivElement>(null);
return (
<Container>
@ -309,11 +329,17 @@ const Admin = () => {
</TabNav>
<TabContentWrapper>
<TabContent>
<NewUserButton>
<Plus color="rgba(115, 103, 240)" size={10} />
<span>Add New</span>
</NewUserButton>
<ListTable />
<MemberActions>
<NewUserButton variant="outline" onClick={onAddUser}>
<Plus color="rgba(115, 103, 240)" size={10} />
<span style={{ paddingLeft: '5px' }}>Create member</span>
</NewUserButton>
<InviteUserButton variant="outline" onClick={onInviteUser}>
<Plus color="rgba(115, 103, 240)" size={10} />
<span style={{ paddingLeft: '5px' }}>Invite member</span>
</InviteUserButton>
</MemberActions>
<ListTable users={users} />
</TabContent>
</TabContentWrapper>
</Container>

View File

@ -42,7 +42,7 @@ type Props = {
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
description?: null | string;
dueDate?: DueDate;
checklists?: Checklist;
checklists?: Checklist | null;
labels?: Array<ProjectLabel>;
watched?: boolean;
wrapperProps?: any;

View File

@ -501,7 +501,7 @@ const ChecklistTitleEditor = React.forwardRef(
);
type ChecklistProps = {
checklistID: string;
onDeleteChecklist: (checklistID: string) => void;
onDeleteChecklist: ($target: React.RefObject<HTMLElement>, checklistID: string) => void;
name: string;
onChangeName: (item: string) => void;
onToggleItem: (taskID: string, complete: boolean) => void;
@ -554,8 +554,8 @@ const Checklist: React.FC<ChecklistProps> = ({
<WindowTitleText onClick={() => setEditting(true)}>{name}</WindowTitleText>
<WindowOptions>
<DeleteButton
onClick={() => {
onDeleteChecklist(checklistID);
onClick={$target => {
onDeleteChecklist($target, checklistID);
}}
color="danger"
variant="outline"

View File

@ -51,6 +51,7 @@ export const Default = () => {
</Container>
{menu.isOpen && (
<DropdownMenu
onAdminConsole={action('admin')}
onCloseDropdown={() => {
setMenu({ top: 0, left: 0, isOpen: false });
}}

View File

@ -1,6 +1,6 @@
import React, { useRef } from 'react';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { Exit, User } from 'shared/icons';
import { Exit, User, Cog } from 'shared/icons';
import { Separator, Container, WrapperDiamond, Wrapper, ActionsList, ActionItem, ActionTitle } from './Styles';
type DropdownMenuProps = {
@ -8,15 +8,16 @@ type DropdownMenuProps = {
top: number;
onLogout: () => void;
onCloseDropdown: () => void;
onAdminConsole: () => void;
};
const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout, onCloseDropdown }) => {
const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout, onCloseDropdown, onAdminConsole }) => {
const $containerRef = useRef<HTMLDivElement>(null);
useOnOutsideClick($containerRef, true, onCloseDropdown, null);
return (
<Container ref={$containerRef} left={left} top={top}>
<Wrapper>
<ActionItem>
<ActionItem onClick={onAdminConsole}>
<User size={16} color="#c2c6dc" />
<ActionTitle>Profile</ActionTitle>
</ActionItem>
@ -36,16 +37,21 @@ const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout, onClos
type ProfileMenuProps = {
onProfile: () => void;
onLogout: () => void;
onAdminConsole: () => void;
};
const ProfileMenu: React.FC<ProfileMenuProps> = ({ onProfile, onLogout }) => {
const ProfileMenu: React.FC<ProfileMenuProps> = ({ onAdminConsole, onProfile, onLogout }) => {
return (
<>
<ActionItem onClick={onAdminConsole}>
<Cog size={16} color="#c2c6dc" />
<ActionTitle>Admin Console</ActionTitle>
</ActionItem>
<Separator />
<ActionItem onClick={onProfile}>
<User size={16} color="#c2c6dc" />
<ActionTitle>Profile</ActionTitle>
</ActionItem>
<Separator />
<ActionsList>
<ActionItem onClick={onLogout}>
<Exit size={16} color="#c2c6dc" />

View File

@ -80,8 +80,10 @@ type InputProps = {
variant?: 'normal' | 'alternate';
label?: string;
width?: string;
floatingLabel?: boolean;
placeholder?: string;
icon?: JSX.Element;
autocomplete?: boolean;
id?: string;
name?: string;
className?: string;
@ -95,6 +97,7 @@ const Input = React.forwardRef(
{
width = 'auto',
variant = 'normal',
autocomplete,
label,
placeholder,
icon,
@ -102,6 +105,7 @@ const Input = React.forwardRef(
onChange,
className,
onClick,
floatingLabel,
value: initialValue,
id,
}: InputProps,
@ -124,12 +128,13 @@ const Input = React.forwardRef(
return (
<InputWrapper className={className} width={width}>
<InputInput
hasValue={value !== ''}
hasValue={floatingLabel || value !== ''}
ref={$ref}
id={id}
name={name}
onClick={onClick}
onChange={handleChange}
autoComplete={autocomplete ? 'on' : 'off'}
value={value}
hasIcon={typeof icon !== 'undefined'}
width={width}

View File

@ -1,6 +1,7 @@
import styled from 'styled-components';
export const Container = styled.div`
flex: 1;
user-select: none;
white-space: nowrap;
margin-bottom: 8px;

View File

@ -181,6 +181,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
onClick={() => {
onTaskClick(task);
}}
checklists={task.badges && task.badges.checklist}
onCardMemberClick={onCardMemberClick}
onContextMenu={onQuickEditorOpen}
/>

View File

@ -38,7 +38,7 @@ const Login = ({ onSubmit }: LoginProps) => {
<LoginFormWrapper>
<LoginFormContainer>
<LogoWrapper>
<Citadel size={42} />
<Citadel width={42} height={42} />
<LogoTitle>Citadel</LogoTitle>
</LogoWrapper>
<Title>Login</Title>
@ -66,7 +66,7 @@ const Login = ({ onSubmit }: LoginProps) => {
ref={register({ required: 'Password is required' })}
/>
<FormIcon>
<Lock color="#c2c6dc" size={20} />
<Lock width={20} height={20} />
</FormIcon>
</FormLabel>
{errors.password && <FormError>{errors.password.message}</FormError>}

View File

@ -28,10 +28,10 @@ export const Default = () => {
<PrimaryLogo />
<ButtonContainer>
<ActionButton name="Home">
<Home size={28} color="#c2c6dc" />
<Home width={28} height={28} />
</ActionButton>
<ActionButton name="Home">
<Home size={28} color="#c2c6dc" />
<Home width={28} height={28} />
</ActionButton>
</ButtonContainer>
</Navbar>

View File

@ -35,7 +35,7 @@ export const ButtonContainer: React.FC = ({ children }) => (
export const PrimaryLogo = () => {
return (
<LogoWrapper>
<Citadel size={42} />
<Citadel width={42} height={42} />
<LogoTitle>Citadel</LogoTitle>
</LogoWrapper>
);

View File

@ -23,6 +23,7 @@ export const Default = () => {
<NormalizeStyles />
<BaseStyles />
<NewProject
initialTeamID={null}
onCreateProject={action('create project')}
teams={[{ name: 'General', id: 'general', createdAt: '' }]}
onClose={() => {}}

View File

@ -207,14 +207,15 @@ const CreateButton = styled.button`
}
`;
type NewProjectProps = {
initialTeamID: string | null;
teams: Array<Team>;
onClose: () => void;
onCreateProject: (projectName: string, teamID: string) => void;
};
const NewProject: React.FC<NewProjectProps> = ({ teams, onClose, onCreateProject }) => {
const NewProject: React.FC<NewProjectProps> = ({ initialTeamID, teams, onClose, onCreateProject }) => {
const [projectName, setProjectName] = useState('');
const [team, setTeam] = useState<null | string>(null);
const [team, setTeam] = useState<null | string>(initialTeamID);
const options = teams.map(t => ({ label: t.name, value: t.id }));
return (
<Overlay>

View File

@ -58,7 +58,7 @@ const LabelManager: React.FC<Props> = ({ labels, taskLabels, onLabelToggle, onLa
onLabelEdit(label.id);
}}
>
<Pencil color="#c2c6dc" />
<Pencil width={16} height={16} />
</LabelIcon>
<CardLabel
key={label.id}

View File

@ -42,7 +42,6 @@ export const HeaderTitle = styled.span`
box-sizing: border-box;
color: #c2c6dc;
display: block;
line-height: 40px;
border-bottom: 1px solid #414561;
margin: 0 12px;
overflow: hidden;
@ -51,6 +50,13 @@ export const HeaderTitle = styled.span`
text-overflow: ellipsis;
white-space: nowrap;
z-index: 1;
height: 40px;
line-height: 18px;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
`;
export const Content = styled.div`
@ -138,7 +144,7 @@ export const CardLabel = styled.span<{ active: boolean; color: string }>`
`;
export const CloseButton = styled.div`
padding: 10px 12px 10px 8px;
padding: 18px 18px 14px 12px;
position: absolute;
top: 0;
right: 0;
@ -312,7 +318,7 @@ export const CreateLabelButton = styled.button`
`;
export const PreviousButton = styled.div`
padding: 10px 12px 10px 8px;
padding: 18px 18px 14px 12px;
position: absolute;
top: 0;
left: 0;

View File

@ -2,7 +2,6 @@ import React, { useRef } from 'react';
import styled from 'styled-components';
export const Container = styled.div<{ size: number | string; bgColor: string | null; backgroundURL: string | null }>`
margin-left: 10px;
width: ${props => props.size}px;
height: ${props => props.size}px;
border-radius: 9999px;

View File

@ -52,6 +52,21 @@ const ProjectSettings: React.FC<Props> = ({ onDeleteProject }) => {
);
};
type TeamSettingsProps = {
onDeleteTeam: () => void;
};
export const TeamSettings: React.FC<TeamSettingsProps> = ({ onDeleteTeam }) => {
return (
<>
<ListActionsWrapper>
<ListActionItemWrapper onClick={() => onDeleteTeam()}>
<ListActionItem>Delete Team</ListActionItem>
</ListActionItemWrapper>
</ListActionsWrapper>
</>
);
};
const ConfirmWrapper = styled.div``;
const ConfirmSubTitle = styled.h3`
@ -76,25 +91,40 @@ const ConfirmDeleteButton = styled(Button)`
padding: 6px 12px;
`;
type DeleteProjectProps = {
name: string;
onDeleteProject: () => void;
type DeleteConfirmProps = {
description: string;
deletedItems: Array<string>;
onConfirmDelete: () => void;
};
const DeleteProject: React.FC<DeleteProjectProps> = ({ name, onDeleteProject }) => {
export const DELETE_INFO = {
DELETE_PROJECTS: {
description: 'Deleting the project will also delete the following:',
deletedItems: ['Task groups and tasks'],
},
DELETE_TEAMS: {
description: 'Deleting the team will also delete the following:',
deletedItems: ['Projects under the team', 'All task groups & tasks associated with the team projects'],
},
};
const DeleteConfirm: React.FC<DeleteConfirmProps> = ({ description, deletedItems, onConfirmDelete }) => {
return (
<ConfirmWrapper>
<ConfirmDescription>
Deleting the project will also delete the following:
{description}
<DeleteList>
<DeleteListItem>Task groups and tasks</DeleteListItem>
{deletedItems.map(item => (
<DeleteListItem>{item}</DeleteListItem>
))}
</DeleteList>
</ConfirmDescription>
<ConfirmDeleteButton onClick={() => onDeleteProject()} color="danger">
<ConfirmDeleteButton onClick={() => onConfirmDelete()} color="danger">
Delete
</ConfirmDeleteButton>
</ConfirmWrapper>
);
};
export { DeleteProject };
export { DeleteConfirm };
export default ProjectSettings;

View File

@ -71,6 +71,9 @@ export const Default = () => {
onToggleTaskComplete={action('toggle task complete')}
onToggleChecklistItem={action('toggle checklist item')}
onOpenAddLabelPopup={action('open add label popup')}
onChangeChecklistName={action('change checklist name')}
onDeleteChecklist={action('delete checklist')}
onOpenAddChecklistPopup={action(' open checklist')}
onOpenDueDatePopop={action('open due date popup')}
/>
);

View File

@ -140,13 +140,19 @@ type TaskDetailsProps = {
onOpenAddMemberPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenAddLabelPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenDueDatePopop: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenAddChecklistPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onMemberProfile: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
onChangeChecklistName: (checklistID: string, name: string) => void;
onDeleteChecklist: ($target: React.RefObject<HTMLElement>, checklistID: string) => void;
onCloseModal: () => void;
};
const TaskDetails: React.FC<TaskDetailsProps> = ({
task,
onDeleteChecklist,
onTaskNameChange,
onOpenAddChecklistPopup,
onChangeChecklistName,
onToggleTaskComplete,
onTaskDescriptionChange,
onChangeItemName,
@ -183,6 +189,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
const onAddMember = ($target: React.RefObject<HTMLElement>) => {
onOpenAddMemberPopup(task, $target);
};
const onAddChecklist = ($target: React.RefObject<HTMLElement>) => {
onOpenAddChecklistPopup(task, $target)
}
const $dueDateLabel = useRef<HTMLDivElement>(null);
const $addLabelRef = useRef<HTMLDivElement>(null);
@ -290,16 +299,16 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
name={checklist.name}
checklistID={checklist.id}
items={checklist.items}
onDeleteChecklist={() => {}}
onChangeName={() => {}}
onDeleteChecklist={onDeleteChecklist}
onChangeName={newName => onChangeChecklistName(checklist.id, newName)}
onToggleItem={onToggleChecklistItem}
onDeleteItem={onDeleteItem}
onAddItem={n => {
if (task.checklists) {
let position = 1;
const lastChecklist = task.checklists.sort((a, b) => a.position - b.position)[-1];
if (lastChecklist) {
position = lastChecklist.position * 2 + 1;
let position = 65535;
const [lastItem] = checklist.items.sort((a, b) => a.position - b.position).slice(-1);
if (lastItem) {
position = lastItem.position * 2 + 1;
}
onAddItem(checklist.id, n, position);
}
@ -317,7 +326,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
</ActionButton>
<ActionButton onClick={$target => onAddMember($target)}>Members</ActionButton>
<ActionButton onClick={$target => onAddLabel($target)}>Labels</ActionButton>
<ActionButton>Checklist</ActionButton>
<ActionButton onClick={$target => onAddChecklist($target)}>Checklist</ActionButton>
<ActionButton onClick={$target => onOpenDueDatePopop(task, $target)}>Due Date</ActionButton>
<ActionButton>Attachment</ActionButton>
<ActionButton>Cover</ActionButton>

View File

@ -2,6 +2,8 @@ import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea';
import { mixin } from 'shared/utils/styles';
import Button from 'shared/components/Button';
import { Citadel } from 'shared/icons';
import { Link } from 'react-router-dom';
export const NavbarWrapper = styled.div`
width: 100%;
@ -9,7 +11,6 @@ export const NavbarWrapper = styled.div`
export const ProjectMembers = styled.div`
display: flex;
padding-right: 18px;
align-items: center;
`;
export const NavbarHeader = styled.header`
@ -34,9 +35,9 @@ export const BreadcrumpSeparator = styled.span`
`;
export const ProjectActions = styled.div`
flex: 1;
align-items: flex-start;
display: flex;
flex: 1;
flex-direction: column;
min-width: 1px;
`;
@ -56,10 +57,11 @@ export const ProfileNameWrapper = styled.div`
line-height: 1.25;
`;
export const NotificationContainer = styled.div`
export const IconContainer = styled.div`
margin-right: 20px;
cursor: pointer;
`;
export const ProfileNamePrimary = styled.div`
color: #c2c6dc;
font-weight: 600;
@ -70,7 +72,6 @@ export const ProfileNameSecondary = styled.small`
`;
export const ProfileIcon = styled.div<{ bgColor: string | null; backgroundURL: string | null }>`
margin-left: 10px;
width: 40px;
height: 40px;
border-radius: 9999px;
@ -84,9 +85,9 @@ export const ProfileIcon = styled.div<{ bgColor: string | null; backgroundURL: s
background-size: contain;
`;
export const ProjectMeta = styled.div`
export const ProjectMeta = styled.div<{ nameOnly?: boolean }>`
display: flex;
padding-top: 9px;
${props => !props.nameOnly && 'padding-top: 9px;'}
margin-left: -14px;
align-items: center;
max-width: 100%;
@ -188,7 +189,7 @@ export const ProjectSwitcher = styled.button`
export const Separator = styled.div`
color: #c2c6dc;
font-size: 16px;
font-size: 20px;
padding-left: 4px;
padding-right: 4px;
`;
@ -214,3 +215,36 @@ export const InviteButton = styled(Button)`
margin: 0 0 0 8px;
padding: 6px 12px;
`;
export const ProjectFinder = styled(Button)`
margin-right: 20px;
padding: 6px 12px;
`;
export const NavSeparator = styled.div`
width: 1px;
background: rgba(${props => props.theme.colors.border});
height: 34px;
margin: 0 20px;
`;
export const LogoContainer = styled(Link)`
display: block;
left: 50%;
position: absolute;
transform: translateX(-50%);
display: flex;
align-items: center;
justify-content: center;
`;
export const CitadelTitle = styled.h2`
margin-left: 5px;
color: rgba(${props => props.theme.colors.text.primary});
font-size: 20px;
`;
export const CitadelLogo = styled(Citadel)`
fill: rgba(${props => props.theme.colors.text.primary});
stroke: rgba(${props => props.theme.colors.text.primary});
`;

View File

@ -26,7 +26,8 @@ export const Default = () => {
<NormalizeStyles />
<BaseStyles />
<TopNavbar
projectName="Projects"
onOpenProjectFinder={action('finder')}
name="Projects"
user={{
id: '1',
fullName: 'Jordan Knott',
@ -38,6 +39,7 @@ export const Default = () => {
}}
onNotificationClick={action('notifications click')}
onOpenSettings={action('open settings')}
onDashboardClick={action('open dashboard')}
onProfileClick={action('profile click')}
/>
</>

View File

@ -1,22 +1,27 @@
import React, { useRef, useState, useEffect } from 'react';
import { Star, Ellipsis, Bell, Cog, AngleDown } from 'shared/icons';
import { Home, Star, Bell, AngleDown, BarChart, CheckCircle } from 'shared/icons';
import styled from 'styled-components';
import ProfileIcon from 'shared/components/ProfileIcon';
import TaskAssignee from 'shared/components/TaskAssignee';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import MiniProfile from 'shared/components/MiniProfile';
import {
NotificationContainer,
CitadelLogo,
CitadelTitle,
ProjectFinder,
LogoContainer,
NavSeparator,
IconContainer,
ProjectNameTextarea,
InviteButton,
GlobalActions,
ProjectActions,
ProjectSwitcher,
Separator,
ProjectMeta,
ProjectName,
ProjectTabs,
ProjectTab,
NavbarWrapper,
NavbarHeader,
Breadcrumbs,
BreadcrumpSeparator,
ProjectSettingsButton,
ProfileContainer,
ProfileNameWrapper,
@ -24,18 +29,20 @@ import {
ProfileNameSecondary,
ProjectMembers,
} from './Styles';
import TaskAssignee from 'shared/components/TaskAssignee';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import MiniProfile from 'shared/components/MiniProfile';
import { Link } from 'react-router-dom';
const HomeDashboard = styled(Home)``;
type ProjectHeadingProps = {
projectName: string;
onFavorite?: () => void;
name: string;
onSaveProjectName?: (projectName: string) => void;
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
};
const ProjectHeading: React.FC<ProjectHeadingProps> = ({
projectName: initialProjectName,
onFavorite,
name: initialProjectName,
onSaveProjectName,
onOpenSettings,
}) => {
@ -73,7 +80,6 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({
const $settings = useRef<HTMLButtonElement>(null);
return (
<>
<Separator>»</Separator>
{isEditProjectName ? (
<ProjectNameTextarea
ref={$projectName}
@ -100,28 +106,54 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({
>
<AngleDown color="#c2c6dc" />
</ProjectSettingsButton>
<ProjectSettingsButton>
<Star width={16} height={16} color="#c2c6dc" />
</ProjectSettingsButton>
{onFavorite && (
<ProjectSettingsButton onClick={() => onFavorite()}>
<Star width={16} height={16} color="#c2c6dc" />
</ProjectSettingsButton>
)}
</>
);
};
type MenuType = {
[key: number]: string;
};
type MenuTypes = {
[key: string]: Array<string>;
};
export const MENU_TYPES: MenuTypes = {
PROJECT_MENU: ['Board', 'Timeline', 'Calender'],
TEAM_MENU: ['Projects', 'Members', 'Settings'],
};
type NavBarProps = {
projectName: string | null;
menuType?: Array<string> | null;
name: string | null;
currentTab?: number;
onOpenProjectFinder: ($target: React.RefObject<HTMLElement>) => void;
onFavorite?: () => void;
onProfileClick: ($target: React.RefObject<HTMLElement>) => void;
onSaveProjectName?: (projectName: string) => void;
onTabClick?: (tab: number) => void;
onSaveName?: (name: string) => void;
onNotificationClick: () => void;
onDashboardClick: () => void;
user: TaskUser | null;
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
projectMembers?: Array<TaskUser> | null;
};
const NavBar: React.FC<NavBarProps> = ({
projectName,
onSaveProjectName,
menuType,
currentTab,
onOpenProjectFinder,
onFavorite,
onTabClick,
name,
onSaveName,
onProfileClick,
onNotificationClick,
onDashboardClick,
user,
projectMembers,
onOpenSettings,
@ -152,45 +184,61 @@ const NavBar: React.FC<NavBarProps> = ({
<NavbarHeader>
<ProjectActions>
<ProjectMeta>
<ProjectSwitcher>Projects</ProjectSwitcher>
{projectName && (
{name && (
<ProjectHeading
onFavorite={onFavorite}
onOpenSettings={onOpenSettings}
projectName={projectName}
onSaveProjectName={onSaveProjectName}
name={name}
onSaveProjectName={onSaveName}
/>
)}
</ProjectMeta>
{projectName && (
{name && (
<ProjectTabs>
<ProjectTab active>Board</ProjectTab>
<ProjectTab>Calender</ProjectTab>
<ProjectTab>Timeline</ProjectTab>
<ProjectTab>Wiki</ProjectTab>
{menuType &&
menuType.map((name, idx) => {
console.log(`${name} : ${idx} === ${currentTab}`);
return <ProjectTab active={currentTab === idx}>{name}</ProjectTab>;
})}
</ProjectTabs>
)}
</ProjectActions>
<LogoContainer to="/">
<CitadelLogo width={24} height={24} />
<CitadelTitle>Citadel</CitadelTitle>
</LogoContainer>
<GlobalActions>
{projectMembers && (
<ProjectMembers>
{projectMembers.map(member => (
<TaskAssignee key={member.id} size={28} member={member} onMemberProfile={onMemberProfile} />
))}
<InviteButton variant="outline">Invite</InviteButton>
</ProjectMembers>
<>
<ProjectMembers>
{projectMembers.map(member => (
<TaskAssignee key={member.id} size={28} member={member} onMemberProfile={onMemberProfile} />
))}
<InviteButton variant="outline">Invite</InviteButton>
</ProjectMembers>
<NavSeparator />
</>
)}
<NotificationContainer onClick={onNotificationClick}>
<ProjectFinder onClick={onOpenProjectFinder} variant="gradient">
Projects
</ProjectFinder>
<IconContainer onClick={onDashboardClick}>
<HomeDashboard width={20} height={20} />
</IconContainer>
<IconContainer>
<CheckCircle width={20} height={20} />
</IconContainer>
<IconContainer onClick={onNotificationClick}>
<Bell color="#c2c6dc" size={20} />
</NotificationContainer>
</IconContainer>
<IconContainer>
<BarChart width={20} height={20} />
</IconContainer>
{user && (
<ProfileContainer>
<ProfileNameWrapper>
<ProfileNamePrimary>{user.fullName}</ProfileNamePrimary>
<ProfileNameSecondary>Manager</ProfileNameSecondary>
</ProfileNameWrapper>
<ProfileIcon user={user} size={40} onProfileClick={handleProfileClick} />}
</ProfileContainer>
<IconContainer>
<ProfileIcon user={user} size={30} onProfileClick={handleProfileClick} />
</IconContainer>
)}
</GlobalActions>
</NavbarHeader>

View File

@ -131,7 +131,7 @@ export type Task = {
};
export type ProjectsFilter = {
teamID?: Maybe<Scalars['String']>;
teamID?: Maybe<Scalars['UUID']>;
};
export type FindUser = {
@ -152,6 +152,10 @@ export type Organization = {
name: Scalars['String'];
};
export type FindTeam = {
teamID: Scalars['UUID'];
};
export type Query = {
__typename?: 'Query';
organizations: Array<Organization>;
@ -160,6 +164,7 @@ export type Query = {
findProject: Project;
findTask: Task;
projects: Array<Project>;
findTeam: Team;
teams: Array<Team>;
labelColors: Array<LabelColor>;
taskGroups: Array<TaskGroup>;
@ -186,6 +191,11 @@ export type QueryProjectsArgs = {
input?: Maybe<ProjectsFilter>;
};
export type QueryFindTeamArgs = {
input: FindTeam;
};
export type NewRefreshToken = {
userId: Scalars['String'];
};
@ -419,10 +429,48 @@ export type DeleteProjectPayload = {
project: Project;
};
export type DeleteTeam = {
teamID: Scalars['UUID'];
};
export type DeleteTeamPayload = {
__typename?: 'DeleteTeamPayload';
ok: Scalars['Boolean'];
team: Team;
projects: Array<Project>;
};
export type DeleteUserAccount = {
userID: Scalars['UUID'];
};
export type DeleteUserAccountPayload = {
__typename?: 'DeleteUserAccountPayload';
ok: Scalars['Boolean'];
userAccount: UserAccount;
};
export type UpdateTaskChecklistName = {
taskChecklistID: Scalars['UUID'];
name: Scalars['String'];
};
export type DeleteTaskChecklist = {
taskChecklistID: Scalars['UUID'];
};
export type DeleteTaskChecklistPayload = {
__typename?: 'DeleteTaskChecklistPayload';
ok: Scalars['Boolean'];
taskChecklist: TaskChecklist;
};
export type Mutation = {
__typename?: 'Mutation';
createRefreshToken: RefreshToken;
createUserAccount: UserAccount;
deleteUserAccount: DeleteUserAccountPayload;
deleteTeam: DeleteTeamPayload;
createTeam: Team;
clearProfileAvatar: UserAccount;
createTeamMember: CreateTeamMemberPayload;
@ -442,6 +490,8 @@ export type Mutation = {
removeTaskLabel: Task;
toggleTaskLabel: ToggleTaskLabelPayload;
createTaskChecklist: TaskChecklist;
deleteTaskChecklist: DeleteTaskChecklistPayload;
updateTaskChecklistName: TaskChecklist;
createTaskChecklistItem: TaskChecklistItem;
updateTaskChecklistItemName: TaskChecklistItem;
setTaskChecklistItemComplete: TaskChecklistItem;
@ -469,6 +519,16 @@ export type MutationCreateUserAccountArgs = {
};
export type MutationDeleteUserAccountArgs = {
input: DeleteUserAccount;
};
export type MutationDeleteTeamArgs = {
input: DeleteTeam;
};
export type MutationCreateTeamArgs = {
input: NewTeam;
};
@ -559,6 +619,16 @@ export type MutationCreateTaskChecklistArgs = {
};
export type MutationDeleteTaskChecklistArgs = {
input: DeleteTaskChecklist;
};
export type MutationUpdateTaskChecklistNameArgs = {
input: UpdateTaskChecklistName;
};
export type MutationCreateTaskChecklistItemArgs = {
input: CreateTaskChecklistItem;
};
@ -699,43 +769,6 @@ export type CreateProjectLabelMutation = (
) }
);
export type CreateTaskMutationVariables = {
taskGroupID: Scalars['String'];
name: Scalars['String'];
position: Scalars['Float'];
};
export type CreateTaskMutation = (
{ __typename?: 'Mutation' }
& { createTask: (
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'position' | 'description' | 'dueDate'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id' | 'name' | 'position'>
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'fullName'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)> }
) }
);
export type CreateTaskGroupMutationVariables = {
projectID: Scalars['String'];
name: Scalars['String'];
@ -849,6 +882,12 @@ export type FindTaskQuery = (
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
), badges: (
{ __typename?: 'TaskBadges' }
& { checklist?: Maybe<(
{ __typename?: 'ChecklistBadge' }
& Pick<ChecklistBadge, 'total' | 'complete'>
)> }
), checklists: Array<(
{ __typename?: 'TaskChecklist' }
& Pick<TaskChecklist, 'id' | 'name' | 'position'>
@ -881,9 +920,15 @@ export type FindTaskQuery = (
export type TaskFieldsFragment = (
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'description' | 'dueDate' | 'complete' | 'position'>
& { taskGroup: (
& { badges: (
{ __typename?: 'TaskBadges' }
& { checklist?: Maybe<(
{ __typename?: 'ChecklistBadge' }
& Pick<ChecklistBadge, 'complete' | 'total'>
)> }
), taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
& Pick<TaskGroup, 'id' | 'name' | 'position'>
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
@ -958,6 +1003,40 @@ export type DeleteProjectMutation = (
) }
);
export type CreateTaskMutationVariables = {
taskGroupID: Scalars['String'];
name: Scalars['String'];
position: Scalars['Float'];
};
export type CreateTaskMutation = (
{ __typename?: 'Mutation' }
& { createTask: (
{ __typename?: 'Task' }
& TaskFieldsFragment
) }
);
export type CreateTaskChecklistMutationVariables = {
taskID: Scalars['UUID'];
name: Scalars['String'];
position: Scalars['Float'];
};
export type CreateTaskChecklistMutation = (
{ __typename?: 'Mutation' }
& { createTaskChecklist: (
{ __typename?: 'TaskChecklist' }
& Pick<TaskChecklist, 'id' | 'name' | 'position'>
& { items: Array<(
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name' | 'taskChecklistID' | 'complete' | 'position'>
)> }
) }
);
export type CreateTaskChecklistItemMutationVariables = {
taskChecklistID: Scalars['UUID'];
name: Scalars['String'];
@ -973,6 +1052,23 @@ export type CreateTaskChecklistItemMutation = (
) }
);
export type DeleteTaskChecklistMutationVariables = {
taskChecklistID: Scalars['UUID'];
};
export type DeleteTaskChecklistMutation = (
{ __typename?: 'Mutation' }
& { deleteTaskChecklist: (
{ __typename?: 'DeleteTaskChecklistPayload' }
& Pick<DeleteTaskChecklistPayload, 'ok'>
& { taskChecklist: (
{ __typename?: 'TaskChecklist' }
& Pick<TaskChecklist, 'id'>
) }
) }
);
export type DeleteTaskChecklistItemMutationVariables = {
taskChecklistItemID: Scalars['UUID'];
};
@ -1000,7 +1096,7 @@ export type SetTaskChecklistItemCompleteMutation = (
{ __typename?: 'Mutation' }
& { setTaskChecklistItemComplete: (
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name' | 'taskChecklistID' | 'complete' | 'position'>
& Pick<TaskChecklistItem, 'id' | 'complete'>
) }
);
@ -1032,6 +1128,24 @@ export type UpdateTaskChecklistItemNameMutation = (
) }
);
export type UpdateTaskChecklistNameMutationVariables = {
taskChecklistID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateTaskChecklistNameMutation = (
{ __typename?: 'Mutation' }
& { updateTaskChecklistName: (
{ __typename?: 'TaskChecklist' }
& Pick<TaskChecklist, 'id' | 'name' | 'position'>
& { items: Array<(
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name' | 'taskChecklistID' | 'complete' | 'position'>
)> }
) }
);
export type UpdateTaskGroupNameMutationVariables = {
taskGroupID: Scalars['UUID'];
name: Scalars['String'];
@ -1060,6 +1174,43 @@ export type CreateTeamMutation = (
) }
);
export type DeleteTeamMutationVariables = {
teamID: Scalars['UUID'];
};
export type DeleteTeamMutation = (
{ __typename?: 'Mutation' }
& { deleteTeam: (
{ __typename?: 'DeleteTeamPayload' }
& Pick<DeleteTeamPayload, 'ok'>
& { team: (
{ __typename?: 'Team' }
& Pick<Team, 'id'>
) }
) }
);
export type GetTeamQueryVariables = {
teamID: Scalars['UUID'];
};
export type GetTeamQuery = (
{ __typename?: 'Query' }
& { findTeam: (
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'createdAt' | 'name'>
), projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
& { team: (
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
) }
)> }
);
export type ToggleTaskLabelMutationVariables = {
taskID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
@ -1220,6 +1371,42 @@ export type UpdateTaskNameMutation = (
) }
);
export type CreateUserAccountMutationVariables = {
username: Scalars['String'];
email: Scalars['String'];
fullName: Scalars['String'];
initials: Scalars['String'];
password: Scalars['String'];
};
export type CreateUserAccountMutation = (
{ __typename?: 'Mutation' }
& { createUserAccount: (
{ __typename?: 'UserAccount' }
& Pick<UserAccount, 'id' | 'email' | 'fullName' | 'initials' | 'username'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
) }
);
export type UsersQueryVariables = {};
export type UsersQuery = (
{ __typename?: 'Query' }
& { users: Array<(
{ __typename?: 'UserAccount' }
& Pick<UserAccount, 'id' | 'email' | 'fullName' | 'username'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)> }
);
export const TaskFieldsFragmentDoc = gql`
fragment TaskFields on Task {
id
@ -1228,8 +1415,16 @@ export const TaskFieldsFragmentDoc = gql`
dueDate
complete
position
badges {
checklist {
complete
total
}
}
taskGroup {
id
name
position
}
labels {
id
@ -1412,73 +1607,6 @@ export function useCreateProjectLabelMutation(baseOptions?: ApolloReactHooks.Mut
export type CreateProjectLabelMutationHookResult = ReturnType<typeof useCreateProjectLabelMutation>;
export type CreateProjectLabelMutationResult = ApolloReactCommon.MutationResult<CreateProjectLabelMutation>;
export type CreateProjectLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateProjectLabelMutation, CreateProjectLabelMutationVariables>;
export const CreateTaskDocument = gql`
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: {taskGroupID: $taskGroupID, name: $name, position: $position}) {
id
name
position
description
dueDate
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
fullName
profileIcon {
url
initials
bgColor
}
}
}
}
`;
export type CreateTaskMutationFn = ApolloReactCommon.MutationFunction<CreateTaskMutation, CreateTaskMutationVariables>;
/**
* __useCreateTaskMutation__
*
* To run a mutation, you first call `useCreateTaskMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateTaskMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createTaskMutation, { data, loading, error }] = useCreateTaskMutation({
* variables: {
* taskGroupID: // value for 'taskGroupID'
* name: // value for 'name'
* position: // value for 'position'
* },
* });
*/
export function useCreateTaskMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskMutation, CreateTaskMutationVariables>) {
return ApolloReactHooks.useMutation<CreateTaskMutation, CreateTaskMutationVariables>(CreateTaskDocument, baseOptions);
}
export type CreateTaskMutationHookResult = ReturnType<typeof useCreateTaskMutation>;
export type CreateTaskMutationResult = ApolloReactCommon.MutationResult<CreateTaskMutation>;
export type CreateTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskMutation, CreateTaskMutationVariables>;
export const CreateTaskGroupDocument = gql`
mutation createTaskGroup($projectID: String!, $name: String!, $position: Float!) {
createTaskGroup(input: {projectID: $projectID, name: $name, position: $position}) {
@ -1698,6 +1826,12 @@ export const FindTaskDocument = gql`
taskGroup {
id
}
badges {
checklist {
total
complete
}
}
checklists {
id
name
@ -1882,6 +2016,83 @@ export function useDeleteProjectMutation(baseOptions?: ApolloReactHooks.Mutation
export type DeleteProjectMutationHookResult = ReturnType<typeof useDeleteProjectMutation>;
export type DeleteProjectMutationResult = ApolloReactCommon.MutationResult<DeleteProjectMutation>;
export type DeleteProjectMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectMutation, DeleteProjectMutationVariables>;
export const CreateTaskDocument = gql`
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: {taskGroupID: $taskGroupID, name: $name, position: $position}) {
...TaskFields
}
}
${TaskFieldsFragmentDoc}`;
export type CreateTaskMutationFn = ApolloReactCommon.MutationFunction<CreateTaskMutation, CreateTaskMutationVariables>;
/**
* __useCreateTaskMutation__
*
* To run a mutation, you first call `useCreateTaskMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateTaskMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createTaskMutation, { data, loading, error }] = useCreateTaskMutation({
* variables: {
* taskGroupID: // value for 'taskGroupID'
* name: // value for 'name'
* position: // value for 'position'
* },
* });
*/
export function useCreateTaskMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskMutation, CreateTaskMutationVariables>) {
return ApolloReactHooks.useMutation<CreateTaskMutation, CreateTaskMutationVariables>(CreateTaskDocument, baseOptions);
}
export type CreateTaskMutationHookResult = ReturnType<typeof useCreateTaskMutation>;
export type CreateTaskMutationResult = ApolloReactCommon.MutationResult<CreateTaskMutation>;
export type CreateTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskMutation, CreateTaskMutationVariables>;
export const CreateTaskChecklistDocument = gql`
mutation createTaskChecklist($taskID: UUID!, $name: String!, $position: Float!) {
createTaskChecklist(input: {taskID: $taskID, name: $name, position: $position}) {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
}
`;
export type CreateTaskChecklistMutationFn = ApolloReactCommon.MutationFunction<CreateTaskChecklistMutation, CreateTaskChecklistMutationVariables>;
/**
* __useCreateTaskChecklistMutation__
*
* To run a mutation, you first call `useCreateTaskChecklistMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateTaskChecklistMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createTaskChecklistMutation, { data, loading, error }] = useCreateTaskChecklistMutation({
* variables: {
* taskID: // value for 'taskID'
* name: // value for 'name'
* position: // value for 'position'
* },
* });
*/
export function useCreateTaskChecklistMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskChecklistMutation, CreateTaskChecklistMutationVariables>) {
return ApolloReactHooks.useMutation<CreateTaskChecklistMutation, CreateTaskChecklistMutationVariables>(CreateTaskChecklistDocument, baseOptions);
}
export type CreateTaskChecklistMutationHookResult = ReturnType<typeof useCreateTaskChecklistMutation>;
export type CreateTaskChecklistMutationResult = ApolloReactCommon.MutationResult<CreateTaskChecklistMutation>;
export type CreateTaskChecklistMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskChecklistMutation, CreateTaskChecklistMutationVariables>;
export const CreateTaskChecklistItemDocument = gql`
mutation createTaskChecklistItem($taskChecklistID: UUID!, $name: String!, $position: Float!) {
createTaskChecklistItem(input: {taskChecklistID: $taskChecklistID, name: $name, position: $position}) {
@ -1920,6 +2131,41 @@ export function useCreateTaskChecklistItemMutation(baseOptions?: ApolloReactHook
export type CreateTaskChecklistItemMutationHookResult = ReturnType<typeof useCreateTaskChecklistItemMutation>;
export type CreateTaskChecklistItemMutationResult = ApolloReactCommon.MutationResult<CreateTaskChecklistItemMutation>;
export type CreateTaskChecklistItemMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskChecklistItemMutation, CreateTaskChecklistItemMutationVariables>;
export const DeleteTaskChecklistDocument = gql`
mutation deleteTaskChecklist($taskChecklistID: UUID!) {
deleteTaskChecklist(input: {taskChecklistID: $taskChecklistID}) {
ok
taskChecklist {
id
}
}
}
`;
export type DeleteTaskChecklistMutationFn = ApolloReactCommon.MutationFunction<DeleteTaskChecklistMutation, DeleteTaskChecklistMutationVariables>;
/**
* __useDeleteTaskChecklistMutation__
*
* To run a mutation, you first call `useDeleteTaskChecklistMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteTaskChecklistMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [deleteTaskChecklistMutation, { data, loading, error }] = useDeleteTaskChecklistMutation({
* variables: {
* taskChecklistID: // value for 'taskChecklistID'
* },
* });
*/
export function useDeleteTaskChecklistMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteTaskChecklistMutation, DeleteTaskChecklistMutationVariables>) {
return ApolloReactHooks.useMutation<DeleteTaskChecklistMutation, DeleteTaskChecklistMutationVariables>(DeleteTaskChecklistDocument, baseOptions);
}
export type DeleteTaskChecklistMutationHookResult = ReturnType<typeof useDeleteTaskChecklistMutation>;
export type DeleteTaskChecklistMutationResult = ApolloReactCommon.MutationResult<DeleteTaskChecklistMutation>;
export type DeleteTaskChecklistMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteTaskChecklistMutation, DeleteTaskChecklistMutationVariables>;
export const DeleteTaskChecklistItemDocument = gql`
mutation deleteTaskChecklistItem($taskChecklistItemID: UUID!) {
deleteTaskChecklistItem(input: {taskChecklistItemID: $taskChecklistItemID}) {
@ -1960,10 +2206,7 @@ export const SetTaskChecklistItemCompleteDocument = gql`
mutation setTaskChecklistItemComplete($taskChecklistItemID: UUID!, $complete: Boolean!) {
setTaskChecklistItemComplete(input: {taskChecklistItemID: $taskChecklistItemID, complete: $complete}) {
id
name
taskChecklistID
complete
position
}
}
`;
@ -2060,6 +2303,48 @@ export function useUpdateTaskChecklistItemNameMutation(baseOptions?: ApolloReact
export type UpdateTaskChecklistItemNameMutationHookResult = ReturnType<typeof useUpdateTaskChecklistItemNameMutation>;
export type UpdateTaskChecklistItemNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskChecklistItemNameMutation>;
export type UpdateTaskChecklistItemNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskChecklistItemNameMutation, UpdateTaskChecklistItemNameMutationVariables>;
export const UpdateTaskChecklistNameDocument = gql`
mutation updateTaskChecklistName($taskChecklistID: UUID!, $name: String!) {
updateTaskChecklistName(input: {taskChecklistID: $taskChecklistID, name: $name}) {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
}
`;
export type UpdateTaskChecklistNameMutationFn = ApolloReactCommon.MutationFunction<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>;
/**
* __useUpdateTaskChecklistNameMutation__
*
* To run a mutation, you first call `useUpdateTaskChecklistNameMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateTaskChecklistNameMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [updateTaskChecklistNameMutation, { data, loading, error }] = useUpdateTaskChecklistNameMutation({
* variables: {
* taskChecklistID: // value for 'taskChecklistID'
* name: // value for 'name'
* },
* });
*/
export function useUpdateTaskChecklistNameMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>) {
return ApolloReactHooks.useMutation<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>(UpdateTaskChecklistNameDocument, baseOptions);
}
export type UpdateTaskChecklistNameMutationHookResult = ReturnType<typeof useUpdateTaskChecklistNameMutation>;
export type UpdateTaskChecklistNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskChecklistNameMutation>;
export type UpdateTaskChecklistNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>;
export const UpdateTaskGroupNameDocument = gql`
mutation updateTaskGroupName($taskGroupID: UUID!, $name: String!) {
updateTaskGroupName(input: {taskGroupID: $taskGroupID, name: $name}) {
@ -2129,6 +2414,84 @@ export function useCreateTeamMutation(baseOptions?: ApolloReactHooks.MutationHoo
export type CreateTeamMutationHookResult = ReturnType<typeof useCreateTeamMutation>;
export type CreateTeamMutationResult = ApolloReactCommon.MutationResult<CreateTeamMutation>;
export type CreateTeamMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTeamMutation, CreateTeamMutationVariables>;
export const DeleteTeamDocument = gql`
mutation deleteTeam($teamID: UUID!) {
deleteTeam(input: {teamID: $teamID}) {
ok
team {
id
}
}
}
`;
export type DeleteTeamMutationFn = ApolloReactCommon.MutationFunction<DeleteTeamMutation, DeleteTeamMutationVariables>;
/**
* __useDeleteTeamMutation__
*
* To run a mutation, you first call `useDeleteTeamMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteTeamMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [deleteTeamMutation, { data, loading, error }] = useDeleteTeamMutation({
* variables: {
* teamID: // value for 'teamID'
* },
* });
*/
export function useDeleteTeamMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteTeamMutation, DeleteTeamMutationVariables>) {
return ApolloReactHooks.useMutation<DeleteTeamMutation, DeleteTeamMutationVariables>(DeleteTeamDocument, baseOptions);
}
export type DeleteTeamMutationHookResult = ReturnType<typeof useDeleteTeamMutation>;
export type DeleteTeamMutationResult = ApolloReactCommon.MutationResult<DeleteTeamMutation>;
export type DeleteTeamMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteTeamMutation, DeleteTeamMutationVariables>;
export const GetTeamDocument = gql`
query getTeam($teamID: UUID!) {
findTeam(input: {teamID: $teamID}) {
id
createdAt
name
}
projects(input: {teamID: $teamID}) {
id
name
team {
id
name
}
}
}
`;
/**
* __useGetTeamQuery__
*
* To run a query within a React component, call `useGetTeamQuery` and pass it any options that fit your needs.
* When your component renders, `useGetTeamQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetTeamQuery({
* variables: {
* teamID: // value for 'teamID'
* },
* });
*/
export function useGetTeamQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<GetTeamQuery, GetTeamQueryVariables>) {
return ApolloReactHooks.useQuery<GetTeamQuery, GetTeamQueryVariables>(GetTeamDocument, baseOptions);
}
export function useGetTeamLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<GetTeamQuery, GetTeamQueryVariables>) {
return ApolloReactHooks.useLazyQuery<GetTeamQuery, GetTeamQueryVariables>(GetTeamDocument, baseOptions);
}
export type GetTeamQueryHookResult = ReturnType<typeof useGetTeamQuery>;
export type GetTeamLazyQueryHookResult = ReturnType<typeof useGetTeamLazyQuery>;
export type GetTeamQueryResult = ApolloReactCommon.QueryResult<GetTeamQuery, GetTeamQueryVariables>;
export const ToggleTaskLabelDocument = gql`
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) {
@ -2472,4 +2835,89 @@ export function useUpdateTaskNameMutation(baseOptions?: ApolloReactHooks.Mutatio
}
export type UpdateTaskNameMutationHookResult = ReturnType<typeof useUpdateTaskNameMutation>;
export type UpdateTaskNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskNameMutation>;
export type UpdateTaskNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>;
export type UpdateTaskNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>;
export const CreateUserAccountDocument = gql`
mutation createUserAccount($username: String!, $email: String!, $fullName: String!, $initials: String!, $password: String!) {
createUserAccount(input: {username: $username, email: $email, fullName: $fullName, initials: $initials, password: $password}) {
id
email
fullName
initials
username
profileIcon {
url
initials
bgColor
}
}
}
`;
export type CreateUserAccountMutationFn = ApolloReactCommon.MutationFunction<CreateUserAccountMutation, CreateUserAccountMutationVariables>;
/**
* __useCreateUserAccountMutation__
*
* To run a mutation, you first call `useCreateUserAccountMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateUserAccountMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createUserAccountMutation, { data, loading, error }] = useCreateUserAccountMutation({
* variables: {
* username: // value for 'username'
* email: // value for 'email'
* fullName: // value for 'fullName'
* initials: // value for 'initials'
* password: // value for 'password'
* },
* });
*/
export function useCreateUserAccountMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateUserAccountMutation, CreateUserAccountMutationVariables>) {
return ApolloReactHooks.useMutation<CreateUserAccountMutation, CreateUserAccountMutationVariables>(CreateUserAccountDocument, baseOptions);
}
export type CreateUserAccountMutationHookResult = ReturnType<typeof useCreateUserAccountMutation>;
export type CreateUserAccountMutationResult = ApolloReactCommon.MutationResult<CreateUserAccountMutation>;
export type CreateUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateUserAccountMutation, CreateUserAccountMutationVariables>;
export const UsersDocument = gql`
query users {
users {
id
email
fullName
username
profileIcon {
url
initials
bgColor
}
}
}
`;
/**
* __useUsersQuery__
*
* To run a query within a React component, call `useUsersQuery` and pass it any options that fit your needs.
* When your component renders, `useUsersQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useUsersQuery({
* variables: {
* },
* });
*/
export function useUsersQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<UsersQuery, UsersQueryVariables>) {
return ApolloReactHooks.useQuery<UsersQuery, UsersQueryVariables>(UsersDocument, baseOptions);
}
export function useUsersLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<UsersQuery, UsersQueryVariables>) {
return ApolloReactHooks.useLazyQuery<UsersQuery, UsersQueryVariables>(UsersDocument, baseOptions);
}
export type UsersQueryHookResult = ReturnType<typeof useUsersQuery>;
export type UsersLazyQueryHookResult = ReturnType<typeof useUsersLazyQuery>;
export type UsersQueryResult = ApolloReactCommon.QueryResult<UsersQuery, UsersQueryVariables>;

View File

@ -1,38 +0,0 @@
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: { taskGroupID: $taskGroupID, name: $name, position: $position }) {
id
name
position
description
dueDate
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
fullName
profileIcon {
url
initials
bgColor
}
}
}
}

View File

@ -9,6 +9,12 @@ query findTask($taskID: UUID!) {
taskGroup {
id
}
badges {
checklist {
total
complete
}
}
checklists {
id
name

View File

@ -8,8 +8,16 @@ const TASK_FRAGMENT = gql`
dueDate
complete
position
badges {
checklist {
complete
total
}
}
taskGroup {
id
name
position
}
labels {
id

View File

@ -0,0 +1,13 @@
import gql from 'graphql-tag';
import TASK_FRAGMENT from '../fragments/task';
const CREATE_TASK_MUTATION = gql`
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: { taskGroupID: $taskGroupID, name: $name, position: $position }) {
...TaskFields
}
}
${TASK_FRAGMENT}
`;
export default CREATE_TASK_MUTATION;

View File

@ -0,0 +1,20 @@
import gql from 'graphql-tag';
const CREATE_TASK_CHECKLIST_MUTATION = gql`
mutation createTaskChecklist($taskID: UUID!, $name: String!, $position: Float!) {
createTaskChecklist(input: { taskID: $taskID, name: $name, position: $position }) {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
}
`;
export default CREATE_TASK_CHECKLIST_MUTATION;

View File

@ -0,0 +1,14 @@
import gql from 'graphql-tag';
const DELETE_TASK_CHECKLIST_MUTATION = gql`
mutation deleteTaskChecklist($taskChecklistID: UUID!) {
deleteTaskChecklist(input: { taskChecklistID: $taskChecklistID }) {
ok
taskChecklist {
id
}
}
}
`;
export default DELETE_TASK_CHECKLIST_MUTATION;

View File

@ -4,10 +4,7 @@ const SET_TASK_CHECKLIST_ITEM_COMPLETE = gql`
mutation setTaskChecklistItemComplete($taskChecklistItemID: UUID!, $complete: Boolean!) {
setTaskChecklistItemComplete(input: { taskChecklistItemID: $taskChecklistItemID, complete: $complete }) {
id
name
taskChecklistID
complete
position
}
}
`;

View File

@ -0,0 +1,19 @@
import gql from 'graphql-tag';
const UPDATE_TASK_CHECKLIST_NAME_MUTATION = gql`
mutation updateTaskChecklistName($taskChecklistID: UUID!, $name: String!) {
updateTaskChecklistName(input: { taskChecklistID: $taskChecklistID, name: $name }) {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
}
`;
export default UPDATE_TASK_CHECKLIST_NAME_MUTATION;

View File

@ -0,0 +1,14 @@
import gql from 'graphql-tag';
export const DELETE_TEAM_MUTATION = gql`
mutation deleteTeam($teamID: UUID!) {
deleteTeam(input: { teamID: $teamID }) {
ok
team {
id
}
}
}
`;
export default DELETE_TEAM_MUTATION;

View File

@ -0,0 +1,21 @@
import gql from 'graphql-tag';
export const GET_TEAM_QUERY = gql`
query getTeam($teamID: UUID!) {
findTeam(input: { teamID: $teamID }) {
id
createdAt
name
}
projects(input: { teamID: $teamID }) {
id
name
team {
id
name
}
}
}
`;
export default GET_TEAM_QUERY;

View File

@ -0,0 +1,28 @@
import gql from 'graphql-tag';
export const CREATE_USER_MUTATION = gql`
mutation createUserAccount(
$username: String!
$email: String!
$fullName: String!
$initials: String!
$password: String!
) {
createUserAccount(
input: { username: $username, email: $email, fullName: $fullName, initials: $initials, password: $password }
) {
id
email
fullName
initials
username
profileIcon {
url
initials
bgColor
}
}
}
`;
export default CREATE_USER_MUTATION;

View File

@ -0,0 +1,14 @@
query users {
users {
id
email
fullName
username
profileIcon {
url
initials
bgColor
}
}
}

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const BarChart: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
<path d="M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z" />
</Icon>
);
};
export default BarChart;

View File

@ -1,29 +1,15 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Citadel = ({ size, color }: Props) => {
const Citadel: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 12.7 12.7">
<Icon width={width} height={height} className={className} viewBox="0 0 12.7 12.7">
<g transform="translate(-.26 -24.137) scale(.1249)">
<path
d="M50.886 286.515l-40.4-44.46 44.459-40.401 40.401 44.46z"
fill="none"
stroke={color}
strokeWidth="11.90597031"
/>
<circle cx="52.917" cy="244.083" r="11.025" fill={color} />
<path d="M50.886 286.515l-40.4-44.46 44.459-40.401 40.401 44.46z" fill="none" strokeWidth="11.90597031" />
<circle cx="52.917" cy="244.083" r="11.025" />
</g>
</svg>
</Icon>
);
};
Citadel.defaultProps = {
size: 16,
color: '#7367f0',
};
export default Citadel;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const Filter: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
<path d="M487.976 0H24.028C2.71 0-8.047 25.866 7.058 40.971L192 225.941V432c0 7.831 3.821 15.17 10.237 19.662l80 55.98C298.02 518.69 320 507.493 320 487.98V225.941l184.947-184.97C520.021 25.896 509.338 0 487.976 0z" />
</Icon>
);
};
export default Filter;

View File

@ -1,21 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Home = ({ size, color }: Props) => {
const Checkmark: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 16 16">
<path d="M16 9.226l-8-6.21-8 6.21v-2.532l8-6.21 8 6.21zM14 9v6h-4v-4h-4v4h-4v-6l6-4.5z" />
</svg>
<Icon width={width} height={height} className={className} viewBox="0 0 576 512">
<path d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z" />
</Icon>
);
};
Home.defaultProps = {
size: 16,
color: '#000',
};
export default Home;
export default Checkmark;

View File

@ -18,6 +18,7 @@ type Props = {
const Svg = styled.svg`
fill: rgba(${props => props.theme.colors.text.primary});
stroke: rgba(${props => props.theme.colors.text.primary});
`;
const Icon: React.FC<Props> = ({ width, height, viewBox, className, onClick, children }) => {

View File

@ -1,21 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Lock = ({ size, color }: Props) => {
const Lock: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 16 16">
<path d="M9.25 7h-0.25v-3c0-1.654-1.346-3-3-3h-2c-1.654 0-3 1.346-3 3v3h-0.25c-0.412 0-0.75 0.338-0.75 0.75v7.5c0 0.412 0.338 0.75 0.75 0.75h8.5c0.412 0 0.75-0.338 0.75-0.75v-7.5c0-0.412-0.338-0.75-0.75-0.75zM3 4c0-0.551 0.449-1 1-1h2c0.551 0 1 0.449 1 1v3h-4v-3z" />
</svg>
<Icon width={width} height={height} className={className} viewBox="0 0 448 512">
<path d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z" />
</Icon>
);
};
Lock.defaultProps = {
size: 16,
color: '#000',
};
export default Lock;

View File

@ -1,21 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Pencil = ({ size, color }: Props) => {
const Sort: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" fill={color} width={size} height={size} viewBox="0 0 16 16">
<path d="M13.5 0c1.381 0 2.5 1.119 2.5 2.5 0 0.563-0.186 1.082-0.5 1.5l-1 1-3.5-3.5 1-1c0.418-0.314 0.937-0.5 1.5-0.5zM1 11.5l-1 4.5 4.5-1 9.25-9.25-3.5-3.5-9.25 9.25zM11.181 5.681l-7 7-0.862-0.862 7-7 0.862 0.862z" />
</svg>
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
<path d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z" />
</Icon>
);
};
Pencil.defaultProps = {
size: 16,
color: '#000',
};
export default Pencil;
export default Sort;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const Sort: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 320 512">
<path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41zm255-105L177 64c-9.4-9.4-24.6-9.4-33.9 0L24 183c-15.1 15.1-4.4 41 17 41h238c21.4 0 32.1-25.9 17-41z" />
</Icon>
);
};
export default Sort;

View File

@ -1,5 +1,8 @@
import Cross from './Cross';
import Cog from './Cog';
import Sort from './Sort';
import Filter from './Filter';
import BarChart from './BarChart';
import Trash from './Trash';
import CheckCircle from './CheckCircle';
import Clock from './Clock';
@ -58,6 +61,9 @@ export {
Clock,
CheckSquareOutline,
CheckSquare,
BarChart,
Square,
Filter,
Sort,
ToggleOn,
};

View File

@ -2,6 +2,21 @@
# yarn lockfile v1
"@apollo/client@^3.0.0-rc.8":
version "3.0.0-rc.8"
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.0.0-rc.8.tgz#70643547ed8bb10d1d96f64320c3510a57f0cf6a"
integrity sha512-8Sw4Htao2mZb4vERbvMj9GOf4x/+CLYNuF5gULY7TtMWG4ZANLxbe1DeFCicxVo2xRVQ960nf5vVJhxKcjDOQA==
dependencies:
"@types/zen-observable" "^0.8.0"
"@wry/equality" "^0.1.9"
fast-json-stable-stringify "^2.0.0"
graphql-tag "^2.10.2"
optimism "^0.12.1"
symbol-observable "^1.2.0"
ts-invariant "^0.4.4"
tslib "^1.10.0"
zen-observable "^0.8.14"
"@apollo/federation@0.13.2":
version "0.13.2"
resolved "https://registry.yarnpkg.com/@apollo/federation/-/federation-0.13.2.tgz#a9f842abd1619fe5cd732c56cfbc45dab0ae784a"
@ -3637,6 +3652,13 @@
"@types/node" ">=6"
tslib "^1.9.3"
"@wry/context@^0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.5.2.tgz#f2a5d5ab9227343aa74c81e06533c1ef84598ec7"
integrity sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==
dependencies:
tslib "^1.9.3"
"@wry/equality@^0.1.2", "@wry/equality@^0.1.9":
version "0.1.9"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.9.tgz#b13e18b7a8053c6858aa6c85b54911fb31e3a909"
@ -8349,7 +8371,7 @@ graphql-request@^1.5.0:
dependencies:
cross-fetch "2.2.2"
graphql-tag@2.10.3, graphql-tag@^2.10.1, graphql-tag@^2.10.3:
graphql-tag@2.10.3, graphql-tag@^2.10.1, graphql-tag@^2.10.2, graphql-tag@^2.10.3:
version "2.10.3"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03"
integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA==
@ -11668,6 +11690,13 @@ optimism@^0.10.0:
dependencies:
"@wry/context" "^0.4.0"
optimism@^0.12.1:
version "0.12.1"
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.12.1.tgz#933f9467b9aef0e601655adb9638f893e486ad02"
integrity sha512-t8I7HM1dw0SECitBYAqFOVHoBAHEQBTeKjIL9y9ImHzAVkdyPK4ifTgM4VJRDtTUY4r/u5Eqxs4XcGPHaoPkeQ==
dependencies:
"@wry/context" "^0.5.2"
optimize-css-assets-webpack-plugin@5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz#e2f1d4d94ad8c0af8967ebd7cf138dcb1ef14572"
@ -17236,7 +17265,7 @@ zen-observable-ts@^0.8.21:
tslib "^1.9.3"
zen-observable "^0.8.0"
zen-observable@^0.8.0:
zen-observable@^0.8.0, zen-observable@^0.8.14:
version "0.8.15"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==