From c5ed242f124a58a59932540ffbb5948a345efead Mon Sep 17 00:00:00 2001 From: Sudarsh1010 Date: Fri, 15 Nov 2024 09:02:48 +0530 Subject: [PATCH] add zustand and integrate with cookies to store user profile data, and authenticated layout --- internal/controllers/auth_controller.go | 10 +- internal/models/main.go | 2 +- internal/services/session_service.go | 13 +- internal/utils/session_helpers.go | 17 +- web/package.json | 2 + web/pnpm-lock.yaml | 364 ++++++++++-------- web/src/actions/auth/sign-in.ts | 9 +- web/src/actions/auth/verify-otp.ts | 7 +- web/src/actions/auth/verify-token.ts | 8 +- web/src/axios.ts | 4 +- web/src/components/sign-in/form.tsx | 20 +- web/src/components/sign-up/form.tsx | 12 +- web/src/components/sign-up/verify-otp.tsx | 6 +- .../global-state/persistant-storage/token.ts | 73 ++++ web/src/global-state/zustand.ts | 18 + web/src/route-tree.gen.ts | 82 ++-- web/src/routes/_authenticated.tsx | 48 +++ web/src/routes/{ => _authenticated}/index.tsx | 5 +- web/src/schema/user.ts | 14 + 19 files changed, 485 insertions(+), 229 deletions(-) create mode 100644 web/src/global-state/persistant-storage/token.ts create mode 100644 web/src/global-state/zustand.ts create mode 100644 web/src/routes/_authenticated.tsx rename web/src/routes/{ => _authenticated}/index.tsx (51%) create mode 100644 web/src/schema/user.ts diff --git a/internal/controllers/auth_controller.go b/internal/controllers/auth_controller.go index 8f5edf2e..cea7852f 100644 --- a/internal/controllers/auth_controller.go +++ b/internal/controllers/auth_controller.go @@ -3,7 +3,6 @@ package controllers import ( "errors" "fmt" - "keizer-auth/internal/models" "keizer-auth/internal/services" "keizer-auth/internal/utils" @@ -65,7 +64,7 @@ func (ac *AuthController) SignIn(c *fiber.Ctx) error { } if !isValid { return c. - Status(fiber.StatusUnauthorized). + Status(fiber.StatusBadRequest). JSON(fiber.Map{"error": "Invalid email or password. Please try again."}) } @@ -76,8 +75,9 @@ func (ac *AuthController) SignIn(c *fiber.Ctx) error { JSON(fiber.Map{"error": "Something went wrong, Failed to create session"}) } + fmt.Print(sessionId) utils.SetSessionCookie(c, sessionId) - return c.JSON(fiber.Map{"message": "signed in successfully"}) + return c.JSON(user) } func (ac *AuthController) SignUp(c *fiber.Ctx) error { @@ -149,11 +149,13 @@ func (ac *AuthController) VerifyOTP(c *fiber.Ctx) error { } utils.SetSessionCookie(c, sessionID) - return c.JSON(fiber.Map{"message": "OTP Verified!"}) + return c.JSON(user) } func (ac *AuthController) VerifyTokenHandler(c *fiber.Ctx) error { sessionID := utils.GetSessionCookie(c) + fmt.Print("\n") + fmt.Print(sessionID) if sessionID == "" { return c. Status(fiber.StatusUnauthorized). diff --git a/internal/models/main.go b/internal/models/main.go index bc297c16..9473356a 100644 --- a/internal/models/main.go +++ b/internal/models/main.go @@ -12,7 +12,7 @@ type Base struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt *time.Time `sql:"index" json:"deleted_at"` - ID uuid.UUID `gorm:"type:uuid"` + ID uuid.UUID `json:"id" gorm:"type:uuid"` } func (base *Base) BeforeCreate(tx *gorm.DB) (err error) { diff --git a/internal/services/session_service.go b/internal/services/session_service.go index 56221722..ef25f1d0 100644 --- a/internal/services/session_service.go +++ b/internal/services/session_service.go @@ -3,11 +3,10 @@ package services import ( "encoding/json" "fmt" - "time" - "keizer-auth/internal/models" "keizer-auth/internal/repositories" "keizer-auth/internal/utils" + "time" "github.com/redis/go-redis/v9" ) @@ -22,22 +21,18 @@ func NewSessionService(redisRepo *repositories.RedisRepository, userRepo *reposi } func (ss *SessionService) CreateSession(user *models.User) (string, error) { - sessionID, err := utils.GenerateSessionID() - if err != nil { - return "", fmt.Errorf("error in generating session %w", err) - } + sessionID := utils.GenerateSessionID() userJson, err := json.Marshal(user) if err != nil { return "", fmt.Errorf("error occured %w", err) } - err = ss.redisRepo.Set( + if err = ss.redisRepo.Set( "dashboard-user-session-"+sessionID, string(userJson), utils.SessionExpiresIn, - ) - if err != nil { + ); err != nil { return "", fmt.Errorf("error in setting session %w", err) } diff --git a/internal/utils/session_helpers.go b/internal/utils/session_helpers.go index 4f33dc31..a21a2425 100644 --- a/internal/utils/session_helpers.go +++ b/internal/utils/session_helpers.go @@ -1,7 +1,6 @@ package utils import ( - "fmt" "time" "github.com/gofiber/fiber/v2" @@ -10,16 +9,8 @@ import ( const SessionExpiresIn = 30 * 24 * time.Hour -func GenerateSessionID() (string, error) { - generate, err := cuid2.Init( - cuid2.WithLength(15), - ) - if err != nil { - fmt.Println(err.Error()) - return "", err - } - - return generate(), nil +func GenerateSessionID() string { + return cuid2.Generate() } func SetSessionCookie(c *fiber.Ctx, sessionID string) { @@ -28,11 +19,9 @@ func SetSessionCookie(c *fiber.Ctx, sessionID string) { Value: sessionID, Expires: time.Now().Add(SessionExpiresIn), HTTPOnly: true, - Secure: true, + Secure: false, SameSite: fiber.CookieSameSiteNoneMode, // TODO: handle domain - Domain: "localhost", - Path: "/", }) } diff --git a/web/package.json b/web/package.json index 022ba662..8a7d066a 100644 --- a/web/package.json +++ b/web/package.json @@ -26,6 +26,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "input-otp": "^1.4.1", + "js-cookie": "^3.0.5", "lucide-react": "^0.454.0", "next-themes": "^0.4.3", "react": "^18.3.1", @@ -41,6 +42,7 @@ "@eslint/js": "^9.14.0", "@tanstack/eslint-plugin-query": "^5.59.7", "@tanstack/router-plugin": "^1.79.0", + "@types/js-cookie": "^3.0.6", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react-swc": "^3.5.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index d96083b1..64511f24 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -10,40 +10,40 @@ importers: dependencies: '@hookform/resolvers': specifier: ^3.9.1 - version: 3.9.1(react-hook-form@7.53.1) + version: 3.9.1(react-hook-form@7.53.1(react@18.3.1)) '@radix-ui/react-alert-dialog': specifier: ^1.1.2 - version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-avatar': specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': specifier: ^1.1.2 - version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-icons': specifier: ^1.3.1 version: 1.3.1(react@18.3.1) '@radix-ui/react-label': specifier: ^2.1.0 - version: 2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + version: 2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-separator': specifier: ^1.1.0 - version: 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + version: 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.1.0 version: 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-tooltip': specifier: ^1.1.3 - version: 1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + version: 1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-query': specifier: ^5.59.19 version: 5.59.20(react@18.3.1) '@tanstack/react-router': specifier: ^1.79.0 - version: 1.79.0(react-dom@18.3.1)(react@18.3.1) + version: 1.79.0(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/router-devtools': specifier: ^1.79.0 - version: 1.79.0(@tanstack/react-router@1.79.0)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1) + version: 1.79.0(@tanstack/react-router@1.79.0(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) axios: specifier: ^1.7.7 version: 1.7.7 @@ -55,13 +55,16 @@ importers: version: 2.1.1 input-otp: specifier: ^1.4.1 - version: 1.4.1(react-dom@18.3.1)(react@18.3.1) + version: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 lucide-react: specifier: ^0.454.0 version: 0.454.0(react@18.3.1) next-themes: specifier: ^0.4.3 - version: 0.4.3(react-dom@18.3.1)(react@18.3.1) + version: 0.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -73,7 +76,7 @@ importers: version: 7.53.1(react@18.3.1) sonner: specifier: ^1.7.0 - version: 1.7.0(react-dom@18.3.1)(react@18.3.1) + version: 1.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwind-merge: specifier: ^2.5.4 version: 2.5.4 @@ -85,17 +88,20 @@ importers: version: 3.23.8 zustand: specifier: ^5.0.1 - version: 5.0.1(@types/react@18.3.12)(react@18.3.1) + version: 5.0.1(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1)) devDependencies: '@eslint/js': specifier: ^9.14.0 version: 9.14.0 '@tanstack/eslint-plugin-query': specifier: ^5.59.7 - version: 5.59.20(eslint@9.14.0)(typescript@5.6.3) + version: 5.59.20(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) '@tanstack/router-plugin': specifier: ^1.79.0 version: 1.79.0(vite@5.4.10) + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 '@types/react': specifier: ^18.3.12 version: 18.3.12 @@ -110,22 +116,22 @@ importers: version: 10.4.20(postcss@8.4.47) eslint: specifier: ^9.13.0 - version: 9.14.0 + version: 9.14.0(jiti@1.21.6) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@9.14.0) + version: 9.1.0(eslint@9.14.0(jiti@1.21.6)) eslint-plugin-react-hooks: specifier: ^5.0.0 - version: 5.0.0(eslint@9.14.0) + version: 5.0.0(eslint@9.14.0(jiti@1.21.6)) eslint-plugin-react-refresh: specifier: ^0.4.14 - version: 0.4.14(eslint@9.14.0) + version: 0.4.14(eslint@9.14.0(jiti@1.21.6)) eslint-plugin-simple-import-sort: specifier: ^12.1.1 - version: 12.1.1(eslint@9.14.0) + version: 12.1.1(eslint@9.14.0(jiti@1.21.6)) eslint-plugin-unused-imports: specifier: ^4.1.4 - version: 4.1.4(eslint@9.14.0) + version: 4.1.4(@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6)) globals: specifier: ^15.11.0 version: 15.12.0 @@ -146,7 +152,7 @@ importers: version: 5.6.3 typescript-eslint: specifier: ^8.13.0 - version: 8.13.0(eslint@9.14.0)(typescript@5.6.3) + version: 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) vite: specifier: ^5.4.10 version: 5.4.10 @@ -1183,6 +1189,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1701,6 +1710,10 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2597,9 +2610,9 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.14.0)': + '@eslint-community/eslint-utils@4.4.1(eslint@9.14.0(jiti@1.21.6))': dependencies: - eslint: 9.14.0 + eslint: 9.14.0(jiti@1.21.6) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -2645,7 +2658,7 @@ snapshots: '@floating-ui/core': 1.6.8 '@floating-ui/utils': 0.2.8 - '@floating-ui/react-dom@2.1.2(react-dom@18.3.1)(react@18.3.1)': + '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/dom': 1.6.12 react: 18.3.1 @@ -2653,7 +2666,7 @@ snapshots: '@floating-ui/utils@0.2.8': {} - '@hookform/resolvers@3.9.1(react-hook-form@7.53.1)': + '@hookform/resolvers@3.9.1(react-hook-form@7.53.1(react@18.3.1))': dependencies: react-hook-form: 7.53.1(react@18.3.1) @@ -2713,100 +2726,110 @@ snapshots: '@radix-ui/primitive@1.1.0': {} - '@radix-ui/react-alert-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-alert-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - - '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + optionalDependencies: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + + '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 - '@radix-ui/react-avatar@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-avatar@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-context@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-context@1.1.1(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 - '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-remove-scroll: 2.6.0(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 - '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 - '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@radix-ui/react-icons@1.3.1(react@18.3.1)': dependencies: @@ -2815,134 +2838,150 @@ snapshots: '@radix-ui/react-id@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 - '@radix-ui/react-label@2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-label@2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 - '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-context': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/rect': 1.1.0 - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 - '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 - '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 - '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - - '@radix-ui/react-separator@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + optionalDependencies: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + + '@radix-ui/react-separator@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@radix-ui/react-slot@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 - '@radix-ui/react-tooltip@1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-tooltip@1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/rect': 1.1.0 - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-use-size@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 - '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@radix-ui/rect@1.1.0': {} @@ -3052,10 +3091,10 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/eslint-plugin-query@5.59.20(eslint@9.14.0)(typescript@5.6.3)': + '@tanstack/eslint-plugin-query@5.59.20(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@typescript-eslint/utils': 8.13.0(eslint@9.14.0)(typescript@5.6.3) - eslint: 9.14.0 + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + eslint: 9.14.0(jiti@1.21.6) transitivePeerDependencies: - supports-color - typescript @@ -3069,25 +3108,27 @@ snapshots: '@tanstack/query-core': 5.59.20 react: 18.3.1 - '@tanstack/react-router@1.79.0(react-dom@18.3.1)(react@18.3.1)': + '@tanstack/react-router@1.79.0(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/history': 1.61.1 - '@tanstack/react-store': 0.5.6(react-dom@18.3.1)(react@18.3.1) + '@tanstack/react-store': 0.5.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 + optionalDependencies: + '@tanstack/router-generator': 1.79.0 - '@tanstack/react-store@0.5.6(react-dom@18.3.1)(react@18.3.1)': + '@tanstack/react-store@0.5.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/store': 0.5.5 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) use-sync-external-store: 1.2.2(react@18.3.1) - '@tanstack/router-devtools@1.79.0(@tanstack/react-router@1.79.0)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1)': + '@tanstack/router-devtools@1.79.0(@tanstack/react-router@1.79.0(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/react-router': 1.79.0(react-dom@18.3.1)(react@18.3.1) + '@tanstack/react-router': 1.79.0(@tanstack/router-generator@1.79.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) react: 18.3.1 @@ -3121,8 +3162,9 @@ snapshots: babel-dead-code-elimination: 1.0.6 chokidar: 3.6.0 unplugin: 1.15.0 - vite: 5.4.10 zod: 3.23.8 + optionalDependencies: + vite: 5.4.10 transitivePeerDependencies: - supports-color - webpack-sources @@ -3154,6 +3196,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/js-cookie@3.0.6': {} + '@types/json-schema@7.0.15': {} '@types/prop-types@15.7.13': {} @@ -3167,31 +3211,33 @@ snapshots: '@types/prop-types': 15.7.13 csstype: 3.1.3 - '@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0)(eslint@9.14.0)(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.13.0(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/parser': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) '@typescript-eslint/scope-manager': 8.13.0 - '@typescript-eslint/type-utils': 8.13.0(eslint@9.14.0)(typescript@5.6.3) - '@typescript-eslint/utils': 8.13.0(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/type-utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) '@typescript-eslint/visitor-keys': 8.13.0 - eslint: 9.14.0 + eslint: 9.14.0(jiti@1.21.6) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.13.0(eslint@9.14.0)(typescript@5.6.3)': + '@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 8.13.0 '@typescript-eslint/types': 8.13.0 '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 8.13.0 debug: 4.3.7 - eslint: 9.14.0 + eslint: 9.14.0(jiti@1.21.6) + optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -3201,12 +3247,13 @@ snapshots: '@typescript-eslint/types': 8.13.0 '@typescript-eslint/visitor-keys': 8.13.0 - '@typescript-eslint/type-utils@8.13.0(eslint@9.14.0)(typescript@5.6.3)': + '@typescript-eslint/type-utils@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.13.0(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) debug: 4.3.7 ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - eslint @@ -3224,17 +3271,18 @@ snapshots: minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.13.0(eslint@9.14.0)(typescript@5.6.3)': + '@typescript-eslint/utils@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0(jiti@1.21.6)) '@typescript-eslint/scope-manager': 8.13.0 '@typescript-eslint/types': 8.13.0 '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) - eslint: 9.14.0 + eslint: 9.14.0(jiti@1.21.6) transitivePeerDependencies: - supports-color - typescript @@ -3478,25 +3526,27 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@9.1.0(eslint@9.14.0): + eslint-config-prettier@9.1.0(eslint@9.14.0(jiti@1.21.6)): dependencies: - eslint: 9.14.0 + eslint: 9.14.0(jiti@1.21.6) - eslint-plugin-react-hooks@5.0.0(eslint@9.14.0): + eslint-plugin-react-hooks@5.0.0(eslint@9.14.0(jiti@1.21.6)): dependencies: - eslint: 9.14.0 + eslint: 9.14.0(jiti@1.21.6) - eslint-plugin-react-refresh@0.4.14(eslint@9.14.0): + eslint-plugin-react-refresh@0.4.14(eslint@9.14.0(jiti@1.21.6)): dependencies: - eslint: 9.14.0 + eslint: 9.14.0(jiti@1.21.6) - eslint-plugin-simple-import-sort@12.1.1(eslint@9.14.0): + eslint-plugin-simple-import-sort@12.1.1(eslint@9.14.0(jiti@1.21.6)): dependencies: - eslint: 9.14.0 + eslint: 9.14.0(jiti@1.21.6) - eslint-plugin-unused-imports@4.1.4(eslint@9.14.0): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6)): dependencies: - eslint: 9.14.0 + eslint: 9.14.0(jiti@1.21.6) + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) eslint-scope@8.2.0: dependencies: @@ -3507,9 +3557,9 @@ snapshots: eslint-visitor-keys@4.2.0: {} - eslint@9.14.0: + eslint@9.14.0(jiti@1.21.6): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0(jiti@1.21.6)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.18.0 '@eslint/core': 0.7.0 @@ -3544,6 +3594,8 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 text-table: 0.2.0 + optionalDependencies: + jiti: 1.21.6 transitivePeerDependencies: - supports-color @@ -3675,7 +3727,7 @@ snapshots: imurmurhash@0.1.4: {} - input-otp@1.4.1(react-dom@18.3.1)(react@18.3.1): + input-otp@1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -3712,6 +3764,8 @@ snapshots: jiti@1.21.6: {} + js-cookie@3.0.5: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -3798,7 +3852,7 @@ snapshots: natural-compare@1.4.0: {} - next-themes@0.4.3(react-dom@18.3.1)(react@18.3.1): + next-themes@0.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -3870,8 +3924,9 @@ snapshots: postcss-load-config@4.0.2(postcss@8.4.47): dependencies: lilconfig: 3.1.2 - postcss: 8.4.47 yaml: 2.6.0 + optionalDependencies: + postcss: 8.4.47 postcss-nested@6.2.0(postcss@8.4.47): dependencies: @@ -3917,28 +3972,31 @@ snapshots: react-remove-scroll-bar@2.3.6(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 react: 18.3.1 react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 react-remove-scroll@2.6.0(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 react: 18.3.1 react-remove-scroll-bar: 2.3.6(@types/react@18.3.12)(react@18.3.1) react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) tslib: 2.8.1 use-callback-ref: 1.3.2(@types/react@18.3.12)(react@18.3.1) use-sidecar: 1.1.2(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 react-style-singleton@2.2.1(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 react@18.3.1: dependencies: @@ -4008,7 +4066,7 @@ snapshots: signal-exit@4.1.0: {} - sonner@1.7.0(react-dom@18.3.1)(react@18.3.1): + sonner@1.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -4123,11 +4181,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.13.0(eslint@9.14.0)(typescript@5.6.3): + typescript-eslint@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.13.0(@typescript-eslint/parser@8.13.0)(eslint@9.14.0)(typescript@5.6.3) - '@typescript-eslint/parser': 8.13.0(eslint@9.14.0)(typescript@5.6.3) - '@typescript-eslint/utils': 8.13.0(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/eslint-plugin': 8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/parser': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - eslint @@ -4152,16 +4211,18 @@ snapshots: use-callback-ref@1.3.2(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 react: 18.3.1 tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 use-sidecar@1.1.2(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 detect-node-es: 1.1.0 react: 18.3.1 tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 use-sync-external-store@1.2.2(react@18.3.1): dependencies: @@ -4205,7 +4266,8 @@ snapshots: zod@3.23.8: {} - zustand@5.0.1(@types/react@18.3.12)(react@18.3.1): - dependencies: + zustand@5.0.1(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1)): + optionalDependencies: '@types/react': 18.3.12 react: 18.3.1 + use-sync-external-store: 1.2.2(react@18.3.1) diff --git a/web/src/actions/auth/sign-in.ts b/web/src/actions/auth/sign-in.ts index 31a8a57b..a2f31915 100644 --- a/web/src/actions/auth/sign-in.ts +++ b/web/src/actions/auth/sign-in.ts @@ -2,12 +2,11 @@ import { z } from "zod"; import apiClient from "~/axios"; import type { emailPassSignInSchema } from "~/schema/auth"; - -interface SignInRes { - message: string; -} +import { UserInterface } from "~/schema/user"; export const signInMutationFn = async ( data: z.infer, ) => - await apiClient.post("auth/sign-in", data).then((res) => res.data); + await apiClient + .post("auth/sign-in", data) + .then((res) => res.data); diff --git a/web/src/actions/auth/verify-otp.ts b/web/src/actions/auth/verify-otp.ts index 5536c405..adbc7452 100644 --- a/web/src/actions/auth/verify-otp.ts +++ b/web/src/actions/auth/verify-otp.ts @@ -2,15 +2,12 @@ import { z } from "zod"; import apiClient from "~/axios"; import { verifyOtpSchema } from "~/schema/auth"; +import { UserInterface } from "~/schema/user"; export type VerifyOtpInterface = z.infer; -interface VerifyOtpRes { - message: string; -} - export async function verifyOtpMutationFn(values: VerifyOtpInterface) { return apiClient - .post("auth/verify-otp", values) + .post("auth/verify-otp", values) .then((r) => r.data); } diff --git a/web/src/actions/auth/verify-token.ts b/web/src/actions/auth/verify-token.ts index e93c6aaf..1faf9aed 100644 --- a/web/src/actions/auth/verify-token.ts +++ b/web/src/actions/auth/verify-token.ts @@ -1,7 +1,7 @@ import apiClient from "~/axios"; +import { UserInterface } from "~/schema/user"; -export const verifyTokenQueryFn = async () => +export const verifyToken = async () => await apiClient - .get("auth/verify-token") - .then((res) => console.log(res.data)) - .catch(console.log); + .get("auth/verify-token") + .then((res) => res.data); diff --git a/web/src/axios.ts b/web/src/axios.ts index a69f3ede..40515c25 100644 --- a/web/src/axios.ts +++ b/web/src/axios.ts @@ -2,6 +2,7 @@ import axios from "axios"; const apiClient = axios.create({ baseURL: import.meta.env.VITE_BACKEND_URL, + withCredentials: true, }); apiClient.interceptors.request.use((config) => { @@ -10,9 +11,6 @@ apiClient.interceptors.request.use((config) => { } else { config.headers["Content-Type"] = "application/json"; } - - config.withCredentials = true; - config.headers.Accept = "application/json"; return config; }); diff --git a/web/src/components/sign-in/form.tsx b/web/src/components/sign-in/form.tsx index a7c51651..f8c81963 100644 --- a/web/src/components/sign-in/form.tsx +++ b/web/src/components/sign-in/form.tsx @@ -3,7 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { GitHubLogoIcon } from "@radix-ui/react-icons"; import { useMutation } from "@tanstack/react-query"; -import { useRouter } from "@tanstack/react-router"; +import { Link, useRouter } from "@tanstack/react-router"; import { AxiosError } from "axios"; import * as React from "react"; import { useForm } from "react-hook-form"; @@ -11,6 +11,7 @@ import { toast } from "sonner"; import { z } from "zod"; import { signInMutationFn } from "~/actions/auth/sign-in"; +import { setUser } from "~/global-state/persistant-storage/token"; import { cn } from "~/lib/utils"; import { emailPassSignInSchema } from "~/schema/auth"; @@ -18,13 +19,13 @@ import { Button } from "../ui/button"; import { Form, FormField } from "../ui/form"; import { Input } from "../ui/input"; import { PasswordInput } from "../ui/password-input"; +import { Separator } from "../ui/separator"; type UserAuthFormProps = React.HTMLAttributes; type EmailSignInSchema = z.infer; export function SignInForm({ className, ...props }: UserAuthFormProps) { const router = useRouter(); - const form = useForm({ resolver: zodResolver(emailPassSignInSchema), }); @@ -32,7 +33,9 @@ export function SignInForm({ className, ...props }: UserAuthFormProps) { const { mutate, isPending } = useMutation({ mutationFn: signInMutationFn, onSuccess: (res) => { - toast.success(res.message); + console.log(res); + setUser(res); + toast.success("Logged in successfully"); router.navigate({ to: "/" }); }, onError: (err) => { @@ -109,6 +112,17 @@ export function SignInForm({ className, ...props }: UserAuthFormProps) { + + + Don't have an account?{" "} + + Sign up + + + ); } diff --git a/web/src/components/sign-up/form.tsx b/web/src/components/sign-up/form.tsx index f8fd20ce..40cba915 100644 --- a/web/src/components/sign-up/form.tsx +++ b/web/src/components/sign-up/form.tsx @@ -3,7 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { GitHubLogoIcon } from "@radix-ui/react-icons"; import { useMutation } from "@tanstack/react-query"; -import { useRouter } from "@tanstack/react-router"; +import { Link, useRouter } from "@tanstack/react-router"; import { AxiosError } from "axios"; import * as React from "react"; import { useForm } from "react-hook-form"; @@ -128,6 +128,16 @@ export function SignUpForm({ className, ...props }: UserAuthFormProps) { + + + Already have an account?{" "} + + Sign in + + ); } diff --git a/web/src/components/sign-up/verify-otp.tsx b/web/src/components/sign-up/verify-otp.tsx index 377f227b..67df83a7 100644 --- a/web/src/components/sign-up/verify-otp.tsx +++ b/web/src/components/sign-up/verify-otp.tsx @@ -11,6 +11,7 @@ import { VerifyOtpInterface, verifyOtpMutationFn, } from "~/actions/auth/verify-otp"; +import { setUser } from "~/global-state/persistant-storage/token"; import { cn } from "~/lib/utils"; import { verifyOtpSchema } from "~/schema/auth"; @@ -32,7 +33,10 @@ export function VerifyOtpForm({ id, className, ...props }: UserAuthFormProps) { const { mutate, isPending } = useMutation({ mutationFn: verifyOtpMutationFn, - onSuccess: (res) => toast.success(res.message), + onSuccess: (res) => { + setUser(res); + toast.success("OTP verified!"); + }, onError: (err) => { let errMessage = "An unknown error occurred."; if (err instanceof AxiosError && err.response?.data?.error) diff --git a/web/src/global-state/persistant-storage/token.ts b/web/src/global-state/persistant-storage/token.ts new file mode 100644 index 00000000..a8e7a277 --- /dev/null +++ b/web/src/global-state/persistant-storage/token.ts @@ -0,0 +1,73 @@ +import Cookies from "js-cookie"; +import { create } from "zustand"; +import { persist, StorageValue } from "zustand/middleware"; + +import { verifyToken } from "~/actions/auth/verify-token"; +import { UserInterface } from "~/schema/user"; + +import { createSelectors } from "../zustand"; + +interface UserStoreInterface { + data: UserInterface | null; + logout: () => void; + setData: (data: UserInterface) => void; +} + +const _useUserStore = create()( + persist( + (set) => ({ + data: null, + signIn: (data: UserInterface) => { + set({ data }); + }, + setData: (data: UserInterface) => set({ data }), + setProfileImage: (profileImage: string) => + set((state) => ({ + data: state.data + ? { ...state.data, profile_image: profileImage } + : null, + })), + logout: () => { + Cookies.remove("user-storage"); + set({ data: null }); + window.location.reload(); + }, + }), + { + name: "user-storage", + storage: { + getItem: async (name) => { + try { + const str = Cookies.get(name); + let data: StorageValue; + if (!str) { + data = { + state: await verifyToken(), + version: 0, + }; + setUser(data.state); + } else data = JSON.parse(str); + return data; + } catch { + logout(); + return null; + } + }, + setItem: (name, user: StorageValue) => { + const str = JSON.stringify(user); + Cookies.set(name, str); + }, + removeItem: (name) => Cookies.remove(name), + }, + }, + ), +); + +const useUserStore = createSelectors(_useUserStore); + +export const getUser = () => _useUserStore.getState().data; +export const setUser = (data: UserInterface) => + _useUserStore.getState().setData(data); +export const logout = () => _useUserStore.getState().logout(); + +export default useUserStore; diff --git a/web/src/global-state/zustand.ts b/web/src/global-state/zustand.ts new file mode 100644 index 00000000..9cae955e --- /dev/null +++ b/web/src/global-state/zustand.ts @@ -0,0 +1,18 @@ +import type { StoreApi, UseBoundStore } from "zustand"; + +type WithSelectors = S extends { getState: () => infer T } + ? S & { use: { [K in keyof T]: () => T[K] } } + : never; + +export const createSelectors = >>( + _store: S, +) => { + const store = _store as WithSelectors; + store.use = {}; + for (const k of Object.keys(store.getState())) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (store.use as any)[k] = () => store((s) => s[k as keyof typeof s]); + } + + return store; +}; diff --git a/web/src/route-tree.gen.ts b/web/src/route-tree.gen.ts index 03f4cade..d3bd4452 100644 --- a/web/src/route-tree.gen.ts +++ b/web/src/route-tree.gen.ts @@ -11,23 +11,29 @@ // Import Routes import { Route as rootRoute } from './routes/__root' +import { Route as AuthenticatedImport } from './routes/_authenticated' import { Route as AuthImport } from './routes/_auth' -import { Route as IndexImport } from './routes/index' +import { Route as AuthenticatedIndexImport } from './routes/_authenticated/index' import { Route as AuthSignUpImport } from './routes/_auth/sign-up' import { Route as AuthSignInImport } from './routes/_auth/sign-in' import { Route as AuthVerifyOtpIdImport } from './routes/_auth/verify-otp/$id' // Create/Update Routes +const AuthenticatedRoute = AuthenticatedImport.update({ + id: '/_authenticated', + getParentRoute: () => rootRoute, +} as any) + const AuthRoute = AuthImport.update({ id: '/_auth', getParentRoute: () => rootRoute, } as any) -const IndexRoute = IndexImport.update({ +const AuthenticatedIndexRoute = AuthenticatedIndexImport.update({ id: '/', path: '/', - getParentRoute: () => rootRoute, + getParentRoute: () => AuthenticatedRoute, } as any) const AuthSignUpRoute = AuthSignUpImport.update({ @@ -52,13 +58,6 @@ const AuthVerifyOtpIdRoute = AuthVerifyOtpIdImport.update({ declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexImport - parentRoute: typeof rootRoute - } '/_auth': { id: '/_auth' path: '' @@ -66,6 +65,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthImport parentRoute: typeof rootRoute } + '/_authenticated': { + id: '/_authenticated' + path: '' + fullPath: '' + preLoaderRoute: typeof AuthenticatedImport + parentRoute: typeof rootRoute + } '/_auth/sign-in': { id: '/_auth/sign-in' path: '/sign-in' @@ -80,6 +86,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthSignUpImport parentRoute: typeof AuthImport } + '/_authenticated/': { + id: '/_authenticated/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof AuthenticatedIndexImport + parentRoute: typeof AuthenticatedImport + } '/_auth/verify-otp/$id': { id: '/_auth/verify-otp/$id' path: '/verify-otp/$id' @@ -106,54 +119,68 @@ const AuthRouteChildren: AuthRouteChildren = { const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren) +interface AuthenticatedRouteChildren { + AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute +} + +const AuthenticatedRouteChildren: AuthenticatedRouteChildren = { + AuthenticatedIndexRoute: AuthenticatedIndexRoute, +} + +const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren( + AuthenticatedRouteChildren, +) + export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '': typeof AuthRouteWithChildren + '': typeof AuthenticatedRouteWithChildren '/sign-in': typeof AuthSignInRoute '/sign-up': typeof AuthSignUpRoute + '/': typeof AuthenticatedIndexRoute '/verify-otp/$id': typeof AuthVerifyOtpIdRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute '': typeof AuthRouteWithChildren '/sign-in': typeof AuthSignInRoute '/sign-up': typeof AuthSignUpRoute + '/': typeof AuthenticatedIndexRoute '/verify-otp/$id': typeof AuthVerifyOtpIdRoute } export interface FileRoutesById { __root__: typeof rootRoute - '/': typeof IndexRoute '/_auth': typeof AuthRouteWithChildren + '/_authenticated': typeof AuthenticatedRouteWithChildren '/_auth/sign-in': typeof AuthSignInRoute '/_auth/sign-up': typeof AuthSignUpRoute + '/_authenticated/': typeof AuthenticatedIndexRoute '/_auth/verify-otp/$id': typeof AuthVerifyOtpIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '' | '/sign-in' | '/sign-up' | '/verify-otp/$id' + fullPaths: '' | '/sign-in' | '/sign-up' | '/' | '/verify-otp/$id' fileRoutesByTo: FileRoutesByTo - to: '/' | '' | '/sign-in' | '/sign-up' | '/verify-otp/$id' + to: '' | '/sign-in' | '/sign-up' | '/' | '/verify-otp/$id' id: | '__root__' - | '/' | '/_auth' + | '/_authenticated' | '/_auth/sign-in' | '/_auth/sign-up' + | '/_authenticated/' | '/_auth/verify-otp/$id' fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute AuthRoute: typeof AuthRouteWithChildren + AuthenticatedRoute: typeof AuthenticatedRouteWithChildren } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, AuthRoute: AuthRouteWithChildren, + AuthenticatedRoute: AuthenticatedRouteWithChildren, } export const routeTree = rootRoute @@ -166,13 +193,10 @@ export const routeTree = rootRoute "__root__": { "filePath": "__root.tsx", "children": [ - "/", - "/_auth" + "/_auth", + "/_authenticated" ] }, - "/": { - "filePath": "index.tsx" - }, "/_auth": { "filePath": "_auth.tsx", "children": [ @@ -181,6 +205,12 @@ export const routeTree = rootRoute "/_auth/verify-otp/$id" ] }, + "/_authenticated": { + "filePath": "_authenticated.tsx", + "children": [ + "/_authenticated/" + ] + }, "/_auth/sign-in": { "filePath": "_auth/sign-in.tsx", "parent": "/_auth" @@ -189,6 +219,10 @@ export const routeTree = rootRoute "filePath": "_auth/sign-up.tsx", "parent": "/_auth" }, + "/_authenticated/": { + "filePath": "_authenticated/index.tsx", + "parent": "/_authenticated" + }, "/_auth/verify-otp/$id": { "filePath": "_auth/verify-otp/$id.tsx", "parent": "/_auth" diff --git a/web/src/routes/_authenticated.tsx b/web/src/routes/_authenticated.tsx new file mode 100644 index 00000000..85d2a5a4 --- /dev/null +++ b/web/src/routes/_authenticated.tsx @@ -0,0 +1,48 @@ +import { useQuery } from "@tanstack/react-query"; +import { createFileRoute, Outlet, useRouter } from "@tanstack/react-router"; +import { Loader } from "lucide-react"; +import { useEffect } from "react"; + +import { verifyToken } from "~/actions/auth/verify-token"; +import { setUser } from "~/global-state/persistant-storage/token"; + +export const Route = createFileRoute("/_authenticated")({ + component: RouteComponent, +}); + +function RouteComponent() { + const router = useRouter(); + + const { + data: user, + isPending, + isError, + } = useQuery({ + queryKey: ["verify-token-app-layout"], + queryFn: verifyToken, + }); + + useEffect(() => { + if (user) setUser(user); + }, [user]); + + if (isPending) { + return ( +
+ +
+ ); + } + + console.log(isError); + if (isError) { + router.navigate({ + replace: true, + to: "/sign-in", + search: { redirect: location.href }, + }); + return; + } + + return ; +} diff --git a/web/src/routes/index.tsx b/web/src/routes/_authenticated/index.tsx similarity index 51% rename from web/src/routes/index.tsx rename to web/src/routes/_authenticated/index.tsx index d6d3c332..a221e067 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/_authenticated/index.tsx @@ -1,12 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; -import { verifyTokenQueryFn } from "~/actions/auth/verify-token"; - -export const Route = createFileRoute("/")({ +export const Route = createFileRoute("/_authenticated/")({ component: RouteComponent, }); function RouteComponent() { - verifyTokenQueryFn(); return "Hello /!"; } diff --git a/web/src/schema/user.ts b/web/src/schema/user.ts new file mode 100644 index 00000000..63cbdd51 --- /dev/null +++ b/web/src/schema/user.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +export const userSchema = z.object({ + last_login: z.string().datetime(), + created_at: z.string().datetime(), + updated_at: z.string().datetime(), + email: z.string().email(), + first_name: z.string(), + last_name: z.string().optional(), + is_verified: z.boolean(), + is_active: z.boolean(), +}); + +export type UserInterface = z.infer;