diff --git a/.devcontainer/compose.dev.yml b/.devcontainer/compose.dev.yml index 8400184..d4b298a 100644 --- a/.devcontainer/compose.dev.yml +++ b/.devcontainer/compose.dev.yml @@ -1,17 +1,18 @@ services: workspace: - build: + build: dockerfile: Dockerfile volumes: - ../:/workspace:cached - command: /bin/sh -c "while sleep 1000; do :; done" + command: /bin/sh -c "while sleep 1000; do :; done" depends_on: - database - + database: image: postgres:17.2-alpine environment: POSTGRES_DB: acme POSTGRES_USER: postgres POSTGRES_PASSWORD: root - \ No newline at end of file + ports: + - 5432:5432 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8ad4f1f..a7db65a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,11 +1,25 @@ { - "name": "Next.js Auth Template", + "name": "Next.js Dev", "dockerComposeFile": ["compose.dev.yml"], "service": "workspace", "workspaceFolder": "/workspace", - "postCreateCommand": "pnpm config set store-dir $HOME/.pnpm-store", + "postCreateCommand": "bash ./.devcontainer/postCreateCommand.sh", "postStartCommand": "pnpm install", - "forwardPorts": [3000], + "forwardPorts": [3000, 9323, 4983], + "portsAttributes": { + "3000": { + "label": "Next.js Server", + "onAutoForward": "silent" + }, + "9323": { + "label": "Playwright Reports", + "onAutoForward": "silent" + }, + "4983": { + "label": "Drizzle-kit Studio", + "onAutoForward": "silent" + } + }, "features": { "ghcr.io/devcontainers-extra/features/pnpm": "latest" }, diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh new file mode 100644 index 0000000..f43c46e --- /dev/null +++ b/.devcontainer/postCreateCommand.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +pnpm config set store-dir $HOME/.pnpm-store +pnpm exec playwright install chromium --with-deps \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 6cc81fd..f40e2e4 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -24,7 +24,10 @@ const config = { fixStyle: "inline-type-imports", }, ], - "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], "@typescript-eslint/require-await": "off", "@typescript-eslint/no-misused-promises": [ "error", diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 4235c7e..95a169e 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -8,6 +8,20 @@ concurrency: group: ci-${{ github.ref }} cancel-in-progress: true +env: + DATABASE_URL: postgres://postgres:root@localhost:5432/acme + DISCORD_CLIENT_ID: ${{secrets.DISCORD_CLIENT_ID}} + DISCORD_CLIENT_SECRET: ${{secrets.DISCORD_CLIENT_SECRET}} + MOCK_SEND_EMAIL: "true" + SMTP_HOST: host + SMTP_PORT: 587 + SMTP_USER: user + SMTP_PASSWORD: password + NEXT_PUBLIC_APP_URL: http://localhost:3000 + STRIPE_API_KEY: stripe_api_key + STRIPE_WEBHOOK_SECRET: stripe_webhook_secret + STRIPE_PRO_MONTHLY_PLAN_ID: stripe_pro_monthly_plan_id + permissions: contents: read @@ -30,27 +44,22 @@ jobs: - name: Type Check and Lint run: pnpm run typecheck && pnpm run lint - env: - SKIP_ENV_VALIDATION: true e2e-test: needs: typecheck-and-lint timeout-minutes: 60 runs-on: ubuntu-latest - env: - DATABASE_URL: ${{secrets.DATABASE_URL}} - DISCORD_CLIENT_ID: ${{secrets.DISCORD_CLIENT_ID}} - DISCORD_CLIENT_SECRET: ${{secrets.DISCORD_CLIENT_SECRET}} - MOCK_SEND_EMAIL: "true" - SMTP_HOST: host - SMTP_PORT: 587 - SMTP_USER: user - SMTP_PASSWORD: password - NEXT_PUBLIC_APP_URL: http://localhost:3000 - STRIPE_API_KEY: stripe_api_key - STRIPE_WEBHOOK_SECRET: stripe_webhook_secret - STRIPE_PRO_MONTHLY_PLAN_ID: stripe_pro_monthly_plan_id + services: + postgres: + image: postgres:17.2-alpine + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: root + POSTGRES_DB: acme + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v2 @@ -62,7 +71,7 @@ jobs: node-version: 20.x cache: "pnpm" - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile && pnpm db:push - name: Build the app run: pnpm build - name: Install Playwright Browsers diff --git a/README.md b/README.md index 8a37464..5c9338b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,8 @@ -# Next.js Auth Starter Template +# Next.js Session-based Auth Starter Template ## Motivation -Implementing authentication in Next.js, especially Email+Password authentication, can be challenging. NextAuth intentionally limits email password functionality to discourage the use of passwords due to security risks and added complexity. However, in certain projects, clients may require user password authentication. Lucia offers a flexible alternative to NextAuth.js, providing more customization options without compromising on security. This template serves as a starting point for building a Next.js app with Lucia authentication. - -## Lucia vs. NextAuth.js - -Lucia is less opinionated than NextAuth, offering greater flexibility for customization. While Lucia involves more setup, it provides a higher degree of flexibility, making it a suitable choice for projects requiring unique authentication configurations. +Implementing authentication in Next.js, especially Email+Password authentication, can be challenging. NextAuth intentionally limits email password functionality to discourage the use of passwords due to security risks and added complexity. However, in certain projects, clients may require user password authentication. Lucia offers a flexible alternative to NextAuth.js, providing more customization options without compromising on security. This template serves as a starting point for building a Next.js app with secure session based authentication. ## Key Features @@ -14,7 +10,7 @@ Lucia is less opinionated than NextAuth, offering greater flexibility for custom - **Authorization:** ๐Ÿ”’ Easily manage public and protected routes within the `app directory`. - **Email Verification:** ๐Ÿ“ง Verify user identities through email. - **Password Reset:** ๐Ÿ”‘ Streamline password resets by sending email password reset links. -- **Lucia + tRPC:** ๐Ÿ”„ Similar to NextAuth with tRPC, granting access to sessions and user information through tRPC procedures. +- **Auth + tRPC:** ๐Ÿ”„ Similar to NextAuth with tRPC, granting access to sessions and user information through tRPC procedures. - **E2E tests:** ๐Ÿงช Catch every issue before your users do with comprehensive E2E testing. - **Stripe Payment:** ๐Ÿ’ณ Setup user subscriptions seamlessly with stripe. - **Email template with react-email:** โœ‰๏ธ Craft your email templates using React. @@ -24,7 +20,6 @@ Lucia is less opinionated than NextAuth, offering greater flexibility for custom ## Tech Stack - [Next.js](https://nextjs.org) -- [Lucia](https://lucia-auth.com/) - [tRPC](https://trpc.io) - [Drizzle ORM](https://orm.drizzle.team/) - [PostgreSQL](https://www.postgresql.org/) diff --git a/drizzle.config.ts b/drizzle.config.ts index 4bea3ac..2882ea2 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,8 +1,8 @@ -import { defineConfig } from "drizzle-kit"; import { DATABASE_PREFIX } from "@/lib/constants"; +import { defineConfig } from "drizzle-kit"; export default defineConfig({ - schema: "./src/server/db/schema.ts", + schema: "./src/server/db/schema", out: "./drizzle", dialect: "postgresql", dbCredentials: { diff --git a/package.json b/package.json index 9c67fb9..015f36e 100644 --- a/package.json +++ b/package.json @@ -18,16 +18,17 @@ }, "dependencies": { "@hookform/resolvers": "^3.9.0", - "@lucia-auth/adapter-drizzle": "1.0.7", - "@radix-ui/react-alert-dialog": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.1", - "@radix-ui/react-dropdown-menu": "^2.1.1", - "@radix-ui/react-icons": "^1.3.0", + "@oslojs/crypto": "^1.0.1", + "@oslojs/encoding": "^1.1.0", + "@radix-ui/react-alert-dialog": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", - "@react-email/components": "^0.0.12", - "@react-email/render": "^0.0.10", + "@react-email/components": "^0.0.31", + "@react-email/render": "^1.0.3", "@t3-oss/env-nextjs": "^0.7.3", "@tanstack/react-query": "^4.36.1", "@trpc/client": "^10.45.2", @@ -35,47 +36,48 @@ "@trpc/react-query": "^10.45.2", "@trpc/server": "^10.45.2", "arctic": "^1.9.2", + "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "lucia": "3.2.0", - "next": "^14.2.5", + "drizzle-orm": "^0.38.3", + "nanoid": "^5.0.9", + "next": "^15.1.5", "next-themes": "^0.2.1", "nodemailer": "^6.9.14", - "oslo": "^1.2.1", "postgres": "^3.4.4", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "19.0.0", + "react-dom": "19.0.0", "react-hook-form": "^7.52.1", "react-markdown": "^9.0.1", "react-syntax-highlighter": "^15.5.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "server-only": "^0.0.1", - "sonner": "^1.5.0", + "sonner": "^1.7.1", "stripe": "^14.25.0", "superjson": "^2.2.1", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", - "vaul": "^0.8.9", + "vaul": "^1.1.2", "zod": "^3.23.8" }, "devDependencies": { "@next/eslint-plugin-next": "^14.2.5", "@playwright/test": "^1.45.3", "@tailwindcss/typography": "^0.5.13", + "@types/bcryptjs": "^2.4.6", "@types/eslint": "^8.56.11", "@types/node": "^18.19.42", "@types/nodemailer": "^6.4.15", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", + "@types/react": "^19.0.3", + "@types/react-dom": "^19.0.2", "@types/react-syntax-highlighter": "^15.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "autoprefixer": "^10.4.19", "dotenv": "^16.4.5", "dotenv-cli": "^7.4.2", - "drizzle-kit": "^0.23.0", - "drizzle-orm": "^0.32.1", + "drizzle-kit": "^0.30.1", "eslint": "^8.57.0", "pg": "^8.12.0", "postcss": "^8.4.40", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55efc4f..2d41daf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,97 +10,103 @@ importers: dependencies: '@hookform/resolvers': specifier: ^3.9.0 - version: 3.9.0(react-hook-form@7.52.1(react@18.2.0)) - '@lucia-auth/adapter-drizzle': - specifier: 1.0.7 - version: 1.0.7(lucia@3.2.0) + version: 3.9.0(react-hook-form@7.52.1(react@19.0.0)) + '@oslojs/crypto': + specifier: ^1.0.1 + version: 1.0.1 + '@oslojs/encoding': + specifier: ^1.1.0 + version: 1.1.0 '@radix-ui/react-alert-dialog': - specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^1.1.4 + version: 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-dialog': - specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^1.1.4 + version: 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-dropdown-menu': - specifier: ^2.1.1 - version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^2.1.4 + version: 2.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-icons': - specifier: ^1.3.0 - version: 1.3.0(react@18.2.0) + specifier: ^1.3.2 + version: 1.3.2(react@19.0.0) '@radix-ui/react-label': specifier: ^2.1.0 - version: 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 2.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-slot': specifier: ^1.1.0 - version: 1.1.0(@types/react@18.3.3)(react@18.2.0) + version: 1.1.0(@types/react@19.0.3)(react@19.0.0) '@radix-ui/react-tabs': specifier: ^1.1.0 - version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 1.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@react-email/components': - specifier: ^0.0.12 - version: 0.0.12(@types/react@18.3.3)(react@18.2.0) + specifier: ^0.0.31 + version: 0.0.31(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@react-email/render': - specifier: ^0.0.10 - version: 0.0.10 + specifier: ^1.0.3 + version: 1.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@t3-oss/env-nextjs': specifier: ^0.7.3 version: 0.7.3(typescript@5.5.4)(zod@3.23.8) '@tanstack/react-query': specifier: ^4.36.1 - version: 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@trpc/client': specifier: ^10.45.2 version: 10.45.2(@trpc/server@10.45.2) '@trpc/next': specifier: ^10.45.2 - version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@14.2.5(@playwright/test@1.45.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@trpc/server@10.45.2)(next@15.1.5(@playwright/test@1.45.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@trpc/react-query': specifier: ^10.45.2 - version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@trpc/server': specifier: ^10.45.2 version: 10.45.2 arctic: specifier: ^1.9.2 version: 1.9.2 + bcryptjs: + specifier: ^2.4.3 + version: 2.4.3 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 clsx: specifier: ^2.1.1 version: 2.1.1 - lucia: - specifier: 3.2.0 - version: 3.2.0 + drizzle-orm: + specifier: ^0.38.3 + version: 0.38.3(@types/react@19.0.3)(pg@8.12.0)(postgres@3.4.4)(react@19.0.0) + nanoid: + specifier: ^5.0.9 + version: 5.0.9 next: - specifier: ^14.2.5 - version: 14.2.5(@playwright/test@1.45.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^15.1.5 + version: 15.1.5(@playwright/test@1.45.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.5(@playwright/test@1.45.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.2.1(next@15.1.5(@playwright/test@1.45.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nodemailer: specifier: ^6.9.14 version: 6.9.14 - oslo: - specifier: ^1.2.1 - version: 1.2.1 postgres: specifier: ^3.4.4 version: 3.4.4 react: - specifier: 18.2.0 - version: 18.2.0 + specifier: 19.0.0 + version: 19.0.0 react-dom: - specifier: 18.2.0 - version: 18.2.0(react@18.2.0) + specifier: 19.0.0 + version: 19.0.0(react@19.0.0) react-hook-form: specifier: ^7.52.1 - version: 7.52.1(react@18.2.0) + version: 7.52.1(react@19.0.0) react-markdown: specifier: ^9.0.1 - version: 9.0.1(@types/react@18.3.3)(react@18.2.0) + version: 9.0.1(@types/react@19.0.3)(react@19.0.0) react-syntax-highlighter: specifier: ^15.5.0 - version: 15.5.0(react@18.2.0) + version: 15.5.0(react@19.0.0) rehype-raw: specifier: ^7.0.0 version: 7.0.0 @@ -111,8 +117,8 @@ importers: specifier: ^0.0.1 version: 0.0.1 sonner: - specifier: ^1.5.0 - version: 1.5.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^1.7.1 + version: 1.7.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) stripe: specifier: ^14.25.0 version: 14.25.0 @@ -126,8 +132,8 @@ importers: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.7) vaul: - specifier: ^0.8.9 - version: 0.8.9(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) zod: specifier: ^3.23.8 version: 3.23.8 @@ -141,6 +147,9 @@ importers: '@tailwindcss/typography': specifier: ^0.5.13 version: 0.5.13(tailwindcss@3.4.7) + '@types/bcryptjs': + specifier: ^2.4.6 + version: 2.4.6 '@types/eslint': specifier: ^8.56.11 version: 8.56.11 @@ -151,11 +160,11 @@ importers: specifier: ^6.4.15 version: 6.4.15 '@types/react': - specifier: ^18.3.3 - version: 18.3.3 + specifier: ^19.0.3 + version: 19.0.3 '@types/react-dom': - specifier: ^18.3.0 - version: 18.3.0 + specifier: ^19.0.2 + version: 19.0.2(@types/react@19.0.3) '@types/react-syntax-highlighter': specifier: ^15.5.13 version: 15.5.13 @@ -175,11 +184,8 @@ importers: specifier: ^7.4.2 version: 7.4.2 drizzle-kit: - specifier: ^0.23.0 - version: 0.23.0 - drizzle-orm: - specifier: ^0.32.1 - version: 0.32.1(@types/react@18.3.3)(pg@8.12.0)(postgres@3.4.4)(react@18.2.0) + specifier: ^0.30.1 + version: 0.30.1 eslint: specifier: ^8.57.0 version: 8.57.0 @@ -215,17 +221,25 @@ packages: resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==} engines: {node: '>=6.9.0'} + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@emnapi/core@0.45.0': resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} '@emnapi/runtime@0.45.0': resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==} + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' '@esbuild-kit/esm-loader@2.6.5': resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' '@esbuild/aix-ppc64@0.19.12': resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} @@ -686,6 +700,111 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -708,67 +827,56 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@lucia-auth/adapter-drizzle@1.0.7': - resolution: {integrity: sha512-X/V7fLBca8EC/gPXCntwbQpb0+F9oEuRoHElvsi9rCrdnGhCMNxHgwAvgiQ6pes+rIYpyvx4n3hvjqo/fPo03A==} - peerDependencies: - lucia: 3.x - - '@next/env@14.2.5': - resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} + '@next/env@15.1.5': + resolution: {integrity: sha512-jg8ygVq99W3/XXb9Y6UQsritwhjc+qeiO7QrGZRYOfviyr/HcdnhdBQu4gbp2rBIh2ZyBYTBMWbPw3JSCb0GHw==} '@next/eslint-plugin-next@14.2.5': resolution: {integrity: sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==} - '@next/swc-darwin-arm64@14.2.5': - resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} + '@next/swc-darwin-arm64@15.1.5': + resolution: {integrity: sha512-5ttHGE75Nw9/l5S8zR2xEwR8OHEqcpPym3idIMAZ2yo+Edk0W/Vf46jGqPOZDk+m/SJ+vYZDSuztzhVha8rcdA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.5': - resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} + '@next/swc-darwin-x64@15.1.5': + resolution: {integrity: sha512-8YnZn7vDURUUTInfOcU5l0UWplZGBqUlzvqKKUFceM11SzfNEz7E28E1Arn4/FsOf90b1Nopboy7i7ufc4jXag==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.5': - resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} + '@next/swc-linux-arm64-gnu@15.1.5': + resolution: {integrity: sha512-rDJC4ctlYbK27tCyFUhgIv8o7miHNlpCjb2XXfTLQszwAUOSbcMN9q2y3urSrrRCyGVOd9ZR9a4S45dRh6JF3A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.5': - resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} + '@next/swc-linux-arm64-musl@15.1.5': + resolution: {integrity: sha512-FG5RApf4Gu+J+pHUQxXPM81oORZrKBYKUaBTylEIQ6Lz17hKVDsLbSXInfXM0giclvXbyiLXjTv42sQMATmZ0A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.5': - resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} + '@next/swc-linux-x64-gnu@15.1.5': + resolution: {integrity: sha512-NX2Ar3BCquAOYpnoYNcKz14eH03XuF7SmSlPzTSSU4PJe7+gelAjxo3Y7F2m8+hLT8ZkkqElawBp7SWBdzwqQw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.5': - resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} + '@next/swc-linux-x64-musl@15.1.5': + resolution: {integrity: sha512-EQgqMiNu3mrV5eQHOIgeuh6GB5UU57tu17iFnLfBEhYfiOfyK+vleYKh2dkRVkV6ayx3eSqbIYgE7J7na4hhcA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.5': - resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} + '@next/swc-win32-arm64-msvc@15.1.5': + resolution: {integrity: sha512-HPULzqR/VqryQZbZME8HJE3jNFmTGcp+uRMHabFbQl63TtDPm+oCXAz3q8XyGv2AoihwNApVlur9Up7rXWRcjg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.5': - resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@next/swc-win32-x64-msvc@14.2.5': - resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} + '@next/swc-win32-x64-msvc@15.1.5': + resolution: {integrity: sha512-n74fUb/Ka1dZSVYfjwQ+nSJ+ifUff7jGurFcTuJNKZmI62FFOxQXUYit/uZXPTj2cirm1rvGWHG2GhbSol5Ikw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -959,8 +1067,17 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@one-ini/wasm@0.1.1': - resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@oslojs/asn1@1.0.0': + resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==} + + '@oslojs/binary@1.0.0': + resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==} + + '@oslojs/crypto@1.0.1': + resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -974,8 +1091,11 @@ packages: '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} - '@radix-ui/react-alert-dialog@1.1.1': - resolution: {integrity: sha512-wmCoJwj7byuVuiLKqDLlX7ClSUU0vd9sdCeM+2Ls+uf13+cpSJoMgwysHq1SGVVkJj5Xn0XWi1NoRCdkMpr6Mw==} + '@radix-ui/primitive@1.1.1': + resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + + '@radix-ui/react-alert-dialog@1.1.4': + resolution: {integrity: sha512-A6Kh23qZDLy3PSU4bh2UJZznOrUdHImIXqF8YtUa6CN73f8EOO9XlXSCd9IHyPvIquTaa/kwaSWzZTtUvgXVGw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -987,8 +1107,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-arrow@1.1.0': - resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} + '@radix-ui/react-arrow@1.1.1': + resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1013,14 +1133,18 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-compose-refs@1.0.1': - resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} + '@radix-ui/react-collection@1.1.1': + resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true + '@types/react-dom': + optional: true '@radix-ui/react-compose-refs@1.1.0': resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} @@ -1031,6 +1155,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.1': + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.1.0': resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -1040,8 +1173,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.1': - resolution: {integrity: sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==} + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.4': + resolution: {integrity: sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1062,8 +1204,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.0': - resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==} + '@radix-ui/react-dismissable-layer@1.1.3': + resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1075,8 +1217,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.1': - resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==} + '@radix-ui/react-dropdown-menu@2.1.4': + resolution: {integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1088,8 +1230,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.1.0': - resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} + '@radix-ui/react-focus-guards@1.1.1': + resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1097,8 +1239,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.1.0': - resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} + '@radix-ui/react-focus-scope@1.1.1': + resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1110,10 +1252,10 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-icons@1.3.0': - resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==} + '@radix-ui/react-icons@1.3.2': + resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==} peerDependencies: - react: ^16.x || ^17.x || ^18.x + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc '@radix-ui/react-id@1.1.0': resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} @@ -1137,8 +1279,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-menu@2.1.1': - resolution: {integrity: sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==} + '@radix-ui/react-menu@2.1.4': + resolution: {integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1150,8 +1292,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.2.0': - resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} + '@radix-ui/react-popper@1.2.1': + resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1163,8 +1305,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.1': - resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} + '@radix-ui/react-portal@1.1.3': + resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1189,6 +1331,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.2': + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.0.0': resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} peerDependencies: @@ -1202,6 +1357,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.0.1': + resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-roving-focus@1.1.0': resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} peerDependencies: @@ -1215,14 +1383,18 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.0.2': - resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + '@radix-ui/react-roving-focus@1.1.1': + resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true + '@types/react-dom': + optional: true '@radix-ui/react-slot@1.1.0': resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} @@ -1233,6 +1405,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.1.1': + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-tabs@1.1.0': resolution: {integrity: sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==} peerDependencies: @@ -1303,111 +1484,130 @@ packages: '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} - '@react-email/body@0.0.4': - resolution: {integrity: sha512-NmHOumdmyjWvOXomqhQt06KbgRxhHrVznxQp/oWiPWes8nAJo2Y4L27aPHR9nTcs7JF7NmcJe9YSN42pswK+GQ==} + '@react-email/body@0.0.11': + resolution: {integrity: sha512-ZSD2SxVSgUjHGrB0Wi+4tu3MEpB4fYSbezsFNEJk2xCWDBkFiOeEsjTmR5dvi+CxTK691hQTQlHv0XWuP7ENTg==} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/button@0.0.11': - resolution: {integrity: sha512-mB5ySfZifwE5ybtIWwXGbmKk1uKkH4655gftL4+mMxZAZCkINVa2KXTi5pO+xZhMtJI9xtAsikOrOEU1gTDoww==} + '@react-email/button@0.0.19': + resolution: {integrity: sha512-HYHrhyVGt7rdM/ls6FuuD6XE7fa7bjZTJqB2byn6/oGsfiEZaogY77OtoLL/mrQHjHjZiJadtAMSik9XLcm7+A==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/column@0.0.8': - resolution: {integrity: sha512-blChqGU8e/L6KZiB5EPww8bkZfdyHDuS0vKIvU+iS14uK+xfAw+5P5CU9BYXccEuJh2Gftfngu1bWMFp2Sc6ag==} + '@react-email/code-block@0.0.11': + resolution: {integrity: sha512-4D43p+LIMjDzm66gTDrZch0Flkip5je91mAT7iGs6+SbPyalHgIA+lFQoQwhz/VzHHLxuD0LV6gwmU/WUQ2WEg==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/components@0.0.12': - resolution: {integrity: sha512-siVu1xcMV3u14aBIIMWQKVma/EC9TitRYf9tOL1bNXhMFtJhY4ozBudF0Lk4Gm5p8Dn9ZeitoenKiJ5XiVXrdg==} + '@react-email/code-inline@0.0.5': + resolution: {integrity: sha512-MmAsOzdJpzsnY2cZoPHFPk6uDO/Ncpb4Kh1hAt9UZc1xOW3fIzpe1Pi9y9p6wwUmpaeeDalJxAxH6/fnTquinA==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/container@0.0.10': - resolution: {integrity: sha512-goishY7ocq+lord0043/LZK268bqvMFW/sxpUt/dSCPJyrrZZNCbpW2t8w8HztU38cYj0qGQLxO5Qvpn/RER3w==} + '@react-email/column@0.0.13': + resolution: {integrity: sha512-Lqq17l7ShzJG/d3b1w/+lVO+gp2FM05ZUo/nW0rjxB8xBICXOVv6PqjDnn3FXKssvhO5qAV20lHM6S+spRhEwQ==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/font@0.0.4': - resolution: {integrity: sha512-rN/pFlAcDNmfYFxpufT/rFRrM5KYBJM4nTA2uylTehlVOro6fb/q6n0zUwLF6OmQ4QIuRbqdEy7DI9mmJiNHxA==} + '@react-email/components@0.0.31': + resolution: {integrity: sha512-rQsTY9ajobncix9raexhBjC7O6cXUMc87eNez2gnB1FwtkUO8DqWZcktbtwOJi7GKmuAPTx0o/IOFtiBNXziKA==} + engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/head@0.0.6': - resolution: {integrity: sha512-9BrBDalb34nBOmmQVQc7/pjJotcuAeC3rhBl4G88Ohiipuv15vPIKqwy8vPJcFNi4l7yGlitfG3EESIjkLkoIw==} + '@react-email/container@0.0.15': + resolution: {integrity: sha512-Qo2IQo0ru2kZq47REmHW3iXjAQaKu4tpeq/M8m1zHIVwKduL2vYOBQWbC2oDnMtWPmkBjej6XxgtZByxM6cCFg==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/heading@0.0.9': - resolution: {integrity: sha512-xzkcGlm+/aFrNlJZBKzxRKkRYJ2cRx92IqmSKAuGnwuKQ/uMKomXzPsHPu3Dclmnhn3wVKj4uprkgQOoxP6uXQ==} - engines: {node: '>=16.0.0'} + '@react-email/font@0.0.9': + resolution: {integrity: sha512-4zjq23oT9APXkerqeslPH3OZWuh5X4crHK6nx82mVHV2SrLba8+8dPEnWbaACWTNjOCbcLIzaC9unk7Wq2MIXw==} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/head@0.0.12': + resolution: {integrity: sha512-X2Ii6dDFMF+D4niNwMAHbTkeCjlYYnMsd7edXOsi0JByxt9wNyZ9EnhFiBoQdqkE+SMDcu8TlNNttMrf5sJeMA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/hr@0.0.6': - resolution: {integrity: sha512-W+wINBz7z7BRv3i9GS+QoJBae1PESNhv6ZY6eLnEpqtBI/2++suuRNJOU/KpZzE6pykeTp6I/Z7UcL0LEYKgyg==} + '@react-email/heading@0.0.15': + resolution: {integrity: sha512-xF2GqsvBrp/HbRHWEfOgSfRFX+Q8I5KBEIG5+Lv3Vb2R/NYr0s8A5JhHHGf2pWBMJdbP4B2WHgj/VUrhy8dkIg==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/html@0.0.6': - resolution: {integrity: sha512-8Fo20VOqxqc087gGEPjT8uos06fTXIC8NSoiJxpiwAkwiKtQnQH/jOdoLv6XaWh5Zt2clj1uokaoklnaM5rY1w==} + '@react-email/hr@0.0.11': + resolution: {integrity: sha512-S1gZHVhwOsd1Iad5IFhpfICwNPMGPJidG/Uysy1AwmspyoAP5a4Iw3OWEpINFdgh9MHladbxcLKO2AJO+cA9Lw==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/img@0.0.6': - resolution: {integrity: sha512-Wd7xKI3b1Jvb2ZEHyVpJ9D98u0GHrRl+578b8LV24PavM/65V61Q5LN5Fr9sAhj+4VGqnHDIVeXIYEzVbWaa3Q==} + '@react-email/html@0.0.11': + resolution: {integrity: sha512-qJhbOQy5VW5qzU74AimjAR9FRFQfrMa7dn4gkEXKMB/S9xZN8e1yC1uA9C15jkXI/PzmJ0muDIWmFwatm5/+VA==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/link@0.0.6': - resolution: {integrity: sha512-bYYHroWGS//nDl9yhh8V6K2BrNwAsyX7N/XClSCRku3x56NrZ6D0nBKWewYDPlJ9rW9TIaJm1jDYtO9XBzLlkQ==} + '@react-email/img@0.0.11': + resolution: {integrity: sha512-aGc8Y6U5C3igoMaqAJKsCpkbm1XjguQ09Acd+YcTKwjnC2+0w3yGUJkjWB2vTx4tN8dCqQCXO8FmdJpMfOA9EQ==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/preview@0.0.7': - resolution: {integrity: sha512-YLfIwHdexPi8IgP1pSuVXdAmKzMQ8ctCCLEjkMttT2vkSFqT6m/e6UFWK2l30rKm2dDsLvQyEvo923mPXjnNzg==} + '@react-email/link@0.0.12': + resolution: {integrity: sha512-vF+xxQk2fGS1CN7UPQDbzvcBGfffr+GjTPNiWM38fhBfsLv6A/YUfaqxWlmL7zLzVmo0K2cvvV9wxlSyNba1aQ==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/render@0.0.10': - resolution: {integrity: sha512-FdLhg/E5PH5qZU/jf9NbvRi5v5134kbX7o8zIhOJIk/TALxB18ggprnH5tQX96dGQFqlLob8OLReaRwrpEF7YA==} + '@react-email/markdown@0.0.14': + resolution: {integrity: sha512-5IsobCyPkb4XwnQO8uFfGcNOxnsg3311GRXhJ3uKv51P7Jxme4ycC/MITnwIZ10w2zx7HIyTiqVzTj4XbuIHbg==} engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/render@0.0.9': - resolution: {integrity: sha512-nrim7wiACnaXsGtL7GF6jp3Qmml8J6vAjAH88jkC8lIbfNZaCyuPQHANjyYIXlvQeAbsWADQJFZgOHUqFqjh/A==} + '@react-email/preview@0.0.12': + resolution: {integrity: sha512-g/H5fa9PQPDK6WUEG7iTlC19sAktI23qyoiJtMLqQiXFCfWeQMhqjLGKeLSKkfzszqmfJCjZtpSiKtBoOdxp3Q==} engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/row@0.0.6': - resolution: {integrity: sha512-msJ2TnDJNwpgDfDzUO63CvhusJHeaGLMM+8Zz86VPvxzwe/DkT7N48QKRWRCkt8urxVz5U+EgivORA9Dum9p3Q==} + '@react-email/render@1.0.3': + resolution: {integrity: sha512-VQ8g4SuIq/jWdfBTdTjb7B8Np0jj+OoD7VebfdHhLTZzVQKesR2aigpYqE/ZXmwj4juVxDm8T2b6WIIu48rPCg==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/section@0.0.10': - resolution: {integrity: sha512-x9B2KYFqj+d8I1fK9bgeVm/3mLE4Qgn4mm/GbDtcJeSzKU/G7bTb7/3+BMDk9SARPGkg5XAuZm1XgcqQQutt2A==} + '@react-email/row@0.0.12': + resolution: {integrity: sha512-HkCdnEjvK3o+n0y0tZKXYhIXUNPDx+2vq1dJTmqappVHXS5tXS6W5JOPZr5j+eoZ8gY3PShI2LWj5rWF7ZEtIQ==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/tailwind@0.0.13': - resolution: {integrity: sha512-uC/nSBau//LYq0ZJea5jO9ZNyLvgJ04Wl9wx63jw26ye+f2nKqQ2gO9OQcpNrEzSvjQXlUXa5rpV/9ZtMGSJHA==} + '@react-email/section@0.0.16': + resolution: {integrity: sha512-FjqF9xQ8FoeUZYKSdt8sMIKvoT9XF8BrzhT3xiFKdEMwYNbsDflcjfErJe3jb7Wj/es/lKTbV5QR1dnLzGpL3w==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/text@0.0.6': - resolution: {integrity: sha512-PDUTAD1PjlzXFOIUrR1zuV2xxguL62yne5YLcn1k+u/dVUyzn6iU/5lFShxCfzuh3QDWCf4+JRNnXN9rmV6jzw==} + '@react-email/tailwind@1.0.4': + resolution: {integrity: sha512-tJdcusncdqgvTUYZIuhNC6LYTfL9vNTSQpwWdTCQhQ1lsrNCEE4OKCSdzSV3S9F32pi0i0xQ+YPJHKIzGjdTSA==} engines: {node: '>=18.0.0'} peerDependencies: - react: 18.2.0 + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/text@0.0.11': + resolution: {integrity: sha512-a7nl/2KLpRHOYx75YbYZpWspUbX1DFY7JIZbOv5x0QU8SvwDbJt+Hm01vG34PffFyYvHEXrc6Qnip2RTjljNjg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} @@ -1415,8 +1615,8 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/helpers@0.5.5': - resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} '@t3-oss/env-core@0.7.3': resolution: {integrity: sha512-hhtj59TKC6TKVdwJ0CcbKsvkr9R8Pc/SNKd4IgGUIC9T9X6moB8EZZ3FTJdABA/h9UABCK4J+KsF8gzmvMvHPg==} @@ -1487,6 +1687,9 @@ packages: '@tybys/wasm-util@0.8.3': resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==} + '@types/bcryptjs@2.4.6': + resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1523,8 +1726,10 @@ packages: '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - '@types/react-dom@18.3.0': - resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + '@types/react-dom@19.0.2': + resolution: {integrity: sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==} + peerDependencies: + '@types/react': ^19.0.0 '@types/react-syntax-highlighter@15.5.13': resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} @@ -1532,6 +1737,9 @@ packages: '@types/react@18.3.3': resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/react@19.0.3': + resolution: {integrity: sha512-UavfHguIjnnuq9O67uXfgy/h3SRJbidAYvNjLceB+2RIKVRBzVsh0QO+Pw6BCSQqFS9xwzKfwstXx0m6AbAREA==} + '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -1602,10 +1810,6 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - abbrev@2.0.0: - resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1672,6 +1876,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bcryptjs@2.4.3: + resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1766,16 +1973,19 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} - commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1783,13 +1993,6 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - condense-newlines@0.2.1: - resolution: {integrity: sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==} - engines: {node: '>=0.10.0'} - - config-chain@1.1.13: - resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -1833,6 +2036,10 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} @@ -1878,18 +2085,19 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} - drizzle-kit@0.23.0: - resolution: {integrity: sha512-w9jE97z193dd4jzAyj4Uv2SOh8Ydue70Ki6W0awy4bGM1aPXan6zD6Yv+nNTA6oGgNTDl2MJFxutjHG4fden5g==} + drizzle-kit@0.30.1: + resolution: {integrity: sha512-HmA/NeewvHywhJ2ENXD3KvOuM/+K2dGLJfxVfIHsGwaqKICJnS+Ke2L6UcSrSrtMJLJaT0Im1Qv4TFXfaZShyw==} hasBin: true - drizzle-orm@0.32.1: - resolution: {integrity: sha512-Wq1J+lL8PzwR5K3a1FfoWsbs8powjr3pGA4+5+2ueN1VTLDNFYEolUyUWFtqy8DVRvYbL2n7sXZkgVmK9dQkng==} + drizzle-orm@0.38.3: + resolution: {integrity: sha512-w41Y+PquMpSff/QDRGdItG0/aWca+/J3Sda9PPGkTxBtjWQvgU1jxlFBXdjog5tYvTu58uvi3PwR1NuCx0KeZg==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' - '@electric-sql/pglite': '>=0.1.1' - '@libsql/client': '*' - '@neondatabase/serverless': '>=0.1' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 '@planetscale/database': '>=1' @@ -1903,7 +2111,7 @@ packages: '@xata.io/client': '*' better-sqlite3: '>=7' bun-types: '*' - expo-sqlite: '>=13.2.0' + expo-sqlite: '>=14.0.0' knex: '*' kysely: '*' mysql2: '>=2' @@ -1922,6 +2130,8 @@ packages: optional: true '@libsql/client': optional: true + '@libsql/client-wasm': + optional: true '@neondatabase/serverless': optional: true '@op-engineering/op-sqlite': @@ -1974,11 +2184,6 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - editorconfig@1.0.4: - resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} - engines: {node: '>=14'} - hasBin: true - electron-to-chromium@1.5.2: resolution: {integrity: sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ==} @@ -2068,13 +2273,12 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2186,9 +2390,6 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -2273,9 +2474,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - inline-style-parser@0.2.3: resolution: {integrity: sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==} @@ -2294,13 +2492,13 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - is-core-module@2.15.0: resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} engines: {node: '>= 0.4'} @@ -2311,10 +2509,6 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2349,10 +2543,6 @@ packages: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} - is-whitespace@0.3.0: - resolution: {integrity: sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==} - engines: {node: '>=0.10.0'} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -2367,15 +2557,6 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true - js-beautify@1.15.1: - resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==} - engines: {node: '>=14'} - 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==} @@ -2395,10 +2576,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kind-of@3.2.2: - resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} - engines: {node: '>=0.10.0'} - leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -2443,12 +2620,19 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lucia@3.2.0: - resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==} - markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + marked@7.0.4: + resolution: {integrity: sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==} + engines: {node: '>= 16'} + hasBin: true + + md-to-react-email@5.0.5: + resolution: {integrity: sha512-OvAXqwq57uOk+WZqFFNCMZz8yDp8BD3WazW1wAKHUrPbbdr89K9DWS6JXY09vd9xNdPNeurI8DU/X4flcfaD8A==} + peerDependencies: + react: ^18.0 || ^19.0 + mdast-util-find-and-replace@3.0.1: resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} @@ -2596,10 +2780,6 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.1: - resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} @@ -2626,6 +2806,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.0.9: + resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==} + engines: {node: ^18 || >=20} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -2636,21 +2821,24 @@ packages: react: '*' react-dom: '*' - next@14.2.5: - resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} - engines: {node: '>=18.17.0'} + next@15.1.5: + resolution: {integrity: sha512-Cf/TEegnt01hn3Hoywh6N8fvkhbOuChO4wFje24+a86wKOubgVaWkDqxGVgoWlz2Hp9luMJ9zw3epftujdnUOg==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': optional: true '@playwright/test': optional: true + babel-plugin-react-compiler: + optional: true sass: optional: true @@ -2661,11 +2849,6 @@ packages: resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==} engines: {node: '>=6.0.0'} - nopt@7.2.1: - resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2696,9 +2879,6 @@ packages: oslo@1.2.0: resolution: {integrity: sha512-OoFX6rDsNcOQVAD2gQD/z03u4vEjWZLzJtwkmgfRF+KpQUXwdgEXErD7zNhyowmHwHefP+PM9Pw13pgpHMRlzw==} - oslo@1.2.1: - resolution: {integrity: sha512-HfIhB5ruTdQv0XX2XlncWQiJ5SIHZ7NHZhVyHth0CSZ/xzge00etRyYy/3wp/Dsu+PkxMC+6+B2lS/GcKoewkA==} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -2941,10 +3121,6 @@ packages: engines: {node: '>=14'} hasBin: true - pretty@2.0.0: - resolution: {integrity: sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==} - engines: {node: '>=0.10.0'} - prismjs@1.27.0: resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} engines: {node: '>=6'} @@ -2959,9 +3135,6 @@ packages: property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} - proto-list@1.2.4: - resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2973,10 +3146,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@18.2.0: - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: - react: ^18.2.0 + react: ^19.0.0 react-hook-form@7.52.1: resolution: {integrity: sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg==} @@ -2990,22 +3163,25 @@ packages: '@types/react': '>=18' react: '>=18' - react-remove-scroll-bar@2.3.6: - resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + react-promise-suspense@0.3.4: + resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': optional: true - react-remove-scroll@2.5.7: - resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==} + react-remove-scroll@2.6.2: + resolution: {integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true @@ -3020,13 +3196,23 @@ packages: '@types/react': optional: true + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react-syntax-highlighter@15.5.0: resolution: {integrity: sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==} peerDependencies: react: '>= 0.14.0' - react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -3080,8 +3266,8 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} @@ -3098,6 +3284,10 @@ packages: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3114,15 +3304,18 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - sonner@1.5.0: - resolution: {integrity: sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==} + sonner@1.7.1: + resolution: {integrity: sha512-b6LHBfH32SoVasRFECrdY8p8s7hXPDn3OHUFbZZbiB1ctLS9Gdh6rpX2dVrpQA0kiL5jcRzDDldwwLkSKk3+QQ==} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} @@ -3179,13 +3372,13 @@ packages: style-to-object@1.0.6: resolution: {integrity: sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==} - styled-jsx@5.1.1: - resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} peerDependencies: '@babel/core': '*' babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' peerDependenciesMeta: '@babel/core': optional: true @@ -3254,6 +3447,9 @@ packages: tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.16.2: resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==} engines: {node: '>=18.0.0'} @@ -3305,12 +3501,12 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-callback-ref@1.3.2: - resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true @@ -3333,11 +3529,11 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vaul@0.8.9: - resolution: {integrity: sha512-gpmtmZRWDPP6niQh14JfRIFUYZVyfvAWyA/7rUINOfNlO/2K7uEvI5rLXEXkxZIRFyUZj+TPHLFMirkegPHjrw==} + vaul@1.1.2: + resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -3398,14 +3594,21 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@drizzle-team/brocli@0.10.2': {} + '@emnapi/core@0.45.0': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 optional: true '@emnapi/runtime@0.45.0': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.8.1 optional: true '@esbuild-kit/core-utils@3.3.2': @@ -3654,17 +3857,17 @@ snapshots: '@floating-ui/core': 1.6.5 '@floating-ui/utils': 0.2.5 - '@floating-ui/react-dom@2.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@floating-ui/react-dom@2.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@floating-ui/dom': 1.6.8 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) '@floating-ui/utils@0.2.5': {} - '@hookform/resolvers@3.9.0(react-hook-form@7.52.1(react@18.2.0))': + '@hookform/resolvers@3.9.0(react-hook-form@7.52.1(react@19.0.0))': dependencies: - react-hook-form: 7.52.1(react@18.2.0) + react-hook-form: 7.52.1(react@19.0.0) '@humanwhocodes/config-array@0.11.14': dependencies: @@ -3678,6 +3881,81 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.3.1 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3704,41 +3982,34 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@lucia-auth/adapter-drizzle@1.0.7(lucia@3.2.0)': - dependencies: - lucia: 3.2.0 - - '@next/env@14.2.5': {} + '@next/env@15.1.5': {} '@next/eslint-plugin-next@14.2.5': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@14.2.5': - optional: true - - '@next/swc-darwin-x64@14.2.5': + '@next/swc-darwin-arm64@15.1.5': optional: true - '@next/swc-linux-arm64-gnu@14.2.5': + '@next/swc-darwin-x64@15.1.5': optional: true - '@next/swc-linux-arm64-musl@14.2.5': + '@next/swc-linux-arm64-gnu@15.1.5': optional: true - '@next/swc-linux-x64-gnu@14.2.5': + '@next/swc-linux-arm64-musl@15.1.5': optional: true - '@next/swc-linux-x64-musl@14.2.5': + '@next/swc-linux-x64-gnu@15.1.5': optional: true - '@next/swc-win32-arm64-msvc@14.2.5': + '@next/swc-linux-x64-musl@15.1.5': optional: true - '@next/swc-win32-ia32-msvc@14.2.5': + '@next/swc-win32-arm64-msvc@15.1.5': optional: true - '@next/swc-win32-x64-msvc@14.2.5': + '@next/swc-win32-x64-msvc@15.1.5': optional: true '@node-rs/argon2-android-arm-eabi@1.7.0': @@ -3881,7 +4152,18 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@one-ini/wasm@0.1.1': {} + '@oslojs/asn1@1.0.0': + dependencies: + '@oslojs/binary': 1.0.0 + + '@oslojs/binary@1.0.0': {} + + '@oslojs/crypto@1.0.1': + dependencies: + '@oslojs/asn1': 1.0.0 + '@oslojs/binary': 1.0.0 + + '@oslojs/encoding@1.1.0': {} '@pkgjs/parseargs@0.11.0': optional: true @@ -3892,420 +4174,481 @@ snapshots: '@radix-ui/primitive@1.1.0': {} - '@radix-ui/react-alert-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/primitive@1.1.1': {} + + '@radix-ui/react-alert-dialog@1.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-dialog': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-dialog': 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-arrow@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-collection@1.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-context': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-collection@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@babel/runtime': 7.25.0 - react: 18.2.0 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-compose-refs@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-context@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.3)(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-context@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) - aria-hidden: 1.2.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.7(@types/react@18.3.3)(react@18.2.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 - '@radix-ui/react-direction@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-context@1.1.1(@types/react@19.0.3)(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 + + '@radix-ui/react-dialog@1.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.3)(react@19.0.0) + aria-hidden: 1.2.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.2(@types/react@19.0.3)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-direction@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 - '@radix-ui/react-dropdown-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-menu': 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) + + '@radix-ui/react-dropdown-menu@2.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-menu': 2.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.3)(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-icons@1.3.0(react@18.2.0)': + '@radix-ui/react-icons@1.3.2(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@radix-ui/react-id@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-id@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-label@2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-label@2.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 - - '@radix-ui/react-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': - dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) + + '@radix-ui/react-menu@2.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.3)(react@19.0.0) aria-hidden: 1.2.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.7(@types/react@18.3.3)(react@18.2.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.2(@types/react@19.0.3)(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 - - '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': - dependencies: - '@floating-ui/react-dom': 2.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) + + '@radix-ui/react-popper@1.2.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/react-dom': 2.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-arrow': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-rect': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@19.0.3)(react@19.0.0) '@radix-ui/rect': 1.1.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-portal@1.1.3(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-presence@1.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-presence@1.1.2(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) + + '@radix-ui/react-primitive@2.0.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-primitive@2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) + + '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-collection': 1.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-context': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) + + '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-slot@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - '@babel/runtime': 7.25.0 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-slot@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-slot@1.1.1(@types/react@19.0.3)(react@19.0.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-tabs@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-tabs@1.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) - '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-context': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.3)(react@19.0.0) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 + '@types/react': 19.0.3 + '@types/react-dom': 19.0.2(@types/react@19.0.3) - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-use-rect@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: '@radix-ui/rect': 1.1.0 - react: 18.2.0 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - '@radix-ui/react-use-size@1.1.0(@types/react@18.3.3)(react@18.2.0)': + '@radix-ui/react-use-size@1.1.0(@types/react@19.0.3)(react@19.0.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.3)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 '@radix-ui/rect@1.1.0': {} - '@react-email/body@0.0.4(react@18.2.0)': - dependencies: - react: 18.2.0 - - '@react-email/button@0.0.11(react@18.2.0)': + '@react-email/body@0.0.11(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/column@0.0.8(react@18.2.0)': + '@react-email/button@0.0.19(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/components@0.0.12(@types/react@18.3.3)(react@18.2.0)': + '@react-email/code-block@0.0.11(react@19.0.0)': dependencies: - '@react-email/body': 0.0.4(react@18.2.0) - '@react-email/button': 0.0.11(react@18.2.0) - '@react-email/column': 0.0.8(react@18.2.0) - '@react-email/container': 0.0.10(react@18.2.0) - '@react-email/font': 0.0.4(react@18.2.0) - '@react-email/head': 0.0.6(react@18.2.0) - '@react-email/heading': 0.0.9(@types/react@18.3.3) - '@react-email/hr': 0.0.6(react@18.2.0) - '@react-email/html': 0.0.6(react@18.2.0) - '@react-email/img': 0.0.6(react@18.2.0) - '@react-email/link': 0.0.6(react@18.2.0) - '@react-email/preview': 0.0.7(react@18.2.0) - '@react-email/render': 0.0.9 - '@react-email/row': 0.0.6(react@18.2.0) - '@react-email/section': 0.0.10(react@18.2.0) - '@react-email/tailwind': 0.0.13(react@18.2.0) - '@react-email/text': 0.0.6(react@18.2.0) - react: 18.2.0 + prismjs: 1.29.0 + react: 19.0.0 + + '@react-email/code-inline@0.0.5(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@react-email/column@0.0.13(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@react-email/components@0.0.31(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@react-email/body': 0.0.11(react@19.0.0) + '@react-email/button': 0.0.19(react@19.0.0) + '@react-email/code-block': 0.0.11(react@19.0.0) + '@react-email/code-inline': 0.0.5(react@19.0.0) + '@react-email/column': 0.0.13(react@19.0.0) + '@react-email/container': 0.0.15(react@19.0.0) + '@react-email/font': 0.0.9(react@19.0.0) + '@react-email/head': 0.0.12(react@19.0.0) + '@react-email/heading': 0.0.15(react@19.0.0) + '@react-email/hr': 0.0.11(react@19.0.0) + '@react-email/html': 0.0.11(react@19.0.0) + '@react-email/img': 0.0.11(react@19.0.0) + '@react-email/link': 0.0.12(react@19.0.0) + '@react-email/markdown': 0.0.14(react@19.0.0) + '@react-email/preview': 0.0.12(react@19.0.0) + '@react-email/render': 1.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@react-email/row': 0.0.12(react@19.0.0) + '@react-email/section': 0.0.16(react@19.0.0) + '@react-email/tailwind': 1.0.4(react@19.0.0) + '@react-email/text': 0.0.11(react@19.0.0) + react: 19.0.0 transitivePeerDependencies: - - '@types/react' + - react-dom - '@react-email/container@0.0.10(react@18.2.0)': + '@react-email/container@0.0.15(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/font@0.0.4(react@18.2.0)': + '@react-email/font@0.0.9(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/head@0.0.6(react@18.2.0)': + '@react-email/head@0.0.12(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/heading@0.0.9(@types/react@18.3.3)': + '@react-email/heading@0.0.15(react@19.0.0)': dependencies: - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.2.0) - react: 18.2.0 - transitivePeerDependencies: - - '@types/react' + react: 19.0.0 - '@react-email/hr@0.0.6(react@18.2.0)': + '@react-email/hr@0.0.11(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/html@0.0.6(react@18.2.0)': + '@react-email/html@0.0.11(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/img@0.0.6(react@18.2.0)': + '@react-email/img@0.0.11(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/link@0.0.6(react@18.2.0)': + '@react-email/link@0.0.12(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/preview@0.0.7(react@18.2.0)': + '@react-email/markdown@0.0.14(react@19.0.0)': dependencies: - react: 18.2.0 + md-to-react-email: 5.0.5(react@19.0.0) + react: 19.0.0 - '@react-email/render@0.0.10': + '@react-email/preview@0.0.12(react@19.0.0)': dependencies: - html-to-text: 9.0.5 - pretty: 2.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 19.0.0 - '@react-email/render@0.0.9': + '@react-email/render@1.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: html-to-text: 9.0.5 - pretty: 2.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + prettier: 3.3.3 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-promise-suspense: 0.3.4 - '@react-email/row@0.0.6(react@18.2.0)': + '@react-email/row@0.0.12(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/section@0.0.10(react@18.2.0)': + '@react-email/section@0.0.16(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 - '@react-email/tailwind@0.0.13(react@18.2.0)': + '@react-email/tailwind@1.0.4(react@19.0.0)': dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 19.0.0 - '@react-email/text@0.0.6(react@18.2.0)': + '@react-email/text@0.0.11(react@19.0.0)': dependencies: - react: 18.2.0 + react: 19.0.0 '@selderee/plugin-htmlparser2@0.11.0': dependencies: @@ -4314,10 +4657,9 @@ snapshots: '@swc/counter@0.1.3': {} - '@swc/helpers@0.5.5': + '@swc/helpers@0.5.15': dependencies: - '@swc/counter': 0.1.3 - tslib: 2.6.3 + tslib: 2.8.1 '@t3-oss/env-core@0.7.3(typescript@5.5.4)(zod@3.23.8)': dependencies: @@ -4342,43 +4684,45 @@ snapshots: '@tanstack/query-core@4.36.1': {} - '@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@tanstack/react-query@4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@tanstack/query-core': 4.36.1 - react: 18.2.0 - use-sync-external-store: 1.2.2(react@18.2.0) + react: 19.0.0 + use-sync-external-store: 1.2.2(react@19.0.0) optionalDependencies: - react-dom: 18.2.0(react@18.2.0) + react-dom: 19.0.0(react@19.0.0) '@trpc/client@10.45.2(@trpc/server@10.45.2)': dependencies: '@trpc/server': 10.45.2 - '@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@14.2.5(@playwright/test@1.45.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@trpc/server@10.45.2)(next@15.1.5(@playwright/test@1.45.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@tanstack/react-query': 4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@trpc/client': 10.45.2(@trpc/server@10.45.2) - '@trpc/react-query': 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@trpc/react-query': 10.45.2(@tanstack/react-query@4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@trpc/server': 10.45.2 - next: 14.2.5(@playwright/test@1.45.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + next: 15.1.5(@playwright/test@1.45.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - '@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@tanstack/react-query': 4.36.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@trpc/client': 10.45.2(@trpc/server@10.45.2) '@trpc/server': 10.45.2 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) '@trpc/server@10.45.2': {} '@tybys/wasm-util@0.8.3': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 optional: true + '@types/bcryptjs@2.4.6': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 @@ -4420,9 +4764,9 @@ snapshots: '@types/prop-types@15.7.12': {} - '@types/react-dom@18.3.0': + '@types/react-dom@19.0.2(@types/react@19.0.3)': dependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 '@types/react-syntax-highlighter@15.5.13': dependencies: @@ -4433,6 +4777,10 @@ snapshots: '@types/prop-types': 15.7.12 csstype: 3.1.3 + '@types/react@19.0.3': + dependencies: + csstype: 3.1.3 + '@types/semver@7.5.8': {} '@types/unist@2.0.10': {} @@ -4527,8 +4875,6 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - abbrev@2.0.0: {} - acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 @@ -4569,7 +4915,7 @@ snapshots: aria-hidden@1.2.4: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 array-union@2.1.0: {} @@ -4587,6 +4933,8 @@ snapshots: balanced-match@1.0.2: {} + bcryptjs@2.4.3: {} + binary-extensions@2.3.0: {} brace-expansion@1.1.11: @@ -4678,27 +5026,26 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + comma-separated-tokens@1.0.8: {} comma-separated-tokens@2.0.3: {} - commander@10.0.1: {} - commander@4.1.1: {} concat-map@0.0.1: {} - condense-newlines@0.2.1: - dependencies: - extend-shallow: 2.0.1 - is-whitespace: 0.3.0 - kind-of: 3.2.2 - - config-chain@1.1.13: - dependencies: - ini: 1.3.8 - proto-list: 1.2.4 - copy-anything@3.0.5: dependencies: is-what: 4.1.16 @@ -4733,6 +5080,9 @@ snapshots: dequal@2.0.3: {} + detect-libc@2.0.3: + optional: true + detect-node-es@1.1.0: {} devlop@1.1.0: @@ -4780,30 +5130,24 @@ snapshots: dotenv@16.4.5: {} - drizzle-kit@0.23.0: + drizzle-kit@0.30.1: dependencies: + '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.6.5 esbuild: 0.19.12 esbuild-register: 3.6.0(esbuild@0.19.12) transitivePeerDependencies: - supports-color - drizzle-orm@0.32.1(@types/react@18.3.3)(pg@8.12.0)(postgres@3.4.4)(react@18.2.0): + drizzle-orm@0.38.3(@types/react@19.0.3)(pg@8.12.0)(postgres@3.4.4)(react@19.0.0): optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 pg: 8.12.0 postgres: 3.4.4 - react: 18.2.0 + react: 19.0.0 eastasianwidth@0.2.0: {} - editorconfig@1.0.4: - dependencies: - '@one-ini/wasm': 0.1.1 - commander: 10.0.1 - minimatch: 9.0.1 - semver: 7.6.3 - electron-to-chromium@1.5.2: {} emoji-regex@8.0.0: {} @@ -4978,12 +5322,10 @@ snapshots: esutils@2.0.3: {} - extend-shallow@2.0.1: - dependencies: - is-extendable: 0.1.1 - extend@3.0.2: {} + fast-deep-equal@2.0.1: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -5114,8 +5456,6 @@ snapshots: dependencies: get-intrinsic: 1.2.4 - graceful-fs@4.2.11: {} - graphemer@1.4.0: {} has-flag@4.0.0: {} @@ -5252,8 +5592,6 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: {} - inline-style-parser@0.2.3: {} invariant@2.2.4: @@ -5274,12 +5612,13 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 + is-arrayish@0.3.2: + optional: true + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-buffer@1.1.6: {} - is-core-module@2.15.0: dependencies: hasown: 2.0.2 @@ -5288,8 +5627,6 @@ snapshots: is-decimal@2.0.1: {} - is-extendable@0.1.1: {} - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -5310,8 +5647,6 @@ snapshots: is-what@4.1.16: {} - is-whitespace@0.3.0: {} - isexe@2.0.0: {} jackspeak@2.3.6: @@ -5328,16 +5663,6 @@ snapshots: jiti@1.21.6: {} - js-beautify@1.15.1: - dependencies: - config-chain: 1.1.13 - editorconfig: 1.0.4 - glob: 10.4.5 - js-cookie: 3.0.5 - nopt: 7.2.1 - - js-cookie@3.0.5: {} - js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -5354,10 +5679,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kind-of@3.2.2: - dependencies: - is-buffer: 1.1.6 - leac@0.6.0: {} levn@0.4.1: @@ -5394,12 +5715,15 @@ snapshots: lru-cache@10.4.3: {} - lucia@3.2.0: - dependencies: - oslo: 1.2.0 - markdown-table@3.0.3: {} + marked@7.0.4: {} + + md-to-react-email@5.0.5(react@19.0.0): + dependencies: + marked: 7.0.4 + react: 19.0.0 + mdast-util-find-and-replace@3.0.1: dependencies: '@types/mdast': 4.0.4 @@ -5765,10 +6089,6 @@ snapshots: dependencies: brace-expansion: 1.1.11 - minimatch@9.0.1: - dependencies: - brace-expansion: 2.0.1 - minimatch@9.0.3: dependencies: brace-expansion: 2.0.1 @@ -5791,36 +6111,38 @@ snapshots: nanoid@3.3.7: {} + nanoid@5.0.9: {} + natural-compare@1.4.0: {} - next-themes@0.2.1(next@14.2.5(@playwright/test@1.45.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + next-themes@0.2.1(next@15.1.5(@playwright/test@1.45.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - next: 14.2.5(@playwright/test@1.45.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + next: 15.1.5(@playwright/test@1.45.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - next@14.2.5(@playwright/test@1.45.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + next@15.1.5(@playwright/test@1.45.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@next/env': 14.2.5 - '@swc/helpers': 0.5.5 + '@next/env': 15.1.5 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 busboy: 1.6.0 caniuse-lite: 1.0.30001643 - graceful-fs: 4.2.11 postcss: 8.4.31 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.1(react@18.2.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.5 - '@next/swc-darwin-x64': 14.2.5 - '@next/swc-linux-arm64-gnu': 14.2.5 - '@next/swc-linux-arm64-musl': 14.2.5 - '@next/swc-linux-x64-gnu': 14.2.5 - '@next/swc-linux-x64-musl': 14.2.5 - '@next/swc-win32-arm64-msvc': 14.2.5 - '@next/swc-win32-ia32-msvc': 14.2.5 - '@next/swc-win32-x64-msvc': 14.2.5 + '@next/swc-darwin-arm64': 15.1.5 + '@next/swc-darwin-x64': 15.1.5 + '@next/swc-linux-arm64-gnu': 15.1.5 + '@next/swc-linux-arm64-musl': 15.1.5 + '@next/swc-linux-x64-gnu': 15.1.5 + '@next/swc-linux-x64-musl': 15.1.5 + '@next/swc-win32-arm64-msvc': 15.1.5 + '@next/swc-win32-x64-msvc': 15.1.5 '@playwright/test': 1.45.3 + sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -5829,10 +6151,6 @@ snapshots: nodemailer@6.9.14: {} - nopt@7.2.1: - dependencies: - abbrev: 2.0.0 - normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -5861,11 +6179,6 @@ snapshots: '@node-rs/argon2': 1.7.0 '@node-rs/bcrypt': 1.9.0 - oslo@1.2.1: - dependencies: - '@node-rs/argon2': 1.7.0 - '@node-rs/bcrypt': 1.9.0 - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -6045,12 +6358,6 @@ snapshots: prettier@3.3.3: {} - pretty@2.0.0: - dependencies: - condense-newlines: 0.2.1 - extend-shallow: 2.0.1 - js-beautify: 1.15.1 - prismjs@1.27.0: {} prismjs@1.29.0: {} @@ -6061,8 +6368,6 @@ snapshots: property-information@6.5.0: {} - proto-list@1.2.4: {} - punycode@2.3.1: {} qs@6.12.3: @@ -6071,25 +6376,24 @@ snapshots: queue-microtask@1.2.3: {} - react-dom@18.2.0(react@18.2.0): + react-dom@19.0.0(react@19.0.0): dependencies: - loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.2 + react: 19.0.0 + scheduler: 0.25.0 - react-hook-form@7.52.1(react@18.2.0): + react-hook-form@7.52.1(react@19.0.0): dependencies: - react: 18.2.0 + react: 19.0.0 - react-markdown@9.0.1(@types/react@18.3.3)(react@18.2.0): + react-markdown@9.0.1(@types/react@19.0.3)(react@19.0.0): dependencies: '@types/hast': 3.0.4 - '@types/react': 18.3.3 + '@types/react': 19.0.3 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.0 html-url-attributes: 3.0.0 mdast-util-to-hast: 13.2.0 - react: 18.2.0 + react: 19.0.0 remark-parse: 11.0.0 remark-rehype: 11.1.0 unified: 11.0.5 @@ -6098,46 +6402,56 @@ snapshots: transitivePeerDependencies: - supports-color - react-remove-scroll-bar@2.3.6(@types/react@18.3.3)(react@18.2.0): + react-promise-suspense@0.3.4: dependencies: - react: 18.2.0 - react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.2.0) - tslib: 2.6.3 + fast-deep-equal: 2.0.1 + + react-remove-scroll-bar@2.3.8(@types/react@19.0.3)(react@19.0.0): + dependencies: + react: 19.0.0 + react-style-singleton: 2.2.3(@types/react@19.0.3)(react@19.0.0) + tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - react-remove-scroll@2.5.7(@types/react@18.3.3)(react@18.2.0): + react-remove-scroll@2.6.2(@types/react@19.0.3)(react@19.0.0): dependencies: - react: 18.2.0 - react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.2.0) - react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.2.0) - tslib: 2.6.3 - use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.2.0) - use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.2.0) + react: 19.0.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.0.3)(react@19.0.0) + react-style-singleton: 2.2.1(@types/react@19.0.3)(react@19.0.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.0.3)(react@19.0.0) + use-sidecar: 1.1.2(@types/react@19.0.3)(react@19.0.0) optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - react-style-singleton@2.2.1(@types/react@18.3.3)(react@18.2.0): + react-style-singleton@2.2.1(@types/react@19.0.3)(react@19.0.0): dependencies: get-nonce: 1.0.1 invariant: 2.2.4 - react: 18.2.0 + react: 19.0.0 tslib: 2.6.3 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - react-syntax-highlighter@15.5.0(react@18.2.0): + react-style-singleton@2.2.3(@types/react@19.0.3)(react@19.0.0): + dependencies: + get-nonce: 1.0.1 + react: 19.0.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.0.3 + + react-syntax-highlighter@15.5.0(react@19.0.0): dependencies: '@babel/runtime': 7.25.0 highlight.js: 10.7.3 lowlight: 1.20.0 prismjs: 1.29.0 - react: 18.2.0 + react: 19.0.0 refractor: 3.6.0 - react@18.2.0: - dependencies: - loose-envify: 1.4.0 + react@19.0.0: {} read-cache@1.0.0: dependencies: @@ -6215,9 +6529,7 @@ snapshots: dependencies: queue-microtask: 1.2.3 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.25.0: {} selderee@0.11.0: dependencies: @@ -6236,6 +6548,33 @@ snapshots: gopd: 1.0.1 has-property-descriptors: 1.0.2 + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -6251,12 +6590,17 @@ snapshots: signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + slash@3.0.0: {} - sonner@1.5.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + sonner@1.7.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) source-map-js@1.2.0: {} @@ -6311,10 +6655,10 @@ snapshots: dependencies: inline-style-parser: 0.2.3 - styled-jsx@5.1.1(react@18.2.0): + styled-jsx@5.1.6(react@19.0.0): dependencies: client-only: 0.0.1 - react: 18.2.0 + react: 19.0.0 sucrase@3.35.0: dependencies: @@ -6395,6 +6739,8 @@ snapshots: tslib@2.6.3: {} + tslib@2.8.1: {} + tsx@4.16.2: dependencies: esbuild: 0.21.5 @@ -6460,32 +6806,32 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.2(@types/react@18.3.3)(react@18.2.0): + use-callback-ref@1.3.3(@types/react@19.0.3)(react@19.0.0): dependencies: - react: 18.2.0 - tslib: 2.6.3 + react: 19.0.0 + tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - use-sidecar@1.1.2(@types/react@18.3.3)(react@18.2.0): + use-sidecar@1.1.2(@types/react@19.0.3)(react@19.0.0): dependencies: detect-node-es: 1.1.0 - react: 18.2.0 + react: 19.0.0 tslib: 2.6.3 optionalDependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.3 - use-sync-external-store@1.2.2(react@18.2.0): + use-sync-external-store@1.2.2(react@19.0.0): dependencies: - react: 18.2.0 + react: 19.0.0 util-deprecate@1.0.2: {} - vaul@0.8.9(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + vaul@1.1.2(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@radix-ui/react-dialog': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-dialog': 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.3))(@types/react@19.0.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) transitivePeerDependencies: - '@types/react' - '@types/react-dom' diff --git a/src/app/(auth)/login/discord/callback/route.ts b/src/app/(auth)/login/discord/callback/route.ts index 7b35cb3..8126b56 100644 --- a/src/app/(auth)/login/discord/callback/route.ts +++ b/src/app/(auth)/login/discord/callback/route.ts @@ -1,17 +1,19 @@ -import { cookies } from "next/headers"; -import { generateId } from "lucia"; -import { OAuth2RequestError } from "arctic"; -import { eq } from "drizzle-orm"; -import { discord, lucia } from "@/lib/auth"; -import { db } from "@/server/db"; +import { discord } from "@/lib/auth/config"; +import { createSession, setCookie } from "@/lib/auth/utils"; import { Paths } from "@/lib/constants"; +import { db } from "@/server/db"; import { users } from "@/server/db/schema"; +import { OAuth2RequestError } from "arctic"; +import { eq } from "drizzle-orm"; +import { nanoid } from "nanoid"; +import { cookies } from "next/headers"; export async function GET(request: Request): Promise { const url = new URL(request.url); const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); - const storedState = cookies().get("discord_oauth_state")?.value ?? null; + const cookieStore = await cookies(); + const storedState = cookieStore.get("discord_oauth_state")?.value ?? null; if (!code || !state || !storedState || state !== storedState) { return new Response(null, { @@ -48,7 +50,7 @@ export async function GET(request: Request): Promise { : null; if (!existingUser) { - const userId = generateId(21); + const userId = nanoid(21); await db.insert(users).values({ id: userId, email: discordUser.email, @@ -56,9 +58,8 @@ export async function GET(request: Request): Promise { discordId: discordUser.id, avatar, }); - const session = await lucia.createSession(userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + const session = await createSession(userId); + await setCookie(session.id); return new Response(null, { status: 302, headers: { Location: Paths.Dashboard }, @@ -75,9 +76,9 @@ export async function GET(request: Request): Promise { }) .where(eq(users.id, existingUser.id)); } - const session = await lucia.createSession(existingUser.id, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + const session = await createSession(existingUser.id); + await setCookie(session.id); + return new Response(null, { status: 302, headers: { Location: Paths.Dashboard }, diff --git a/src/app/(auth)/login/discord/route.ts b/src/app/(auth)/login/discord/route.ts index 89c01c2..15b06e9 100644 --- a/src/app/(auth)/login/discord/route.ts +++ b/src/app/(auth)/login/discord/route.ts @@ -1,15 +1,16 @@ -import { cookies } from "next/headers"; -import { generateState } from "arctic"; -import { discord } from "@/lib/auth"; import { env } from "@/env"; +import { discord } from "@/lib/auth/config"; +import { generateState } from "arctic"; +import { cookies } from "next/headers"; export async function GET(): Promise { const state = generateState(); const url = await discord.createAuthorizationURL(state, { scopes: ["identify", "email"], }); + const cookieStore = await cookies(); - cookies().set("discord_oauth_state", state, { + cookieStore.set("discord_oauth_state", state, { path: "/", secure: env.NODE_ENV === "production", httpOnly: true, diff --git a/src/app/(auth)/login/login.tsx b/src/app/(auth)/login/login.tsx index c1b5e7a..659bf6b 100644 --- a/src/app/(auth)/login/login.tsx +++ b/src/app/(auth)/login/login.tsx @@ -1,19 +1,18 @@ "use client"; -import Link from "next/link"; -import { useFormState } from "react-dom"; -import { Input } from "@/components/ui/input"; +import { DiscordLogoIcon } from "@/components/icons"; +import { SubmitButton } from "@/components/submit-button"; +import TextInput from "@/components/text-input"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { PasswordInput } from "@/components/password-input"; -import { DiscordLogoIcon } from "@/components/icons"; +import { loginAction } from "@/lib/auth/actions"; import { APP_TITLE } from "@/lib/constants"; -import { login } from "@/lib/auth/actions"; -import { Label } from "@/components/ui/label"; -import { SubmitButton } from "@/components/submit-button"; +import { cn } from "@/lib/utils"; +import Link from "next/link"; +import { useActionState } from "react"; export function Login() { - const [state, formAction] = useFormState(login, null); + const [state, formAction] = useActionState(loginAction, null); return ( @@ -34,29 +33,41 @@ export function Login() {
-
- - -
- -
- - -
- + + + {state?.message ? ( +

+ {state?.message} +

+ ) : null}
- {state?.fieldError ? ( -
    - {Object.values(state.fieldError).map((err) => ( -
  • - {err} -
  • - ))} -
- ) : state?.formError ? ( -

- {state?.formError} -

- ) : null} Log In diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 919ea84..1aba9a2 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,6 +1,6 @@ -import { redirect } from "next/navigation"; -import { validateRequest } from "@/lib/auth/validate-request"; +import { validateRequest } from "@/lib/auth"; import { Paths } from "@/lib/constants"; +import { redirect } from "next/navigation"; import { Login } from "./login"; export const metadata = { diff --git a/src/app/(auth)/reset-password/[token]/page.tsx b/src/app/(auth)/reset-password/[token]/page.tsx index 8f2c034..6ed935b 100644 --- a/src/app/(auth)/reset-password/[token]/page.tsx +++ b/src/app/(auth)/reset-password/[token]/page.tsx @@ -1,10 +1,4 @@ -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { ResetPassword } from "./reset-password"; export const metadata = { @@ -12,11 +6,12 @@ export const metadata = { description: "Reset Password Page", }; -export default function ResetPasswordPage({ - params, -}: { - params: { token: string }; -}) { +type Params = { + token: string; +}; + +export default async function ResetPasswordPage({ params }: { params: Promise }) { + const { token } = await params; return ( @@ -24,7 +19,7 @@ export default function ResetPasswordPage({ Enter new password. - + ); diff --git a/src/app/(auth)/reset-password/[token]/reset-password.tsx b/src/app/(auth)/reset-password/[token]/reset-password.tsx index a3f4add..4d896bd 100644 --- a/src/app/(auth)/reset-password/[token]/reset-password.tsx +++ b/src/app/(auth)/reset-password/[token]/reset-password.tsx @@ -1,24 +1,24 @@ "use client"; -import { useEffect } from "react"; -import { useFormState } from "react-dom"; -import { toast } from "sonner"; import { ExclamationTriangleIcon } from "@/components/icons"; -import { SubmitButton } from "@/components/submit-button"; import { PasswordInput } from "@/components/password-input"; +import { SubmitButton } from "@/components/submit-button"; import { Label } from "@/components/ui/label"; -import { resetPassword } from "@/lib/auth/actions"; +import { resetPasswordAction } from "@/lib/auth/actions"; +import { useEffect } from "react"; +import { useFormState } from "react-dom"; +import { toast } from "sonner"; export function ResetPassword({ token }: { token: string }) { - const [state, formAction] = useFormState(resetPassword, null); + const [state, formAction] = useFormState(resetPasswordAction, null); useEffect(() => { - if (state?.error) { - toast(state.error, { + if (state?.success === false) { + toast(state?.message, { icon: , }); } - }, [state?.error]); + }, [state?.success, state?.message]); return ( @@ -29,6 +29,7 @@ export function ResetPassword({ token }: { token: string }) { name="password" required autoComplete="new-password" + defaultValue={state?.input?.password} placeholder="********" /> diff --git a/src/app/(auth)/reset-password/page.tsx b/src/app/(auth)/reset-password/page.tsx index b016333..da892b4 100644 --- a/src/app/(auth)/reset-password/page.tsx +++ b/src/app/(auth)/reset-password/page.tsx @@ -1,14 +1,8 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { validateRequest } from "@/lib/auth"; +import { Paths } from "@/lib/constants"; import { redirect } from "next/navigation"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; import { SendResetEmail } from "./send-reset-email"; -import { validateRequest } from "@/lib/auth/validate-request"; -import { Paths } from "@/lib/constants"; export const metadata = { title: "Forgot Password", @@ -24,9 +18,7 @@ export default async function ForgotPasswordPage() { Forgot password? - - Password reset link will be sent to your email. - + Password reset link will be sent to your email. diff --git a/src/app/(auth)/reset-password/send-reset-email.tsx b/src/app/(auth)/reset-password/send-reset-email.tsx index e37e48a..3e5de48 100644 --- a/src/app/(auth)/reset-password/send-reset-email.tsx +++ b/src/app/(auth)/reset-password/send-reset-email.tsx @@ -1,42 +1,42 @@ "use client"; -import { useEffect } from "react"; -import { useFormState } from "react-dom"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { toast } from "sonner"; -import { Input } from "@/components/ui/input"; +import { ExclamationTriangleIcon } from "@/components/icons"; +import { SubmitButton } from "@/components/submit-button"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { SubmitButton } from "@/components/submit-button"; -import { sendPasswordResetLink } from "@/lib/auth/actions"; -import { ExclamationTriangleIcon } from "@/components/icons"; +import { sendPasswordResetEmailAction } from "@/lib/auth/actions"; import { Paths } from "@/lib/constants"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; +import { useFormState } from "react-dom"; +import { toast } from "sonner"; export function SendResetEmail() { - const [state, formAction] = useFormState(sendPasswordResetLink, null); + const [state, sendPasswordResetEmail] = useFormState(sendPasswordResetEmailAction, null); const router = useRouter(); useEffect(() => { if (state?.success) { toast("A password reset link has been sent to your email."); router.push(Paths.Login); - } - if (state?.error) { - toast(state.error, { + } else if (state?.success === false) { + toast(state?.message ?? "An error occured", { icon: , }); } - }, [state?.error, state?.success]); + }, [state?.message, state?.success]); return ( - +
diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index 97126c7..79fdc06 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -1,7 +1,7 @@ +import { validateRequest } from "@/lib/auth"; +import { Paths } from "@/lib/constants"; import { redirect } from "next/navigation"; import { Signup } from "./signup"; -import { validateRequest } from "@/lib/auth/validate-request"; -import { Paths } from "@/lib/constants"; export const metadata = { title: "Sign Up", diff --git a/src/app/(auth)/signup/signup.tsx b/src/app/(auth)/signup/signup.tsx index 3467be9..67230b5 100644 --- a/src/app/(auth)/signup/signup.tsx +++ b/src/app/(auth)/signup/signup.tsx @@ -1,19 +1,18 @@ "use client"; -import { useFormState } from "react-dom"; -import Link from "next/link"; -import { PasswordInput } from "@/components/password-input"; +import { DiscordLogoIcon } from "@/components/icons"; +import { SubmitButton } from "@/components/submit-button"; +import TextInput from "@/components/text-input"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { DiscordLogoIcon } from "@/components/icons"; +import { signupAction } from "@/lib/auth/actions"; import { APP_TITLE } from "@/lib/constants"; -import { Label } from "@/components/ui/label"; -import { signup } from "@/lib/auth/actions"; -import { SubmitButton } from "@/components/submit-button"; +import { cn } from "@/lib/utils"; +import Link from "next/link"; +import { useActionState } from "react"; export function Signup() { - const [state, formAction] = useFormState(signup, null); + const [state, formAction] = useActionState(signupAction, null); return ( @@ -35,39 +34,40 @@ export function Signup() {
-
- - -
-
- - -
+ + - {state?.fieldError ? ( -
    - {Object.values(state.fieldError).map((err) => ( -
  • - {err} -
  • - ))} -
- ) : state?.formError ? ( -

- {state?.formError} + {state?.message ? ( +

+ {state?.message}

) : null}
diff --git a/src/app/(auth)/verify-email/page.tsx b/src/app/(auth)/verify-email/page.tsx index 611ade4..58f119b 100644 --- a/src/app/(auth)/verify-email/page.tsx +++ b/src/app/(auth)/verify-email/page.tsx @@ -1,14 +1,8 @@ -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { validateRequest } from "@/lib/auth"; +import { Paths } from "@/lib/constants"; import { redirect } from "next/navigation"; -import { validateRequest } from "@/lib/auth/validate-request"; import { VerifyCode } from "./verify-code"; -import { Paths } from "@/lib/constants"; export const metadata = { title: "Verify Email", @@ -26,8 +20,8 @@ export default async function VerifyEmailPage() { Verify Email - Verification code was sent to {user.email}. Check - your spam folder if you can't find the email. + Verification code was sent to {user.email}. Check your spam folder if you + can't find the email. diff --git a/src/app/(auth)/verify-email/verify-code.tsx b/src/app/(auth)/verify-email/verify-code.tsx index d1eab32..b3201b9 100644 --- a/src/app/(auth)/verify-email/verify-code.tsx +++ b/src/app/(auth)/verify-email/verify-code.tsx @@ -1,42 +1,52 @@ "use client"; +import { ExclamationTriangleIcon } from "@/components/icons"; +import { SubmitButton } from "@/components/submit-button"; import { Input } from "@/components/ui/input"; +import { + logoutAction, + resendVerificationEmail as resendEmail, + verifyEmailAction, +} from "@/lib/auth/actions"; import { Label } from "@radix-ui/react-label"; -import { useEffect, useRef } from "react"; -import { useFormState } from "react-dom"; +import { useActionState, useEffect, useRef } from "react"; import { toast } from "sonner"; -import { ExclamationTriangleIcon } from "@/components/icons"; -import { logout, verifyEmail, resendVerificationEmail as resendEmail } from "@/lib/auth/actions"; -import { SubmitButton } from "@/components/submit-button"; export const VerifyCode = () => { - const [verifyEmailState, verifyEmailAction] = useFormState(verifyEmail, null); - const [resendState, resendAction] = useFormState(resendEmail, null); + const [verifyEmailState, verifyEmail] = useActionState(verifyEmailAction, null); + const [resendState, resendAction] = useActionState(resendEmail, null); + const [_, logout] = useActionState(logoutAction, null); const codeFormRef = useRef(null); useEffect(() => { - if (resendState?.success) { - toast("Email sent!"); - } - if (resendState?.error) { - toast(resendState.error, { + if (resendState?.success === false) { + toast(resendState?.message ?? "An error occured", { icon: , }); + } else if (resendState?.success) { + toast("Email sent!"); } - }, [resendState?.error, resendState?.success]); + }, [resendState?.message, resendState?.success]); useEffect(() => { - if (verifyEmailState?.error) { - toast(verifyEmailState.error, { + if (verifyEmailState?.success === false) { + toast(verifyEmailState?.message ?? "An error occured", { icon: , }); } - }, [verifyEmailState?.error]); + }, [verifyEmailState?.success, verifyEmailState?.message]); return (
- + - + Verify diff --git a/src/app/(landing)/_components/header.tsx b/src/app/(landing)/_components/header.tsx index 30156b8..1680d94 100644 --- a/src/app/(landing)/_components/header.tsx +++ b/src/app/(landing)/_components/header.tsx @@ -1,6 +1,4 @@ -import Link from "next/link"; import { RocketIcon } from "@/components/icons"; -import { APP_TITLE } from "@/lib/constants"; import { Button } from "@/components/ui/button"; import { DropdownMenu, @@ -8,14 +6,16 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { APP_TITLE } from "@/lib/constants"; import { HamburgerMenuIcon } from "@radix-ui/react-icons"; +import Link from "next/link"; const routes = [ { name: "Home", href: "/" }, { name: "Features", href: "/#features" }, { name: "Documentation", - href: "https://www.touha.dev/posts/simple-nextjs-t3-authentication-with-lucia", + href: "/documentation", }, ] as const; @@ -43,10 +43,7 @@ export const Header = () => {
- + {APP_TITLE}
); } diff --git a/src/app/(main)/dashboard/_components/verificiation-warning.tsx b/src/app/(main)/dashboard/_components/verificiation-warning.tsx index 66270b4..671d728 100644 --- a/src/app/(main)/dashboard/_components/verificiation-warning.tsx +++ b/src/app/(main)/dashboard/_components/verificiation-warning.tsx @@ -2,7 +2,7 @@ import { ExclamationTriangleIcon } from "@/components/icons"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; -import { validateRequest } from "@/lib/auth/validate-request"; +import { validateRequest } from "@/lib/auth"; import Link from "next/link"; export async function VerificiationWarning() { diff --git a/src/app/(main)/dashboard/billing/page.tsx b/src/app/(main)/dashboard/billing/page.tsx index 0dc5a7b..9a56687 100644 --- a/src/app/(main)/dashboard/billing/page.tsx +++ b/src/app/(main)/dashboard/billing/page.tsx @@ -5,7 +5,7 @@ import { ExclamationTriangleIcon } from "@/components/icons"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { env } from "@/env"; -import { validateRequest } from "@/lib/auth/validate-request"; +import { validateRequest } from "@/lib/auth"; import { APP_TITLE } from "@/lib/constants"; import { api } from "@/trpc/server"; import * as React from "react"; diff --git a/src/app/(main)/dashboard/layout.tsx b/src/app/(main)/dashboard/layout.tsx index 9c29f02..5337536 100644 --- a/src/app/(main)/dashboard/layout.tsx +++ b/src/app/(main)/dashboard/layout.tsx @@ -7,9 +7,9 @@ interface Props { export default function DashboardLayout({ children }: Props) { return ( -
-
- +
+
+
{children}
diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx index 2ff425d..3fc2adc 100644 --- a/src/app/(main)/dashboard/page.tsx +++ b/src/app/(main)/dashboard/page.tsx @@ -1,13 +1,9 @@ import { env } from "@/env"; -import { validateRequest } from "@/lib/auth/validate-request"; +import { validateRequest } from "@/lib/auth"; import { Paths } from "@/lib/constants"; -import { myPostsSchema } from "@/server/api/routers/post/post.input"; -import { api } from "@/trpc/server"; import { type Metadata } from "next"; import { redirect } from "next/navigation"; -import * as React from "react"; import { Posts } from "./_components/posts"; -import { PostsSkeleton } from "./_components/posts-skeleton"; export const metadata: Metadata = { metadataBase: new URL(env.NEXT_PUBLIC_APP_URL), @@ -16,35 +12,25 @@ export const metadata: Metadata = { }; interface Props { - searchParams: Record; + searchParams: Promise>; } -export default async function DashboardPage({ searchParams }: Props) { - const { page, perPage } = myPostsSchema.parse(searchParams); - +export default async function DashboardPage(props: Props) { + const { page, perPage } = await props.searchParams; const { user } = await validateRequest(); if (!user) redirect(Paths.Login); - /** - * Passing multiple promises to `Promise.all` to fetch data in parallel to prevent waterfall requests. - * Passing promises to the `Posts` component to make them hot promises (they can run without being awaited) to prevent waterfall requests. - * @see https://www.youtube.com/shorts/A7GGjutZxrs - * @see https://nextjs.org/docs/app/building-your-application/data-fetching/patterns#parallel-data-fetching - */ - const promises = Promise.all([ - api.post.myPosts.query({ page, perPage }), - api.stripe.getPlan.query(), - ]); - return (

Posts

Manage your posts here

- }> - - + +
); } diff --git a/src/app/(main)/dashboard/settings/page.tsx b/src/app/(main)/dashboard/settings/page.tsx index edf0afd..baf7c87 100644 --- a/src/app/(main)/dashboard/settings/page.tsx +++ b/src/app/(main)/dashboard/settings/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { redirect } from "next/navigation"; import { env } from "@/env"; -import { validateRequest } from "@/lib/auth/validate-request"; +import { validateRequest } from "@/lib/auth"; export const metadata: Metadata = { metadataBase: new URL(env.NEXT_PUBLIC_APP_URL), @@ -14,7 +14,7 @@ export default async function BillingPage() { const { user } = await validateRequest(); if (!user) { - redirect("/signin"); + redirect("/login"); } return ( diff --git a/src/app/(main)/editor/[postId]/_components/post-preview.tsx b/src/app/(main)/editor/[postId]/_components/post-preview.tsx index d5a6586..1d6148e 100644 --- a/src/app/(main)/editor/[postId]/_components/post-preview.tsx +++ b/src/app/(main)/editor/[postId]/_components/post-preview.tsx @@ -1,8 +1,12 @@ +import React from "react"; import Markdown, { type Components } from "react-markdown"; -import remarkGfm from "remark-gfm"; -import rehypeRaw from "rehype-raw"; -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { Prism } from "react-syntax-highlighter"; import { materialOceanic } from "react-syntax-highlighter/dist/cjs/styles/prism"; +import rehypeRaw from "rehype-raw"; +import remarkGfm from "remark-gfm"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const SyntaxHighlighter = Prism as unknown as React.FC; const options: Components = { code: (props) => ( @@ -19,11 +23,7 @@ const options: Components = { export const PostPreview = ({ text }: { text: string }) => { return ( - + {text} ); diff --git a/src/app/(main)/editor/[postId]/page.tsx b/src/app/(main)/editor/[postId]/page.tsx index b62966b..623fa2d 100644 --- a/src/app/(main)/editor/[postId]/page.tsx +++ b/src/app/(main)/editor/[postId]/page.tsx @@ -1,23 +1,23 @@ -import React from "react"; +import { ArrowLeftIcon } from "@/components/icons"; +import { validateRequest } from "@/lib/auth"; +import { Paths } from "@/lib/constants"; import { api } from "@/trpc/server"; +import Link from "next/link"; import { notFound, redirect } from "next/navigation"; import { PostEditor } from "./_components/post-editor"; -import { ArrowLeftIcon } from "@/components/icons"; -import Link from "next/link"; -import { validateRequest } from "@/lib/auth/validate-request"; -import { Paths } from "@/lib/constants"; interface Props { - params: { + params: Promise<{ postId: string; - }; + }>; } export default async function EditPostPage({ params }: Props) { const { user } = await validateRequest(); + const { postId } = await params; if (!user) redirect(Paths.Login); - const post = await api.post.get.query({ id: params.postId }); + const post = await api.post.get.query({ id: postId }); if (!post) notFound(); return ( diff --git a/src/app/api/trpc/[trpc]/route.ts b/src/app/api/trpc/[trpc]/route.ts index e04046c..b8c2464 100644 --- a/src/app/api/trpc/[trpc]/route.ts +++ b/src/app/api/trpc/[trpc]/route.ts @@ -22,9 +22,7 @@ const handler = (req: NextRequest) => onError: env.NODE_ENV === "development" ? ({ path, error }) => { - console.error( - `โŒ tRPC failed on ${path ?? ""}: ${error.message}`, - ); + console.error(`โŒ tRPC failed on ${path ?? ""}: ${error.message}`); } : undefined, }); diff --git a/src/app/api/webhooks/stripe/route.ts b/src/app/api/webhooks/stripe/route.ts index e588df6..3f6c8c9 100644 --- a/src/app/api/webhooks/stripe/route.ts +++ b/src/app/api/webhooks/stripe/route.ts @@ -10,21 +10,17 @@ import { eq } from "drizzle-orm"; export async function POST(req: Request) { const body = await req.text(); - const signature = headers().get("Stripe-Signature") ?? ""; + const headerStore = await headers(); + const signature = headerStore.get("Stripe-Signature") ?? ""; let event: Stripe.Event; try { - event = stripe.webhooks.constructEvent( - body, - signature, - env.STRIPE_WEBHOOK_SECRET, - ); + event = stripe.webhooks.constructEvent(body, signature, env.STRIPE_WEBHOOK_SECRET); } catch (err) { - return new Response( - `Webhook Error: ${err instanceof Error ? err.message : "Unknown error."}`, - { status: 400 }, - ); + return new Response(`Webhook Error: ${err instanceof Error ? err.message : "Unknown error."}`, { + status: 400, + }); } switch (event.type) { @@ -53,9 +49,7 @@ export async function POST(req: Request) { stripeSubscriptionId: subscription.id, stripeCustomerId: subscription.customer as string, stripePriceId: subscription.items.data[0]?.price.id, - stripeCurrentPeriodEnd: new Date( - subscription.current_period_end * 1000, - ), + stripeCurrentPeriodEnd: new Date(subscription.current_period_end * 1000), }) .where(eq(users.id, userId)); @@ -82,9 +76,7 @@ export async function POST(req: Request) { .update(users) .set({ stripePriceId: subscription.items.data[0]?.price.id, - stripeCurrentPeriodEnd: new Date( - subscription.current_period_end * 1000, - ), + stripeCurrentPeriodEnd: new Date(subscription.current_period_end * 1000), }) .where(eq(users.id, userId)); diff --git a/src/app/icon.tsx b/src/app/icon.tsx deleted file mode 100644 index 817cdc9..0000000 --- a/src/app/icon.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { ImageResponse } from "next/og"; - -// Route segment config -export const runtime = "edge"; - -// Image metadata -export const size = { - width: 32, - height: 32, -}; -export const contentType = "image/png"; - -// Image generation -export default function Icon() { - return new ImageResponse( - ( - // ImageResponse JSX element -
- - - -
- ), - // ImageResponse options - { - // For convenience, we can re-use the exported icons size metadata - // config to also set the ImageResponse's width and height. - ...size, - }, - ); -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d92f9ec..537862f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -24,19 +24,10 @@ export const viewport: Viewport = { ], }; -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + >( - ({ className, ...props }, ref) => ( +export function AnimatedSpinnerIcon(props: SVGProps) { + return ( - - - - - + + + + + - ), -); -AnimatedSpinner.displayName = "AnimatedSpinner"; + ); +} -const CreditCard = forwardRef>( - ({ className, ...props }, ref) => ( +export function CreditCardIcon(props: SVGProps) { + return ( >( - ), -); -CreditCard.displayName = "CreditCard"; - -export { AnimatedSpinner, CreditCard }; - -export { - EyeOpenIcon, - EyeNoneIcon as EyeCloseIcon, - SunIcon, - MoonIcon, - ExclamationTriangleIcon, - ExitIcon, - EnterIcon, - GearIcon, - RocketIcon, - PlusIcon, - HamburgerMenuIcon, - Pencil2Icon, - UpdateIcon, - CheckCircledIcon, - PlayIcon, - TrashIcon, - ArchiveIcon, - ResetIcon, - DiscordLogoIcon, - FileTextIcon, - IdCardIcon, - PlusCircledIcon, - FilePlusIcon, - CheckIcon, - ChevronLeftIcon, - ChevronRightIcon, - DotsHorizontalIcon, - ArrowLeftIcon, -} from "@radix-ui/react-icons"; + ); +} diff --git a/src/components/link-button.tsx b/src/components/link-button.tsx new file mode 100644 index 0000000..1bfdf1d --- /dev/null +++ b/src/components/link-button.tsx @@ -0,0 +1,29 @@ +import { Button, type ButtonProps } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import Link from "next/link"; +import type { ComponentProps } from "react"; + +type LinkProps = Omit, "href">; +export type LoadingButtonProps = Omit & { + href: string; + linkProps?: LinkProps; +}; + +export function LinkButton({ href, children, linkProps, disabled, ...props }: LoadingButtonProps) { + return ( + + ); +} diff --git a/src/components/loading-button.tsx b/src/components/loading-button.tsx index 6fa9f67..d174fa1 100644 --- a/src/components/loading-button.tsx +++ b/src/components/loading-button.tsx @@ -1,7 +1,6 @@ "use client"; -import { forwardRef } from "react"; -import { AnimatedSpinner } from "@/components/icons"; +import { AnimatedSpinnerIcon } from "@/components/icons"; import { Button, type ButtonProps } from "@/components/ui/button"; import { cn } from "@/lib/utils"; @@ -10,26 +9,19 @@ export interface LoadingButtonProps extends ButtonProps { loading?: boolean; } -const LoadingButton = forwardRef( - ({ loading = false, className, children, ...props }, ref) => { - return ( - - ); - }, -); - -LoadingButton.displayName = "LoadingButton"; - -export { LoadingButton }; +export function LoadingButton({ loading = false, children, ...props }: LoadingButtonProps) { + return ( + + ); +} diff --git a/src/components/password-input.tsx b/src/components/password-input.tsx index ef576f6..ec25989 100644 --- a/src/components/password-input.tsx +++ b/src/components/password-input.tsx @@ -1,14 +1,14 @@ "use client"; -import * as React from "react"; -import { EyeOpenIcon, EyeCloseIcon } from "@/components/icons"; +import { EyeCloseIcon, EyeOpenIcon } from "@/components/icons"; import { Button } from "@/components/ui/button"; import { Input, type InputProps } from "@/components/ui/input"; +import * as React from "react"; import { cn } from "@/lib/utils"; const PasswordInputComponent = React.forwardRef( - ({ className, ...props }, ref) => { + ({ className, type: _, ...props }, ref) => { const [showPassword, setShowPassword] = React.useState(false); return ( @@ -32,9 +32,7 @@ const PasswordInputComponent = React.forwardRef( ) : (
); diff --git a/src/components/text-input.tsx b/src/components/text-input.tsx new file mode 100644 index 0000000..15b318d --- /dev/null +++ b/src/components/text-input.tsx @@ -0,0 +1,51 @@ +import { cn } from "@/lib/utils"; +import { Label } from "@radix-ui/react-label"; +import { useId, type ComponentProps } from "react"; +import { PasswordInput } from "./password-input"; +import { Input } from "./ui/input"; + +export default function TextInput({ + label, + helperText, + error, + className, + containerClassName, + helperTextClassName, + ...props +}: ComponentProps<"input"> & { + label: string; + name: string; + helperText?: string; + error?: boolean; + containerClassName?: string; + helperTextClassName?: string; +}) { + const autoId = useId(); + const id = props.id?.length ? props.id : autoId; + + return ( +
+ + {props.type === "password" ? ( + + ) : ( + + )} + {helperText ? ( +

+ {helperText} +

+ ) : null} +
+ ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 85d20f2..4b16c24 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,22 +1,19 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { - default: - "bg-primary text-primary-foreground shadow hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, @@ -31,27 +28,23 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); export interface ButtonProps - extends React.ButtonHTMLAttributes, + extends React.ComponentProps<"button">, VariantProps { - asChild?: boolean + asChild?: boolean; } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : "button"; return ( - - ) - } -) -Button.displayName = "Button" + + ); + }, +); +Button.displayName = "Button"; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/src/lib/action-utils.ts b/src/lib/action-utils.ts new file mode 100644 index 0000000..11c8e7e --- /dev/null +++ b/src/lib/action-utils.ts @@ -0,0 +1,51 @@ +import type { z } from "zod"; + +export type ValidatedActionOutput = { + input: z.input> | null | undefined; + errors?: z.typeToFlattenedError; +} & ActionCallbackOutput; + +export type ActionCallbackOutput = { + message?: string; +} & ({ success: false; data?: undefined } | { success: true; data: T }); + +export type ValidatedActionFn = ( + prevState: ValidatedActionOutput | null | undefined, + formData: FormData, +) => Promise>; + +export type ValidatedActionCallback = ( + prevState: ValidatedActionOutput | null | undefined, + data: z.infer>, +) => Promise>; + +export function validatedAction( + schema: z.ZodObject, + callback: ValidatedActionCallback, +): ValidatedActionFn { + return async (prevState, formData) => { + const obj = Object.fromEntries(formData.entries()); + const parsed = schema.safeParse(obj); + if (!parsed.success) { + const errors = parsed.error.flatten(); + return { + success: false, + message: "Invalid form input", + input: obj, + errors, + } as ValidatedActionOutput; + } + const data = await callback(prevState, parsed.data); + return { + ...data, + input: parsed.data, + }; + }; +} + +export function action< + T extends ActionCallbackOutput, + U extends ActionCallbackOutput, +>(callback: (input: T | null | undefined) => U | Promise) { + return async (prevState: T | null | undefined, _?: FormData): Promise => callback(prevState); +} diff --git a/src/lib/auth/actions.ts b/src/lib/auth/actions.ts deleted file mode 100644 index 04af97b..0000000 --- a/src/lib/auth/actions.ts +++ /dev/null @@ -1,282 +0,0 @@ -"use server"; - -/* eslint @typescript-eslint/no-explicit-any:0, @typescript-eslint/prefer-optional-chain:0 */ - -import { z } from "zod"; -import { cookies } from "next/headers"; -import { redirect } from "next/navigation"; -import { generateId, Scrypt } from "lucia"; -import { isWithinExpirationDate, TimeSpan, createDate } from "oslo"; -import { generateRandomString, alphabet } from "oslo/crypto"; -import { eq } from "drizzle-orm"; -import { lucia } from "@/lib/auth"; -import { db } from "@/server/db"; -import { - loginSchema, - signupSchema, - type LoginInput, - type SignupInput, - resetPasswordSchema, -} from "@/lib/validators/auth"; -import { emailVerificationCodes, passwordResetTokens, users } from "@/server/db/schema"; -import { sendMail, EmailTemplate } from "@/lib/email"; -import { validateRequest } from "@/lib/auth/validate-request"; -import { Paths } from "../constants"; -import { env } from "@/env"; - -export interface ActionResponse { - fieldError?: Partial>; - formError?: string; -} - -export async function login(_: any, formData: FormData): Promise> { - const obj = Object.fromEntries(formData.entries()); - - const parsed = loginSchema.safeParse(obj); - if (!parsed.success) { - const err = parsed.error.flatten(); - return { - fieldError: { - email: err.fieldErrors.email?.[0], - password: err.fieldErrors.password?.[0], - }, - }; - } - - const { email, password } = parsed.data; - - const existingUser = await db.query.users.findFirst({ - where: (table, { eq }) => eq(table.email, email), - }); - - if (!existingUser || !existingUser?.hashedPassword) { - return { - formError: "Incorrect email or password", - }; - } - - const validPassword = await new Scrypt().verify(existingUser.hashedPassword, password); - if (!validPassword) { - return { - formError: "Incorrect email or password", - }; - } - - const session = await lucia.createSession(existingUser.id, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); - return redirect(Paths.Dashboard); -} - -export async function signup(_: any, formData: FormData): Promise> { - const obj = Object.fromEntries(formData.entries()); - - const parsed = signupSchema.safeParse(obj); - if (!parsed.success) { - const err = parsed.error.flatten(); - return { - fieldError: { - email: err.fieldErrors.email?.[0], - password: err.fieldErrors.password?.[0], - }, - }; - } - - const { email, password } = parsed.data; - - const existingUser = await db.query.users.findFirst({ - where: (table, { eq }) => eq(table.email, email), - columns: { email: true }, - }); - - if (existingUser) { - return { - formError: "Cannot create account with that email", - }; - } - - const userId = generateId(21); - const hashedPassword = await new Scrypt().hash(password); - await db.insert(users).values({ - id: userId, - email, - hashedPassword, - }); - - const verificationCode = await generateEmailVerificationCode(userId, email); - await sendMail(email, EmailTemplate.EmailVerification, { code: verificationCode }); - - const session = await lucia.createSession(userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); - return redirect(Paths.VerifyEmail); -} - -export async function logout(): Promise<{ error: string } | void> { - const { session } = await validateRequest(); - if (!session) { - return { - error: "No session found", - }; - } - await lucia.invalidateSession(session.id); - const sessionCookie = lucia.createBlankSessionCookie(); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); - return redirect("/"); -} - -export async function resendVerificationEmail(): Promise<{ - error?: string; - success?: boolean; -}> { - const { user } = await validateRequest(); - if (!user) { - return redirect(Paths.Login); - } - const lastSent = await db.query.emailVerificationCodes.findFirst({ - where: (table, { eq }) => eq(table.userId, user.id), - columns: { expiresAt: true }, - }); - - if (lastSent && isWithinExpirationDate(lastSent.expiresAt)) { - return { - error: `Please wait ${timeFromNow(lastSent.expiresAt)} before resending`, - }; - } - const verificationCode = await generateEmailVerificationCode(user.id, user.email); - await sendMail(user.email, EmailTemplate.EmailVerification, { code: verificationCode }); - - return { success: true }; -} - -export async function verifyEmail(_: any, formData: FormData): Promise<{ error: string } | void> { - const code = formData.get("code"); - if (typeof code !== "string" || code.length !== 8) { - return { error: "Invalid code" }; - } - const { user } = await validateRequest(); - if (!user) { - return redirect(Paths.Login); - } - - const dbCode = await db.transaction(async (tx) => { - const item = await tx.query.emailVerificationCodes.findFirst({ - where: (table, { eq }) => eq(table.userId, user.id), - }); - if (item) { - await tx.delete(emailVerificationCodes).where(eq(emailVerificationCodes.id, item.id)); - } - return item; - }); - - if (!dbCode || dbCode.code !== code) return { error: "Invalid verification code" }; - - if (!isWithinExpirationDate(dbCode.expiresAt)) return { error: "Verification code expired" }; - - if (dbCode.email !== user.email) return { error: "Email does not match" }; - - await lucia.invalidateUserSessions(user.id); - await db.update(users).set({ emailVerified: true }).where(eq(users.id, user.id)); - const session = await lucia.createSession(user.id, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); - redirect(Paths.Dashboard); -} - -export async function sendPasswordResetLink( - _: any, - formData: FormData, -): Promise<{ error?: string; success?: boolean }> { - const email = formData.get("email"); - const parsed = z.string().trim().email().safeParse(email); - if (!parsed.success) { - return { error: "Provided email is invalid." }; - } - try { - const user = await db.query.users.findFirst({ - where: (table, { eq }) => eq(table.email, parsed.data), - }); - - if (!user || !user.emailVerified) return { error: "Provided email is invalid." }; - - const verificationToken = await generatePasswordResetToken(user.id); - - const verificationLink = `${env.NEXT_PUBLIC_APP_URL}/reset-password/${verificationToken}`; - - await sendMail(user.email, EmailTemplate.PasswordReset, { link: verificationLink }); - - return { success: true }; - } catch (error) { - return { error: "Failed to send verification email." }; - } -} - -export async function resetPassword( - _: any, - formData: FormData, -): Promise<{ error?: string; success?: boolean }> { - const obj = Object.fromEntries(formData.entries()); - - const parsed = resetPasswordSchema.safeParse(obj); - - if (!parsed.success) { - const err = parsed.error.flatten(); - return { - error: err.fieldErrors.password?.[0] ?? err.fieldErrors.token?.[0], - }; - } - const { token, password } = parsed.data; - - const dbToken = await db.transaction(async (tx) => { - const item = await tx.query.passwordResetTokens.findFirst({ - where: (table, { eq }) => eq(table.id, token), - }); - if (item) { - await tx.delete(passwordResetTokens).where(eq(passwordResetTokens.id, item.id)); - } - return item; - }); - - if (!dbToken) return { error: "Invalid password reset link" }; - - if (!isWithinExpirationDate(dbToken.expiresAt)) return { error: "Password reset link expired." }; - - await lucia.invalidateUserSessions(dbToken.userId); - const hashedPassword = await new Scrypt().hash(password); - await db.update(users).set({ hashedPassword }).where(eq(users.id, dbToken.userId)); - const session = await lucia.createSession(dbToken.userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); - redirect(Paths.Dashboard); -} - -const timeFromNow = (time: Date) => { - const now = new Date(); - const diff = time.getTime() - now.getTime(); - const minutes = Math.floor(diff / 1000 / 60); - const seconds = Math.floor(diff / 1000) % 60; - return `${minutes}m ${seconds}s`; -}; - -async function generateEmailVerificationCode(userId: string, email: string): Promise { - await db.delete(emailVerificationCodes).where(eq(emailVerificationCodes.userId, userId)); - const code = generateRandomString(8, alphabet("0-9")); // 8 digit code - await db.insert(emailVerificationCodes).values({ - userId, - email, - code, - expiresAt: createDate(new TimeSpan(10, "m")), // 10 minutes - }); - return code; -} - -async function generatePasswordResetToken(userId: string): Promise { - await db.delete(passwordResetTokens).where(eq(passwordResetTokens.userId, userId)); - const tokenId = generateId(40); - await db.insert(passwordResetTokens).values({ - id: tokenId, - userId, - expiresAt: createDate(new TimeSpan(2, "h")), - }); - return tokenId; -} diff --git a/src/lib/auth/adapter.ts b/src/lib/auth/adapter.ts new file mode 100644 index 0000000..c6a0e42 --- /dev/null +++ b/src/lib/auth/adapter.ts @@ -0,0 +1,92 @@ +import type { + db, + NewEmailVerificationCode, + NewPasswordResetToken, + NewSession, + NewUser, + schema, + Session, + User, +} from "@/server/db"; +import { desc, eq } from "drizzle-orm"; + +type DBSchema = typeof schema; +type DBConnection = typeof db; + +export interface Adapter { + getUser(field: "email" | "id", value: string): Promise; + insertUser(data: NewUser): Promise; + updateUser(userId: string, data: Omit, "id">): Promise; + getSession(sessionId: string): Promise<(Session & { user: User }) | undefined>; + createSession(session: NewSession, deleteAllPrev?: boolean): Promise; + deleteSession(sessionId: string): Promise; + updateSession(sessionId: string, data: Omit, "id">): Promise; + insertVerificationCode(data: NewEmailVerificationCode, deleteAllPrev?: boolean): Promise; + getVerificationCodes(userId: string, deleteAfter?: boolean): Promise; + insertPasswordResetToken(data: NewPasswordResetToken, deleteAllPrev?: boolean): Promise; + getPasswordResetToken( + token: string, + deleteAfter?: boolean, + ): Promise; +} + +export function initAdapter(db: DBConnection, schema: DBSchema): Adapter { + const { users, sessions, verificationCodes: codes, passwordResetTokens: tokens } = schema; + return { + getUser(field, value) { + return db.query.users.findFirst({ + where: (table, { eq }) => eq(field === "id" ? table.id : table.email, value), + }); + }, + insertUser: async (data) => { + await db.insert(users).values(data); + }, + async updateUser(userId, data) { + await db.update(users).set(data).where(eq(users.id, userId)); + }, + async getSession(id) { + return db.query.sessions.findFirst({ where: eq(sessions.id, id), with: { user: true } }); + }, + async createSession(session, deleteAllPrev = true) { + if (deleteAllPrev) { + await db.delete(sessions).where(eq(sessions.userId, session.userId)); + } + await db.insert(sessions).values(session); + return session; + }, + async deleteSession(sessionId) { + await db.delete(sessions).where(eq(sessions.id, sessionId)); + }, + async updateSession(sessionId, data) { + await db.update(sessions).set(data).where(eq(sessions.id, sessionId)); + }, + async insertVerificationCode(data, deleteAllPrev = true) { + if (deleteAllPrev) { + await db.delete(codes).where(eq(codes.userId, data.userId)); + } + await db.insert(codes).values(data); + }, + async getVerificationCodes(userId, deleteAfter = false) { + const query = eq(codes.userId, userId); + if (deleteAfter) { + const items = await db.delete(codes).where(query).returning(); + items.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); + return items; + } + return db.query.verificationCodes.findMany({ where: query, orderBy: desc(codes.createdAt) }); + }, + async insertPasswordResetToken(data, deleteAllPrev = true) { + if (deleteAllPrev) { + await db.delete(tokens).where(eq(tokens.userId, data.userId)); + } + await db.insert(tokens).values(data); + }, + async getPasswordResetToken(token, deleteAfter = false) { + const item = await db.query.passwordResetTokens.findFirst({ where: eq(tokens.id, token) }); + if (deleteAfter && item) { + await db.delete(tokens).where(eq(tokens.id, item.id)); + } + return item; + }, + }; +} diff --git a/src/lib/auth/config.ts b/src/lib/auth/config.ts new file mode 100644 index 0000000..cc8d0cb --- /dev/null +++ b/src/lib/auth/config.ts @@ -0,0 +1,18 @@ +import { env } from "@/env"; +import { Discord } from "arctic"; +import { absoluteUrl } from "../utils"; + +export const discord = new Discord( + env.DISCORD_CLIENT_ID, + env.DISCORD_CLIENT_SECRET, + absoluteUrl("/login/discord/callback"), +); + +export const sessionCookieName = "auth_session"; +export const sessionCookieOptions = { + httpOnly: true, + secure: env.NODE_ENV === "production", + sameSite: "lax", + path: "/", +} as const; +export const sessionExpiration = 1000 * 60 * 60 * 24 * 7; // 1 week diff --git a/src/lib/auth/core.ts b/src/lib/auth/core.ts new file mode 100644 index 0000000..0931ad3 --- /dev/null +++ b/src/lib/auth/core.ts @@ -0,0 +1,427 @@ +"use server"; + +import { env } from "@/env"; +import { EmailTemplate, sendMail } from "@/lib/email"; +import type { Discord } from "arctic"; +import bcrypt from "bcryptjs"; +import { nanoid } from "nanoid"; +import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; +import { z } from "zod"; +import { action, validatedAction } from "../action-utils"; +import { Paths } from "../constants"; +import type { Adapter } from "./adapter"; +import { + clearCookie, + createSession, + invalidateSession, + invalidateUserSessions, + setCookie, + validateRequest, +} from "./utils"; + +const SALT_ROUNDS = 10; +const SESSION_COOKIE_NAME = "auth_session"; + +const loginSchema = z.object({ + email: z.string().email("Please enter a valid email"), + password: z.string().min(1, "Please provide your password.").max(255), +}); +const signupSchema = z.object({ + email: z.string().email("Please enter a valid email."), + password: z.string().min(8, "Password is too short. Minimum 8 characters required.").max(255), +}); +const resetPasswordSchema = z.object({ + token: z.string().min(1, "Invalid token"), + password: z.string().min(8, "Password is too short").max(255), +}); + +interface Config { + adapter: Adapter; + discord: Discord; + cookieOptions: { + httpOnly: boolean; + sameSite: "lax" | "strict" | "none"; + secure: boolean; + path: string; + }; + paths: { + login: string; + loginRedirect: string; + signup: string; + verifyEmail: string; + }; + callbacks?: { + onLogin?: (user: Auth["$User"]) => Promise | void; + onSignup?: (user: Auth["$User"], verificationCode?: string) => Promise | void; + onLogout?: () => Promise | void; + }; + sessionExpiration: number; + verificationCodeExpiration: number; + passwordResetTokenExpiration: number; +} +interface Auth { + $User: Pick< + NonNullable>>, + "id" | "email" | "emailVerified" + >; + $Session: NonNullable>>; + login: (input: { email: string; password: string }) => Promise; + signup: (input: { email: string; password: string }) => Promise; + logout: () => Promise; + verifyEmail: (input: { code: string }) => Promise; + resetPassword: (input: { token: string; password: string }) => Promise; + sendPasswordResetEmail: (input: { email: string }) => Promise; + resendVerificationEmail: () => Promise; +} + +type AuthErrorCode = + | "USER_NOT_FOUND" + | "INVALID_PASSWORD" + | "INVALID_SESSION" + | "EMAIL_NOT_VERIFIED" + | "EMAIL_ALREADY_EXISTS" + | "INVALID_EMAIL" + | "INVALID_PASSWORD_RESET_TOKEN" + | "INVALID_EMAIL_VERIFICATION_CODE" + | "EMAIL_VERIFICATION_CODE_EXPIRED" + | "PASSWORD_RESET_TOKEN_EXPIRED"; +export class AuthError extends Error { + constructor( + public code: AuthErrorCode, + public message = "An error occurred", + ) { + super(message); + } +} +const getExpiryDate = (timeSpan: number) => new Date(Date.now() + timeSpan); +const isWithinExpirationDate = (date: Date) => Date.now() < date.getTime(); + +function initAuth(config: Config): Auth { + const { + adapter, + discord, + cookieOptions, + sessionExpiration, + verificationCodeExpiration, + passwordResetTokenExpiration, + callbacks, + paths, + } = config; + const setCookie = async (sessionId: string) => + (await cookies()).set(SESSION_COOKIE_NAME, sessionId, { + ...cookieOptions, + expires: getExpiryDate(sessionExpiration), + }); + const clearCookie = async () => + (await cookies()).set(SESSION_COOKIE_NAME, "", { ...cookieOptions, expires: getExpiryDate(0) }); + const createSession = async (userId: string) => + adapter.createSession({ id: nanoid(25), userId, expiresAt: getExpiryDate(sessionExpiration) }); + const validateSession = async (sessionId: string) => { + const session = await adapter.getSession(sessionId); + if (!session) return { session: null, user: null }; + const { user, ...data } = session; + if (!isWithinExpirationDate(data.expiresAt)) { + await adapter.deleteSession(sessionId); + return { session: null, user: null }; + } + const activePeriodExpiration = new Date(data.expiresAt.getTime() - sessionExpiration / 2); + let fresh = false; + if (!isWithinExpirationDate(activePeriodExpiration)) { + const newExpiration = getExpiryDate(sessionExpiration); + await adapter.updateSession(sessionId, { expiresAt: newExpiration }); + fresh = true; + } + const { email, emailVerified, id } = user; + return { session: data, user: { email, emailVerified, id } as Auth["$User"], fresh }; + }; + const validateRequest = async () => { + const cookieStore = await cookies(); + const sessionId = cookieStore.get(SESSION_COOKIE_NAME)?.value ?? null; + if (!sessionId) { + return { user: null, session: null }; + } + const result = await validateSession(sessionId); + // next.js throws when you attempt to set cookie when rendering page + try { + if (result.session && result.fresh) { + await setCookie(result.session.id); + } + if (!result.session) { + await clearCookie(); + } + } catch { + console.warn("Failed to set session cookie"); + } + return result; + }; + const createdVerificationCode = async (userId: string, email: string) => { + const data = { + email, + userId, + code: nanoid(8), + expiresAt: getExpiryDate(verificationCodeExpiration), + }; + await adapter.insertVerificationCode(data); + return data; + }; + + return { + $User: null as never, + $Session: null as never, + async login({ email, password }) { + const existingUser = await adapter.getUser("email", email); + if (!existingUser) throw new AuthError("USER_NOT_FOUND"); + if (!existingUser.hashedPassword) throw new AuthError("INVALID_PASSWORD"); + const validPassword = await bcrypt.compare(password, existingUser.hashedPassword); + if (!validPassword) throw new AuthError("INVALID_PASSWORD"); + + await callbacks?.onLogin?.({ id: userId, email, emailVerified: false }, code); + + const session = await createSession(existingUser.id); + await setCookie(session.id); + redirect(paths.loginRedirect); + }, + async signup({ email, password }) { + const existingUser = await adapter.getUser("email", email); + if (existingUser) throw new AuthError("EMAIL_ALREADY_EXISTS"); + const userId = nanoid(21); + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS); + await adapter.insertUser({ id: userId, email, hashedPassword }); + const { code } = await createdVerificationCode(userId, email); + try { + await callbacks?.onSignup?.({ id: userId, email, emailVerified: false }, code); + } catch (err) { + console.error(err); + } + const session = await createSession(userId); + await setCookie(session.id); + redirect(paths.verifyEmail); + }, + async logout() { + const { session } = await validateRequest(); + if (!session) throw new AuthError("INVALID_SESSION"); + await adapter.deleteSession(session.id); + await clearCookie(); + redirect(paths.login); + }, + async getNewVerificationCode() { + const { user } = await validateRequest(); + if (!user) { + return redirect(Paths.Login); + } + const [lastSent] = await adapter.getVerificationCodes(user.id); + if (lastSent && isWithinExpirationDate(lastSent.expiresAt)) { + return { + success: false, + message: `Please wait ${timeFromNow(lastSent.expiresAt)} before resending`, + }; + } + const verificationCode = await generateEmailVerificationCode(user.id, user.email); + await sendMail(user.email, EmailTemplate.EmailVerification, { code: verificationCode }); + return { success: true, data: null }; + }, + }; +} +class Auth2 { + private adapter: Adapter; + private discord: Discord; + private cookieOptions: Config["cookieOptions"]; + private sessionExpiration: number; + constructor(config: Config) { + this.adapter = config.adapter; + this.discord = config.discord; + this.cookieOptions = config.cookieOptions; + this.sessionExpiration = config.sessionExpiration; + } +} + +export const loginAction = validatedAction(loginSchema, async (_, input) => { + const { email, password } = input; + try { + const existingUser = await adapter.getUserWithEmail(email); + + if (!existingUser?.hashedPassword) { + return { + success: false, + input, + message: "Incorrect email or password", + }; + } + const validPassword = await bcrypt.compare(password, existingUser.hashedPassword); + if (!validPassword) { + return { + success: false, + input, + message: "Incorrect email or password", + }; + } + const session = await createSession(existingUser.id); + await setCookie(session.id); + } catch (error) { + console.error(error); + return { + success: false, + input, + message: "An error occurred", + }; + } + redirect(Paths.Dashboard); +}); + +export const signupAction = validatedAction(signupSchema, async (_, input) => { + const { email, password } = input; + const existingUser = await adapter.getUserWithEmail(email); + if (existingUser) { + return { + input, + success: false, + message: "Cannot create account with that email", + }; + } + const userId = nanoid(21); + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS); + await adapter.insertUser({ id: userId, email, hashedPassword }); + const verificationCode = await generateEmailVerificationCode(userId, email); + await sendMail(email, EmailTemplate.EmailVerification, { code: verificationCode }); + const session = await createSession(userId); + await setCookie(session.id); + return redirect(Paths.VerifyEmail); +}); + +export const logoutAction = action(async () => { + const { session } = await validateRequest(); + if (!session) { + return { + success: false, + message: "No session found", + }; + } + await invalidateSession(session.id); + await clearCookie(); + redirect("/"); +}); + +export const resendVerificationEmail = action(async () => { + const { user } = await validateRequest(); + if (!user) { + return redirect(Paths.Login); + } + const lastSent = await adapter.getEmailVerificationCodeWithUserId(user.id); + if (lastSent && isWithinExpirationDate(lastSent.expiresAt)) { + return { + success: false, + message: `Please wait ${timeFromNow(lastSent.expiresAt)} before resending`, + }; + } + const verificationCode = await generateEmailVerificationCode(user.id, user.email); + await sendMail(user.email, EmailTemplate.EmailVerification, { code: verificationCode }); + return { success: true, data: null }; +}); + +export const verifyEmailAction = validatedAction( + z.object({ code: z.string().length(8) }), + async (_, { code }) => { + const { user } = await validateRequest(); + if (!user) { + redirect(Paths.Login); + } + const dbCode = await adapter.retriveAndDeleteEmailVerificationCode(user.id); + if (!dbCode || dbCode.code !== code) { + return { + success: false, + message: "Invalid verification code", + }; + } + if (!isWithinExpirationDate(dbCode.expiresAt)) { + return { + success: false, + message: "Verification code expired", + }; + } + if (dbCode.email !== user.email) { + return { + success: false, + message: "Email does not match", + }; + } + await invalidateUserSessions(user.id); + await adapter.updateUser(user.id, { emailVerified: true }); + const session = await createSession(user.id); + await setCookie(session.id); + redirect(Paths.Dashboard); + }, +); + +export const resetPasswordAction = validatedAction(resetPasswordSchema, async (data, input) => { + const { token, password } = input; + + const dbToken = await adapter.retriveAndDeletePasswordResetToken(token); + if (!dbToken) { + return { + input, + success: false, + message: "Invalid password reset link", + }; + } + if (!isWithinExpirationDate(dbToken.expiresAt)) { + return { + input, + success: false, + message: "Password reset link expired.", + }; + } + await invalidateUserSessions(dbToken.userId); + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS); + await adapter.updateUser(dbToken.userId, { hashedPassword }); + const session = await createSession(dbToken.userId); + await setCookie(session.id); + redirect(Paths.Dashboard); +}); + +export const sendPasswordResetEmailAction = validatedAction( + z.object({ email: z.string().trim().email() }), + async (_, { email }) => { + const user = await adapter.getUserWithEmail(email); + if (!user?.emailVerified) + return { + success: false, + message: "User not found", + }; + const verificationToken = await generatePasswordResetToken(user.id); + const verificationLink = `${env.NEXT_PUBLIC_APP_URL}/reset-password/${verificationToken}`; + await sendMail(user.email, EmailTemplate.PasswordReset, { link: verificationLink }); + return { success: true, message: "Email sent", data: null }; + }, +); + +const timeFromNow = (time: Date) => { + const now = new Date(); + const diff = time.getTime() - now.getTime(); + const minutes = Math.floor(diff / 1000 / 60); + const seconds = Math.floor(diff / 1000) % 60; + return `${minutes}m ${seconds}s`; +}; + +async function generateEmailVerificationCode(userId: string, email: string): Promise { + await adapter.deleteUserEmailVerificationCodes(userId); + const code = nanoid(8); + await adapter.insertEmailVerificationCode({ + userId, + email, + code, + expiresAt: new Date(Date.now() + 1000 * 60 * 10) /* 10 minutes */, + }); + return code; +} + +async function generatePasswordResetToken(userId: string): Promise { + await adapter.deleteUserPasswordResetTokens(userId); + const tokenId = nanoid(40); + await adapter.insertPasswordResetToken({ + id: tokenId, + userId, + expiresAt: new Date(Date.now() + 1000 * 60 * 120) /* 2 hours */, + }); + return tokenId; +} diff --git a/src/lib/auth/index.ts b/src/lib/auth/index.ts index 1be9c4e..a867745 100644 --- a/src/lib/auth/index.ts +++ b/src/lib/auth/index.ts @@ -1,55 +1 @@ -import { Lucia, TimeSpan } from "lucia"; -import { Discord } from "arctic"; -import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle"; -import { env } from "@/env.js"; -import { db } from "@/server/db"; -import { sessions, users, type User as DbUser } from "@/server/db/schema"; -import { absoluteUrl } from "@/lib/utils" - -// Uncomment the following lines if you are using nodejs 18 or lower. Not required in Node.js 20, CloudFlare Workers, Deno, Bun, and Vercel Edge Functions. -// import { webcrypto } from "node:crypto"; -// globalThis.crypto = webcrypto as Crypto; - -const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users); - -export const lucia = new Lucia(adapter, { - getSessionAttributes: (/* attributes */) => { - return {}; - }, - getUserAttributes: (attributes) => { - return { - id: attributes.id, - email: attributes.email, - emailVerified: attributes.emailVerified, - avatar: attributes.avatar, - createdAt: attributes.createdAt, - updatedAt: attributes.updatedAt, - }; - }, - sessionExpiresIn: new TimeSpan(30, "d"), - sessionCookie: { - name: "session", - - expires: false, // session cookies have very long lifespan (2 years) - attributes: { - secure: env.NODE_ENV === "production", - }, - }, -}); - -export const discord = new Discord( - env.DISCORD_CLIENT_ID, - env.DISCORD_CLIENT_SECRET, - absoluteUrl("/login/discord/callback") -); - -declare module "lucia" { - interface Register { - Lucia: typeof lucia; - DatabaseSessionAttributes: DatabaseSessionAttributes; - DatabaseUserAttributes: DatabaseUserAttributes; - } -} - -interface DatabaseSessionAttributes {} -interface DatabaseUserAttributes extends Omit {} +export { unCachedValidateRequest, validateRequest } from "./utils"; diff --git a/src/lib/auth/utils.ts b/src/lib/auth/utils.ts new file mode 100644 index 0000000..1978ae1 --- /dev/null +++ b/src/lib/auth/utils.ts @@ -0,0 +1,103 @@ +import { nanoid } from "nanoid"; +import { cookies } from "next/headers"; +import { cache } from "react"; +import type { AuthSession, AuthUser } from "./adapter"; +import adapter from "./adapter"; +import { sessionCookieName, sessionCookieOptions, sessionExpiration } from "./config"; + +export async function createSession(userId: string) { + const session: AuthSession = { + userId, + id: nanoid(25), + expiresAt: createExpiryDate(sessionExpiration), + }; + await adapter.createSession(session); + return session; +} + +export async function invalidateSession(sessionId: string) { + await adapter.deleteSession(sessionId); +} + +export async function invalidateUserSessions(userId: string) { + await adapter.deleteUserSessions(userId); +} + +async function validateSession( + sessionId: string, +): Promise< + { session: AuthSession; user: AuthUser; fresh: boolean } | { session: null; user: null } +> { + const dbSession = await adapter.getSessionAndUser(sessionId); + + if (!dbSession) { + return { session: null, user: null }; + } + + if (!isWithinExpirationDate(dbSession.expiresAt)) { + await adapter.deleteSession(sessionId); + return { session: null, user: null }; + } + const { user: dbUser, ...session } = dbSession; + const { hashedPassword: _, ...user } = dbUser; + + const activePeriodExpirationDate = new Date( + dbSession.expiresAt.getTime() - sessionExpiration / 2, + ); + if (!isWithinExpirationDate(activePeriodExpirationDate)) { + const newExpirationDate = createExpiryDate(sessionExpiration); + await adapter.updateSessionExpiration(sessionId, newExpirationDate); + return { session, user, fresh: true }; + } + + return { session, user, fresh: false }; +} + +export async function setCookie(sessionId: string) { + const cookieStore = await cookies(); + cookieStore.set(sessionCookieName, sessionId, { + ...sessionCookieOptions, + expires: createExpiryDate(sessionExpiration), + }); +} +export async function clearCookie() { + const cookieStore = await cookies(); + cookieStore.set(sessionCookieName, "", { + ...sessionCookieOptions, + expires: new Date(0), + }); +} + +export function isWithinExpirationDate(date: Date): boolean { + return Date.now() < date.getTime(); +} + +/** @param timeSpan The time span in milliseconds */ +function createExpiryDate(timeSpan: number) { + return new Date(Date.now() + timeSpan); +} + +export const validateRequest = cache(unCachedValidateRequest); + +export async function unCachedValidateRequest(): Promise< + Omit, "fresh"> +> { + const cookieStore = await cookies(); + const sessionId = cookieStore.get(sessionCookieName)?.value ?? null; + if (!sessionId) { + return { user: null, session: null }; + } + const result = await validateSession(sessionId); + // next.js throws when you attempt to set cookie when rendering page + try { + if (result.session && result.fresh) { + await setCookie(result.session.id); + } + if (!result.session) { + await clearCookie(); + } + } catch { + console.warn("Failed to set session cookie"); + } + return result; +} diff --git a/src/lib/auth/validate-request.ts b/src/lib/auth/validate-request.ts deleted file mode 100644 index a5fe7ca..0000000 --- a/src/lib/auth/validate-request.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { cache } from "react"; -import { cookies } from "next/headers"; -import type { Session, User } from "lucia"; -import { lucia } from "@/lib/auth"; - - -export const uncachedValidateRequest = async (): Promise< - { user: User; session: Session } | { user: null; session: null } -> => { - const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null; - if (!sessionId) { - return { user: null, session: null }; - } - const result = await lucia.validateSession(sessionId); - // next.js throws when you attempt to set cookie when rendering page - try { - if (result.session && result.session.fresh) { - const sessionCookie = lucia.createSessionCookie(result.session.id); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - } - if (!result.session) { - const sessionCookie = lucia.createBlankSessionCookie(); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - } - } catch { - console.error("Failed to set session cookie"); - } - return result; -}; - -export const validateRequest = cache(uncachedValidateRequest); diff --git a/src/lib/email/index.tsx b/src/lib/email/index.tsx index be06295..2fd0bbd 100644 --- a/src/lib/email/index.tsx +++ b/src/lib/email/index.tsx @@ -1,13 +1,13 @@ import "server-only"; -import { EmailVerificationTemplate } from "./templates/email-verification"; -import { ResetPasswordTemplate } from "./templates/reset-password"; -import { render } from "@react-email/render"; import { env } from "@/env"; import { EMAIL_SENDER } from "@/lib/constants"; +import { render } from "@react-email/render"; import { createTransport, type TransportOptions } from "nodemailer"; import type { ComponentProps } from "react"; import { logger } from "../logger"; +import { EmailVerificationTemplate } from "./templates/email-verification"; +import { ResetPasswordTemplate } from "./templates/reset-password"; export enum EmailTemplate { EmailVerification = "EmailVerification", @@ -19,19 +19,22 @@ export type PropsMap = { [EmailTemplate.PasswordReset]: ComponentProps; }; -const getEmailTemplate = (template: T, props: PropsMap[NoInfer]) => { +const getEmailTemplate = async ( + template: T, + props: PropsMap[NoInfer], +) => { switch (template) { case EmailTemplate.EmailVerification: return { subject: "Verify your email address", - body: render( + body: await render( , ), }; case EmailTemplate.PasswordReset: return { subject: "Reset your password", - body: render( + body: await render( , ), }; @@ -57,11 +60,9 @@ export const sendMail = async ( props: PropsMap[NoInfer], ) => { if (env.MOCK_SEND_EMAIL) { - logger.info("๐Ÿ“จ Email sent to:", to, "with template:", template, "and props:", props); + logger.info(`๐Ÿ“จ Email sent to: "${to}"`, { ...props, to, template } as Record); return; } - - const { subject, body } = getEmailTemplate(template, props); - + const { subject, body } = await getEmailTemplate(template, props); return transporter.sendMail({ from: EMAIL_SENDER, to, subject, html: body }); }; diff --git a/src/lib/hooks/use-debounce.ts b/src/lib/hooks/use-debounce.ts deleted file mode 100644 index 0cee67a..0000000 --- a/src/lib/hooks/use-debounce.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useEffect, useState } from "react"; - -export function useDebounce(value: T, delay: number) { - const [debouncedValue, setDebouncedValue] = useState(value); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - return () => { - clearTimeout(handler); - }; - }, [value, delay]); - - return debouncedValue; -} diff --git a/src/lib/logger.ts b/src/lib/logger.ts index f86f7e0..00d52d9 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -9,60 +9,42 @@ enum LogLevel { ERROR = "ERROR", } +type LoggerParams = [string, Record]; + class Logger { - private level: LogLevel; private logFilePath: string; - - constructor(level: LogLevel = LogLevel.INFO, logFilePath = "application.log") { - this.level = level; + constructor(logFilePath = "application.log") { this.logFilePath = path.resolve(logFilePath); } - private getTimestamp(): string { return new Date().toISOString(); } - - private formatMessage(level: LogLevel, args: unknown[]): string { - const message = args - .map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : arg)) - .join(" "); - + private formatMessage(level: LogLevel, msg: string, args: Record): string { + const message = { timestamp: this.getTimestamp(), level, msg, ...args }; if (env.NODE_ENV === "development") { console.log(message); } - - return `[${this.getTimestamp()}] [${level}] ${message}`; + return JSON.stringify(message); } - private log(level: LogLevel, ...args: unknown[]): void { - if (this.shouldLog(level)) { - const logMessage = this.formatMessage(level, args) + "\n"; - fs.appendFile(this.logFilePath, logMessage, (err) => { - if (err) throw err; - }); - } + private log(level: LogLevel, msg: string, args: Record): void { + const logMessage = this.formatMessage(level, msg, args) + "\n"; + fs.appendFile(this.logFilePath, logMessage, (err) => { + if (err) throw err; + }); } - - private shouldLog(level: LogLevel): boolean { - const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR]; - return levels.indexOf(level) >= levels.indexOf(this.level); - } - - debug(...args: unknown[]): void { - this.log(LogLevel.DEBUG, ...args); + debug(...args: LoggerParams) { + if (env.NODE_ENV !== "production") this.log(LogLevel.DEBUG, ...args); } - - info(...args: unknown[]): void { + info(...args: LoggerParams) { this.log(LogLevel.INFO, ...args); } - - warn(...args: unknown[]): void { + warn(...args: LoggerParams) { this.log(LogLevel.WARN, ...args); } - - error(...args: unknown[]): void { + error(...args: LoggerParams) { this.log(LogLevel.ERROR, ...args); } } -export const logger = new Logger(env.NODE_ENV === "development" ? LogLevel.DEBUG : LogLevel.INFO); +export const logger = new Logger(); diff --git a/src/lib/validators/auth.ts b/src/lib/validators/auth.ts deleted file mode 100644 index 9d96436..0000000 --- a/src/lib/validators/auth.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { z } from "zod"; - -export const signupSchema = z.object({ - email: z.string().email("Please enter a valid email"), - password: z.string().min(1, "Please provide your password.").max(255), -}); -export type SignupInput = z.infer; - -export const loginSchema = z.object({ - email: z.string().email("Please enter a valid email."), - password: z - .string() - .min(8, "Password is too short. Minimum 8 characters required.") - .max(255), -}); -export type LoginInput = z.infer; - -export const forgotPasswordSchema = z.object({ - email: z.string().email(), -}); -export type ForgotPasswordInput = z.infer; - -export const resetPasswordSchema = z.object({ - token: z.string().min(1, "Invalid token"), - password: z.string().min(8, "Password is too short").max(255), -}); -export type ResetPasswordInput = z.infer; diff --git a/src/middleware.ts b/src/middleware.ts index c789a41..e2e678f 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,28 +1,36 @@ -// middleware.ts -import { verifyRequestOrigin } from "lucia"; import { NextResponse } from "next/server"; + import type { NextRequest } from "next/server"; +import { env } from "./env"; export async function middleware(request: NextRequest): Promise { + const response = NextResponse.next(); if (request.method === "GET") { - return NextResponse.next(); - } - const originHeader = request.headers.get("Origin"); - const hostHeader = request.headers.get("Host"); - if ( - !originHeader || - !hostHeader || - !verifyRequestOrigin(originHeader, [hostHeader]) - ) { - return new NextResponse(null, { - status: 403, - }); + const token = request.cookies.get("session")?.value ?? null; + if (token !== null) { + // Only extend cookie expiration on GET requests since we can be sure + // a new session wasn't set when handling the request. + response.cookies.set("session", token, { + httpOnly: true, + secure: env.NODE_ENV === "production", + sameSite: "lax", + path: "/", + maxAge: 60 * 60 * 24 * 30, + }); + } } - return NextResponse.next(); + return response; } export const config = { matcher: [ - "/((?!api|static|.*\\..*|_next|favicon.ico|sitemap.xml|robots.txt).*)", + /* + * Match all request paths except for the ones starting with: + * - api (API routes) + * - _next/static (static files) + * - _next/image (image optimization files) + * - favicon.ico, sitemap.xml, robots.txt (metadata files) + */ + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)", ], }; diff --git a/src/server/api/routers/post/post.service.ts b/src/server/api/routers/post/post.service.ts index 8570887..b1d47ad 100644 --- a/src/server/api/routers/post/post.service.ts +++ b/src/server/api/routers/post/post.service.ts @@ -1,4 +1,6 @@ -import { generateId } from "lucia"; +import { posts } from "@/server/db/schema"; +import { eq } from "drizzle-orm"; +import { nanoid } from "nanoid"; import type { ProtectedTRPCContext } from "../../trpc"; import type { CreatePostInput, @@ -8,8 +10,6 @@ import type { MyPostsInput, UpdatePostInput, } from "./post.input"; -import { posts } from "@/server/db/schema"; -import { eq } from "drizzle-orm"; export const listPosts = async (ctx: ProtectedTRPCContext, input: ListPostsInput) => { return ctx.db.query.posts.findMany({ @@ -17,13 +17,7 @@ export const listPosts = async (ctx: ProtectedTRPCContext, input: ListPostsInput offset: (input.page - 1) * input.perPage, limit: input.perPage, orderBy: (table, { desc }) => desc(table.createdAt), - columns: { - id: true, - title: true, - excerpt: true, - status: true, - createdAt: true, - }, + columns: { content: false }, with: { user: { columns: { email: true } } }, }); }; @@ -36,30 +30,20 @@ export const getPost = async (ctx: ProtectedTRPCContext, { id }: GetPostInput) = }; export const createPost = async (ctx: ProtectedTRPCContext, input: CreatePostInput) => { - const id = generateId(15); - - await ctx.db.insert(posts).values({ - id, - userId: ctx.user.id, - title: input.title, - excerpt: input.excerpt, - content: input.content, + const data = { id: nanoid(15), userId: ctx.user.id, ...input }; + const userPosts = await ctx.db.query.posts.findMany({ + where: (table, { eq }) => eq(table.userId, ctx.user.id), + columns: { id: true }, }); - - return { id }; + if (userPosts.length >= 5) { + throw new Error("You can't have more than 5 posts"); + } + await ctx.db.insert(posts).values(data); + return data; }; -export const updatePost = async (ctx: ProtectedTRPCContext, input: UpdatePostInput) => { - const [item] = await ctx.db - .update(posts) - .set({ - title: input.title, - excerpt: input.excerpt, - content: input.content, - }) - .where(eq(posts.id, input.id)) - .returning(); - +export const updatePost = async (ctx: ProtectedTRPCContext, { id, ...input }: UpdatePostInput) => { + const [item] = await ctx.db.update(posts).set(input).where(eq(posts.id, id)).returning(); return item; }; @@ -74,12 +58,6 @@ export const myPosts = async (ctx: ProtectedTRPCContext, input: MyPostsInput) => offset: (input.page - 1) * input.perPage, limit: input.perPage, orderBy: (table, { desc }) => desc(table.createdAt), - columns: { - id: true, - title: true, - excerpt: true, - status: true, - createdAt: true, - }, + columns: { id: true, title: true, status: true, excerpt: true, createdAt: true }, }); }; diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 66a59d3..ec945e3 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -7,7 +7,7 @@ * need to use are documented accordingly near the end. */ -import { uncachedValidateRequest } from "@/lib/auth/validate-request"; +import { unCachedValidateRequest } from "@/lib/auth"; import { stripe } from "@/lib/stripe"; import { db } from "@/server/db"; import { initTRPC, TRPCError, type inferAsyncReturnType } from "@trpc/server"; @@ -27,7 +27,7 @@ import { ZodError } from "zod"; * @see https://trpc.io/docs/server/context */ export const createTRPCContext = async (opts: { headers: Headers }) => { - const { session, user } = await uncachedValidateRequest(); + const { session, user } = await unCachedValidateRequest(); return { session, user, diff --git a/src/server/db/db-utils.ts b/src/server/db/db-utils.ts new file mode 100644 index 0000000..95ece24 --- /dev/null +++ b/src/server/db/db-utils.ts @@ -0,0 +1,12 @@ +import { DATABASE_PREFIX as prefix } from "@/lib/constants"; +import { pgTableCreator, timestamp } from "drizzle-orm/pg-core"; + +export const pgTable = pgTableCreator((name) => `${prefix}_${name}`); + +/*********************** + * Table definitions + ***********************/ +export const timestampColumns = { + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at", { mode: "date" }).$onUpdate(() => new Date()), +}; diff --git a/src/server/db/index.ts b/src/server/db/index.ts index 10ce938..baf88c6 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -1,11 +1,19 @@ +import { env } from "@/env"; import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; -import { env } from "@/env"; -import * as schema from "./schema"; +import * as posts from "./schema/posts"; +import * as users from "./schema/users"; + +export type DB = typeof db; +const globalForDb = globalThis as unknown as { connection: postgres.Sql | undefined }; + +export const connection = globalForDb.connection ?? postgres(env.DATABASE_URL); -export const connection = postgres(env.DATABASE_URL, { - max_lifetime: 10, // Remove this line if you're deploying to Docker / VPS - // idle_timeout: 20, // Uncomment this line if you're deploying to Docker / VPS -}); +if (env.NODE_ENV !== "production") globalForDb.connection = connection; +export const schema = { + ...users, + ...posts, +} as const; +export * from "./types"; export const db = drizzle(connection, { schema }); diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts deleted file mode 100644 index 8574060..0000000 --- a/src/server/db/schema.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { relations } from "drizzle-orm"; -import { - pgTableCreator, - serial, - boolean, - index, - text, - timestamp, - varchar, -} from "drizzle-orm/pg-core"; -import { DATABASE_PREFIX as prefix } from "@/lib/constants"; - -export const pgTable = pgTableCreator((name) => `${prefix}_${name}`); - -export const users = pgTable( - "users", - { - id: varchar("id", { length: 21 }).primaryKey(), - discordId: varchar("discord_id", { length: 255 }).unique(), - email: varchar("email", { length: 255 }).unique().notNull(), - emailVerified: boolean("email_verified").default(false).notNull(), - hashedPassword: varchar("hashed_password", { length: 255 }), - avatar: varchar("avatar", { length: 255 }), - stripeSubscriptionId: varchar("stripe_subscription_id", { length: 191 }), - stripePriceId: varchar("stripe_price_id", { length: 191 }), - stripeCustomerId: varchar("stripe_customer_id", { length: 191 }), - stripeCurrentPeriodEnd: timestamp("stripe_current_period_end"), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at", { mode: "date" }).$onUpdate(() => new Date()), - }, - (t) => ({ - emailIdx: index("user_email_idx").on(t.email), - discordIdx: index("user_discord_idx").on(t.discordId), - }), -); - -export type User = typeof users.$inferSelect; -export type NewUser = typeof users.$inferInsert; - -export const sessions = pgTable( - "sessions", - { - id: varchar("id", { length: 255 }).primaryKey(), - userId: varchar("user_id", { length: 21 }).notNull(), - expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(), - }, - (t) => ({ - userIdx: index("session_user_idx").on(t.userId), - }), -); - -export const emailVerificationCodes = pgTable( - "email_verification_codes", - { - id: serial("id").primaryKey(), - userId: varchar("user_id", { length: 21 }).unique().notNull(), - email: varchar("email", { length: 255 }).notNull(), - code: varchar("code", { length: 8 }).notNull(), - expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(), - }, - (t) => ({ - userIdx: index("verification_code_user_idx").on(t.userId), - emailIdx: index("verification_code_email_idx").on(t.email), - }), -); - -export const passwordResetTokens = pgTable( - "password_reset_tokens", - { - id: varchar("id", { length: 40 }).primaryKey(), - userId: varchar("user_id", { length: 21 }).notNull(), - expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(), - }, - (t) => ({ - userIdx: index("password_token_user_idx").on(t.userId), - }), -); - -export const posts = pgTable( - "posts", - { - id: varchar("id", { length: 15 }).primaryKey(), - userId: varchar("user_id", { length: 255 }).notNull(), - title: varchar("title", { length: 255 }).notNull(), - excerpt: varchar("excerpt", { length: 255 }).notNull(), - content: text("content").notNull(), - status: varchar("status", { length: 10, enum: ["draft", "published"] }) - .default("draft") - .notNull(), - tags: varchar("tags", { length: 255 }), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at", { mode: "date" }).$onUpdate(() => new Date()), - }, - (t) => ({ - userIdx: index("post_user_idx").on(t.userId), - createdAtIdx: index("post_created_at_idx").on(t.createdAt), - }), -); - -export const postRelations = relations(posts, ({ one }) => ({ - user: one(users, { - fields: [posts.userId], - references: [users.id], - }), -})); - -export type Post = typeof posts.$inferSelect; -export type NewPost = typeof posts.$inferInsert; diff --git a/src/server/db/schema/posts.ts b/src/server/db/schema/posts.ts new file mode 100644 index 0000000..4dc28d8 --- /dev/null +++ b/src/server/db/schema/posts.ts @@ -0,0 +1,27 @@ +import { relations } from "drizzle-orm"; +import { index, text, varchar } from "drizzle-orm/pg-core"; +import { pgTable, timestampColumns } from "../db-utils"; +import { users } from "./users"; + +export const posts = pgTable( + "posts", + { + id: varchar({ length: 21 }).primaryKey(), + userId: varchar("user_id", { length: 21 }) + .references(() => users.id, { onDelete: "cascade" }) + .notNull(), + title: varchar({ length: 255 }).notNull(), + excerpt: varchar({ length: 255 }).notNull(), + content: text().notNull(), + status: varchar({ length: 31, enum: ["draft", "published"] }).default("draft"), + ...timestampColumns, + }, + (t) => [index("post_status_idx").on(t.status)], +); + +export const postRelations = relations(posts, ({ one }) => ({ + user: one(users, { + fields: [posts.userId], + references: [users.id], + }), +})); diff --git a/src/server/db/schema/users.ts b/src/server/db/schema/users.ts new file mode 100644 index 0000000..ac9325c --- /dev/null +++ b/src/server/db/schema/users.ts @@ -0,0 +1,60 @@ +import { relations } from "drizzle-orm"; +import { boolean, index, serial, timestamp, varchar } from "drizzle-orm/pg-core"; +import { pgTable, timestampColumns } from "../db-utils"; + +export const users = pgTable( + "users", + { + id: varchar({ length: 21 }).primaryKey(), + discordId: varchar("discord_id", { length: 255 }).unique(), + email: varchar({ length: 255 }).unique().notNull(), + emailVerified: boolean("email_verified").default(false).notNull(), + hashedPassword: varchar("hashed_password", { length: 63 }), + avatar: varchar({ length: 255 }), + stripeSubscriptionId: varchar("stripe_subscription_id", { length: 191 }), + stripePriceId: varchar("stripe_price_id", { length: 191 }), + stripeCustomerId: varchar("stripe_customer_id", { length: 191 }), + stripeCurrentPeriodEnd: timestamp("stripe_current_period_end"), + ...timestampColumns, + }, + (t) => [index("user_email_idx").on(t.email), index("user_discord_id_idx").on(t.discordId)], +); + +export const sessions = pgTable("sessions", { + id: varchar({ length: 255 }).primaryKey(), + userId: varchar("user_id", { length: 21 }) + .references(() => users.id, { onDelete: "cascade" }) + .notNull(), + expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(), +}); + +export const verificationCodes = pgTable( + "email_verification_codes", + { + id: serial().primaryKey(), + userId: varchar("user_id", { length: 21 }) + .references(() => users.id, { onDelete: "cascade" }) + .unique() + .notNull(), + email: varchar("email", { length: 255 }).notNull(), + code: varchar("code", { length: 8 }).notNull(), + createdAt: timestampColumns.createdAt, + expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(), + }, + (t) => [index("verification_code_email_idx").on(t.email)], +); + +export const passwordResetTokens = pgTable("password_reset_tokens", { + id: varchar({ length: 21 }).primaryKey(), + userId: varchar("user_id", { length: 21 }) + .references(() => users.id, { onDelete: "cascade" }) + .notNull(), + expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(), +}); + +export const sessionRelations = relations(sessions, ({ one }) => ({ + user: one(users, { + fields: [sessions.userId], + references: [users.id], + }), +})); diff --git a/src/server/db/types.ts b/src/server/db/types.ts new file mode 100644 index 0000000..7c0171b --- /dev/null +++ b/src/server/db/types.ts @@ -0,0 +1,17 @@ +import type { posts } from "./schema/posts"; +import type { passwordResetTokens, sessions, users, verificationCodes } from "./schema/users"; + +export type User = typeof users.$inferSelect; +export type NewUser = typeof users.$inferInsert; + +export type Session = typeof sessions.$inferSelect; +export type NewSession = typeof sessions.$inferInsert; + +export type Post = typeof posts.$inferSelect; +export type NewPost = typeof posts.$inferInsert; + +export type EmailVerificationCode = typeof verificationCodes.$inferSelect; +export type NewEmailVerificationCode = typeof verificationCodes.$inferInsert; + +export type PasswordResetToken = typeof passwordResetTokens.$inferSelect; +export type NewPasswordResetToken = typeof passwordResetTokens.$inferInsert; diff --git a/src/trpc/server.ts b/src/trpc/server.ts index 12ee6aa..a599d67 100644 --- a/src/trpc/server.ts +++ b/src/trpc/server.ts @@ -1,15 +1,11 @@ import "server-only"; -import { - createTRPCProxyClient, - loggerLink, - TRPCClientError, -} from "@trpc/client"; +import { createTRPCProxyClient, loggerLink, TRPCClientError } from "@trpc/client"; import { callProcedure } from "@trpc/server"; import { observable } from "@trpc/server/observable"; import { type TRPCErrorResponse } from "@trpc/server/rpc"; -import { cache } from "react"; import { headers } from "next/headers"; +import { cache } from "react"; import { appRouter, type AppRouter } from "@/server/api/root"; import { createTRPCContext } from "@/server/api/trpc"; @@ -19,8 +15,9 @@ import { transformer } from "./shared"; * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when * handling a tRPC call from a React Server Component. */ -const createContext = cache(() => { - const heads = new Headers(headers()); +const createContext = cache(async () => { + const headerStore = await headers(); + const heads = new Headers(headerStore); heads.set("x-trpc-source", "rsc"); return createTRPCContext({ headers: heads, diff --git a/src/trpc/shared.ts b/src/trpc/shared.ts index b77388d..c067e86 100644 --- a/src/trpc/shared.ts +++ b/src/trpc/shared.ts @@ -6,7 +6,7 @@ import { type AppRouter } from "@/server/api/root"; export const transformer = superjson; function getBaseUrl() { - if (typeof window !== "undefined") return ""; + if (typeof window !== "undefined") return window.location.origin; if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; return `http://localhost:${process.env.PORT ?? 3000}`; } diff --git a/tests/e2e/auth-with-credential.spec.ts b/tests/e2e/auth-with-credential.spec.ts index 16b4955..10ace57 100644 --- a/tests/e2e/auth-with-credential.spec.ts +++ b/tests/e2e/auth-with-credential.spec.ts @@ -1,16 +1,19 @@ import { db } from "@/server/db"; import { users } from "@/server/db/schema"; -import { test, expect } from "@playwright/test"; +import { expect, test } from "@playwright/test"; +import bcrypt from "bcryptjs"; import { eq } from "drizzle-orm"; -import { extractLastCode, testUser } from "./utils"; import { readFileSync } from "fs"; +import { testLoginUser, testSignupUser } from "./utils"; -test.beforeAll(() => { - db.delete(users) - .where(eq(users.email, testUser.email)) - .catch((error) => { - console.error(error); - }); +test.beforeAll(async () => { + const hashedPassword = await bcrypt.hash(testLoginUser.password, 10); + await Promise.allSettled([ + db.insert(users).values({ email: testLoginUser.email, hashedPassword, emailVerified: false }), + db.delete(users).where(eq(users.email, testSignupUser.email)), + ]).catch((error) => { + console.error(error); + }); }); test.describe("signup and login", () => { @@ -19,12 +22,12 @@ test.describe("signup and login", () => { await page.getByText("login").click(); await page.getByText(/sign up/i).click(); await page.waitForURL("/signup"); - await page.getByLabel("Email").fill(testUser.email); - await page.getByLabel("Password").fill(testUser.password); + await page.getByLabel("Email").fill(testSignupUser.email); + await page.getByLabel("Password").fill(testSignupUser.password); await page.getByLabel("submit-btn").click(); await page.waitForURL("/verify-email"); const data = readFileSync("application.log", { encoding: "utf-8" }); - const code = extractLastCode(data); + const code = extractVerificationCode(data, testSignupUser.email); expect(code).not.toBeNull(); await page.getByLabel("Verification Code").fill(code!); await page.getByLabel("submit-btn").click(); @@ -33,13 +36,24 @@ test.describe("signup and login", () => { test("login and logout", async ({ page }) => { await page.goto("/"); await page.getByText("login").click(); - await page.getByLabel("Email").fill(testUser.email); - await page.getByLabel("Password").fill(testUser.password); + await page.getByLabel("Email").fill(testLoginUser.email); + await page.getByLabel("Password").fill(testLoginUser.password); await page.getByLabel("submit-btn").click(); await page.waitForURL("/dashboard"); - await page.getByAltText("Avatar").click(); + await page.getByLabel("user dropdown menu").click(); await page.getByText("Sign out").click(); await page.getByText("Continue").click(); await page.waitForURL("/"); }); }); + +function extractVerificationCode(log: string, email: string): string | null { + const logLines = log.split(/\r?\n/).filter((line) => line.includes(email)); + const lastLog = logLines[logLines.length - 1]; + if (!lastLog) return null; + const logObj = JSON.parse(lastLog) as unknown; + if (logObj && logObj instanceof Object && "code" in logObj && typeof logObj.code === "string") { + return logObj.code; + } + return null; +} diff --git a/tests/e2e/utils.ts b/tests/e2e/utils.ts index 9dedbde..69ac713 100644 --- a/tests/e2e/utils.ts +++ b/tests/e2e/utils.ts @@ -1,19 +1,10 @@ -export const testUser = { +export const testSignupUser = { name: "Test User", - email: "test@saasykits.com", + email: "test+signup@saasykits.com", password: "testPass123", }; - -export function extractLastCode(log: string): string | null { - // Regular expression to match the code value - const regex = /"code":"(\d+)"/g; - - let match: RegExpExecArray | null; - let lastCode: string | null = null; - - // Find all matches and keep track of the last one - while ((match = regex.exec(log)) !== null) { - lastCode = match[1] ?? null; - } - return lastCode; -} +export const testLoginUser = { + name: "Test Login User", + password: "testPass123", + email: "test+login@saasykits.com", +};