diff --git a/.env.docker.example b/.env.docker.example index f2c2607b1..70841edd0 100644 --- a/.env.docker.example +++ b/.env.docker.example @@ -40,6 +40,7 @@ TUNNEL_TOKEN="your_cloudflare_tunnel_token_here" # Google reCAPTCHA for form protection NEXT_PUBLIC_RECAPTCHA_SITE_KEY="your_recaptcha_site_key_here" RECAPTCHA_SECRET_KEY="your_recaptcha_secret_key_here" +NEXT_PUBLIC_DISABLE_RECAPTCHA="false" # =========================================== # EMAIL SERVICES (Optional) diff --git a/.env.example b/.env.example index 8c4f20075..5efdaeff9 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,7 @@ IGDB_CLIENT_KEY="IGDB-Client-Secret" # reCAPTCHA v3 NEXT_PUBLIC_RECAPTCHA_SITE_KEY=site-key-here RECAPTCHA_SECRET_KEY=secret-key-here +NEXT_PUBLIC_DISABLE_RECAPTCHA=false # Email Providers (For the future) EMAIL_ENABLED=false diff --git a/.env.test.example b/.env.test.example index 16229ad90..ff39873e6 100644 --- a/.env.test.example +++ b/.env.test.example @@ -9,6 +9,7 @@ CLERK_SECRET_KEY="sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # reCAPTCHA V3 NEXT_PUBLIC_RECAPTCHA_SITE_KEY= RECAPTCHA_SECRET_KEY= +NEXT_PUBLIC_DISABLE_RECAPTCHA=false # Email (For the future) EMAIL_ENABLED=false diff --git a/eslint.config.mjs b/eslint.config.mjs index 19ea77fc9..235e19c2f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,16 +1,8 @@ -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import { FlatCompat } from '@eslint/eslintrc' import typescriptEslint from '@typescript-eslint/eslint-plugin' import typescriptParser from '@typescript-eslint/parser' -import importPlugin from 'eslint-plugin-import' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -const compat = new FlatCompat({ - baseDirectory: __dirname, -}) +import eslintConfigPrettier from 'eslint-config-prettier' +import nextCoreWebVitals from 'eslint-config-next/core-web-vitals' +import nextTypeScript from 'eslint-config-next/typescript' const eslintConfig = [ { @@ -37,10 +29,9 @@ const eslintConfig = [ 'test-results/**', ], }, - // Next.js + Prettier via FlatCompat - ...compat.config({ - extends: ['next/core-web-vitals', 'next/typescript', 'prettier'], - }), // JS and TS global config + ...nextCoreWebVitals, + ...nextTypeScript, + eslintConfigPrettier, { files: ['**/*.{js,jsx,ts,tsx}'], languageOptions: { @@ -55,14 +46,21 @@ const eslintConfig = [ }, }, rules: { - // Your custom JS rules 'prefer-template': 'error', 'no-useless-escape': 'off', 'no-case-declarations': 'off', 'no-prototype-builtins': 'off', 'no-redeclare': 'off', + // TODO: set to error after fixing existing React Compiler rule violations. + 'react-hooks/purity': 'warn', + 'react-hooks/static-components': 'warn', + 'react-hooks/immutability': 'warn', + 'react-hooks/incompatible-library': 'warn', + 'react-hooks/preserve-manual-memoization': 'warn', + 'react-hooks/refs': 'warn', + 'react-hooks/set-state-in-effect': 'warn', }, - }, // TypeScript config + }, { files: ['**/*.{ts,tsx}'], languageOptions: { @@ -74,9 +72,6 @@ const eslintConfig = [ ecmaFeatures: { jsx: true }, }, }, - plugins: { - '@typescript-eslint': typescriptEslint, - }, rules: { ...typescriptEslint.configs.recommended.rules, '@typescript-eslint/array-type': ['error', { default: 'array' }], @@ -101,7 +96,6 @@ const eslintConfig = [ }, // Import plugin { files: ['**/*.{js,jsx,ts,tsx}'], - plugins: { import: importPlugin }, settings: { 'import/resolver': { typescript: { project: './tsconfig.json' }, diff --git a/next.config.ts b/next.config.ts index 76bdd4db9..9dff5afa0 100644 --- a/next.config.ts +++ b/next.config.ts @@ -203,6 +203,31 @@ const nextConfig: NextConfig = { allowedDevOrigins: ['dev.emuready.com', '127.0.0.1'], + cacheComponents: true, + + cacheLife: { + 'seo-record': { + stale: 300, + revalidate: 3600, + expire: 86400, + }, + 'seo-report': { + stale: 300, + revalidate: 1800, + expire: 43200, + }, + 'seo-sitemap': { + stale: 300, + revalidate: 21600, + expire: 172800, + }, + 'seo-miss': { + stale: 30, + revalidate: 60, + expire: 300, + }, + }, + turbopack: { rules: { '*.svg': { @@ -274,10 +299,6 @@ const nextConfig: NextConfig = { ], }, - eslint: { - dirs: ['src', 'tests'], - }, - webpack: (config: WebpackConfiguration) => { config.module?.rules?.push({ test: /\.svg$/, diff --git a/package.json b/package.json index b49c7ad97..26de38473 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@hookform/resolvers": "^5.0.1", - "@next/third-parties": "15.5.18", + "@next/third-parties": "16.2.6", "@prisma/adapter-pg": "7.8.0", "@prisma/client": "7.8.0", "@radix-ui/react-alert-dialog": "^1.1.14", @@ -99,13 +99,13 @@ "lucide-react": "^0.511.0", "mailersend": "^2.6.0", "markdown-it": "14.1.1", - "next": "15.5.18", + "next": "16.2.6", "next-themes": "^0.4.6", "pg": "8.21.0", "pg-pool": "3.14.0", "postcss": "8.5.14", - "react": "19.1.4", - "react-dom": "19.1.4", + "react": "19.2.6", + "react-dom": "19.2.6", "react-error-boundary": "^6.0.0", "react-google-recaptcha-v3": "^1.11.0", "react-hook-form": "^7.56.4", @@ -123,8 +123,8 @@ "@clerk/types": "4.101.23", "@eslint/eslintrc": "^3", "@eslint/js": "^9.28.0", - "@next/bundle-analyzer": "15.5.18", - "@next/eslint-plugin-next": "15.5.18", + "@next/bundle-analyzer": "16.2.6", + "@next/eslint-plugin-next": "16.2.6", "@playwright/test": "^1.60.0", "@svgr/webpack": "^8.1.0", "@tailwindcss/postcss": "^4.1.6", @@ -135,8 +135,8 @@ "@types/jsdom": "^21.1.7", "@types/markdown-it": "^14.1.2", "@types/node": "^20", - "@types/react": "19.1.8", - "@types/react-dom": "19.1.6", + "@types/react": "19.2.15", + "@types/react-dom": "19.2.3", "@types/swagger-jsdoc": "^6.0.4", "@types/webpack": "^5.28.5", "@typescript-eslint/eslint-plugin": "^8.34.0", @@ -145,11 +145,11 @@ "dotenv": "^16.5.0", "dotenv-cli": "^8.0.0", "eslint": "^9", - "eslint-config-next": "15.5.18", + "eslint-config-next": "16.2.6", "eslint-config-prettier": "^10.1.5", "eslint-import-resolver-typescript": "^4.4.3", "eslint-plugin-import": "^2.31.0", - "eslint-plugin-react-hooks": "5.2.0", + "eslint-plugin-react-hooks": "^7.1.1", "husky": "^9.1.7", "lightningcss": "^1.30.1", "lint-staged": "^16.0.0", @@ -173,7 +173,13 @@ "sharp": "^0.33.5" }, "overrides": { - "@types/react": "19.1.8", - "@types/react-dom": "19.1.6" + "@types/react": "19.2.15", + "@types/react-dom": "19.2.3" + }, + "pnpm": { + "overrides": { + "@types/react": "19.2.15", + "@types/react-dom": "19.2.3" + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e5e31beb..4f77ee367 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,76 +16,76 @@ importers: version: 3.896.0 '@clerk/backend': specifier: 2.33.3 - version: 2.33.3(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 2.33.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@clerk/nextjs': specifier: 6.39.3 - version: 6.39.3(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 6.39.3(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@clerk/themes': specifier: ^2.2.48 - version: 2.2.52(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 2.2.52(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@dnd-kit/core': specifier: ^6.3.1 - version: 6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@dnd-kit/sortable': specifier: ^10.0.0 - version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4) + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6) '@dnd-kit/utilities': specifier: ^3.2.2 - version: 3.2.2(react@19.1.4) + version: 3.2.2(react@19.2.6) '@hookform/resolvers': specifier: ^5.0.1 - version: 5.1.1(react-hook-form@7.58.1(react@19.1.4)) + version: 5.1.1(react-hook-form@7.58.1(react@19.2.6)) '@next/third-parties': - specifier: 15.5.18 - version: 15.5.18(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4) + specifier: 16.2.6 + version: 16.2.6(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6) '@prisma/adapter-pg': specifier: 7.8.0 version: 7.8.0 '@prisma/client': specifier: 7.8.0 - version: 7.8.0(prisma@7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3))(typescript@5.8.3) + version: 7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3))(typescript@5.8.3) '@radix-ui/react-alert-dialog': specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-dialog': specifier: ^1.1.14 - version: 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-popover': specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-slot': specifier: ^1.2.3 - version: 1.2.3(@types/react@19.1.8)(react@19.1.4) + version: 1.2.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-switch': specifier: ^1.2.6 - version: 1.2.6(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-tooltip': specifier: ^1.2.7 - version: 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@scalar/api-reference-react': specifier: ^0.7.30 - version: 0.7.30(axios@1.16.0)(react@19.1.4)(tailwindcss@4.1.11)(typescript@5.8.3) + version: 0.7.30(axios@1.16.0)(react@19.2.6)(tailwindcss@4.1.11)(typescript@5.8.3) '@sendgrid/mail': specifier: ^8.1.5 version: 8.1.5 '@sentry/nextjs': specifier: ^10.53.1 - version: 10.53.1(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(webpack@5.100.2) + version: 10.53.1(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(webpack@5.100.2) '@tailwindcss/typography': specifier: ^0.5.16 version: 0.5.16(tailwindcss@4.1.11) '@tanstack/react-query': specifier: 5.100.10 - version: 5.100.10(react@19.1.4) + version: 5.100.10(react@19.2.6) '@trpc/client': specifier: 11.17.0 version: 11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3) '@trpc/next': specifier: 11.17.0 - version: 11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3) + version: 11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3) '@trpc/react-query': specifier: 11.17.0 - version: 11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3) + version: 11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3) '@trpc/server': specifier: 11.17.0 version: 11.17.0(typescript@5.8.3) @@ -94,10 +94,10 @@ importers: version: 15.5.13 '@vercel/analytics': specifier: ^1.5.0 - version: 1.5.0(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3)) + version: 1.5.0(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3)) '@vercel/speed-insights': specifier: ^1.2.0 - version: 1.2.0(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3)) + version: 1.2.0(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3)) axios: specifier: 1.16.0 version: 1.16.0 @@ -109,7 +109,7 @@ importers: version: 2.1.1 cmdk: specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) critters: specifier: ^0.0.23 version: 0.0.23 @@ -121,7 +121,7 @@ importers: version: 3.4.2 framer-motion: specifier: ^12.11.0 - version: 12.19.2(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 12.19.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) franc: specifier: ^6.2.0 version: 6.2.0 @@ -142,7 +142,7 @@ importers: version: 11.1.0 lucide-react: specifier: ^0.511.0 - version: 0.511.0(react@19.1.4) + version: 0.511.0(react@19.2.6) mailersend: specifier: ^2.6.0 version: 2.6.0 @@ -150,11 +150,11 @@ importers: specifier: 14.1.1 version: 14.1.1 next: - specifier: 15.5.18 - version: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + specifier: 16.2.6 + version: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) next-themes: specifier: ^0.4.6 - version: 0.4.6(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) pg: specifier: 8.21.0 version: 8.21.0 @@ -165,29 +165,29 @@ importers: specifier: 8.5.14 version: 8.5.14 react: - specifier: 19.1.4 - version: 19.1.4 + specifier: 19.2.6 + version: 19.2.6 react-dom: - specifier: 19.1.4 - version: 19.1.4(react@19.1.4) + specifier: 19.2.6 + version: 19.2.6(react@19.2.6) react-error-boundary: specifier: ^6.0.0 - version: 6.0.0(react@19.1.4) + version: 6.0.0(react@19.2.6) react-google-recaptcha-v3: specifier: ^1.11.0 - version: 1.11.0(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 1.11.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react-hook-form: specifier: ^7.56.4 - version: 7.58.1(react@19.1.4) + version: 7.58.1(react@19.2.6) react-syntax-highlighter: specifier: ^15.6.6 - version: 15.6.6(react@19.1.4) + version: 15.6.6(react@19.2.6) remeda: specifier: ^2.22.1 version: 2.23.1 sonner: specifier: ^2.0.3 - version: 2.0.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 2.0.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6) superjson: specifier: ^2.2.2 version: 2.2.2 @@ -206,10 +206,10 @@ importers: devDependencies: '@clerk/testing': specifier: ^1.8.0 - version: 1.9.0(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 1.9.0(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@clerk/types': specifier: 4.101.23 - version: 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@eslint/eslintrc': specifier: ^3 version: 3.3.1 @@ -217,11 +217,11 @@ importers: specifier: ^9.28.0 version: 9.30.0 '@next/bundle-analyzer': - specifier: 15.5.18 - version: 15.5.18 + specifier: 16.2.6 + version: 16.2.6 '@next/eslint-plugin-next': - specifier: 15.5.18 - version: 15.5.18 + specifier: 16.2.6 + version: 16.2.6 '@playwright/test': specifier: ^1.60.0 version: 1.60.0 @@ -239,7 +239,7 @@ importers: version: 6.9.1 '@testing-library/react': specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) @@ -253,11 +253,11 @@ importers: specifier: ^20 version: 20.19.1 '@types/react': - specifier: 19.1.8 - version: 19.1.8 + specifier: 19.2.15 + version: 19.2.15 '@types/react-dom': - specifier: 19.1.6 - version: 19.1.6(@types/react@19.1.8) + specifier: 19.2.3 + version: 19.2.3(@types/react@19.2.15) '@types/swagger-jsdoc': specifier: ^6.0.4 version: 6.0.4 @@ -283,8 +283,8 @@ importers: specifier: ^9 version: 9.29.0(jiti@2.7.0) eslint-config-next: - specifier: 15.5.18 - version: 15.5.18(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) + specifier: 16.2.6 + version: 16.2.6(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) eslint-config-prettier: specifier: ^10.1.5 version: 10.1.5(eslint@9.29.0(jiti@2.7.0)) @@ -295,8 +295,8 @@ importers: specifier: ^2.31.0 version: 2.32.0(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.7.0)) eslint-plugin-react-hooks: - specifier: 5.2.0 - version: 5.2.0(eslint@9.29.0(jiti@2.7.0)) + specifier: ^7.1.1 + version: 7.1.1(eslint@9.29.0(jiti@2.7.0)) husky: specifier: ^9.1.7 version: 9.1.7 @@ -314,7 +314,7 @@ importers: version: 3.6.2 prisma: specifier: 7.8.0 - version: 7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3) + version: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3) swagger-jsdoc: specifier: ^6.2.8 version: 6.2.8(openapi-types@12.1.3) @@ -1434,10 +1434,20 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/config-array@0.20.1': resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1904,71 +1914,71 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@next/bundle-analyzer@15.5.18': - resolution: {integrity: sha512-v5/UNFwYbBlRQg/Bt+wU65XuxCxPu1AeCOI6s4s6Cludsj7FdVO9E9uzr7GIj8OykSrYtGuEQAUX0Ulje8W2yw==} + '@next/bundle-analyzer@16.2.6': + resolution: {integrity: sha512-amPkVtHCTJAdBwyhhl5+qztHk24O4JlASgrWqh15AmnYi74apfZR46NGC0u4pM6BMAU1mYld4WdzD3cRBP3dOA==} - '@next/env@15.5.18': - resolution: {integrity: sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g==} + '@next/env@16.2.6': + resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} - '@next/eslint-plugin-next@15.5.18': - resolution: {integrity: sha512-w4MYq8M26a8PNrfto0JosLf5/3ssln1rsyP96g2DkC8uFVymStM5DLSz5ElxxrPRg2XnTMnFo3kREFlhYvxhWw==} + '@next/eslint-plugin-next@16.2.6': + resolution: {integrity: sha512-Z8l6o4JWKUl755x4R+wogD86KPeU+Ckw4K+SYG4kHeOJtRenDeK+OSbGcqZpDtbwn9DsJVdir2UxmwXuinUbUw==} - '@next/swc-darwin-arm64@15.5.18': - resolution: {integrity: sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ==} + '@next/swc-darwin-arm64@16.2.6': + resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.5.18': - resolution: {integrity: sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og==} + '@next/swc-darwin-x64@16.2.6': + resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.5.18': - resolution: {integrity: sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw==} + '@next/swc-linux-arm64-gnu@16.2.6': + resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@15.5.18': - resolution: {integrity: sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw==} + '@next/swc-linux-arm64-musl@16.2.6': + resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@15.5.18': - resolution: {integrity: sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A==} + '@next/swc-linux-x64-gnu@16.2.6': + resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@15.5.18': - resolution: {integrity: sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA==} + '@next/swc-linux-x64-musl@16.2.6': + resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@15.5.18': - resolution: {integrity: sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA==} + '@next/swc-win32-arm64-msvc@16.2.6': + resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.5.18': - resolution: {integrity: sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg==} + '@next/swc-win32-x64-msvc@16.2.6': + resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@next/third-parties@15.5.18': - resolution: {integrity: sha512-Kp51CbeHgYelpkasFI1gAeAeMPfMTctLcmVIfE2MiRRfC6oYbR3+lO4S0NbNRppOZ7RkbB5P6uW/qOewxth6iA==} + '@next/third-parties@16.2.6': + resolution: {integrity: sha512-PDPIPVj1NX6Taxsl8OJteAUJ7iwR+QrokwWig68eh0cOmuNjC6MBL+ZzBjO8Bv0n/HOSqjGArZpM5KMSUxm+MQ==} peerDependencies: - next: ^13.0.0 || ^14.0.0 || ^15.0.0 + next: ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-beta.0 react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 '@nodelib/fs.scandir@2.1.5': @@ -2780,9 +2790,6 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@rushstack/eslint-patch@1.12.0': - resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==} - '@scalar/api-client@2.5.16': resolution: {integrity: sha512-pSsCm0jXqP7doctFL1l47Ctq22zaaTbSy5052hKkxKXosQfv7B77CNdfcIw+ibrt4xx+Epx2q9uc2Tolmcp/9w==} engines: {node: '>=20'} @@ -3630,10 +3637,10 @@ packages: '@types/pg@8.20.0': resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} - '@types/react-dom@19.1.6': - resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: - '@types/react': ^19.0.0 + '@types/react': ^19.2.0 '@types/react-syntax-highlighter@15.5.13': resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} @@ -3641,6 +3648,9 @@ packages: '@types/react@19.1.8': resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + '@types/swagger-jsdoc@6.0.4': resolution: {integrity: sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==} @@ -3681,6 +3691,14 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/eslint-plugin@8.59.4': + resolution: {integrity: sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.59.4 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/parser@8.35.0': resolution: {integrity: sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3695,6 +3713,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser@8.59.4': + resolution: {integrity: sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/project-service@8.35.0': resolution: {integrity: sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3707,6 +3732,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.59.4': + resolution: {integrity: sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@8.35.0': resolution: {integrity: sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3715,6 +3746,10 @@ packages: resolution: {integrity: sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.59.4': + resolution: {integrity: sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.35.0': resolution: {integrity: sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3727,6 +3762,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/tsconfig-utils@8.59.4': + resolution: {integrity: sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/type-utils@8.35.0': resolution: {integrity: sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3741,6 +3782,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/type-utils@8.59.4': + resolution: {integrity: sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/types@8.35.0': resolution: {integrity: sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3749,6 +3797,10 @@ packages: resolution: {integrity: sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.59.4': + resolution: {integrity: sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.35.0': resolution: {integrity: sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3761,6 +3813,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/typescript-estree@8.59.4': + resolution: {integrity: sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.35.0': resolution: {integrity: sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3775,6 +3833,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.59.4': + resolution: {integrity: sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/visitor-keys@8.35.0': resolution: {integrity: sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3783,6 +3848,10 @@ packages: resolution: {integrity: sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.59.4': + resolution: {integrity: sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} deprecated: Potential CWE-502 - Update to 1.3.1 or higher @@ -4329,6 +4398,11 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.10.31: + resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==} + engines: {node: '>=6.0.0'} + hasBin: true + better-result@2.9.2: resolution: {integrity: sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==} @@ -4612,6 +4686,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cva@1.0.0-beta.2: resolution: {integrity: sha512-dqcOFe247I5pKxfuzqfq3seLL5iMYsTgo40Uw7+pKZAntPgFtR7Tmy59P5IVIq/XgB0NQWoIvYDt9TwHkuK8Cg==} peerDependencies: @@ -4666,6 +4743,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} @@ -4883,10 +4969,10 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-config-next@15.5.18: - resolution: {integrity: sha512-HuoJU6uUPD00eyiud78IBnT4HLhztFj2V+ild2Uon5ZUrYZKe0Olu2QRD99e9IgL4/H1eg5Onka3BsfRW2U0Xw==} + eslint-config-next@16.2.6: + resolution: {integrity: sha512-z2ELYSkyrrJ6cuunTU8vhsT/RpouPkjaSah06nVW6Rg2Hpg0Vs8s497/e5s8G8qtdp4ccsiovz5P1rv+5VSW2Q==} peerDependencies: - eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + eslint: '>=9.0.0' typescript: '>=3.3.1' peerDependenciesMeta: typescript: @@ -4979,6 +5065,12 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} engines: {node: '>=4'} @@ -5001,6 +5093,10 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.29.0: resolution: {integrity: sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5284,6 +5380,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@16.4.0: + resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -5399,6 +5499,12 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -6230,9 +6336,9 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@15.5.18: - resolution: {integrity: sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ==} - engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + next@16.2.6: + resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} + engines: {node: '>=20.9.0'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -6617,10 +6723,10 @@ packages: rc9@3.0.1: resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} - react-dom@19.1.4: - resolution: {integrity: sha512-s2868ab/xo2SI6H4106A7aFI8Mrqa4xC6HZT/pBzYyQ3cBLqa88hu47xYD8xf+uECleN698Awn7RCWlkTiKnqQ==} + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: - react: ^19.1.4 + react: ^19.2.6 react-error-boundary@6.0.0: resolution: {integrity: sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==} @@ -6684,8 +6790,8 @@ packages: peerDependencies: react: '>= 0.14.0' - react@19.1.4: - resolution: {integrity: sha512-DHINL3PAmPUiK1uszfbKiXqfE03eszdt5BpVSuEAHb5nfmNPwnsy7g39h2t8aXFc/Bv99GH81s+j8dobtD+jOw==} + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} readdirp@3.6.0: @@ -6840,8 +6946,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} schema-utils@4.3.2: resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} @@ -7238,6 +7344,12 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-deepmerge@7.0.3: resolution: {integrity: sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==} engines: {node: '>=14.13.1'} @@ -7315,6 +7427,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' + typescript-eslint@8.59.4: + resolution: {integrity: sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} @@ -7744,6 +7863,12 @@ packages: peerDependencies: zod: ^3.24.1 + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + zod@3.24.1: resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} @@ -9010,52 +9135,52 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@clerk/backend@2.33.3(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@clerk/backend@2.33.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@clerk/types': 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@clerk/types': 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6) standardwebhooks: 1.0.0 tslib: 2.8.1 transitivePeerDependencies: - react - react-dom - '@clerk/clerk-react@5.61.6(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@clerk/clerk-react@5.61.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 - '@clerk/nextjs@6.39.3(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@clerk/nextjs@6.39.3(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@clerk/backend': 2.33.3(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@clerk/clerk-react': 5.61.6(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@clerk/types': 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@clerk/backend': 2.33.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@clerk/clerk-react': 5.61.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@clerk/types': 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) server-only: 0.0.1 tslib: 2.8.1 - '@clerk/shared@3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@clerk/shared@3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: csstype: 3.1.3 dequal: 2.0.3 glob-to-regexp: 0.4.1 js-cookie: 3.0.5 std-env: 3.9.0 - swr: 2.3.4(react@19.1.4) + swr: 2.3.4(react@19.2.6) optionalDependencies: - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@clerk/testing@1.9.0(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@clerk/testing@1.9.0(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@clerk/backend': 2.33.3(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@clerk/types': 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + '@clerk/backend': 2.33.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@clerk/types': 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6) dotenv: 16.4.7 optionalDependencies: '@playwright/test': 1.60.0 @@ -9063,17 +9188,17 @@ snapshots: - react - react-dom - '@clerk/themes@2.2.52(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@clerk/themes@2.2.52(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@clerk/types': 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + '@clerk/types': 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - react - react-dom - '@clerk/types@4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@clerk/types@4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6) transitivePeerDependencies: - react - react-dom @@ -9204,29 +9329,29 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@dnd-kit/accessibility@3.1.1(react@19.1.4)': + '@dnd-kit/accessibility@3.1.1(react@19.2.6)': dependencies: - react: 19.1.4 + react: 19.2.6 tslib: 2.8.1 - '@dnd-kit/core@6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@dnd-kit/accessibility': 3.1.1(react@19.1.4) - '@dnd-kit/utilities': 3.2.2(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@dnd-kit/accessibility': 3.1.1(react@19.2.6) + '@dnd-kit/utilities': 3.2.2(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 - '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)': + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@dnd-kit/utilities': 3.2.2(react@19.1.4) - react: 19.1.4 + '@dnd-kit/core': 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@dnd-kit/utilities': 3.2.2(react@19.2.6) + react: 19.2.6 tslib: 2.8.1 - '@dnd-kit/utilities@3.2.2(react@19.1.4)': + '@dnd-kit/utilities@3.2.2(react@19.2.6)': dependencies: - react: 19.1.4 + react: 19.2.6 tslib: 2.8.1 '@electric-sql/pglite-socket@0.1.1(@electric-sql/pglite@0.4.1)': @@ -9335,8 +9460,15 @@ snapshots: eslint: 9.29.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@9.29.0(jiti@2.7.0))': + dependencies: + eslint: 9.29.0(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} + '@eslint/config-array@0.20.1': dependencies: '@eslint/object-schema': 2.1.6 @@ -9399,11 +9531,11 @@ snapshots: '@floating-ui/core': 1.7.5 '@floating-ui/utils': 0.2.11 - '@floating-ui/react-dom@2.1.8(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@floating-ui/react-dom@2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@floating-ui/dom': 1.7.6 - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) '@floating-ui/utils@0.2.11': {} @@ -9429,10 +9561,10 @@ snapshots: dependencies: hono: 4.12.21 - '@hookform/resolvers@5.1.1(react-hook-form@7.58.1(react@19.1.4))': + '@hookform/resolvers@5.1.1(react-hook-form@7.58.1(react@19.2.6))': dependencies: '@standard-schema/utils': 0.3.0 - react-hook-form: 7.58.1(react@19.1.4) + react-hook-form: 7.58.1(react@19.2.6) '@humanfs/core@0.19.1': {} @@ -9743,47 +9875,47 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@next/bundle-analyzer@15.5.18': + '@next/bundle-analyzer@16.2.6': dependencies: webpack-bundle-analyzer: 4.10.1 transitivePeerDependencies: - bufferutil - utf-8-validate - '@next/env@15.5.18': {} + '@next/env@16.2.6': {} - '@next/eslint-plugin-next@15.5.18': + '@next/eslint-plugin-next@16.2.6': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@15.5.18': + '@next/swc-darwin-arm64@16.2.6': optional: true - '@next/swc-darwin-x64@15.5.18': + '@next/swc-darwin-x64@16.2.6': optional: true - '@next/swc-linux-arm64-gnu@15.5.18': + '@next/swc-linux-arm64-gnu@16.2.6': optional: true - '@next/swc-linux-arm64-musl@15.5.18': + '@next/swc-linux-arm64-musl@16.2.6': optional: true - '@next/swc-linux-x64-gnu@15.5.18': + '@next/swc-linux-x64-gnu@16.2.6': optional: true - '@next/swc-linux-x64-musl@15.5.18': + '@next/swc-linux-x64-musl@16.2.6': optional: true - '@next/swc-win32-arm64-msvc@15.5.18': + '@next/swc-win32-arm64-msvc@16.2.6': optional: true - '@next/swc-win32-x64-msvc@15.5.18': + '@next/swc-win32-x64-msvc@16.2.6': optional: true - '@next/third-parties@15.5.18(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)': + '@next/third-parties@16.2.6(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)': dependencies: - next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 + next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 third-party-capital: 1.0.20 '@nodelib/fs.scandir@2.1.5': @@ -10049,11 +10181,11 @@ snapshots: '@prisma/client-runtime-utils@7.8.0': {} - '@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3))(typescript@5.8.3)': + '@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3))(typescript@5.8.3)': dependencies: '@prisma/client-runtime-utils': 7.8.0 optionalDependencies: - prisma: 7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3) + prisma: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3) typescript: 5.8.3 '@prisma/config@7.8.0': @@ -10134,13 +10266,13 @@ snapshots: env-paths: 3.0.0 proper-lockfile: 4.1.2 - '@prisma/studio-core@0.27.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@prisma/studio-core@0.27.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@types/react': 19.1.8 + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@types/react': 19.2.15 chart.js: 4.5.1 - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - '@types/react-dom' @@ -10148,336 +10280,336 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.15)(react@19.2.6)': dependencies: - react: 19.1.4 + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.15)(react@19.2.6)': dependencies: - react: 19.1.4 + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) - react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.1(@types/react@19.2.15)(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) - react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.1(@types/react@19.2.15)(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.2.15)(react@19.2.6)': dependencies: - react: 19.1.4 + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.15)(react@19.2.6)': dependencies: - react: 19.1.4 + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-popover@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-popover@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) - react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.1(@types/react@19.2.15)(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) - - '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': - dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.4) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-popper@1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) '@radix-ui/rect': 1.1.1 - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: - react: 19.1.4 + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: - react: 19.1.4 + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: - react: 19.1.4 + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: '@radix-ui/rect': 1.1.1 - react: 19.1.4 + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.4)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4) - react: 19.1.4 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) '@radix-ui/rect@1.1.1': {} @@ -10589,8 +10721,6 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@rushstack/eslint-patch@1.12.0': {} - '@scalar/api-client@2.5.16(axios@1.16.0)(tailwindcss@4.1.11)(typescript@5.8.3)': dependencies: '@headlessui/tailwindcss': 0.2.2(tailwindcss@4.1.11) @@ -10644,11 +10774,11 @@ snapshots: - typescript - universal-cookie - '@scalar/api-reference-react@0.7.30(axios@1.16.0)(react@19.1.4)(tailwindcss@4.1.11)(typescript@5.8.3)': + '@scalar/api-reference-react@0.7.30(axios@1.16.0)(react@19.2.6)(tailwindcss@4.1.11)(typescript@5.8.3)': dependencies: '@scalar/api-reference': 1.32.6(axios@1.16.0)(tailwindcss@4.1.11)(typescript@5.8.3) '@scalar/types': 0.2.8 - react: 19.1.4 + react: 19.2.6 transitivePeerDependencies: - '@vue/composition-api' - async-validator @@ -11016,7 +11146,7 @@ snapshots: '@sentry/core@10.53.1': {} - '@sentry/nextjs@10.53.1(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(webpack@5.100.2)': + '@sentry/nextjs@10.53.1(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(webpack@5.100.2)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.40.0 @@ -11026,10 +11156,10 @@ snapshots: '@sentry/core': 10.53.1 '@sentry/node': 10.53.1 '@sentry/opentelemetry': 10.53.1(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) - '@sentry/react': 10.53.1(react@19.1.4) + '@sentry/react': 10.53.1(react@19.2.6) '@sentry/vercel-edge': 10.53.1 '@sentry/webpack-plugin': 5.3.0(webpack@5.100.2) - next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) rollup: 4.60.3 stacktrace-parser: 0.1.11 transitivePeerDependencies: @@ -11096,11 +11226,11 @@ snapshots: '@opentelemetry/semantic-conventions': 1.40.0 '@sentry/core': 10.53.1 - '@sentry/react@10.53.1(react@19.1.4)': + '@sentry/react@10.53.1(react@19.2.6)': dependencies: '@sentry/browser': 10.53.1 '@sentry/core': 10.53.1 - react: 19.1.4 + react: 19.2.6 '@sentry/vercel-edge@10.53.1': dependencies: @@ -11641,10 +11771,10 @@ snapshots: '@tanstack/query-core@5.100.10': {} - '@tanstack/react-query@5.100.10(react@19.1.4)': + '@tanstack/react-query@5.100.10(react@19.2.6)': dependencies: '@tanstack/query-core': 5.100.10 - react: 19.1.4 + react: 19.2.6 '@tanstack/virtual-core@3.13.12': {} @@ -11673,15 +11803,15 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.27.6 '@testing-library/dom': 10.4.1 - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 - '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -11692,24 +11822,24 @@ snapshots: '@trpc/server': 11.17.0(typescript@5.8.3) typescript: 5.8.3 - '@trpc/next@11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3)': + '@trpc/next@11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3)': dependencies: '@trpc/client': 11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3) '@trpc/server': 11.17.0(typescript@5.8.3) - next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) typescript: 5.8.3 optionalDependencies: - '@tanstack/react-query': 5.100.10(react@19.1.4) - '@trpc/react-query': 11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3) + '@tanstack/react-query': 5.100.10(react@19.2.6) + '@trpc/react-query': 11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3) - '@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3)': + '@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3)': dependencies: - '@tanstack/react-query': 5.100.10(react@19.1.4) + '@tanstack/react-query': 5.100.10(react@19.2.6) '@trpc/client': 11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3) '@trpc/server': 11.17.0(typescript@5.8.3) - react: 19.1.4 + react: 19.2.6 typescript: 5.8.3 '@trpc/server@11.17.0(typescript@5.8.3)': @@ -11843,9 +11973,9 @@ snapshots: pg-protocol: 1.14.0 pg-types: 2.2.0 - '@types/react-dom@19.1.6(@types/react@19.1.8)': + '@types/react-dom@19.2.3(@types/react@19.2.15)': dependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 '@types/react-syntax-highlighter@15.5.13': dependencies: @@ -11855,6 +11985,10 @@ snapshots: dependencies: csstype: 3.1.3 + '@types/react@19.2.15': + dependencies: + csstype: 3.2.3 + '@types/swagger-jsdoc@6.0.4': {} '@types/tedious@4.0.14': @@ -11917,6 +12051,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.59.4 + '@typescript-eslint/type-utils': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/utils': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.59.4 + eslint: 9.29.0(jiti@2.7.0) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.35.0 @@ -11941,6 +12091,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.59.4 + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.59.4 + debug: 4.4.3 + eslint: 9.29.0(jiti@2.7.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.35.0(typescript@5.8.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.8.3) @@ -11959,6 +12121,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.59.4(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.8.3) + '@typescript-eslint/types': 8.59.4 + debug: 4.4.3 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.35.0': dependencies: '@typescript-eslint/types': 8.35.0 @@ -11969,6 +12140,11 @@ snapshots: '@typescript-eslint/types': 8.39.0 '@typescript-eslint/visitor-keys': 8.39.0 + '@typescript-eslint/scope-manager@8.59.4': + dependencies: + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/tsconfig-utils@8.35.0(typescript@5.8.3)': dependencies: typescript: 5.8.3 @@ -11977,6 +12153,10 @@ snapshots: dependencies: typescript: 5.8.3 + '@typescript-eslint/tsconfig-utils@8.59.4(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + '@typescript-eslint/type-utils@8.35.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)': dependencies: '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) @@ -12000,10 +12180,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.8.3) + '@typescript-eslint/utils': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) + debug: 4.4.3 + eslint: 9.29.0(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@8.35.0': {} '@typescript-eslint/types@8.39.0': {} + '@typescript-eslint/types@8.59.4': {} + '@typescript-eslint/typescript-estree@8.35.0(typescript@5.8.3)': dependencies: '@typescript-eslint/project-service': 8.35.0(typescript@5.8.3) @@ -12036,6 +12230,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.59.4(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.59.4(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.8.3) + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/visitor-keys': 8.59.4 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.35.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.7.0)) @@ -12058,6 +12267,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.29.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.59.4 + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.8.3) + eslint: 9.29.0(jiti@2.7.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.35.0': dependencies: '@typescript-eslint/types': 8.35.0 @@ -12068,6 +12288,11 @@ snapshots: '@typescript-eslint/types': 8.39.0 eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.59.4': + dependencies: + '@typescript-eslint/types': 8.59.4 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.0': {} '@unhead/dom@1.11.20': @@ -12152,17 +12377,17 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.9.2': optional: true - '@vercel/analytics@1.5.0(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))': + '@vercel/analytics@1.5.0(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))': optionalDependencies: - next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 + next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 vue: 3.5.17(typescript@5.8.3) vue-router: 4.5.1(vue@3.5.17(typescript@5.8.3)) - '@vercel/speed-insights@1.2.0(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))': + '@vercel/speed-insights@1.2.0(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))': optionalDependencies: - next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 + next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 vue: 3.5.17(typescript@5.8.3) vue-router: 4.5.1(vue@3.5.17(typescript@5.8.3)) @@ -12619,6 +12844,8 @@ snapshots: balanced-match@4.0.4: {} + baseline-browser-mapping@2.10.31: {} + better-result@2.9.2: {} binary-extensions@2.3.0: {} @@ -12759,14 +12986,14 @@ snapshots: clsx@2.1.1: {} - cmdk@1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4): + cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -12906,6 +13133,8 @@ snapshots: csstype@3.1.3: {} + csstype@3.2.3: {} + cva@1.0.0-beta.2(typescript@5.8.3): dependencies: clsx: 2.1.1 @@ -12953,6 +13182,10 @@ snapshots: optionalDependencies: supports-color: 5.5.0 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decimal.js@10.5.0: {} decode-named-character-reference@1.2.0: @@ -13232,22 +13465,22 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@15.5.18(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3): + eslint-config-next@16.2.6(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3): dependencies: - '@next/eslint-plugin-next': 15.5.18 - '@rushstack/eslint-patch': 1.12.0 - '@typescript-eslint/eslint-plugin': 8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) - '@typescript-eslint/parser': 8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) + '@next/eslint-plugin-next': 16.2.6 eslint: 9.29.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.7.0)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.7.0)) eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.7.0)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.29.0(jiti@2.7.0)) + eslint-plugin-react-hooks: 7.1.1(eslint@9.29.0(jiti@2.7.0)) + globals: 16.4.0 + typescript-eslint: 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: + - '@typescript-eslint/parser' - eslint-import-resolver-webpack - eslint-plugin-import-x - supports-color @@ -13404,6 +13637,17 @@ snapshots: dependencies: eslint: 9.29.0(jiti@2.7.0) + eslint-plugin-react-hooks@7.1.1(eslint@9.29.0(jiti@2.7.0)): + dependencies: + '@babel/core': 7.27.7 + '@babel/parser': 7.27.7 + eslint: 9.29.0(jiti@2.7.0) + hermes-parser: 0.25.1 + zod: 3.25.67 + zod-validation-error: 4.0.2(zod@3.25.67) + transitivePeerDependencies: + - supports-color + eslint-plugin-react@7.37.5(eslint@9.29.0(jiti@2.7.0)): dependencies: array-includes: 3.1.9 @@ -13440,6 +13684,8 @@ snapshots: eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} + eslint@9.29.0(jiti@2.7.0): dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.7.0)) @@ -13620,14 +13866,14 @@ snapshots: forwarded-parse@2.1.2: {} - framer-motion@12.19.2(react-dom@19.1.4(react@19.1.4))(react@19.1.4): + framer-motion@12.19.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: motion-dom: 12.19.0 motion-utils: 12.19.0 tslib: 2.8.1 optionalDependencies: - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) franc-min@6.2.0: dependencies: @@ -13746,6 +13992,8 @@ snapshots: globals@14.0.0: {} + globals@16.4.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -13933,6 +14181,12 @@ snapshots: property-information: 7.1.0 space-separated-tokens: 2.0.2 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + highlight.js@10.7.3: {} highlight.js@11.11.1: {} @@ -14455,9 +14709,9 @@ snapshots: lru.min@1.1.4: {} - lucide-react@0.511.0(react@19.1.4): + lucide-react@0.511.0(react@19.2.6): dependencies: - react: 19.1.4 + react: 19.2.6 lz-string@1.5.0: {} @@ -14891,29 +15145,30 @@ snapshots: neo-async@2.6.2: {} - next-themes@0.4.6(react-dom@19.1.4(react@19.1.4))(react@19.1.4): + next-themes@0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4): + next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@next/env': 15.5.18 + '@next/env': 16.2.6 '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.31 caniuse-lite: 1.0.30001726 postcss: 8.4.31 - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) - styled-jsx: 5.1.6(@babel/core@7.27.7)(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + styled-jsx: 5.1.6(@babel/core@7.27.7)(react@19.2.6) optionalDependencies: - '@next/swc-darwin-arm64': 15.5.18 - '@next/swc-darwin-x64': 15.5.18 - '@next/swc-linux-arm64-gnu': 15.5.18 - '@next/swc-linux-arm64-musl': 15.5.18 - '@next/swc-linux-x64-gnu': 15.5.18 - '@next/swc-linux-x64-musl': 15.5.18 - '@next/swc-win32-arm64-msvc': 15.5.18 - '@next/swc-win32-x64-msvc': 15.5.18 + '@next/swc-darwin-arm64': 16.2.6 + '@next/swc-darwin-x64': 16.2.6 + '@next/swc-linux-arm64-gnu': 16.2.6 + '@next/swc-linux-arm64-musl': 16.2.6 + '@next/swc-linux-x64-gnu': 16.2.6 + '@next/swc-linux-x64-musl': 16.2.6 + '@next/swc-win32-arm64-msvc': 16.2.6 + '@next/swc-win32-x64-msvc': 16.2.6 '@opentelemetry/api': 1.9.1 '@playwright/test': 1.60.0 sharp: 0.34.5 @@ -15197,12 +15452,12 @@ snapshots: dependencies: parse-ms: 3.0.0 - prisma@7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3): + prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3): dependencies: '@prisma/config': 7.8.0 '@prisma/dev': 0.24.3(typescript@5.8.3) '@prisma/engines': 7.8.0 - '@prisma/studio-core': 0.27.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4) + '@prisma/studio-core': 0.27.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) mysql2: 3.15.3 postgres: 3.4.7 optionalDependencies: @@ -15286,25 +15541,25 @@ snapshots: defu: 6.1.7 destr: 2.0.5 - react-dom@19.1.4(react@19.1.4): + react-dom@19.2.6(react@19.2.6): dependencies: - react: 19.1.4 - scheduler: 0.26.0 + react: 19.2.6 + scheduler: 0.27.0 - react-error-boundary@6.0.0(react@19.1.4): + react-error-boundary@6.0.0(react@19.2.6): dependencies: '@babel/runtime': 7.27.6 - react: 19.1.4 + react: 19.2.6 - react-google-recaptcha-v3@1.11.0(react-dom@19.1.4(react@19.1.4))(react@19.1.4): + react-google-recaptcha-v3@1.11.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: hoist-non-react-statics: 3.3.2 - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - react-hook-form@7.58.1(react@19.1.4): + react-hook-form@7.58.1(react@19.2.6): dependencies: - react: 19.1.4 + react: 19.2.6 react-is@16.13.1: {} @@ -15312,44 +15567,44 @@ snapshots: react-refresh@0.17.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.4): + react-remove-scroll-bar@2.3.8(@types/react@19.2.15)(react@19.2.6): dependencies: - react: 19.1.4 - react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.4) + react: 19.2.6 + react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.4): + react-remove-scroll@2.7.1(@types/react@19.2.15)(react@19.2.6): dependencies: - react: 19.1.4 - react-remove-scroll-bar: 2.3.8(@types/react@19.1.8)(react@19.1.4) - react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.4) + react: 19.2.6 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.15)(react@19.2.6) + react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.4) - use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.4) + use-callback-ref: 1.3.3(@types/react@19.2.15)(react@19.2.6) + use-sidecar: 1.1.3(@types/react@19.2.15)(react@19.2.6) optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.4): + react-style-singleton@2.2.3(@types/react@19.2.15)(react@19.2.6): dependencies: get-nonce: 1.0.1 - react: 19.1.4 + react: 19.2.6 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - react-syntax-highlighter@15.6.6(react@19.1.4): + react-syntax-highlighter@15.6.6(react@19.2.6): dependencies: '@babel/runtime': 7.27.6 highlight.js: 10.7.3 highlightjs-vue: 1.0.0 lowlight: 1.20.0 prismjs: 1.30.0 - react: 19.1.4 + react: 19.2.6 refractor: 3.6.0 - react@19.1.4: {} + react@19.2.6: {} readdirp@3.6.0: dependencies: @@ -15588,7 +15843,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.26.0: {} + scheduler@0.27.0: {} schema-utils@4.3.2: dependencies: @@ -15762,10 +16017,10 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 - sonner@2.0.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4): + sonner@2.0.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - react: 19.1.4 - react-dom: 19.1.4(react@19.1.4) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) source-map-js@1.2.1: {} @@ -15895,10 +16150,10 @@ snapshots: style-mod@4.1.2: {} - styled-jsx@5.1.6(@babel/core@7.27.7)(react@19.1.4): + styled-jsx@5.1.6(@babel/core@7.27.7)(react@19.2.6): dependencies: client-only: 0.0.1 - react: 19.1.4 + react: 19.2.6 optionalDependencies: '@babel/core': 7.27.7 @@ -15968,11 +16223,11 @@ snapshots: transitivePeerDependencies: - openapi-types - swr@2.3.4(react@19.1.4): + swr@2.3.4(react@19.2.6): dependencies: dequal: 2.0.3 - react: 19.1.4 - use-sync-external-store: 1.6.0(react@19.1.4) + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) symbol-tree@3.2.4: {} @@ -16065,6 +16320,10 @@ snapshots: dependencies: typescript: 5.8.3 + ts-api-utils@2.5.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + ts-deepmerge@7.0.3: {} ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3): @@ -16158,6 +16417,17 @@ snapshots: transitivePeerDependencies: - supports-color + typescript-eslint@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/parser': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.8.3) + '@typescript-eslint/utils': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.7.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + typescript@5.8.3: {} uc.micro@2.1.0: {} @@ -16270,24 +16540,24 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.4): + use-callback-ref@1.3.3(@types/react@19.2.15)(react@19.2.6): dependencies: - react: 19.1.4 + react: 19.2.6 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.4): + use-sidecar@1.1.3(@types/react@19.2.15)(react@19.2.6): dependencies: detect-node-es: 1.1.0 - react: 19.1.4 + react: 19.2.6 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.15 - use-sync-external-store@1.6.0(react@19.1.4): + use-sync-external-store@1.6.0(react@19.2.6): dependencies: - react: 19.1.4 + react: 19.2.6 util-deprecate@1.0.2: {} @@ -16587,6 +16857,10 @@ snapshots: dependencies: zod: 3.25.67 + zod-validation-error@4.0.2(zod@3.25.67): + dependencies: + zod: 3.25.67 + zod@3.24.1: {} zod@3.25.67: {} diff --git a/public/sw-register.js b/public/sw-register.js index 0b73bc814..4b91c508c 100644 --- a/public/sw-register.js +++ b/public/sw-register.js @@ -1,13 +1,10 @@ const CACHE_NAME = 'emuready_v0.13.1' -// Service worker registration script if ('serviceWorker' in navigator) { - // Clean up outdated cache versions on load window.addEventListener('load', function () { if ('caches' in window) { caches.keys().then(function (names) { for (let name of names) { - // Remove cache versions prior if (name.startsWith('emuready') && name !== CACHE_NAME) { caches .delete(name) @@ -30,12 +27,10 @@ if ('serviceWorker' in navigator) { window.location.hostname === '127.0.0.1' || window.location.hostname.includes('.local') || (window.location.protocol === 'http:' && !window.location.hostname.includes('emuready')) + const isServiceWorkerEnabled = window.__EMUREADY_SW_ENABLED__ === true - // Disable SW in development-like environments (including dev.emuready.com behind tunnels) - if (isDevelopment) { - console.log( - 'Service Worker disabled in local development mode; unregistering any existing SW and clearing caches', - ) + if (isDevelopment || !isServiceWorkerEnabled) { + console.log('Service Worker disabled; unregistering any existing SW and clearing caches') // Proactively unregister any active service workers for this origin if (navigator.serviceWorker.getRegistrations) { navigator.serviceWorker @@ -43,7 +38,6 @@ if ('serviceWorker' in navigator) { .then((regs) => Promise.all(regs.map((r) => r.unregister()))) .catch((err) => console.warn('SW unregister failed', err)) } - // Clear all caches to avoid stale assets/pages in dev if ('caches' in window) { caches .keys() @@ -54,7 +48,6 @@ if ('serviceWorker' in navigator) { window.addEventListener('load', async function () { const swUrl = '/service-worker.js' - // Unregister any previous service workers that might be controlling the page if (navigator.serviceWorker.getRegistrations) { const registrations = await navigator.serviceWorker.getRegistrations() const currentSwUrl = new URL(swUrl, window.location.href).href diff --git a/src/app/admin/AdminLayoutClient.tsx b/src/app/admin/AdminLayoutClient.tsx new file mode 100644 index 000000000..72b044b2e --- /dev/null +++ b/src/app/admin/AdminLayoutClient.tsx @@ -0,0 +1,236 @@ +'use client' + +import { RedirectToSignIn, SignedIn, SignedOut, useUser } from '@clerk/nextjs' +import { ChevronLeft, ChevronRight, Home } from 'lucide-react' +import Link from 'next/link' +import { usePathname, useRouter } from 'next/navigation' +import { useEffect, useState, type PropsWithChildren } from 'react' +import { isNumber } from 'remeda' +import { ADMIN_ROUTES } from '@/app/admin/config/routes' +import { LoadingSpinner } from '@/components/ui/LoadingSpinner' +import { api } from '@/lib/api' +import { cn } from '@/lib/utils' +import { hasPermission, PERMISSIONS } from '@/utils/permission-system' +import { hasRolePermission } from '@/utils/permissions' +import { Role } from '@orm' +import AdminNavbar from './components/AdminNavbar' +import { + adminNavItems, + superAdminNavItems, + moderatorNavItems, + getDeveloperNavItems, + type AdminNavItem, +} from './data' + +export default function AdminLayoutClient(props: PropsWithChildren) { + const pathname = usePathname() + const router = useRouter() + const [isCollapsed, setIsCollapsed] = useState(false) + const { user, isLoaded } = useUser() + + const userQuery = api.users.me.useQuery(undefined, { + enabled: !!user, + }) + + const verifiedEmulatorsQuery = api.verifiedDevelopers.getMyVerifiedEmulators.useQuery(undefined, { + enabled: !!userQuery.data && userQuery.data.role === Role.DEVELOPER, + }) + + const gameStatsQuery = api.games.stats.useQuery(undefined, { + enabled: + !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS), + refetchInterval: 30000, + staleTime: 10000, + refetchOnMount: true, + refetchOnWindowFocus: true, + }) + + const listingStatsQuery = api.listings.stats.useQuery(undefined, { + enabled: + !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS), + refetchInterval: 30000, + staleTime: 10000, + refetchOnMount: true, + refetchOnWindowFocus: true, + }) + + const pcListingStatsQuery = api.pcListings.stats.useQuery(undefined, { + enabled: + !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS), + refetchInterval: 30000, + staleTime: 10000, + refetchOnMount: true, + refetchOnWindowFocus: true, + }) + + const isSuperAdmin = hasRolePermission(userQuery.data?.role, Role.SUPER_ADMIN) + + const reportsStatsQuery = api.listingReports.stats.useQuery(undefined, { + enabled: !!userQuery.data && isSuperAdmin, + refetchInterval: 30000, + staleTime: 10000, + refetchOnMount: true, + refetchOnWindowFocus: true, + }) + const isAdmin = hasRolePermission(userQuery.data?.role, Role.ADMIN) + const isModerator = hasRolePermission(userQuery.data?.role, Role.MODERATOR) + const isDeveloper = userQuery.data?.role === Role.DEVELOPER + + const devPendingListingsQuery = api.listings.getPending.useQuery( + { page: 1, limit: 1 }, + { enabled: isDeveloper }, + ) + + const devPendingPcListingsQuery = api.pcListings.pending.useQuery( + { page: 1, limit: 1 }, + { enabled: isDeveloper }, + ) + + const hasAdminPanelAccess = hasPermission( + userQuery.data?.permissions, + PERMISSIONS.ACCESS_ADMIN_PANEL, + ) + + useEffect(() => { + if (!isLoaded || userQuery.isPending || !user) return + + if (userQuery.data && !hasAdminPanelAccess) router.replace('/') + }, [isLoaded, user, userQuery.data, hasAdminPanelAccess, router, userQuery.isPending]) + + if (!isLoaded) return + + if (!user) { + return ( + + + + ) + } + + if (userQuery.isPending) return + + if (!userQuery.data) return null + + if (!hasAdminPanelAccess) return null + + const adminNavItemsWithCounts = adminNavItems.map((item) => { + if (item.href === ADMIN_ROUTES.GAME_APPROVALS && gameStatsQuery.data) { + return { ...item, count: gameStatsQuery.data.pending } + } + if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && listingStatsQuery.data) { + return { ...item, count: listingStatsQuery.data.pending } + } + if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && pcListingStatsQuery.data) { + return { ...item, count: pcListingStatsQuery.data.pending } + } + return item + }) + + const superAdminNavItemsWithCounts = superAdminNavItems.map((item) => { + return item.href === ADMIN_ROUTES.REPORTS && reportsStatsQuery.data + ? { ...item, count: reportsStatsQuery.data.pending } + : item + }) + + let navItems: AdminNavItem[] = [] + let superAdminItems: AdminNavItem[] = [] + + if (isSuperAdmin) { + navItems = adminNavItemsWithCounts + superAdminItems = superAdminNavItemsWithCounts + } else if (isAdmin) { + navItems = adminNavItemsWithCounts + } else if (isModerator) { + navItems = moderatorNavItems.map((item) => { + if (item.href === ADMIN_ROUTES.GAME_APPROVALS && gameStatsQuery.data) { + return { ...item, count: gameStatsQuery.data.pending } + } + if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && listingStatsQuery.data) { + return { ...item, count: listingStatsQuery.data.pending } + } + if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && pcListingStatsQuery.data) { + return { ...item, count: pcListingStatsQuery.data.pending } + } + return item + }) + } else if (isDeveloper && verifiedEmulatorsQuery.data) { + const emulatorIds = verifiedEmulatorsQuery.data.map((e) => e.id) + navItems = getDeveloperNavItems(emulatorIds) + + const pendingHandheldCount = devPendingListingsQuery.data?.pagination.total + const pendingPcCount = devPendingPcListingsQuery.data?.pagination.total + + navItems = navItems.map((item) => { + if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && isNumber(pendingHandheldCount)) { + return { ...item, count: pendingHandheldCount } + } + if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && isNumber(pendingPcCount)) { + return { ...item, count: pendingPcCount } + } + return item + }) + } + + return ( + <> + +
+ + +
+
+
{props.children}
+
+
+
+
+ + + + + ) +} diff --git a/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx b/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx new file mode 100644 index 000000000..a131cd45a --- /dev/null +++ b/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx @@ -0,0 +1,159 @@ +'use client' + +import { ArrowLeft } from 'lucide-react' +import { useRouter } from 'next/navigation' +import { AdminErrorState } from '@/components/admin/AdminErrorState' +import { Button, PageSkeletonLoading } from '@/components/ui' +import { api } from '@/lib/api' +import { isTRPCNotFoundError } from '@/lib/trpc-client-errors' +import { hasPermission, PERMISSIONS } from '@/utils/permission-system' +import { Role } from '@orm' +import EmulatorEditForm from './components/EmulatorEditForm' +import ManageSupportedSystems from './components/ManageSupportedSystems' + +interface Props { + emulatorId: string +} + +export default function EditEmulatorClientPage(props: Props) { + const router = useRouter() + + const emulatorsQuery = api.emulators.byId.useQuery( + { id: props.emulatorId }, + { enabled: !!props.emulatorId }, + ) + const currentUser = api.users.me.useQuery() + const verifiedDeveloperQuery = api.emulators.getVerifiedDeveloper.useQuery( + { emulatorId: props.emulatorId }, + { + enabled: !!props.emulatorId && currentUser.data?.role === Role.DEVELOPER, + }, + ) + + const user = currentUser.data + const hasManagePermission = hasPermission(user?.permissions, PERMISSIONS.MANAGE_CUSTOM_FIELDS) + const isDeveloper = user?.role === Role.DEVELOPER + const isVerifiedDeveloper = Boolean(verifiedDeveloperQuery.data) + const hasAccess = Boolean(user && hasManagePermission && (!isDeveloper || isVerifiedDeveloper)) + + const systemsQuery = api.systems.get.useQuery({}, { enabled: !!user && hasAccess }) + + const isLoading = + currentUser.isPending || emulatorsQuery.isLoading || verifiedDeveloperQuery.isLoading + + if (isLoading) return + + if (!user || !hasAccess) { + return ( +
+ +

+ You do not have permission to manage this emulator. +

+
+ ) + } + + if (systemsQuery.isPending) return + + if (emulatorsQuery.error) { + const isNotFound = isTRPCNotFoundError(emulatorsQuery.error) + return ( + void emulatorsQuery.refetch()} + /> + ) + } + + if (systemsQuery.error) { + return ( + void systemsQuery.refetch()} + /> + ) + } + + if (!emulatorsQuery.data) { + return ( + void emulatorsQuery.refetch()} + /> + ) + } + + if (!systemsQuery.data) { + return ( + window.location.reload()} + /> + ) + } + + return ( +
+ + +

+ Edit Emulator:{' '} + {emulatorsQuery.data.name} +

+ +
+
+

+ Emulator Details +

+ +
+ +
+

+ Supported Systems +

+ +
+ +
+

+ Custom Fields +

+ +
+
+
+ ) +} diff --git a/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx b/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx new file mode 100644 index 000000000..a77c87dc5 --- /dev/null +++ b/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx @@ -0,0 +1,236 @@ +'use client' + +import { PlusCircle, Copy, FileJson, ShieldAlert } from 'lucide-react' +import Link from 'next/link' +import { useState } from 'react' +import UnauthenticatedPage from '@/components/auth/UnauthenticatedPage' +import { Button, LoadingSpinner } from '@/components/ui' +import { api } from '@/lib/api' +import { isTRPCNotFoundError } from '@/lib/trpc-client-errors' +import { hasPermission, PERMISSIONS } from '@/utils/permission-system' +import { hasRolePermission } from '@/utils/permissions' +import { Role } from '@orm' +import ApplyTemplatesModal from './components/ApplyTemplatesModal' +import CustomFieldFormModal from './components/CustomFieldFormModal' +import CustomFieldsDragAndDrop from './components/CustomFieldsDragAndDrop' +import CustomFieldsJsonModal from './components/CustomFieldsJsonModal' +import FeedbackCard from './components/FeedbackCard' + +interface Props { + emulatorId: string +} + +export default function EmulatorCustomFieldsClientPage(props: Props) { + const [isFormModalOpen, setIsFormModalOpen] = useState(false) + const [isApplyTemplatesModalOpen, setIsApplyTemplatesModalOpen] = useState(false) + const [isJsonModalOpen, setIsJsonModalOpen] = useState(false) + const [editingFieldId, setEditingFieldId] = useState(null) + + const userQuery = api.users.me.useQuery() + const emulatorQuery = api.emulators.byId.useQuery( + { id: props.emulatorId }, + { enabled: !!props.emulatorId }, + ) + + const customFieldDefinitionsQuery = api.customFieldDefinitions.getByEmulator.useQuery( + { emulatorId: props.emulatorId }, + { enabled: !!props.emulatorId }, + ) + + const categoriesQuery = api.customFieldCategories.getByEmulator.useQuery( + { emulatorId: props.emulatorId }, + { enabled: !!props.emulatorId }, + ) + + const user = userQuery.data + const isLoading = userQuery.isPending && !userQuery.isError + const isEmulatorLoading = emulatorQuery.isLoading + const hasManagePermission = hasPermission(user?.permissions, PERMISSIONS.MANAGE_CUSTOM_FIELDS) + const isDeveloper = user?.role === Role.DEVELOPER + const isVerifiedDeveloper = Boolean( + emulatorQuery.data?.verifiedDevelopers?.some((developer) => developer.userId === user?.id), + ) + const canAccess = Boolean(user && hasManagePermission && (!isDeveloper || isVerifiedDeveloper)) + + const backToEmulatorsAction = ( + + + + ) + + if (isLoading || isEmulatorLoading) { + return ( +
+ +
+ ) + } + + if (userQuery.isError || !user) { + return ( + + ) + } + + const canApplyTemplates = hasRolePermission(user.role, Role.SUPER_ADMIN) + + function handleOpenCreateModal() { + setEditingFieldId(null) + setIsFormModalOpen(true) + } + + function handleOpenEditModal(fieldId: string) { + setEditingFieldId(fieldId) + setIsFormModalOpen(true) + } + + function handleCloseModal() { + setIsFormModalOpen(false) + setEditingFieldId(null) + void customFieldDefinitionsQuery.refetch() + } + + function handleRefresh() { + void customFieldDefinitionsQuery.refetch() + void categoriesQuery.refetch() + } + + function handleFieldDelete() { + void customFieldDefinitionsQuery.refetch() + } + + if (customFieldDefinitionsQuery.isLoading || categoriesQuery.isLoading) { + return ( +
+ +
+ ) + } + + if (emulatorQuery.error) { + if (isTRPCNotFoundError(emulatorQuery.error)) { + return ( + + ) + } + + return ( + + ) + } + + if (customFieldDefinitionsQuery.error) { + return ( + + ) + } + + if (categoriesQuery.error) { + return ( + + ) + } + + if (!emulatorQuery.data) { + return ( + + ) + } + + if (!canAccess) { + return ( + + ) + } + + return ( +
+
+

Custom Fields for {emulatorQuery.data.name}

+
+ + {canApplyTemplates && ( + + )} + +
+
+ + + + {isFormModalOpen && ( + + )} + + {isApplyTemplatesModalOpen && ( + setIsApplyTemplatesModalOpen(false)} + onSuccess={() => customFieldDefinitionsQuery.refetch()} + /> + )} + + {isJsonModalOpen && emulatorQuery.data && customFieldDefinitionsQuery.data && ( + setIsJsonModalOpen(false)} + emulator={{ id: emulatorQuery.data.id, name: emulatorQuery.data.name }} + customFields={customFieldDefinitionsQuery.data} + /> + )} +
+ ) +} diff --git a/src/app/admin/emulators/[emulatorId]/custom-fields/page.tsx b/src/app/admin/emulators/[emulatorId]/custom-fields/page.tsx index e6b11c7c1..6caf4e739 100644 --- a/src/app/admin/emulators/[emulatorId]/custom-fields/page.tsx +++ b/src/app/admin/emulators/[emulatorId]/custom-fields/page.tsx @@ -1,213 +1,20 @@ -'use client' +import { Suspense } from 'react' +import EmulatorCustomFieldsClientPage from './EmulatorCustomFieldsClientPage' -import { PlusCircle, Copy, FileJson, ShieldAlert } from 'lucide-react' -import Link from 'next/link' -import { useParams } from 'next/navigation' -import { useState } from 'react' -import UnauthenticatedPage from '@/components/auth/UnauthenticatedPage' -import { Button, LoadingSpinner } from '@/components/ui' -import { api } from '@/lib/api' -import { hasPermission, PERMISSIONS } from '@/utils/permission-system' -import { hasRolePermission } from '@/utils/permissions' -import { Role } from '@orm' -import ApplyTemplatesModal from './components/ApplyTemplatesModal' -import CustomFieldFormModal from './components/CustomFieldFormModal' -import CustomFieldsDragAndDrop from './components/CustomFieldsDragAndDrop' -import CustomFieldsJsonModal from './components/CustomFieldsJsonModal' -import FeedbackCard from './components/FeedbackCard' - -export default function EmulatorCustomFieldsPage() { - const params = useParams() - const emulatorId = params.emulatorId as string - - const [isFormModalOpen, setIsFormModalOpen] = useState(false) - const [isApplyTemplatesModalOpen, setIsApplyTemplatesModalOpen] = useState(false) - const [isJsonModalOpen, setIsJsonModalOpen] = useState(false) - const [editingFieldId, setEditingFieldId] = useState(null) - - const userQuery = api.users.me.useQuery() - const emulatorQuery = api.emulators.byId.useQuery({ id: emulatorId }, { enabled: !!emulatorId }) - - const customFieldDefinitionsQuery = api.customFieldDefinitions.getByEmulator.useQuery( - { emulatorId }, - { enabled: !!emulatorId }, - ) - - const categoriesQuery = api.customFieldCategories.getByEmulator.useQuery( - { emulatorId }, - { enabled: !!emulatorId }, - ) - - const user = userQuery.data - const isLoading = userQuery.isPending && !userQuery.isError - const isEmulatorLoading = emulatorQuery.isLoading - const hasManagePermission = hasPermission(user?.permissions, PERMISSIONS.MANAGE_CUSTOM_FIELDS) - const isDeveloper = user?.role === Role.DEVELOPER - const isVerifiedDeveloper = Boolean( - emulatorQuery.data?.verifiedDevelopers?.some((developer) => developer.userId === user?.id), - ) - const canAccess = Boolean(user && hasManagePermission && (!isDeveloper || isVerifiedDeveloper)) - - const backToEmulatorsAction = ( - - - - ) - - if (isLoading || isEmulatorLoading) { - return ( -
- -
- ) - } - - if (userQuery.isError || !user) { - return ( - - ) - } - - if (!canAccess) { - return ( - - ) - } - - // Check if user can see Apply Templates button (only SUPER_ADMIN) - const canApplyTemplates = hasRolePermission(user.role, Role.SUPER_ADMIN) - - function handleOpenCreateModal() { - setEditingFieldId(null) - setIsFormModalOpen(true) - } - - function handleOpenEditModal(fieldId: string) { - setEditingFieldId(fieldId) - setIsFormModalOpen(true) - } - - function handleCloseModal() { - setIsFormModalOpen(false) - setEditingFieldId(null) - void customFieldDefinitionsQuery.refetch() - } - - function handleRefresh() { - void customFieldDefinitionsQuery.refetch() - void categoriesQuery.refetch() - } - - function handleFieldDelete() { - void customFieldDefinitionsQuery.refetch() - } - - if (customFieldDefinitionsQuery.isLoading || categoriesQuery.isLoading) { - return ( -
- -
- ) - } - - if (emulatorQuery.error) { - return ( - - ) - } - - if (customFieldDefinitionsQuery.error) { - return ( - - ) - } - - if (!emulatorQuery.data) { - return ( - - ) - } +interface Props { + params: Promise<{ emulatorId: string }> +} +export default function EmulatorCustomFieldsPage(props: Props) { return ( -
-
-

Custom Fields for {emulatorQuery.data.name}

-
- - {canApplyTemplates && ( - - )} - -
-
- - - - {isFormModalOpen && ( - - )} + Loading custom fields...
}> + + + ) +} - {isApplyTemplatesModalOpen && ( - setIsApplyTemplatesModalOpen(false)} - onSuccess={() => customFieldDefinitionsQuery.refetch()} - /> - )} +async function EmulatorCustomFieldsPageContent(props: Props) { + const { emulatorId } = await props.params - {isJsonModalOpen && emulatorQuery.data && customFieldDefinitionsQuery.data && ( - setIsJsonModalOpen(false)} - emulator={{ id: emulatorQuery.data.id, name: emulatorQuery.data.name }} - customFields={customFieldDefinitionsQuery.data} - /> - )} - - ) + return } diff --git a/src/app/admin/emulators/[emulatorId]/page.tsx b/src/app/admin/emulators/[emulatorId]/page.tsx index 103c4de95..33fa1de08 100644 --- a/src/app/admin/emulators/[emulatorId]/page.tsx +++ b/src/app/admin/emulators/[emulatorId]/page.tsx @@ -1,126 +1,20 @@ -'use client' +import { Suspense } from 'react' +import EditEmulatorClientPage from './EditEmulatorClientPage' -import { ArrowLeft } from 'lucide-react' -import { notFound, useParams, useRouter } from 'next/navigation' -import { Button, PageSkeletonLoading } from '@/components/ui' -import { api } from '@/lib/api' -import { hasPermission, PERMISSIONS } from '@/utils/permission-system' -import { Role } from '@orm' -import EmulatorEditForm from './components/EmulatorEditForm' -import ManageSupportedSystems from './components/ManageSupportedSystems' - -function EditEmulatorPage() { - const router = useRouter() - const params = useParams() - const emulatorId = params.emulatorId as string - - const emulatorsQuery = api.emulators.byId.useQuery({ id: emulatorId }, { enabled: !!emulatorId }) - const currentUser = api.users.me.useQuery() - const verifiedDeveloperQuery = api.emulators.getVerifiedDeveloper.useQuery( - { emulatorId }, - { - enabled: !!emulatorId && currentUser.data?.role === Role.DEVELOPER, - }, - ) - - const user = currentUser.data - const hasManagePermission = hasPermission(user?.permissions, PERMISSIONS.MANAGE_CUSTOM_FIELDS) - const isDeveloper = user?.role === Role.DEVELOPER - const isVerifiedDeveloper = Boolean(verifiedDeveloperQuery.data) - const hasAccess = Boolean(user && hasManagePermission && (!isDeveloper || isVerifiedDeveloper)) - - const systemsQuery = api.systems.get.useQuery({}, { enabled: !!user && hasAccess }) - - const isLoading = - currentUser.isPending || emulatorsQuery.isLoading || verifiedDeveloperQuery.isLoading - - if (isLoading) return - - if (!user || !hasAccess) { - return ( -
- -

- You do not have permission to manage this emulator. -

-
- ) - } - - if (systemsQuery.isPending) return - - if (emulatorsQuery.error || systemsQuery.error) { - return ( -
-

- Error loading data:{' '} - {emulatorsQuery.error?.message ?? - systemsQuery.error?.message ?? - 'An unknown error occurred'} -

-
- ) - } - - if (!emulatorsQuery.data || !systemsQuery.data) return notFound() +interface Props { + params: Promise<{ emulatorId: string }> +} +export default function EditEmulatorPage(props: Props) { return ( -
- - -

- Edit Emulator:{' '} - {emulatorsQuery.data.name} -

- -
- {/* Section for Emulator Details */} -
-

- Emulator Details -

- -
- - {/* Section for Supported Systems */} -
-

- Supported Systems -

- -
- -
-

- Custom Fields -

- -
-
-
+ Loading emulator...}> + + ) } -export default EditEmulatorPage +async function EditEmulatorPageContent(props: Props) { + const { emulatorId } = await props.params + + return +} diff --git a/src/app/admin/games/[id]/AdminGameEditClientPage.tsx b/src/app/admin/games/[id]/AdminGameEditClientPage.tsx new file mode 100644 index 000000000..23cb620b9 --- /dev/null +++ b/src/app/admin/games/[id]/AdminGameEditClientPage.tsx @@ -0,0 +1,118 @@ +'use client' + +import { ArrowLeft } from 'lucide-react' +import Link from 'next/link' +import { AdminErrorState } from '@/components/admin/AdminErrorState' +import { Button, PageSkeletonLoading } from '@/components/ui' +import { api } from '@/lib/api' +import { isTRPCNotFoundError } from '@/lib/trpc-client-errors' +import { GameEditForm } from './components/GameEditForm' +import { GameRelatedData } from './components/GameRelatedData' + +interface Props { + gameId: string +} + +export default function AdminGameEditClientPage(props: Props) { + const gameQuery = api.games.byId.useQuery({ id: props.gameId }) + + if (gameQuery.isPending) return + + if (gameQuery.error) { + const isNotFound = isTRPCNotFoundError(gameQuery.error) + return ( + void gameQuery.refetch()} + /> + ) + } + + if (!gameQuery.data) { + return ( + window.location.reload()} + /> + ) + } + + return ( +
+
+ +

+ Edit Game: {gameQuery.data.title} +

+
+ +
+
+
+

+ Game Details +

+ +
+ + +
+ +
+
+

+ Quick Actions +

+
+ + +
+
+ +
+

+ Compatible Emulators +

+ {gameQuery.data.system.emulators.length > 0 ? ( +
    + {gameQuery.data.system.emulators.map((emulator) => ( +
  • + + {emulator.name} + +
  • + ))} +
+ ) : ( +

+ No emulators support this system yet. +

+ )} +
+
+
+
+ ) +} diff --git a/src/app/admin/games/[id]/page.tsx b/src/app/admin/games/[id]/page.tsx index 29fc9685c..9186fbe45 100644 --- a/src/app/admin/games/[id]/page.tsx +++ b/src/app/admin/games/[id]/page.tsx @@ -1,93 +1,20 @@ -'use client' +import { Suspense } from 'react' +import AdminGameEditClientPage from './AdminGameEditClientPage' -import { ArrowLeft } from 'lucide-react' -import Link from 'next/link' -import { notFound, useParams } from 'next/navigation' -import { Button, PageSkeletonLoading } from '@/components/ui' -import { api } from '@/lib/api' -import { GameEditForm } from './components/GameEditForm' -import { GameRelatedData } from './components/GameRelatedData' - -function AdminGameEditPage() { - const params = useParams() - const id = params.id as string - - const gameQuery = api.games.byId.useQuery({ id }) - - if (gameQuery.isPending) return - - if (gameQuery.error || !gameQuery.data) return notFound() +interface Props { + params: Promise<{ id: string }> +} +export default function AdminGameEditPage(props: Props) { return ( -
-
- -

- Edit Game: {gameQuery.data.title} -

-
- -
-
-
-

- Game Details -

- -
- - -
- -
-
-

- Quick Actions -

-
- - -
-
- -
-

- Compatible Emulators -

- {gameQuery.data.system.emulators.length > 0 ? ( -
    - {gameQuery.data.system.emulators.map((emulator) => ( -
  • - - {emulator.name} - -
  • - ))} -
- ) : ( -

- No emulators support this system yet. -

- )} -
-
-
-
+ Loading game...}> + + ) } -export default AdminGameEditPage +async function AdminGameEditPageContent(props: Props) { + const { id } = await props.params + + return +} diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index b875748ce..46ea7bb9d 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -1,256 +1,10 @@ -'use client' - -import { RedirectToSignIn, SignedIn, SignedOut, useUser } from '@clerk/nextjs' -import { ChevronLeft, ChevronRight, Home } from 'lucide-react' -import Link from 'next/link' -import { usePathname, useRouter } from 'next/navigation' -import { useEffect, useState, type PropsWithChildren } from 'react' -import { isNumber } from 'remeda' -import { ADMIN_ROUTES } from '@/app/admin/config/routes' -import { LoadingSpinner } from '@/components/ui/LoadingSpinner' -import { api } from '@/lib/api' -import { cn } from '@/lib/utils' -import { hasPermission, PERMISSIONS } from '@/utils/permission-system' -import { hasRolePermission as hasRolePermission } from '@/utils/permissions' -import { Role } from '@orm' -import AdminNavbar from './components/AdminNavbar' -import { - adminNavItems, - superAdminNavItems, - moderatorNavItems, - getDeveloperNavItems, - type AdminNavItem, -} from './data' - -function AdminLayout(props: PropsWithChildren) { - const pathname = usePathname() - const router = useRouter() - const [isCollapsed, setIsCollapsed] = useState(false) - const { user, isLoaded } = useUser() - - const userQuery = api.users.me.useQuery(undefined, { - enabled: !!user, - }) - - // For developers, fetch their verified emulators - const verifiedEmulatorsQuery = api.verifiedDevelopers.getMyVerifiedEmulators.useQuery(undefined, { - enabled: !!userQuery.data && userQuery.data.role === Role.DEVELOPER, - }) - - // Get pending games count for admin navigation - const gameStatsQuery = api.games.stats.useQuery(undefined, { - enabled: - !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS), - refetchInterval: 30000, // Refetch every 30 seconds (to keep counts updated) - staleTime: 10000, // Consider data stale after 10 seconds - refetchOnMount: true, - refetchOnWindowFocus: true, - }) - - // Get pending listings count for admin navigation - const listingStatsQuery = api.listings.stats.useQuery(undefined, { - enabled: - !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS), - refetchInterval: 30000, // Refetch every 30 seconds (to keep counts updated) - staleTime: 10000, // Consider data stale after 10 seconds - refetchOnMount: true, - refetchOnWindowFocus: true, - }) - - // Get pending PC listings count for admin navigation - const pcListingStatsQuery = api.pcListings.stats.useQuery(undefined, { - enabled: - !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS), - refetchInterval: 30000, // Refetch every 30 seconds (to keep counts updated) - staleTime: 10000, // Consider data stale after 10 seconds - refetchOnMount: true, - refetchOnWindowFocus: true, - }) - - const isSuperAdmin = hasRolePermission(userQuery.data?.role, Role.SUPER_ADMIN) - - // Get pending reports count for admin navigation - const reportsStatsQuery = api.listingReports.stats.useQuery(undefined, { - enabled: !!userQuery.data && isSuperAdmin, - refetchInterval: 30000, // Refetch every 30 seconds (to keep counts updated) - staleTime: 10000, // Consider data stale after 10 seconds - refetchOnMount: true, - refetchOnWindowFocus: true, - }) - const isAdmin = hasRolePermission(userQuery.data?.role, Role.ADMIN) - const isModerator = hasRolePermission(userQuery.data?.role, Role.MODERATOR) - const isDeveloper = userQuery.data?.role === Role.DEVELOPER // Exact match, not hierarchical - - const devPendingListingsQuery = api.listings.getPending.useQuery( - { page: 1, limit: 1 }, - { enabled: isDeveloper }, - ) - - const devPendingPcListingsQuery = api.pcListings.pending.useQuery( - { page: 1, limit: 1 }, - { enabled: isDeveloper }, - ) - - // New permission-based checks - const hasAdminPanelAccess = hasPermission( - userQuery.data?.permissions, - PERMISSIONS.ACCESS_ADMIN_PANEL, - ) - - useEffect(() => { - if (!isLoaded || userQuery.isPending || !user) return - - // Check if a user has the right permissions once data is loaded - // Use permission-based access control - if (userQuery.data && !hasAdminPanelAccess) router.replace('/') - }, [isLoaded, user, userQuery.data, hasAdminPanelAccess, router, userQuery.isPending]) - - if (!isLoaded) return - - if (!user) { - return ( - - - - ) - } - - if (userQuery.isPending) return - - if (!userQuery.data) return null - - // Don't render admin content if user doesn't have admin panel access - if (!hasAdminPanelAccess) return null - - // Create navigation items with counts - const adminNavItemsWithCounts = adminNavItems.map((item) => { - if (item.href === ADMIN_ROUTES.GAME_APPROVALS && gameStatsQuery.data) { - return { ...item, count: gameStatsQuery.data.pending } - } - if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && listingStatsQuery.data) { - return { ...item, count: listingStatsQuery.data.pending } - } - if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && pcListingStatsQuery.data) { - return { ...item, count: pcListingStatsQuery.data.pending } - } - return item - }) - - // Create super admin navigation items with counts - const superAdminNavItemsWithCounts = superAdminNavItems.map((item) => { - return item.href === ADMIN_ROUTES.REPORTS && reportsStatsQuery.data - ? { ...item, count: reportsStatsQuery.data.pending } - : item - }) - - // Select appropriate nav items based on a user role - let navItems: AdminNavItem[] = [] - let superAdminItems: AdminNavItem[] = [] - - if (isSuperAdmin) { - navItems = adminNavItemsWithCounts - superAdminItems = superAdminNavItemsWithCounts - } else if (isAdmin) { - navItems = adminNavItemsWithCounts - } else if (isModerator) { - navItems = moderatorNavItems.map((item) => { - if (item.href === ADMIN_ROUTES.GAME_APPROVALS && gameStatsQuery.data) { - return { ...item, count: gameStatsQuery.data.pending } - } - if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && listingStatsQuery.data) { - return { ...item, count: listingStatsQuery.data.pending } - } - if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && pcListingStatsQuery.data) { - return { ...item, count: pcListingStatsQuery.data.pending } - } - return item - }) - } else if (isDeveloper && verifiedEmulatorsQuery.data) { - const emulatorIds = verifiedEmulatorsQuery.data.map((e) => e.id) - navItems = getDeveloperNavItems(emulatorIds) - - // Add counts to developer approvals links (handheld + PC) - const pendingHandheldCount = devPendingListingsQuery.data?.pagination.total - const pendingPcCount = devPendingPcListingsQuery.data?.pagination.total - - navItems = navItems.map((item) => { - if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && isNumber(pendingHandheldCount)) { - return { ...item, count: pendingHandheldCount } - } - if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && isNumber(pendingPcCount)) { - return { ...item, count: pendingPcCount } - } - return item - }) - } +import { Suspense, type PropsWithChildren } from 'react' +import AdminLayoutClient from './AdminLayoutClient' +export default function AdminLayout(props: PropsWithChildren) { return ( - <> - -
- {/* Sidebar */} - - - {/* Main Content Area */} -
-
-
{props.children}
-
-
-
-
- - - - + + {props.children} + ) } - -export default AdminLayout diff --git a/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx b/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx new file mode 100644 index 000000000..263054801 --- /dev/null +++ b/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx @@ -0,0 +1,64 @@ +'use client' + +import { AdminErrorState } from '@/components/admin/AdminErrorState' +import { LoadingSpinner } from '@/components/ui' +import { api } from '@/lib/api' +import { isTRPCNotFoundError } from '@/lib/trpc-client-errors' +import ListingEditForm from './components/ListingEditForm' + +interface Props { + listingId: string +} + +export default function EditListingClientPage(props: Props) { + const listingQuery = api.listings.getForEdit.useQuery( + { id: props.listingId }, + { enabled: !!props.listingId }, + ) + + if (listingQuery.isPending) { + return ( +
+ +
+ ) + } + + if (listingQuery.error) { + const isNotFound = isTRPCNotFoundError(listingQuery.error) + return ( + void listingQuery.refetch()} + /> + ) + } + + if (!listingQuery.data) { + return ( +
+
+

Handheld report not found

+
+
+ ) + } + + return ( +
+
+

Edit Handheld Report

+

+ Modify the handheld compatibility report for {listingQuery.data.game.title} +

+
+ + +
+ ) +} diff --git a/src/app/admin/listings/[id]/edit/page.tsx b/src/app/admin/listings/[id]/edit/page.tsx index a32941f8c..7f1f238e1 100644 --- a/src/app/admin/listings/[id]/edit/page.tsx +++ b/src/app/admin/listings/[id]/edit/page.tsx @@ -1,58 +1,20 @@ -'use client' +import { Suspense } from 'react' +import EditListingClientPage from './EditListingClientPage' -import { useParams } from 'next/navigation' -import { LoadingSpinner } from '@/components/ui' -import { api } from '@/lib/api' -import ListingEditForm from './components/ListingEditForm' - -function EditListingPage() { - const params = useParams() - const listingId = params.id as string - - const listingQuery = api.listings.getForEdit.useQuery({ id: listingId }, { enabled: !!listingId }) - - if (listingQuery.isPending) { - return ( -
- -
- ) - } - - if (listingQuery.error) { - return ( -
-
-

- Error loading listing: {listingQuery.error.message} -

-
-
- ) - } - - if (!listingQuery.data) { - return ( -
-
-

Listing not found

-
-
- ) - } +interface Props { + params: Promise<{ id: string }> +} +export default function EditListingPage(props: Props) { return ( -
-
-

Edit Listing

-

- Modify the performance listing for {listingQuery.data.game.title} -

-
- - -
+ Loading listing...}> + + ) } -export default EditListingPage +async function EditListingPageContent(props: Props) { + const { id } = await props.params + + return +} diff --git a/src/app/admin/monitoring/components/MonitoringOverview.tsx b/src/app/admin/monitoring/components/MonitoringOverview.tsx deleted file mode 100644 index 0bfe643e7..000000000 --- a/src/app/admin/monitoring/components/MonitoringOverview.tsx +++ /dev/null @@ -1,73 +0,0 @@ -'use client' - -import { Activity, Database, Gauge, TrendingUp } from 'lucide-react' -import { useEffect, useState } from 'react' -import { Card } from '@/components/ui' -import { seoMetrics, exportMetrics } from '@/lib/monitoring/seo-metrics' - -export function MonitoringOverview() { - const [metrics, setMetrics] = useState({ - cacheHitRate: 0, - avgResponseTime: 0, - bundleSize: 0, - performanceScore: 0, - }) - - useEffect(() => { - const updateMetrics = () => { - const seoData = seoMetrics.getAggregatedMetrics() - const exportData = exportMetrics() - - setMetrics({ - cacheHitRate: seoData.cacheHitRate, - avgResponseTime: seoData.avgTotalTime, - bundleSize: exportData.cache.totalSize, - performanceScore: Math.round(100 - seoData.avgTotalTime / 10), // Simple calculation for now - }) - } - - updateMetrics() - const interval = setInterval(updateMetrics, 5000) - return () => clearInterval(interval) - }, []) - - return ( -
- -
-

Cache Hit Rate

- -
-
{metrics.cacheHitRate.toFixed(1)}%
-

Real-time data

-
- - -
-

Avg Response Time

- -
-
{metrics.avgResponseTime}ms
-

Real-time data

-
- - -
-

Cache Size

- -
-
{(metrics.bundleSize / 1024).toFixed(1)} KB
-

Total cache storage

-
- - -
-

Performance Score

- -
-
{metrics.performanceScore}/100
-

Based on response time

-
-
- ) -} diff --git a/src/app/admin/monitoring/components/SEOMetricsDashboard.tsx b/src/app/admin/monitoring/components/SEOMetricsDashboard.tsx deleted file mode 100644 index 3d0e86120..000000000 --- a/src/app/admin/monitoring/components/SEOMetricsDashboard.tsx +++ /dev/null @@ -1,247 +0,0 @@ -'use client' - -import { BarChart as BarChartIcon, Clock, Database, TrendingUp } from 'lucide-react' -import { useState, useEffect } from 'react' -import { Card, Button, LineChart, DonutChart } from '@/components/ui' -import { exportMetrics, seoMetrics } from '@/lib/monitoring/seo-metrics' -import { cn } from '@/lib/utils' -import { ms } from '@/utils/time' - -export function SEOMetricsDashboard() { - const [metrics, setMetrics] = useState(seoMetrics.getAggregatedMetrics()) - const [exportData, setExportData] = useState(exportMetrics()) - const [timeSeriesData, setTimeSeriesData] = useState<{ x: number; y: number; label: string }[]>( - [], - ) - - useEffect(() => { - const updateMetrics = () => { - const newMetrics = seoMetrics.getAggregatedMetrics() - const newExportData = exportMetrics() - - setMetrics(newMetrics) - setExportData(newExportData) - - // Update time series data for response time chart - setTimeSeriesData((prev) => { - const now = Date.now() - const newPoint = { - x: now, - y: newMetrics.avgTotalTime, - label: `${formatMs(newMetrics.avgTotalTime)} at ${new Date(now).toLocaleTimeString()}`, - } - - // Keep only last 20 points - return [...prev, newPoint].slice(-20) - }) - } - - updateMetrics() - - const interval = setInterval(updateMetrics, ms.seconds(30)) - - return () => { - clearInterval(interval) - } - }, []) - - const formatMs = (ms: number) => { - if (ms < 1000) return `${ms}ms` - return `${(ms / 1000).toFixed(2)}s` - } - - const formatBytes = (bytes: number) => { - if (bytes < 1024) return `${bytes} B` - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` - return `${(bytes / (1024 * 1024)).toFixed(1)} MB` - } - - return ( - -
-
-
-

SEO Metrics

-

Real-time performance monitoring

-
- -
-
-
- {/* Performance Overview */} -
-
-
- - Average Response Time -
-

{formatMs(metrics.avgTotalTime)}

-
- Cache: {formatMs(metrics.avgCacheLookupTime)} - DB: {formatMs(metrics.avgDatabaseQueryTime)} -
-
- -
-
- - Cache Performance -
-
- -
-
- Stale: {metrics.staleServeRate.toFixed(1)}% -
-
Sample: {metrics.sampleSize}
-
-
-
-
- - {/* Response Time Trend */} - {timeSeriesData.length > 1 && ( -
-
- - Response Time Trend (Last 10 minutes) -
-
- -
-
- )} - - {/* Cache Details */} -
-
- - Cache Storage Details -
-
-
-
- Total Size - {formatBytes(exportData.cache.totalSize)} -
-
- Total Entries - {exportData.cache.totalEntries} -
-
- Average Entry Size - {formatBytes(exportData.cache.averageSize)} -
-
- Oldest Entry Age - {formatMs(exportData.cache.oldestEntry)} -
-
-
-
- - {/* Response Time Statistics */} -
-
- - Response Time Distribution -
-
-
-
-
-
Average
-
{formatMs(metrics.avgTotalTime)}
-
-
-
Median
-
{formatMs(metrics.medianResponseTime)}
-
-
-
P95
-
{formatMs(metrics.p95ResponseTime)}
-
-
-
Max
-
{formatMs(metrics.maxResponseTime)}
-
-
-
-
-
- - - -
-
-
-
-
-
- ) -} - -function PerformanceIndicator({ - label, - value, - threshold, -}: { - label: string - value: number - threshold: number -}) { - const isGood = value >= threshold - - return ( -
- {label} -
-
-
-
- {value.toFixed(0)}% -
-
- ) -} diff --git a/src/app/admin/monitoring/page.tsx b/src/app/admin/monitoring/page.tsx index 8b7bf631d..740da45fc 100644 --- a/src/app/admin/monitoring/page.tsx +++ b/src/app/admin/monitoring/page.tsx @@ -1,33 +1,22 @@ import { type Metadata } from 'next' import { AdminPageLayout } from '@/components/admin' -import { CacheMetrics } from '@/components/admin/CacheMetrics' import { BundleSizeMonitor } from './components/BundleSizeMonitor' import { DatabaseConnectionMonitor } from './components/DatabaseConnectionMonitor' -import { MonitoringOverview } from './components/MonitoringOverview' import { PerformanceMetrics } from './components/PerformanceMetrics' -import { SEOMetricsDashboard } from './components/SEOMetricsDashboard' export const metadata: Metadata = { title: 'System Monitoring - Admin Dashboard', - description: 'Monitor system performance, cache metrics, and bundle sizes', + description: 'Monitor system performance and bundle sizes', } export default function MonitoringDashboard() { return ( - - - {/* Main Dashboard Sections */} -
- - -
- diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index a3fea999b..896f99c74 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -33,7 +33,7 @@ import { getRoleVariant } from '@/utils/badge-colors' import getErrorMessage from '@/utils/getErrorMessage' import { PERMISSIONS } from '@/utils/permission-system' import { hasRolePermission } from '@/utils/permissions' -import { type Role, Role as RoleEnum } from '@orm' +import { Role } from '@orm' import UserBadgeModal from './components/UserBadgeModal' import UserDetailsModal, { type ActivityTab } from './components/UserDetailsModal' import UserRoleModal from './components/UserRoleModal' @@ -95,10 +95,10 @@ function AdminUsersPage() { const canManageBadges = currentUserQuery.data?.permissions?.includes(PERMISSIONS.MANAGE_BADGES) const canChangeRoles = currentUserQuery.data?.permissions?.includes(PERMISSIONS.CHANGE_USER_ROLES) const isModerator = - hasRolePermission(currentUserQuery.data?.role, RoleEnum.MODERATOR) && - !hasRolePermission(currentUserQuery.data?.role, RoleEnum.ADMIN) + hasRolePermission(currentUserQuery.data?.role, Role.MODERATOR) && + !hasRolePermission(currentUserQuery.data?.role, Role.ADMIN) const canBanUsers = - hasRolePermission(currentUserQuery.data?.role, RoleEnum.MODERATOR) && + hasRolePermission(currentUserQuery.data?.role, Role.MODERATOR) && currentUserQuery.data?.permissions?.includes(PERMISSIONS.MANAGE_USER_BANS) const usersQuery = api.users.get.useQuery({ diff --git a/src/app/api/account/export/route.ts b/src/app/api/account/export/route.ts index 50a9f3c73..d6500f5b3 100644 --- a/src/app/api/account/export/route.ts +++ b/src/app/api/account/export/route.ts @@ -1,8 +1,10 @@ import { auth } from '@clerk/nextjs/server' -import { NextResponse } from 'next/server' +import { connection, NextResponse } from 'next/server' import { prisma } from '@/server/db' export async function GET() { + await connection() + try { // Get authenticated user const { userId } = await auth() diff --git a/src/app/api/admin/listings/[id]/config/route.ts b/src/app/api/admin/listings/[id]/config/route.ts index c6d306f45..14b79283a 100644 --- a/src/app/api/admin/listings/[id]/config/route.ts +++ b/src/app/api/admin/listings/[id]/config/route.ts @@ -1,11 +1,13 @@ import { auth } from '@clerk/nextjs/server' -import { type NextRequest, NextResponse } from 'next/server' +import { connection, type NextRequest, NextResponse } from 'next/server' import { prisma } from '@/server/db' import { generateEmulatorConfig } from '@/server/utils/emulator-config/emulator-detector' import { roleIncludesRole } from '@/utils/permission-system' import { Role } from '@orm' export async function GET(_: NextRequest, props: { params: Promise<{ id: string }> }) { + await connection() + const { userId } = await auth() const params = await props.params diff --git a/src/app/api/admin/notifications/analytics/route.ts b/src/app/api/admin/notifications/analytics/route.ts index 23500b02c..17482b8ba 100644 --- a/src/app/api/admin/notifications/analytics/route.ts +++ b/src/app/api/admin/notifications/analytics/route.ts @@ -1,11 +1,13 @@ import { auth } from '@clerk/nextjs/server' -import { type NextRequest } from 'next/server' +import { connection, type NextRequest } from 'next/server' import { prisma } from '@/server/db' import { notificationAnalyticsService } from '@/server/notifications/analyticsService' import { hasRolePermission } from '@/utils/permissions' import { Role } from '@orm' export async function GET(request: NextRequest) { + await connection() + try { // Check if user is admin const { userId } = await auth() @@ -14,7 +16,7 @@ export async function GET(request: NextRequest) { } const user = await prisma.user.findUnique({ - where: { id: userId }, + where: { clerkId: userId }, select: { role: true }, }) diff --git a/src/app/api/cache/metrics/route.ts b/src/app/api/cache/metrics/route.ts deleted file mode 100644 index 563c1763e..000000000 --- a/src/app/api/cache/metrics/route.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { auth } from '@clerk/nextjs/server' -import { NextResponse } from 'next/server' -import { getCacheMetrics } from '@/lib/cache/seo-cache' -import { exportMetrics, seoMetrics } from '@/lib/monitoring/seo-metrics' -import { hasRolePermission } from '@/utils/permissions' -import { Role } from '@orm' - -/** - * API endpoint to get cache metrics - * Only accessible by moderators and above - */ -export async function GET() { - const session = await auth() - - if (!session?.userId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check if user has moderator permissions or higher - const { sessionClaims } = session - const metadata = sessionClaims?.metadata as { role?: Role } | undefined - const userRole = metadata?.role - - if (!userRole || !hasRolePermission(userRole, Role.MODERATOR)) { - return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) - } - - const cacheMetrics = getCacheMetrics() - const seoData = seoMetrics.getAggregatedMetrics() - const exportData = exportMetrics() - - // Combine all metrics - const combinedMetrics = { - // Cache metrics from seo-cache - ...cacheMetrics, - // Add seo-metrics data - entries: exportData.cache.entries.length, - size: exportData.cache.totalSize, - hitRate: seoData.cacheHitRate, - missRate: 100 - seoData.cacheHitRate, - avgResponseTime: seoData.avgTotalTime, - timestamp: new Date().toISOString(), - } - - return NextResponse.json(combinedMetrics) -} diff --git a/src/app/api/cache/warm/route.ts b/src/app/api/cache/warm/route.ts deleted file mode 100644 index fab4ce8e6..000000000 --- a/src/app/api/cache/warm/route.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { auth } from '@clerk/nextjs/server' -import { type NextRequest, NextResponse } from 'next/server' -import { - warmAllCaches, - warmPopularGames, - warmRecentListings, - warmSitemapData, -} from '@/server/cache/warming' -import { hasRolePermission } from '@/utils/permissions' -import { Role } from '@orm' - -/** - * API endpoint to manually trigger cache warming - * Only accessible by admins - */ -export async function POST(request: NextRequest) { - const session = await auth() - - if (!session?.userId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check if user has admin permissions - const { sessionClaims } = session - const metadata = sessionClaims?.metadata as { role?: Role } | undefined - const userRole = metadata?.role - - if (!userRole || !hasRolePermission(userRole, Role.ADMIN)) { - return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) - } - - try { - const body = await request.json() - const { strategy = 'all' } = body - - let result - - switch (strategy) { - case 'games': - result = await warmPopularGames(50) - break - case 'listings': - result = await warmRecentListings(100) - break - case 'sitemap': - result = await warmSitemapData() - break - case 'all': - default: - result = await warmAllCaches() - break - } - - return NextResponse.json({ - success: true, - ...result, - strategy, - timestamp: new Date().toISOString(), - }) - } catch (error) { - console.error('Cache warming error:', error) - // Don't expose internal error details to client - return NextResponse.json({ error: 'Failed to warm cache' }, { status: 500 }) - } -} diff --git a/src/app/api/catalog/compatibility/route.ts b/src/app/api/catalog/compatibility/route.ts index d7b71bc0e..236b85e72 100644 --- a/src/app/api/catalog/compatibility/route.ts +++ b/src/app/api/catalog/compatibility/route.ts @@ -1,7 +1,10 @@ import { TRPCError } from '@trpc/server' -import { type NextRequest, NextResponse } from 'next/server' +import { getHTTPStatusCodeFromError } from '@trpc/server/http' +import { connection, type NextRequest, NextResponse } from 'next/server' import { ZodError } from 'zod' import { validateAndConsumeApiKey } from '@/lib/api/validateApiKey' +import { ERROR_CODES } from '@/lib/errors' +import { logger } from '@/lib/logger' import { GetDeviceCompatibilitySchema } from '@/schemas/mobile' import { prisma } from '@/server/db' import { getDeviceCompatibility } from '@/server/services/catalog.service' @@ -23,11 +26,11 @@ import { getDeviceCompatibility } from '@/server/services/catalog.service' * Either deviceId OR both deviceModelName and deviceBrandName must be provided. */ export async function GET(request: NextRequest) { + await connection() + try { - // Validate API key and enforce quota limits const apiKey = await validateAndConsumeApiKey(request) - // Parse query parameters const searchParams = request.nextUrl.searchParams const deviceId = searchParams.get('deviceId') ?? undefined @@ -37,13 +40,11 @@ export async function GET(request: NextRequest) { const includeEmulatorBreakdownParam = searchParams.get('includeEmulatorBreakdown') const minListingCountParam = searchParams.get('minListingCount') - // Parse array and boolean parameters const systemIds = systemIdsParam ? systemIdsParam.split(',').filter(Boolean) : undefined const includeEmulatorBreakdown = includeEmulatorBreakdownParam !== null ? includeEmulatorBreakdownParam === 'true' : undefined const minListingCount = minListingCountParam ? parseInt(minListingCountParam, 10) : undefined - // Validate input with Zod schema const input = GetDeviceCompatibilitySchema.parse({ deviceId, deviceModelName, @@ -59,25 +60,16 @@ export async function GET(request: NextRequest) { userId: apiKey.user.id, }) - // Return JSON response return NextResponse.json(result, { status: 200, headers: { - 'Cache-Control': 'public, s-maxage=600, stale-while-revalidate=300', + // This endpoint consumes API-key quota and may include role-scoped visibility. + // Keep intermediary caches out of the path; the service still uses its own LRU. + 'Cache-Control': 'private, no-store', }, }) } catch (error) { - // Handle TRPCError (includes auth, quota, and other custom errors) if (error instanceof TRPCError) { - const statusCodeMap: Record = { - BAD_REQUEST: 400, - UNAUTHORIZED: 401, - FORBIDDEN: 403, - NOT_FOUND: 404, - TOO_MANY_REQUESTS: 429, - INTERNAL_SERVER_ERROR: 500, - } - const statusCode = statusCodeMap[error.code] || 500 return NextResponse.json( { error: { @@ -85,16 +77,15 @@ export async function GET(request: NextRequest) { message: error.message, }, }, - { status: statusCode }, + { status: getHTTPStatusCodeFromError(error) }, ) } - // Handle validation errors if (error instanceof ZodError) { return NextResponse.json( { error: { - code: 'BAD_REQUEST', + code: ERROR_CODES.BAD_REQUEST, message: 'Invalid query parameters', details: error.errors, }, @@ -103,12 +94,11 @@ export async function GET(request: NextRequest) { ) } - // Handle not found errors if (error instanceof Error && error.message.includes('not found')) { return NextResponse.json( { error: { - code: 'NOT_FOUND', + code: ERROR_CODES.NOT_FOUND, message: error.message, }, }, @@ -116,12 +106,11 @@ export async function GET(request: NextRequest) { ) } - // Handle unexpected errors - console.error('Catalog compatibility API error:', error) + logger.error('Catalog compatibility API error:', error) return NextResponse.json( { error: { - code: 'INTERNAL_SERVER_ERROR', + code: ERROR_CODES.INTERNAL_SERVER_ERROR, message: 'An unexpected error occurred', }, }, diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts index 4dc5dfb9a..32b647bdb 100644 --- a/src/app/api/health/route.ts +++ b/src/app/api/health/route.ts @@ -1,9 +1,7 @@ -import { NextResponse } from 'next/server' +import { connection, NextResponse } from 'next/server' import { prisma } from '@/server/db' import type { NextRequest } from 'next/server' -export const dynamic = 'force-dynamic' - interface HealthResponse { status: 'healthy' | 'unhealthy' timestamp: string @@ -111,19 +109,18 @@ interface HealthResponse { * description: Error message */ export async function GET(_request: NextRequest) { + await connection() + try { - // Test database connectivity const dbStart = Date.now() await prisma.$queryRaw`SELECT 1` const dbLatency = Date.now() - dbStart - // Get memory usage const memUsage = process.memoryUsage() const memoryUsed = memUsage.rss const memoryTotal = memUsage.rss + memUsage.external const memoryPercentage = Math.round((memoryUsed / memoryTotal) * 100) - // Check if Clerk environment variables are set const authAvailable = !!( process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY && process.env.CLERK_SECRET_KEY ) @@ -145,8 +142,8 @@ export async function GET(_request: NextRequest) { }, system: { memory: { - used: Math.round(memoryUsed / 1024 / 1024), // MB - total: Math.round(memoryTotal / 1024 / 1024), // MB + used: Math.round(memoryUsed / 1024 / 1024), + total: Math.round(memoryTotal / 1024 / 1024), percentage: memoryPercentage, }, nodeVersion: process.version, diff --git a/src/app/api/mobile/auth/route.ts b/src/app/api/mobile/auth/route.ts index 758137c2c..ca118682d 100644 --- a/src/app/api/mobile/auth/route.ts +++ b/src/app/api/mobile/auth/route.ts @@ -1,9 +1,11 @@ import { auth } from '@clerk/nextjs/server' -import { type NextRequest, NextResponse } from 'next/server' +import { connection, type NextRequest, NextResponse } from 'next/server' import { getCORSHeaders } from '@/lib/cors' import { prisma } from '@/server/db' export async function GET(request: NextRequest) { + await connection() + try { const { userId } = await auth() @@ -14,7 +16,6 @@ export async function GET(request: NextRequest) { ) } - // Fetch user data from database using clerkId const user = await prisma.user.findUnique({ where: { clerkId: userId }, select: { id: true, name: true, role: true, bio: true, createdAt: true }, diff --git a/src/app/api/mobile/trpc/[trpc]/route.ts b/src/app/api/mobile/trpc/[trpc]/route.ts index ccedbb63b..5d9b36963 100644 --- a/src/app/api/mobile/trpc/[trpc]/route.ts +++ b/src/app/api/mobile/trpc/[trpc]/route.ts @@ -1,5 +1,5 @@ import { fetchRequestHandler } from '@trpc/server/adapters/fetch' -import { type NextRequest, NextResponse } from 'next/server' +import { connection, type NextRequest, NextResponse } from 'next/server' import { getCORSHeaders } from '@/lib/cors' import { createMobileTRPCFetchContext } from '@/server/api/mobileContext' import { mobileRouter } from '@/server/api/routers/mobile' @@ -89,4 +89,9 @@ const handler = async (req: NextRequest) => { } } -export { handler as GET, handler as POST } +async function GET(req: NextRequest) { + await connection() + return handler(req) +} + +export { GET, handler as POST } diff --git a/src/app/api/notifications/stream/route.ts b/src/app/api/notifications/stream/route.ts index 20041c0d3..6d9718015 100644 --- a/src/app/api/notifications/stream/route.ts +++ b/src/app/api/notifications/stream/route.ts @@ -1,26 +1,25 @@ import { auth } from '@clerk/nextjs/server' -import { type NextRequest } from 'next/server' +import { connection, type NextRequest } from 'next/server' +import { logger } from '@/lib/logger' import { realtimeNotificationService, createSSEResponse, } from '@/server/notifications/realtimeService' export async function GET(request: NextRequest) { + await connection() + try { - // Get authenticated user const { userId } = await auth() if (!userId) return new Response('Unauthorized', { status: 401 }) - // Create SSE stream for the user const stream = realtimeNotificationService.createSSEConnection(userId) - - // Pass origin for proper CORS handling const origin = request.headers.get('origin') || undefined return createSSEResponse(stream, origin) } catch (error) { - console.error('SSE connection error:', error) + logger.error('SSE connection error:', error) return new Response('Internal Server Error', { status: 500 }) } } diff --git a/src/app/api/proxy-image/route.ts b/src/app/api/proxy-image/route.ts index 477d7865e..64fa7e902 100644 --- a/src/app/api/proxy-image/route.ts +++ b/src/app/api/proxy-image/route.ts @@ -1,11 +1,9 @@ -import { type NextRequest } from 'next/server' +import { connection, type NextRequest } from 'next/server' +import { env } from '@/lib/env' import { logger } from '@/lib/logger' -export const dynamic = 'force-dynamic' - /** * Only allow http and https URLs to prevent SSRF attacks - * @param raw */ function isAllowedUrl(raw?: string | null): URL | null { if (!raw) return null @@ -19,17 +17,18 @@ function isAllowedUrl(raw?: string | null): URL | null { } export async function GET(req: NextRequest) { + await connection() + const src = req.nextUrl.searchParams.get('url') const url = isAllowedUrl(src) if (!url) return new Response('Invalid or missing url parameter', { status: 400 }) try { const upstream = await fetch(url.toString(), { - // In dev, always bypass caches to avoid stale images; in prod let CDN cache images - cache: process.env.NODE_ENV !== 'production' ? 'no-store' : 'force-cache', + cache: env.IS_PROD ? 'force-cache' : 'no-store', redirect: 'follow', headers: { - 'User-Agent': 'EmuReadyImageProxy/1.0 (+https://emuready.com)', + 'User-Agent': 'EmuReadyImageProxy/1.0 (+https://www.emuready.com)', }, }) @@ -38,17 +37,15 @@ export async function GET(req: NextRequest) { } const contentType = upstream.headers.get('content-type') || 'application/octet-stream' - const cacheControl = - process.env.NODE_ENV !== 'production' - ? 'no-store, no-cache, must-revalidate' - : 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600' + const cacheControl = env.IS_PROD + ? 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600' + : 'no-store, no-cache, must-revalidate' return new Response(upstream.body, { status: 200, headers: { 'Content-Type': contentType, 'Cache-Control': cacheControl, - // Help common CDNs respect our intent 'CDN-Cache-Control': cacheControl, 'Vercel-CDN-Cache-Control': cacheControl, }, diff --git a/src/app/api/trpc/[trpc]/route.ts b/src/app/api/trpc/[trpc]/route.ts index 386d9546e..d89c3841f 100644 --- a/src/app/api/trpc/[trpc]/route.ts +++ b/src/app/api/trpc/[trpc]/route.ts @@ -1,11 +1,8 @@ import { fetchRequestHandler } from '@trpc/server/adapters/fetch' -import { type NextRequest } from 'next/server' +import { connection, type NextRequest } from 'next/server' import { appRouter } from '@/server/api/root' import { createAppRouterTRPCContext } from '@/server/api/trpc' -// Ensure this route is always treated as dynamic and never cached by Next -export const dynamic = 'force-dynamic' - const handler = async (req: NextRequest) => { return fetchRequestHandler({ endpoint: '/api/trpc', @@ -21,4 +18,9 @@ const handler = async (req: NextRequest) => { }) } -export { handler as GET, handler as POST } +async function GET(req: NextRequest) { + await connection() + return handler(req) +} + +export { GET, handler as POST } diff --git a/src/app/api/upload/games/route.ts b/src/app/api/upload/games/route.ts index a48c2ffde..50f69bd72 100644 --- a/src/app/api/upload/games/route.ts +++ b/src/app/api/upload/games/route.ts @@ -4,34 +4,26 @@ import { handleFileUpload } from '@/lib/upload' import getErrorMessage from '@/utils/getErrorMessage' import type { NextRequest } from 'next/server' -// Set route to be dynamic to prevent caching -export const dynamic = 'force-dynamic' - export async function POST(request: NextRequest) { try { - // Check authentication const { userId } = await auth() if (!userId) { return NextResponse.json({ error: 'Unauthorized access' }, { status: 401 }) } - // Get form data const formData = await request.formData() - // Handle upload using shared utility const result = await handleFileUpload(formData, userId, 'games') if (!result.success) { return NextResponse.json({ error: result.error }, { status: result.status }) } - // Return success response with cache headers const response = NextResponse.json({ success: true, imageUrl: result.imageUrl, }) - // Set cache control headers to prevent caching response.headers.set('Cache-Control', 'no-store, max-age=0') return response diff --git a/src/app/api/upload/profile/route.ts b/src/app/api/upload/profile/route.ts index 60ea9dfcf..9a8026463 100644 --- a/src/app/api/upload/profile/route.ts +++ b/src/app/api/upload/profile/route.ts @@ -3,33 +3,26 @@ import { NextResponse, type NextRequest } from 'next/server' import { handleFileUpload } from '@/lib/upload' import getErrorMessage from '@/utils/getErrorMessage' -export const dynamic = 'force-dynamic' - export async function POST(request: NextRequest) { try { - // Check authentication const { userId } = await auth() if (!userId) { return NextResponse.json({ error: 'Unauthorized access' }, { status: 401 }) } - // Get form data const formData = await request.formData() - // Handle upload using shared utility const result = await handleFileUpload(formData, userId, 'profiles') if (!result.success) { return NextResponse.json({ error: result.error }, { status: result.status }) } - // Return success response const response = NextResponse.json({ success: true, imageUrl: result.imageUrl, }) - // Set cache control headers to prevent caching response.headers.set('Cache-Control', 'no-store, max-age=0') return response diff --git a/src/app/games/[id]/page.tsx b/src/app/games/[id]/page.tsx index 97bdfdd5f..30a7ec1e5 100644 --- a/src/app/games/[id]/page.tsx +++ b/src/app/games/[id]/page.tsx @@ -1,5 +1,6 @@ import { type Metadata } from 'next' import { notFound } from 'next/navigation' +import { Suspense } from 'react' import { PageWithMetadata } from '@/components/seo/PageWithMetadata' import { generatePageMetadata, @@ -7,6 +8,7 @@ import { generateBreadcrumbStructuredData, } from '@/lib/seo/metadata' import { getGameForSEO } from '@/server/db/seo-queries' +import { GameDetailsPageSkeleton } from './components/GameDetailsPageSkeleton' import GameDetailsPage from './GameDetailsPage' interface Props { @@ -29,7 +31,15 @@ export async function generateMetadata(props: Props): Promise { ) } -export default async function Page(props: Props) { +export default function Page(props: Props) { + return ( + }> + + + ) +} + +async function GamePageContent(props: Props) { const params = await props.params const game = await getGameForSEO(params.id) @@ -49,7 +59,9 @@ export default async function Page(props: Props) { return ( - + }> + + ) } diff --git a/src/app/games/new/NewGamePage.tsx b/src/app/games/new/NewGamePage.tsx index a1f0d33ab..176b584ff 100644 --- a/src/app/games/new/NewGamePage.tsx +++ b/src/app/games/new/NewGamePage.tsx @@ -20,9 +20,6 @@ import { hasRolePermission } from '@/utils/permissions' import { Role } from '@orm' import NotSignedInMessage from './components/NotSignedInMessage' -export const dynamic = 'force-dynamic' - -// Define the System type for Autocomplete interface SystemOption extends AutocompleteOptionBase { id: string name: string @@ -43,10 +40,8 @@ function AddGamePage() { const systemsQuery = api.systems.get.useQuery() const createGame = api.games.create.useMutation() - // Get user role from database using TRPC const userQuery = api.users.me.useQuery(undefined, { enabled: !!user }) - // Get the selected system's name based on systemId const selectedSystem = systemId ? systemsQuery.data?.find((system) => system.id === systemId) : null @@ -60,7 +55,6 @@ function AddGamePage() { return () => clearTimeout(timer) }, [success, error]) - // Redirect non-authors users to IGDB search page useEffect(() => { if ( isLoaded && @@ -78,7 +72,6 @@ function AddGamePage() { const isAuthor = hasRolePermission(userQuery.data.role, Role.AUTHOR) - // If not author, show loading while redirecting if (!isAuthor) return const handleSubmit = async (ev: FormEvent) => { @@ -95,7 +88,6 @@ function AddGamePage() { isErotic, }) - // Invalidate games queries to refresh the list await utils.games.get.invalidate() await utils.games.checkExistingByTgdbIds.invalidate() await utils.games.checkExistingByNamesAndSystems.invalidate() diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3058ddad7..b8ad31c2a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -7,7 +7,8 @@ import { SpeedInsights } from '@vercel/speed-insights/next' import { type Metadata, type Viewport } from 'next' import { Inter } from 'next/font/google' import Script from 'next/script' -import { type PropsWithChildren } from 'react' +import { connection } from 'next/server' +import { Suspense, type PropsWithChildren } from 'react' import { Toaster } from 'sonner' import CookieConsent from '@/components/CookieConsent' import Footer from '@/components/footer/Footer' @@ -33,52 +34,74 @@ export const metadata: Metadata = defaultMetadata export default function RootLayout(props: PropsWithChildren) { return ( - - - - {/* Service Worker Registration / Unregister in dev - * - In dev (not production), always load to proactively unregister any SW and clear caches - * - In prod, only load when explicitly enabled via NEXT_PUBLIC_ENABLE_SW - */} - {(env.ENABLE_SW || !env.IS_PROD) && ( - + - )} - - {/* Google Analytics Configuration */} - - + + )} + + + {env.IS_PROD && !env.DISABLE_COOKIE_BANNER && }
- + + +
{props.children}
{env.IS_PROD && ( - <> + - + )} - {env.VERCEL_ANALYTICS_ENABLED && } - - -
+ {env.VERCEL_ANALYTICS_ENABLED && ( + + + + )} + + + + ) +} + +function ClerkBoundary(props: PropsWithChildren) { + if (process.env.NODE_ENV !== 'development') { + return ( + + {props.children} + + ) + } + + return ( + + {props.children} + ) } + +async function DevelopmentClerkProvider(props: PropsWithChildren) { + await connection() + + return {props.children} +} diff --git a/src/app/listings/[id]/page.tsx b/src/app/listings/[id]/page.tsx index 052f935a1..6cd974828 100644 --- a/src/app/listings/[id]/page.tsx +++ b/src/app/listings/[id]/page.tsx @@ -1,5 +1,7 @@ import { type Metadata } from 'next' import { notFound } from 'next/navigation' +import { Suspense } from 'react' +import { ListingDetailSkeleton } from '@/app/listings/shared/components' import { PageWithMetadata } from '@/components/seo/PageWithMetadata' import { generatePageMetadata, generateStructuredData } from '@/lib/seo/metadata' import { getListingForSEO } from '@/server/db/seo-queries' @@ -25,7 +27,15 @@ export async function generateMetadata(props: Props): Promise { ) } -export default async function Page(props: Props) { +export default function Page(props: Props) { + return ( + }> + + + ) +} + +async function ListingPageContent(props: Props) { const params = await props.params const listing = await getListingForSEO(params.id) @@ -43,7 +53,9 @@ export default async function Page(props: Props) { return ( - + }> + + ) } diff --git a/src/app/pc-listings/[id]/page.tsx b/src/app/pc-listings/[id]/page.tsx index e4d18bdde..521dbd7a6 100644 --- a/src/app/pc-listings/[id]/page.tsx +++ b/src/app/pc-listings/[id]/page.tsx @@ -1,5 +1,7 @@ import { type Metadata } from 'next' import { notFound } from 'next/navigation' +import { Suspense } from 'react' +import { ListingDetailSkeleton } from '@/app/listings/shared/components' import { PageWithMetadata } from '@/components/seo/PageWithMetadata' import { generatePageMetadata, generateStructuredData } from '@/lib/seo/metadata' import { getPcListingForSEO } from '@/server/db/seo-queries' @@ -27,7 +29,15 @@ export async function generateMetadata(props: Props): Promise { ) } -export default async function Page(props: Props) { +export default function Page(props: Props) { + return ( + }> + + + ) +} + +async function PcListingPageContent(props: Props) { const params = await props.params const listing = await getPcListingForSEO(params.id) @@ -47,7 +57,9 @@ export default async function Page(props: Props) { return ( - + }> + + ) } diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index d083fb22c..14316e79a 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -5,66 +5,53 @@ import { getApprovedPcListingsForSitemap, } from '@/server/db/seo-queries' -// Revalidate sitemap every 6 hours (6 * 60 * 60 = 21600 seconds) -export const revalidate = 21600 - const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://emuready.com' export default async function sitemap(): Promise { - // Static pages const staticPages: MetadataRoute.Sitemap = [ { url: appUrl, - lastModified: new Date(), changeFrequency: 'daily', priority: 1, }, { url: `${appUrl}/games`, - lastModified: new Date(), changeFrequency: 'daily', priority: 0.9, }, { url: `${appUrl}/listings`, - lastModified: new Date(), changeFrequency: 'hourly', priority: 0.9, }, { url: `${appUrl}/pc-listings`, - lastModified: new Date(), changeFrequency: 'hourly', priority: 0.9, }, { url: `${appUrl}/emulators`, - lastModified: new Date(), changeFrequency: 'weekly', priority: 0.8, }, { url: `${appUrl}/devices`, - lastModified: new Date(), changeFrequency: 'weekly', priority: 0.8, }, { url: `${appUrl}/terms`, - lastModified: new Date(), changeFrequency: 'monthly', priority: 0.3, }, { url: `${appUrl}/privacy`, - lastModified: new Date(), changeFrequency: 'monthly', priority: 0.3, }, ] try { - // Fetch games for dynamic sitemap entries const games = await getApprovedGamesForSitemap(1000) const gamePages: MetadataRoute.Sitemap = games @@ -76,7 +63,6 @@ export default async function sitemap(): Promise { })) : [] - // Fetch recent listings const listings = await getApprovedListingsForSitemap(500) const listingPages: MetadataRoute.Sitemap = listings @@ -88,7 +74,6 @@ export default async function sitemap(): Promise { })) : [] - // Fetch PC listings const pcListings = await getApprovedPcListingsForSitemap(500) const pcListingPages: MetadataRoute.Sitemap = pcListings @@ -102,7 +87,6 @@ export default async function sitemap(): Promise { return [...staticPages, ...gamePages, ...listingPages, ...pcListingPages] } catch (error) { - // Return only static pages if there's an error fetching dynamic content console.error('Error generating sitemap:', error) return staticPages } diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx index 686d1e35c..4f5381d72 100644 --- a/src/app/terms/page.tsx +++ b/src/app/terms/page.tsx @@ -1,4 +1,3 @@ -import { LocalizedDate } from '@/components/ui/LocalizedDate' import type { Metadata } from 'next' export const metadata: Metadata = { @@ -17,7 +16,7 @@ function TermsOfServicePage() {

Terms of Service

- Last updated: + Last updated: April 1, 2026

diff --git a/src/app/users/[id]/page.tsx b/src/app/users/[id]/page.tsx index 36192a3c0..d09a08749 100644 --- a/src/app/users/[id]/page.tsx +++ b/src/app/users/[id]/page.tsx @@ -1,7 +1,9 @@ import { type Metadata } from 'next' import { notFound } from 'next/navigation' +import { Suspense } from 'react' import { generatePageMetadata } from '@/lib/seo/metadata' import { getUserForSEO } from '@/server/db/seo-queries' +import UserProfilePageSkeleton from './components/UserProfilePageSkeleton' import UserProfilePage from './UserProfilePage' interface Props { @@ -25,11 +27,23 @@ export async function generateMetadata(props: Props): Promise { ) } -export default async function Page(props: Props) { +export default function Page(props: Props) { + return ( + }> + + + ) +} + +async function UserPageContent(props: Props) { const params = await props.params const user = await getUserForSEO(params.id) if (!user) notFound() - return + return ( + }> + + + ) } diff --git a/src/components/SessionTracker.tsx b/src/components/SessionTracker.tsx index 318be9bb2..45266c9f8 100644 --- a/src/components/SessionTracker.tsx +++ b/src/components/SessionTracker.tsx @@ -21,13 +21,22 @@ function SessionTracker() { const { user } = useUser() const pathname = usePathname() const { analyticsAllowed } = useCookieConsent() - const sessionStartRef = useRef(Date.now()) - const pageLoadTimeRef = useRef(Date.now()) - const sessionIdRef = useRef(generateUUID()) + const sessionStartRef = useRef(null) + const pageLoadTimeRef = useRef(null) + const sessionIdRef = useRef(null) const hasTrackedSessionStart = useRef(false) const discoveredFeatures = useRef>(new Set()) const previousUserIdRef = useRef(undefined) + useEffect(() => { + if (sessionStartRef.current !== null) return + + const now = Date.now() + sessionStartRef.current = now + pageLoadTimeRef.current = now + sessionIdRef.current = generateUUID() + }, []) + // Track user sign-in when a user transitions from null/undefined to having a user useEffect(() => { if (!analyticsAllowed) return @@ -49,7 +58,7 @@ function SessionTracker() { // Track session start on the first load useEffect(() => { - if (!analyticsAllowed || hasTrackedSessionStart.current) return + if (!analyticsAllowed || hasTrackedSessionStart.current || !sessionIdRef.current) return hasTrackedSessionStart.current = true @@ -63,7 +72,7 @@ function SessionTracker() { // Track page views when pathname changes useEffect(() => { - if (!analyticsAllowed) return + if (!analyticsAllowed || pageLoadTimeRef.current === null) return const loadTime = Date.now() - pageLoadTimeRef.current @@ -79,10 +88,12 @@ function SessionTracker() { // Track feature discovery based on page visits const featureMap: Record = { + '/pc-listings/new': 'pc-listing_creation', '/listings/new': 'listing_creation', '/profile': 'profile_management', '/admin': 'admin_panel', '/listings': 'listing_browser', + '/pc-listings': 'pc-listing_browser', '/games': 'game_browser', } @@ -102,12 +113,13 @@ function SessionTracker() { // Track session duration on page unloading useEffect(() => { - if (!analyticsAllowed) return + if (!analyticsAllowed || sessionStartRef.current === null || !sessionIdRef.current) return const handleBeforeUnload = () => { + if (sessionStartRef.current === null || !sessionIdRef.current) return + const sessionDuration = Date.now() - sessionStartRef.current - // Track session end with analytics analytics.session.sessionEnded({ userId: user?.id, sessionId: sessionIdRef.current, diff --git a/src/components/admin/CacheMetrics.tsx b/src/components/admin/CacheMetrics.tsx deleted file mode 100644 index 10c4544e0..000000000 --- a/src/components/admin/CacheMetrics.tsx +++ /dev/null @@ -1,152 +0,0 @@ -'use client' - -import { Loader2, Activity, TrendingUp, Database, Timer } from 'lucide-react' -import { Divider, Card, DonutChart, BarChart } from '@/components/ui' -import { POLLING_INTERVALS } from '@/data/constants' -import { api } from '@/lib/api' - -export function CacheMetrics() { - const { - data: metrics, - isLoading, - error, - } = api.cache.getMetrics.useQuery(undefined, { - refetchInterval: POLLING_INTERVALS.DEFAULT, - refetchOnWindowFocus: false, - }) - - if (isLoading) { - return ( - - - - ) - } - - if (error || !metrics) { - return ( - -

- {error?.message || 'Unable to load cache metrics'} -

-
- ) - } - - const cacheMetrics = metrics.cache - const cacheUsagePercent = (cacheMetrics.size / cacheMetrics.maxSize) * 100 - const totalRequests = cacheMetrics.hits + cacheMetrics.misses - - return ( - -
-
-

- - SEO Cache Performance -

-

- Real-time cache metrics and performance indicators -

-
-
-
-
- - Hit Rate -
-
{cacheMetrics.hitRate}
-

- {totalRequests.toLocaleString()} total requests -

-
- -
-
- - Cache Size -
-
- {cacheMetrics.size} / {cacheMetrics.maxSize} -
-
-
-
-
-
- - - -
-
-
- - Stale Hits -
-
{cacheMetrics.staleHits.toLocaleString()}
-

Served while revalidating

-
- -
-

Cache Performance

- -
-
- - {/* Cache Distribution Chart */} -
-

Cache Request Distribution

-
- -
-
- -
- Last updated: {new Date(metrics.timestamp).toLocaleTimeString()} -
-
- - ) -} diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx index 320e30728..ac29fe935 100644 --- a/src/components/footer/Footer.tsx +++ b/src/components/footer/Footer.tsx @@ -1,6 +1,7 @@ 'use client' import Link from 'next/link' +import { useEffect, useState } from 'react' import { FooterAppLinks } from '@/components/footer/components/FooterAppLinks' import { FooterBetaBadge } from '@/components/footer/components/FooterBetaBadge' import { FooterKofiButton } from '@/components/footer/components/FooterKofiButton' @@ -12,6 +13,12 @@ import analytics from '@/lib/analytics' import { env } from '@/lib/env' function Footer() { + const [copyrightYear, setCopyrightYear] = useState(null) + + useEffect(() => { + setCopyrightYear(new Date().getUTCFullYear()) + }, []) + return (