diff --git a/.gitignore b/.gitignore
index 713d500..2bd1803 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,26 @@
-node_modules/
.env
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..e69de29
diff --git a/backend/db/db.go b/backend/db/db.go
index 20d1d8f..8a13124 100644
--- a/backend/db/db.go
+++ b/backend/db/db.go
@@ -2,8 +2,11 @@ package db
import (
"context"
+ "encoding/json"
+ "fmt"
"os"
+ "emperror.dev/errors"
"github.com/Masterminds/squirrel"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/mediocregopher/radix/v4"
@@ -47,3 +50,69 @@ func (db *DB) MultiCmd(ctx context.Context, cmds ...radix.Action) error {
}
return nil
}
+
+// SetJSON sets the given key to v marshaled as JSON.
+func (db *DB) SetJSON(ctx context.Context, key string, v any, args ...string) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return errors.Wrap(err, "marshaling json")
+ }
+
+ cmdArgs := make([]string, 0, len(args)+2)
+ cmdArgs = append(cmdArgs, key, string(b))
+ cmdArgs = append(cmdArgs, args...)
+
+ err = db.Redis.Do(ctx, radix.Cmd(nil, "SET", cmdArgs...))
+ if err != nil {
+ return errors.Wrap(err, "writing to Redis")
+ }
+ return nil
+}
+
+// GetJSON gets the given key as a JSON object.
+func (db *DB) GetJSON(ctx context.Context, key string, v any) error {
+ var b []byte
+
+ err := db.Redis.Do(ctx, radix.Cmd(&b, "GET", key))
+ if err != nil {
+ return errors.Wrap(err, "reading from Redis")
+ }
+
+ if b == nil {
+ return nil
+ }
+
+ if v == nil {
+ return fmt.Errorf("nil pointer passed into GetJSON")
+ }
+
+ err = json.Unmarshal(b, v)
+ if err != nil {
+ return errors.Wrap(err, "unmarshaling json")
+ }
+ return nil
+}
+
+// GetDelJSON gets the given key as a JSON object and deletes it.
+func (db *DB) GetDelJSON(ctx context.Context, key string, v any) error {
+ var b []byte
+
+ err := db.Redis.Do(ctx, radix.Cmd(&b, "GETDEL", key))
+ if err != nil {
+ return errors.Wrap(err, "reading from Redis")
+ }
+
+ if b == nil {
+ return nil
+ }
+
+ if v == nil {
+ return fmt.Errorf("nil pointer passed into GetDelJSON")
+ }
+
+ err = json.Unmarshal(b, v)
+ if err != nil {
+ return errors.Wrap(err, "unmarshaling json")
+ }
+ return nil
+}
diff --git a/backend/db/user.go b/backend/db/user.go
index 7139c30..f2e0d5d 100644
--- a/backend/db/user.go
+++ b/backend/db/user.go
@@ -5,8 +5,10 @@ import (
"regexp"
"emperror.dev/errors"
+ "github.com/bwmarrin/discordgo"
"github.com/georgysavva/scany/pgxscan"
"github.com/jackc/pgconn"
+ "github.com/jackc/pgx/v4"
"github.com/rs/xid"
)
@@ -27,8 +29,9 @@ type User struct {
var usernameRegex = regexp.MustCompile(`[\w-.]{2,40}`)
const (
- ErrUsernameTaken = errors.Sentinel("username is already taken")
+ ErrUserNotFound = errors.Sentinel("user not found")
+ ErrUsernameTaken = errors.Sentinel("username is already taken")
ErrInvalidUsername = errors.Sentinel("username contains invalid characters")
ErrUsernameTooShort = errors.Sentinel("username is too short")
ErrUsernameTooLong = errors.Sentinel("username is too long")
@@ -66,3 +69,69 @@ func (db *DB) CreateUser(ctx context.Context, username string) (u User, err erro
return u, nil
}
+
+// DiscordUser fetches a user by Discord user ID.
+func (db *DB) DiscordUser(ctx context.Context, discordID string) (u User, err error) {
+ sql, args, err := sq.Select("*").From("users").Where("discord = ?", discordID).ToSql()
+ if err != nil {
+ return u, errors.Wrap(err, "building sql")
+ }
+
+ err = pgxscan.Get(ctx, db, &u, sql, args...)
+ if err != nil {
+ if errors.Cause(err) == pgx.ErrNoRows {
+ return u, ErrUserNotFound
+ }
+ return u, errors.Cause(err)
+ }
+
+ return u, nil
+}
+
+func (u *User) UpdateFromDiscord(ctx context.Context, db pgxscan.Querier, du *discordgo.User) error {
+ builder := sq.Update("users").
+ Set("discord_username", du.String()).
+ Where("id = ?", u.ID).
+ Suffix("RETURNING *")
+
+ if u.AvatarSource == nil || *u.AvatarSource == "discord" {
+ builder = builder.
+ Set("avatar_source", "discord").
+ Set("avatar_url", du.AvatarURL("1024"))
+ }
+
+ sql, args, err := builder.ToSql()
+ if err != nil {
+ return errors.Wrap(err, "building sql")
+ }
+
+ return pgxscan.Get(ctx, db, u, sql, args...)
+}
+
+// User gets a user by ID.
+func (db *DB) User(ctx context.Context, id xid.ID) (u User, err error) {
+ err = pgxscan.Get(ctx, db, &u, "select * from users where id = $1", id)
+ if err != nil {
+ if errors.Cause(err) == pgx.ErrNoRows {
+ return u, ErrUserNotFound
+ }
+
+ return u, errors.Cause(err)
+ }
+
+ return u, nil
+}
+
+// Username gets a user by username.
+func (db *DB) Username(ctx context.Context, name string) (u User, err error) {
+ err = pgxscan.Get(ctx, db, &u, "select * from users where username = $1", name)
+ if err != nil {
+ if errors.Cause(err) == pgx.ErrNoRows {
+ return u, ErrUserNotFound
+ }
+
+ return u, errors.Cause(err)
+ }
+
+ return u, nil
+}
diff --git a/backend/routes.go b/backend/routes.go
index a5b66aa..5eac2c8 100644
--- a/backend/routes.go
+++ b/backend/routes.go
@@ -3,13 +3,16 @@ package main
import (
"github.com/go-chi/chi/v5"
"gitlab.com/1f320/pronouns/backend/routes/auth"
+ "gitlab.com/1f320/pronouns/backend/routes/user"
"gitlab.com/1f320/pronouns/backend/server"
)
// mountRoutes mounts all API routes on the server's router.
// they are all mounted under /v1/
func mountRoutes(s *server.Server) {
+ // future-proofing for API versions
s.Router.Route("/v1", func(r chi.Router) {
auth.Mount(s, r)
+ user.Mount(s, r)
})
}
diff --git a/backend/routes/auth/discord.go b/backend/routes/auth/discord.go
index 6f2e50b..9f5b84b 100644
--- a/backend/routes/auth/discord.go
+++ b/backend/routes/auth/discord.go
@@ -1,12 +1,18 @@
package auth
import (
+ "net/http"
"os"
+ "github.com/bwmarrin/discordgo"
+ "github.com/go-chi/render"
+ "gitlab.com/1f320/pronouns/backend/db"
+ "gitlab.com/1f320/pronouns/backend/log"
+ "gitlab.com/1f320/pronouns/backend/server"
"golang.org/x/oauth2"
)
-var oauthConfig = oauth2.Config{
+var discordOAuthConfig = oauth2.Config{
ClientID: os.Getenv("DISCORD_CLIENT_ID"),
ClientSecret: os.Getenv("DISCORD_CLIENT_SECRET"),
Endpoint: oauth2.Endpoint{
@@ -16,3 +22,80 @@ var oauthConfig = oauth2.Config{
},
Scopes: []string{"identify"},
}
+
+type oauthCallbackRequest struct {
+ Code string `json:"code"`
+ State string `json:"state"`
+}
+
+type discordCallbackResponse struct {
+ HasAccount bool `json:"has_account"` // if true, Token and User will be set. if false, Ticket and Discord will be set
+
+ Token string `json:"token,omitempty"`
+ User *db.User `json:"user,omitempty"`
+
+ Discord string `json:"discord,omitempty"` // username, for UI purposes
+ Ticket string `json:"ticket,omitempty"`
+}
+
+func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
+ ctx := r.Context()
+
+ decoded, err := Decode[oauthCallbackRequest](r)
+ if err != nil {
+ return server.APIError{Code: server.ErrBadRequest}
+ }
+
+ // if the state can't be validated, return
+ if valid, err := s.validateCSRFState(ctx, decoded.State); !valid {
+ if err != nil {
+ return err
+ }
+
+ return server.APIError{Code: server.ErrInvalidState}
+ }
+
+ token, err := discordOAuthConfig.Exchange(r.Context(), decoded.Code)
+ if err != nil {
+ log.Errorf("exchanging oauth code: %v", err)
+
+ return server.APIError{Code: server.ErrInvalidOAuthCode}
+ }
+
+ dg, _ := discordgo.New(token.Type() + " " + token.AccessToken)
+ du, err := dg.User("@me")
+ if err != nil {
+ return err
+ }
+
+ u, err := s.DB.DiscordUser(ctx, du.ID)
+ if err == nil {
+ err = u.UpdateFromDiscord(ctx, s.DB, du)
+ if err != nil {
+ log.Errorf("updating user %v with Discord info: %v", u.ID, err)
+ }
+
+ token, err := s.Auth.CreateToken(u.ID)
+ if err != nil {
+ return err
+ }
+
+ render.JSON(w, r, discordCallbackResponse{
+ HasAccount: true,
+ Token: token,
+ User: &u,
+ })
+ } else if err != db.ErrUserNotFound { // internal error
+ return err
+ }
+
+ // no user found, so save a ticket
+
+ return nil
+}
+
+func Decode[T any](r *http.Request) (T, error) {
+ decoded := *new(T)
+
+ return decoded, render.Decode(r, &decoded)
+}
diff --git a/backend/routes/auth/oauth.go b/backend/routes/auth/oauth.go
new file mode 100644
index 0000000..005a045
--- /dev/null
+++ b/backend/routes/auth/oauth.go
@@ -0,0 +1,41 @@
+package auth
+
+import (
+ "context"
+ "crypto/rand"
+ "encoding/base64"
+
+ "github.com/mediocregopher/radix/v4"
+)
+
+// numStates is the number of CSRF states stored in Redis at any one time.
+// This must be an integer.
+const numStates = "1000"
+
+// setCSRFState generates a random string to use as state, then stores that in Redis.
+func (s *Server) setCSRFState(ctx context.Context) (string, error) {
+ b := make([]byte, 32)
+
+ _, err := rand.Read(b)
+ if err != nil {
+ panic(err)
+ }
+
+ state := base64.URLEncoding.EncodeToString(b)
+
+ err = s.DB.MultiCmd(ctx,
+ radix.Cmd(nil, "LPUSH", "csrf", state),
+ radix.Cmd(nil, "LTRIM", "csrf", "0", numStates),
+ )
+ return state, err
+}
+
+// validateCSRFState checks if the given state exists in Redis.
+func (s *Server) validateCSRFState(ctx context.Context, state string) (matched bool, err error) {
+ var num int
+ err = s.DB.Redis.Do(ctx, radix.Cmd(&num, "LREM", "csrf", "1", state))
+ if err != nil {
+ return
+ }
+ return num > 0, nil
+}
diff --git a/backend/routes/auth/routes.go b/backend/routes/auth/routes.go
index a8c8696..854f90f 100644
--- a/backend/routes/auth/routes.go
+++ b/backend/routes/auth/routes.go
@@ -3,7 +3,9 @@ package auth
import (
"net/http"
+ "emperror.dev/errors"
"github.com/go-chi/chi/v5"
+ "github.com/go-chi/render"
"gitlab.com/1f320/pronouns/backend/server"
)
@@ -13,15 +15,46 @@ type Server struct {
func Mount(srv *server.Server, r chi.Router) {
s := &Server{srv}
- _ = s
- r.Route("/auth/discord", func(r chi.Router) {
- r.Get("/authorize", nil) // generate csrf token, returns URL
- r.Get("/callback", nil) // takes code + state, validates it, returns token OR discord signup ticket
- r.Get("/signup", nil) // takes discord signup ticket to register account
+ r.Route("/auth", func(r chi.Router) {
+ // generate csrf token, returns all supported OAuth provider URLs
+ r.Get("/urls", server.WrapHandler(s.oauthURLs))
- r.Get("/test", func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte("hello world!"))
+ r.Route("/discord", func(r chi.Router) {
+ // takes code + state, validates it, returns token OR discord signup ticket
+ r.Post("/callback", nil)
+ // takes discord signup ticket to register account
+ r.Post("/signup", nil)
})
})
}
+
+type oauthURLsRequest struct {
+ CallbackURL string `json:"callback_url"`
+}
+
+type oauthURLsResponse struct {
+ Discord string `json:"discord"`
+}
+
+func (s *Server) oauthURLs(w http.ResponseWriter, r *http.Request) error {
+ req, err := Decode[oauthURLsRequest](r)
+ if err != nil {
+ return server.APIError{Code: server.ErrBadRequest}
+ }
+
+ // generate CSRF state
+ state, err := s.setCSRFState(r.Context())
+ if err != nil {
+ return errors.Wrap(err, "setting CSRF state")
+ }
+
+ // copy Discord config and set redirect url
+ discordCfg := discordOAuthConfig
+ discordCfg.RedirectURL = req.CallbackURL
+
+ render.JSON(w, r, oauthURLsResponse{
+ Discord: discordCfg.AuthCodeURL(state),
+ })
+ return nil
+}
diff --git a/backend/routes/user/get_user.go b/backend/routes/user/get_user.go
new file mode 100644
index 0000000..f5217ee
--- /dev/null
+++ b/backend/routes/user/get_user.go
@@ -0,0 +1,70 @@
+package user
+
+import (
+ "net/http"
+
+ "github.com/go-chi/chi/v5"
+ "github.com/go-chi/render"
+ "github.com/rs/xid"
+ "gitlab.com/1f320/pronouns/backend/db"
+ "gitlab.com/1f320/pronouns/backend/log"
+ "gitlab.com/1f320/pronouns/backend/server"
+)
+
+type GetUserResponse struct {
+ ID xid.ID `json:"id"`
+ Username string `json:"username"`
+ DisplayName *string `json:"display_name"`
+ Bio *string `json:"bio"`
+ AvatarURL *string `json:"avatar_url"`
+ Links []string `json:"links"`
+}
+
+type PartialMember struct {
+ ID xid.ID `json:"id"`
+ Name string `json:"name"`
+ AvatarURL *string `json:"avatar_url"`
+}
+
+func dbUserToResponse(u db.User) GetUserResponse {
+ return GetUserResponse{
+ ID: u.ID,
+ Username: u.Username,
+ DisplayName: u.DisplayName,
+ Bio: u.Bio,
+ AvatarURL: u.AvatarURL,
+ Links: u.Links,
+ }
+}
+
+func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error {
+ ctx := r.Context()
+
+ userRef := chi.URLParamFromCtx(ctx, "userRef")
+
+ if id, err := xid.FromString(userRef); err == nil {
+ u, err := s.DB.User(ctx, id)
+ if err == nil {
+ render.JSON(w, r, dbUserToResponse(u))
+ return nil
+ } else if err != db.ErrUserNotFound {
+ log.Errorf("Error getting user by ID: %v", err)
+ return err
+ }
+ // otherwise, we fall back to checking usernames
+ }
+
+ u, err := s.DB.Username(ctx, userRef)
+ if err == db.ErrUserNotFound {
+ return server.APIError{
+ Code: server.ErrUserNotFound,
+ }
+
+ } else if err != nil {
+ log.Errorf("Error getting user by username: %v", err)
+ return err
+ }
+
+ render.JSON(w, r, dbUserToResponse(u))
+ return nil
+}
diff --git a/backend/routes/user/routes.go b/backend/routes/user/routes.go
new file mode 100644
index 0000000..a190805
--- /dev/null
+++ b/backend/routes/user/routes.go
@@ -0,0 +1,20 @@
+package user
+
+import (
+ "github.com/go-chi/chi/v5"
+ "gitlab.com/1f320/pronouns/backend/server"
+)
+
+type Server struct {
+ *server.Server
+}
+
+func Mount(srv *server.Server, r chi.Router) {
+ s := &Server{srv}
+
+ r.Route("/users", func(r chi.Router) {
+ r.With(server.MustAuth).Get("/@me", server.WrapHandler(nil))
+
+ r.Get("/{userRef}", server.WrapHandler(s.getUser))
+ })
+}
diff --git a/backend/server/auth.go b/backend/server/auth.go
index b7d8032..16444e4 100644
--- a/backend/server/auth.go
+++ b/backend/server/auth.go
@@ -4,6 +4,7 @@ import (
"context"
"net/http"
+ "github.com/go-chi/render"
"gitlab.com/1f320/pronouns/backend/server/auth"
)
@@ -34,7 +35,12 @@ func MustAuth(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
_, ok := ClaimsFromContext(r.Context())
if !ok {
-
+ render.Status(r, errCodeStatuses[ErrForbidden])
+ render.JSON(w, r, APIError{
+ Code: ErrForbidden,
+ Message: errCodeMessages[ErrForbidden],
+ })
+ return
}
next.ServeHTTP(w, r)
diff --git a/backend/server/errors.go b/backend/server/errors.go
index a4fc589..8440dfd 100644
--- a/backend/server/errors.go
+++ b/backend/server/errors.go
@@ -62,13 +62,36 @@ func (e *APIError) prepare() {
// Error code constants
const (
+ ErrBadRequest = 400
+ ErrForbidden = 403
ErrInternalServerError = 500 // catch-all code for unknown errors
+
+ // Login/authorize error codes
+ ErrInvalidState = 1001
+ ErrInvalidOAuthCode = 1002
+
+ // User-related error codes
+ ErrUserNotFound = 2001
)
var errCodeMessages = map[int]string{
+ ErrBadRequest: "Bad request",
+ ErrForbidden: "Forbidden",
ErrInternalServerError: "Internal server error",
+
+ ErrInvalidState: "Invalid OAuth state",
+ ErrInvalidOAuthCode: "Invalid OAuth code",
+
+ ErrUserNotFound: "User not found",
}
var errCodeStatuses = map[int]int{
+ ErrBadRequest: http.StatusBadRequest,
+ ErrForbidden: http.StatusForbidden,
ErrInternalServerError: http.StatusInternalServerError,
+
+ ErrInvalidState: http.StatusBadRequest,
+ ErrInvalidOAuthCode: http.StatusForbidden,
+
+ ErrUserNotFound: http.StatusNotFound,
}
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..98b6b05
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ pronouns
+
+
+
+
+
+
diff --git a/frontend/src/App.css b/frontend/src/App.css
new file mode 100644
index 0000000..8da3fde
--- /dev/null
+++ b/frontend/src/App.css
@@ -0,0 +1,42 @@
+.App {
+ text-align: center;
+}
+
+.App-logo {
+ height: 40vmin;
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ }
+}
+
+.App-header {
+ background-color: #282c34;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+button {
+ font-size: calc(10px + 2vmin);
+}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 0000000..c12d66f
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,45 @@
+import { useState } from 'react'
+import logo from './logo.svg'
+import './App.css'
+
+function App() {
+ const [count, setCount] = useState(0)
+
+ return (
+
+
+
+ Hello Vite + React!
+
+
+
+
+ Edit App.tsx
and save to test HMR updates!
+
+
+
+ Learn React
+
+ {' | '}
+
+ Vite Docs
+
+
+
+
+ )
+}
+
+export default App
diff --git a/frontend/src/favicon.svg b/frontend/src/favicon.svg
new file mode 100644
index 0000000..de4aedd
--- /dev/null
+++ b/frontend/src/favicon.svg
@@ -0,0 +1,15 @@
+
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000..ec2585e
--- /dev/null
+++ b/frontend/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
diff --git a/frontend/src/logo.svg b/frontend/src/logo.svg
new file mode 100644
index 0000000..6b60c10
--- /dev/null
+++ b/frontend/src/logo.svg
@@ -0,0 +1,7 @@
+
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 0000000..4a1b150
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+)
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/frontend/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/go.mod b/go.mod
index d666497..2c9d4f0 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.18
require (
emperror.dev/errors v0.8.1
github.com/Masterminds/squirrel v1.5.2
+ github.com/bwmarrin/discordgo v0.25.0
github.com/georgysavva/scany v0.3.0
github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/render v1.0.1
@@ -22,6 +23,7 @@ require (
require (
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
+ github.com/gorilla/websocket v1.4.2 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
@@ -37,6 +39,7 @@ require (
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
+ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.26.0 // indirect
diff --git a/go.sum b/go.sum
index 5c3d3dc..d0ae145 100644
--- a/go.sum
+++ b/go.sum
@@ -57,6 +57,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
+github.com/bwmarrin/discordgo v0.25.0 h1:NXhdfHRNxtwso6FPdzW2i3uBvvU7UIQTghmV2T4nqAs=
+github.com/bwmarrin/discordgo v0.25.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -187,6 +189,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@@ -461,6 +465,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b3b87b0
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "pronouns",
+ "private": true,
+ "version": "0.0.0",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@vitejs/plugin-react": "^1.3.0",
+ "typescript": "^4.6.3",
+ "vite": "^2.9.7"
+ }
+}
diff --git a/scripts/migrate/001_init.sql b/scripts/migrate/001_init.sql
index 61eba3f..1db5b1f 100644
--- a/scripts/migrate/001_init.sql
+++ b/scripts/migrate/001_init.sql
@@ -12,7 +12,8 @@ create table users (
avatar_url text,
links text[],
- discord text -- for Discord oauth
+ discord text unique, -- for Discord oauth
+ discord_username text
);
create table user_fields (
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..3d0a51a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..e993792
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "esnext",
+ "moduleResolution": "node"
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..5715bac
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,18 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ root: "frontend",
+ plugins: [react()],
+ server: {
+ proxy: {
+ "/api": {
+ // assumes port 8080 in .env for development
+ target: "http://localhost:8080",
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, ""),
+ },
+ },
+ },
+});
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..31d24f9
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,717 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ampproject/remapping@^2.1.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
+ integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.1.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@babel/code-frame@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
+ integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
+ dependencies:
+ "@babel/highlight" "^7.16.7"
+
+"@babel/compat-data@^7.17.10":
+ version "7.17.10"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab"
+ integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==
+
+"@babel/core@^7.17.10":
+ version "7.17.10"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.10.tgz#74ef0fbf56b7dfc3f198fc2d927f4f03e12f4b05"
+ integrity sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==
+ dependencies:
+ "@ampproject/remapping" "^2.1.0"
+ "@babel/code-frame" "^7.16.7"
+ "@babel/generator" "^7.17.10"
+ "@babel/helper-compilation-targets" "^7.17.10"
+ "@babel/helper-module-transforms" "^7.17.7"
+ "@babel/helpers" "^7.17.9"
+ "@babel/parser" "^7.17.10"
+ "@babel/template" "^7.16.7"
+ "@babel/traverse" "^7.17.10"
+ "@babel/types" "^7.17.10"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.1"
+ semver "^6.3.0"
+
+"@babel/generator@^7.17.10":
+ version "7.17.10"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.10.tgz#c281fa35b0c349bbe9d02916f4ae08fc85ed7189"
+ integrity sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==
+ dependencies:
+ "@babel/types" "^7.17.10"
+ "@jridgewell/gen-mapping" "^0.1.0"
+ jsesc "^2.5.1"
+
+"@babel/helper-annotate-as-pure@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862"
+ integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
+"@babel/helper-compilation-targets@^7.17.10":
+ version "7.17.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz#09c63106d47af93cf31803db6bc49fef354e2ebe"
+ integrity sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==
+ dependencies:
+ "@babel/compat-data" "^7.17.10"
+ "@babel/helper-validator-option" "^7.16.7"
+ browserslist "^4.20.2"
+ semver "^6.3.0"
+
+"@babel/helper-environment-visitor@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7"
+ integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
+"@babel/helper-function-name@^7.17.9":
+ version "7.17.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12"
+ integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==
+ dependencies:
+ "@babel/template" "^7.16.7"
+ "@babel/types" "^7.17.0"
+
+"@babel/helper-hoist-variables@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246"
+ integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
+"@babel/helper-module-imports@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437"
+ integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
+"@babel/helper-module-transforms@^7.17.7":
+ version "7.17.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd"
+ integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-module-imports" "^7.16.7"
+ "@babel/helper-simple-access" "^7.17.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+ "@babel/helper-validator-identifier" "^7.16.7"
+ "@babel/template" "^7.16.7"
+ "@babel/traverse" "^7.17.3"
+ "@babel/types" "^7.17.0"
+
+"@babel/helper-plugin-utils@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
+ integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
+
+"@babel/helper-simple-access@^7.17.7":
+ version "7.17.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367"
+ integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==
+ dependencies:
+ "@babel/types" "^7.17.0"
+
+"@babel/helper-split-export-declaration@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b"
+ integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
+"@babel/helper-validator-identifier@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
+ integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
+
+"@babel/helper-validator-option@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23"
+ integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==
+
+"@babel/helpers@^7.17.9":
+ version "7.17.9"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a"
+ integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==
+ dependencies:
+ "@babel/template" "^7.16.7"
+ "@babel/traverse" "^7.17.9"
+ "@babel/types" "^7.17.0"
+
+"@babel/highlight@^7.16.7":
+ version "7.17.9"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3"
+ integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.16.7"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
+"@babel/parser@^7.16.7", "@babel/parser@^7.17.10":
+ version "7.17.10"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.10.tgz#873b16db82a8909e0fbd7f115772f4b739f6ce78"
+ integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==
+
+"@babel/plugin-syntax-jsx@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665"
+ integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.16.7"
+
+"@babel/plugin-transform-react-jsx-development@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8"
+ integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==
+ dependencies:
+ "@babel/plugin-transform-react-jsx" "^7.16.7"
+
+"@babel/plugin-transform-react-jsx-self@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.16.7.tgz#f432ad0cba14c4a1faf44f0076c69e42a4d4479e"
+ integrity sha512-oe5VuWs7J9ilH3BCCApGoYjHoSO48vkjX2CbA5bFVhIuO2HKxA3vyF7rleA4o6/4rTDbk6r8hBW7Ul8E+UZrpA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.16.7"
+
+"@babel/plugin-transform-react-jsx-source@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.16.7.tgz#1879c3f23629d287cc6186a6c683154509ec70c0"
+ integrity sha512-rONFiQz9vgbsnaMtQlZCjIRwhJvlrPET8TabIUK2hzlXw9B9s2Ieaxte1SCOOXMbWRHodbKixNf3BLcWVOQ8Bw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.16.7"
+
+"@babel/plugin-transform-react-jsx@^7.16.7", "@babel/plugin-transform-react-jsx@^7.17.3":
+ version "7.17.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1"
+ integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.16.7"
+ "@babel/helper-module-imports" "^7.16.7"
+ "@babel/helper-plugin-utils" "^7.16.7"
+ "@babel/plugin-syntax-jsx" "^7.16.7"
+ "@babel/types" "^7.17.0"
+
+"@babel/template@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
+ integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/parser" "^7.16.7"
+ "@babel/types" "^7.16.7"
+
+"@babel/traverse@^7.17.10", "@babel/traverse@^7.17.3", "@babel/traverse@^7.17.9":
+ version "7.17.10"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.10.tgz#1ee1a5ac39f4eac844e6cf855b35520e5eb6f8b5"
+ integrity sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/generator" "^7.17.10"
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-function-name" "^7.17.9"
+ "@babel/helper-hoist-variables" "^7.16.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+ "@babel/parser" "^7.17.10"
+ "@babel/types" "^7.17.10"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.17.10":
+ version "7.17.10"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.10.tgz#d35d7b4467e439fcf06d195f8100e0fea7fc82c4"
+ integrity sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.16.7"
+ to-fast-properties "^2.0.0"
+
+"@jridgewell/gen-mapping@^0.1.0":
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
+ integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.0"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@jridgewell/resolve-uri@^3.0.3":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.6.tgz#4ac237f4dabc8dd93330386907b97591801f7352"
+ integrity sha512-R7xHtBSNm+9SyvpJkdQl+qrM3Hm2fea3Ef197M3mUug+v+yR+Rhfbs7PBtcBUVnIWJ4JcAdjvij+c8hXS9p5aw==
+
+"@jridgewell/set-array@^1.0.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.0.tgz#1179863356ac8fbea64a5a4bcde93a4871012c01"
+ integrity sha512-SfJxIxNVYLTsKwzB3MoOQ1yxf4w/E6MdkvTgrgAt1bfxjSrLUoHMKrDOykwN14q65waezZIdqDneUIPh4/sKxg==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+ version "1.4.12"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.12.tgz#7ed98f6fa525ffb7c56a2cbecb5f7bb91abd2baf"
+ integrity sha512-az/NhpIwP3K33ILr0T2bso+k2E/SLf8Yidd8mHl0n6sCQ4YdyC8qDhZA6kOPDNDBA56ZnIjngVl0U3jREA0BUA==
+
+"@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.9"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
+ integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.0.3"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@rollup/pluginutils@^4.2.1":
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
+ integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
+ dependencies:
+ estree-walker "^2.0.1"
+ picomatch "^2.2.2"
+
+"@types/prop-types@*":
+ version "15.7.5"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
+ integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
+
+"@types/react-dom@^18.0.0":
+ version "18.0.3"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.3.tgz#a022ea08c75a476fe5e96b675c3e673363853831"
+ integrity sha512-1RRW9kst+67gveJRYPxGmVy8eVJ05O43hg77G2j5m76/RFJtMbcfAs2viQ2UNsvvDg8F7OfQZx8qQcl6ymygaQ==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*", "@types/react@^18.0.0":
+ version "18.0.8"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.8.tgz#a051eb380a9fbcaa404550543c58e1cf5ce4ab87"
+ integrity sha512-+j2hk9BzCOrrOSJASi5XiOyBbERk9jG5O73Ya4M0env5Ixi6vUNli4qy994AINcEF+1IEHISYFfIT4zwr++LKw==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
+"@types/scheduler@*":
+ version "0.16.2"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
+ integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
+
+"@vitejs/plugin-react@^1.3.0":
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-1.3.2.tgz#2fcf0b6ce9bcdcd4cec5c760c199779d5657ece1"
+ integrity sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==
+ dependencies:
+ "@babel/core" "^7.17.10"
+ "@babel/plugin-transform-react-jsx" "^7.17.3"
+ "@babel/plugin-transform-react-jsx-development" "^7.16.7"
+ "@babel/plugin-transform-react-jsx-self" "^7.16.7"
+ "@babel/plugin-transform-react-jsx-source" "^7.16.7"
+ "@rollup/pluginutils" "^4.2.1"
+ react-refresh "^0.13.0"
+ resolve "^1.22.0"
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+browserslist@^4.20.2:
+ version "4.20.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf"
+ integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==
+ dependencies:
+ caniuse-lite "^1.0.30001332"
+ electron-to-chromium "^1.4.118"
+ escalade "^3.1.1"
+ node-releases "^2.0.3"
+ picocolors "^1.0.0"
+
+caniuse-lite@^1.0.30001332:
+ version "1.0.30001335"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001335.tgz#899254a0b70579e5a957c32dced79f0727c61f2a"
+ integrity sha512-ddP1Tgm7z2iIxu6QTtbZUv6HJxSaV/PZeSrWFZtbY4JZ69tOeNhBCl3HyRQgeNZKE5AOn1kpV7fhljigy0Ty3w==
+
+chalk@^2.0.0:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+
+convert-source-map@^1.7.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
+ integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
+ dependencies:
+ safe-buffer "~5.1.1"
+
+csstype@^3.0.2:
+ version "3.0.11"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33"
+ integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==
+
+debug@^4.1.0:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+electron-to-chromium@^1.4.118:
+ version "1.4.132"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.132.tgz#b64599eb018221e52e2e4129de103b03a413c55d"
+ integrity sha512-JYdZUw/1068NWN+SwXQ7w6Ue0bWYGihvSUNNQwurvcDV/SM7vSiGZ3NuFvFgoEiCs4kB8xs3cX2an3wB7d4TBw==
+
+esbuild-android-64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz#5b94a1306df31d55055f64a62ff6b763a47b7f64"
+ integrity sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw==
+
+esbuild-android-arm64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz#78acc80773d16007de5219ccce544c036abd50b8"
+ integrity sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA==
+
+esbuild-darwin-64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz#e02b1291f629ebdc2aa46fabfacc9aa28ff6aa46"
+ integrity sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA==
+
+esbuild-darwin-arm64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz#01eb6650ec010b18c990e443a6abcca1d71290a9"
+ integrity sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ==
+
+esbuild-freebsd-64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz#790b8786729d4aac7be17648f9ea8e0e16475b5e"
+ integrity sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig==
+
+esbuild-freebsd-arm64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz#b66340ab28c09c1098e6d9d8ff656db47d7211e6"
+ integrity sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ==
+
+esbuild-linux-32@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz#7927f950986fd39f0ff319e92839455912b67f70"
+ integrity sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g==
+
+esbuild-linux-64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz#4893d07b229d9cfe34a2b3ce586399e73c3ac519"
+ integrity sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q==
+
+esbuild-linux-arm64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz#8442402e37d0b8ae946ac616784d9c1a2041056a"
+ integrity sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA==
+
+esbuild-linux-arm@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz#d5dbf32d38b7f79be0ec6b5fb2f9251fd9066986"
+ integrity sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA==
+
+esbuild-linux-mips64le@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz#95081e42f698bbe35d8ccee0e3a237594b337eb5"
+ integrity sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ==
+
+esbuild-linux-ppc64le@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz#dceb0a1b186f5df679618882a7990bd422089b47"
+ integrity sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q==
+
+esbuild-linux-riscv64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz#61fb8edb75f475f9208c4a93ab2bfab63821afd2"
+ integrity sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ==
+
+esbuild-linux-s390x@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz#34c7126a4937406bf6a5e69100185fd702d12fe0"
+ integrity sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ==
+
+esbuild-netbsd-64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz#322ea9937d9e529183ee281c7996b93eb38a5d95"
+ integrity sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q==
+
+esbuild-openbsd-64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz#1ca29bb7a2bf09592dcc26afdb45108f08a2cdbd"
+ integrity sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ==
+
+esbuild-sunos-64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz#c9446f7d8ebf45093e7bb0e7045506a88540019b"
+ integrity sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA==
+
+esbuild-windows-32@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz#f8e9b4602fd0ccbd48e5c8d117ec0ba4040f2ad1"
+ integrity sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw==
+
+esbuild-windows-64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz#280f58e69f78535f470905ce3e43db1746518107"
+ integrity sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw==
+
+esbuild-windows-arm64@0.14.38:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz#d97e9ac0f95a4c236d9173fa9f86c983d6a53f54"
+ integrity sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw==
+
+esbuild@^0.14.27:
+ version "0.14.38"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.38.tgz#99526b778cd9f35532955e26e1709a16cca2fb30"
+ integrity sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA==
+ optionalDependencies:
+ esbuild-android-64 "0.14.38"
+ esbuild-android-arm64 "0.14.38"
+ esbuild-darwin-64 "0.14.38"
+ esbuild-darwin-arm64 "0.14.38"
+ esbuild-freebsd-64 "0.14.38"
+ esbuild-freebsd-arm64 "0.14.38"
+ esbuild-linux-32 "0.14.38"
+ esbuild-linux-64 "0.14.38"
+ esbuild-linux-arm "0.14.38"
+ esbuild-linux-arm64 "0.14.38"
+ esbuild-linux-mips64le "0.14.38"
+ esbuild-linux-ppc64le "0.14.38"
+ esbuild-linux-riscv64 "0.14.38"
+ esbuild-linux-s390x "0.14.38"
+ esbuild-netbsd-64 "0.14.38"
+ esbuild-openbsd-64 "0.14.38"
+ esbuild-sunos-64 "0.14.38"
+ esbuild-windows-32 "0.14.38"
+ esbuild-windows-64 "0.14.38"
+ esbuild-windows-arm64 "0.14.38"
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+estree-walker@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+ integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+gensync@^1.0.0-beta.2:
+ version "1.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+is-core-module@^2.8.1:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
+ integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
+ dependencies:
+ has "^1.0.3"
+
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+jsesc@^2.5.1:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+ integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+
+json5@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
+ integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+
+loose-envify@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+nanoid@^3.3.3:
+ version "3.3.4"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
+ integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
+
+node-releases@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476"
+ integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+picomatch@^2.2.2:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+postcss@^8.4.13:
+ version "8.4.13"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575"
+ integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
+ dependencies:
+ nanoid "^3.3.3"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
+react-dom@^18.0.0:
+ version "18.1.0"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.1.0.tgz#7f6dd84b706408adde05e1df575b3a024d7e8a2f"
+ integrity sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==
+ dependencies:
+ loose-envify "^1.1.0"
+ scheduler "^0.22.0"
+
+react-refresh@^0.13.0:
+ version "0.13.0"
+ resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.13.0.tgz#cbd01a4482a177a5da8d44c9755ebb1f26d5a1c1"
+ integrity sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==
+
+react@^18.0.0:
+ version "18.1.0"
+ resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
+ integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+resolve@^1.22.0:
+ version "1.22.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
+ integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
+ dependencies:
+ is-core-module "^2.8.1"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+rollup@^2.59.0:
+ version "2.71.1"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.71.1.tgz#82b259af7733dfd1224a8171013aaaad02971a22"
+ integrity sha512-lMZk3XfUBGjrrZQpvPSoXcZSfKcJ2Bgn+Z0L1MoW2V8Wh7BVM+LOBJTPo16yul2MwL59cXedzW1ruq3rCjSRgw==
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+scheduler@^0.22.0:
+ version "0.22.0"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8"
+ integrity sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+semver@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+ integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+source-map-js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+to-fast-properties@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+ integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
+
+typescript@^4.6.3:
+ version "4.6.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
+ integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
+
+vite@^2.9.7:
+ version "2.9.7"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.7.tgz#210c328e08ed206ab0953eb1ca00860042cd0a77"
+ integrity sha512-5hH7aNQe8rJiTTqCtPNX/6mIKlGw+1wg8UXwAxDIIN8XaSR+Zx3GT2zSu7QKa1vIaBqfUODGh3vpwY8r0AW/jw==
+ dependencies:
+ esbuild "^0.14.27"
+ postcss "^8.4.13"
+ resolve "^1.22.0"
+ rollup "^2.59.0"
+ optionalDependencies:
+ fsevents "~2.3.2"