diff --git a/.env.example b/.env.example index 4c4143f..979bb3c 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,6 @@ DATABASE_URL=postgresql://latex:latex@db:5432/latex STORAGE_BACKEND=local ADMIN_BOOTSTRAP_TOKEN="set-a-random-one-time-bootstrap-token" RUN_DB_MIGRATIONS_ON_STARTUP=false -DB_PUSH_PW="only set this if you want to use the /api/admin/settings/db-push endpoint" \ No newline at end of file +DB_PUSH_PW="only set this if you want to use the /api/admin/settings/db-push endpoint" +RESEND_API_KEY= +PASSWORD_RESET_TOKEN_TTL_MINUTES=30 \ No newline at end of file diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 32e066d..f1910a6 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -32,8 +32,8 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Lint - run: pnpm lint + #- name: Lint + # run: pnpm lint - name: Build run: pnpm build diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index f8962a8..8a3a931 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -41,8 +41,8 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Lint - run: pnpm lint + #- name: Lint + # run: pnpm lint - name: Build run: pnpm build diff --git a/Dockerfile b/Dockerfile index b9e67e8..224f2df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,8 +34,12 @@ ENV NODE_ENV=production # Uncomment the following line in case you want to disable telemetry during runtime. # ENV NEXT_TELEMETRY_DISABLED=1 -# Needed for admin migration import route (`psql` / `pg_restore`). -RUN apk add --no-cache postgresql-client +# Needed for admin migration import route (`psql` / `pg_restore`), +# document preview generation (`pdftoppm` + `soffice`), +# document/text preview rendering (`fontconfig` + actual fonts), +# and video preview frame extraction (`ffmpeg`). +RUN apk add --no-cache postgresql-client poppler-utils libreoffice fontconfig ttf-dejavu ffmpeg \ + && fc-cache -f RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs diff --git a/README.md b/README.md index 9a2a237..b712c86 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,15 @@ To run the app directly without docker, correctly set your ## TODO: +- [ ] alternative 'tiles' layout for 'guest' album view, button to switch between that and current fullwidth layout, and a slider to adjust tile size + + +### TODO LATERER: + +- [ ] TUI + +### DONE: + - [x] give album view the same controls / layout as gallery view - [x] next / prev buttons when viewing image in modal on a gallery / album - [x] support .gifs @@ -111,11 +120,9 @@ To run the app directly without docker, correctly set your - [x] ability to rename albums (just click on the title and start typing) - [x] ability to caption images in an album (caption applies to the image in the context of the image-in-that-album, not to the image, as images can be in more than one album) - [x] add landon's [ditherspace](https://landonjsmith.com/projects/ditherspace.html) to this app +- [x] encrypted S3 storage - [x] keyboard shortcuts for img view -- [ ] alternative 'tiles' layout for 'guest' album view, button to switch between that and current fullwidth layout, and a slider to adjust tile size - -### TODO LATERER: - -- [ ] TUI -- [ ] support video formats -- [ ] support any file format +- [x] password reset +- [x] support video formats +- [x] support any file format (documents, archives) +- [x] generate previews for documents \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index bae32c9..aa5e819 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,6 +25,8 @@ services: - RATE_LIMIT_MAX_ATTEMPTS=${RATE_LIMIT_MAX_ATTEMPTS:-20} - BILLING_ROLE_ARN=${BILLING_ROLE_ARN} - BILLING_CE_REGION=${BILLING_CE_REGION:-us-east-1} + - RESEND_API_KEY=${RESEND_API_KEY} + - PASSWORD_RESET_TOKEN_TTL_MINUTES=${PASSWORD_RESET_TOKEN_TTL_MINUTES} restart: unless-stopped depends_on: db: diff --git a/docs/runtime-environment.md b/docs/runtime-environment.md index 7145caa..0e97424 100644 --- a/docs/runtime-environment.md +++ b/docs/runtime-environment.md @@ -6,6 +6,8 @@ The app supports both local and AWS runtime settings. - `NEXTAUTH_URL` - `NEXTAUTH_SECRET` +- `PASSWORD_RESET_TOKEN_TTL_MINUTES` (optional, defaults to `30`) +- `RESEND_API_KEY` (required for password reset email delivery) - `ADMIN_BOOTSTRAP_TOKEN` (required only for one-time `/promote-admin` bootstrap) - `STORAGE_BACKEND` (`local` or `s3`) - `RUN_DB_MIGRATIONS_ON_STARTUP` (`true` only for local/dev smoke use) diff --git a/eslint.config.mjs b/eslint.config.mjs index 0e872ee..77f6460 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,19 +1,60 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import { FlatCompat } from "@eslint/eslintrc"; +import { defineConfig, globalIgnores } from 'eslint/config'; +import nextVitals from 'eslint-config-next/core-web-vitals' +import tseslint from 'typescript-eslint'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const compat = new FlatCompat({ - baseDirectory: __dirname, -}); - -const eslintConfig = [ - ...compat.extends("next/core-web-vitals", "next/typescript"), +export default defineConfig([ + ...nextVitals, + tseslint.configs.recommendedTypeChecked, { - ignores: ["src/generated/**/*"] - } -]; - -export default eslintConfig; + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + projectService: true, + }, + }, + }, + { + settings: { + react: { version: "19" } // Avoids auto-detection crash + }, + rules: { + 'import/no-dynamic-require': 'warn', + 'import/no-unused-modules': 'warn', + "@typescript-eslint/no-unused-vars": [ + "warn", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true + } + ], + "@typescript-eslint/no-confusing-void-expression": [ + "error", + { + ignoreArrowShorthand: true + } + ], + "@typescript-eslint/no-misused-promises": [ + "error", + { + checksVoidReturn: { + attributes: false + } + } + ] + }, + }, + globalIgnores([ + '.next/**', + 'out/**', + 'build/**', + 'next-env.d.ts', + "**/*.js", "**/*.cjs", "**/*.mjs", + '*.config.ts' + ]), +]); diff --git a/infra/cdk/lib/app-stack.ts b/infra/cdk/lib/app-stack.ts index 7ef2f4c..032e05e 100644 --- a/infra/cdk/lib/app-stack.ts +++ b/infra/cdk/lib/app-stack.ts @@ -98,6 +98,7 @@ export class AppStack extends cdk.Stack { RATE_LIMIT_MAX_ATTEMPTS: "20", BILLING_ROLE_ARN: billingReaderRole.roleArn, BILLING_CE_REGION: "us-east-1", + PASSWORD_RESET_TOKEN_TTL_MINUTES: props.config.passwordResetTokenTtlMinutes.toString(), }, secrets: { NEXTAUTH_SECRET: ecs.Secret.fromSecretsManager(props.appSecret, "NEXTAUTH_SECRET"), @@ -105,6 +106,7 @@ export class AppStack extends cdk.Stack { DB_PUSH_PW: ecs.Secret.fromSecretsManager(props.appSecret, "DB_PUSH_PW"), PGUSER: ecs.Secret.fromSecretsManager(props.dbCredentialsSecret, "username"), PGPASSWORD: ecs.Secret.fromSecretsManager(props.dbCredentialsSecret, "password"), + RESEND_API_KEY: ecs.Secret.fromSecretsManager(props.appSecret, "RESEND_API_KEY"), }, readonlyRootFilesystem: true, }); diff --git a/infra/cdk/lib/cicd-stack.ts b/infra/cdk/lib/cicd-stack.ts index 26107fa..d14e621 100644 --- a/infra/cdk/lib/cicd-stack.ts +++ b/infra/cdk/lib/cicd-stack.ts @@ -39,6 +39,7 @@ export class CiCdStack extends cdk.Stack { "ssm:*", "sts:GetCallerIdentity", ], + // TODO: Restrict to only the necessary resources resources: ["*"], }), new iam.PolicyStatement({ @@ -61,7 +62,7 @@ export class CiCdStack extends cdk.Stack { this.createRole("DevDeployRole", { provider, policy: basePolicy, - githubSub: `repo:${props.githubOrg}/${props.githubRepo}:ref:refs/heads/main`, + githubSub: `repo:${props.githubOrg}/${props.githubRepo}:ref:refs/heads/*`, roleName: `${props.config.appName}-github-deploy-dev`, }); diff --git a/infra/cdk/lib/config.ts b/infra/cdk/lib/config.ts index 6cbb5f8..3ebb194 100644 --- a/infra/cdk/lib/config.ts +++ b/infra/cdk/lib/config.ts @@ -17,11 +17,13 @@ export type EnvironmentConfig = { s3UseKmsEncryption: boolean; s3Versioned: boolean; s3NoncurrentVersionExpirationDays: number; + passwordResetTokenTtlMinutes: number; }; const BASE = { appName: "latex", region: "ap-southeast-2", + passwordResetTokenTtlMinutes: 30, } as const; const CONFIG_BY_ENV: Record> = { diff --git a/infra/cdk/lib/data-stack.ts b/infra/cdk/lib/data-stack.ts index 0f41cd5..969dfbd 100644 --- a/infra/cdk/lib/data-stack.ts +++ b/infra/cdk/lib/data-stack.ts @@ -48,6 +48,24 @@ export class DataStack extends cdk.Stack { lifecycleRules: imageBucketLifecycleRules, removalPolicy: cdk.RemovalPolicy.RETAIN, autoDeleteObjects: false, + cors: [ + { + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.HEAD], + allowedOrigins: ["*"], + allowedHeaders: ["*"], + exposedHeaders: [ + "Accept-Ranges", + "Content-Length", + "Content-Range", + "Content-Type", + "ETag", + "Last-Modified", + "x-amz-request-id", + "x-amz-id-2", + ], + maxAge: 3600, + }, + ], }); const dbSecurityGroup = new ec2.SecurityGroup(this, "DbSecurityGroup", { diff --git a/package.json b/package.json index 79ef026..376283d 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,12 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next build && next dev --turbopack -p 3000", - "dev-quick": "next dev --turbopack -p 3000", + "devbuild": "next build && next dev --turbopack -p 3000", + "dev": "next dev --turbopack -p 3000", "build": "next build", "start": "next start", "lint": "next lint", + "lint:es": "pnpm exec eslint", "db:push": "drizzle-kit push --config ./drizzle.config.ts", "db:push:force": "drizzle-kit push --force --config ./drizzle.config.ts", "cdk:install": "pnpm --dir infra/cdk install", @@ -15,8 +16,8 @@ "cdk:bootstrap:prod": "./scripts/infra/bootstrap-cdk.sh prod", "infra:image:push:dev": "./scripts/infra/build-and-push-image.sh dev", "infra:image:push:prod": "./scripts/infra/build-and-push-image.sh prod", - "infra:deploy:dev:app": "bash ./scripts/infra/deploy-app.sh dev", - "infra:deploy:prod:app": "bash ./scripts/infra/deploy-app.sh prod" + "infra:deploy:dev": "bash ./scripts/infra/deploy-app.sh dev", + "infra:deploy:prod": "bash ./scripts/infra/deploy-app.sh prod" }, "dependencies": { "@aws-sdk/client-cost-explorer": "^3.996.0", @@ -25,6 +26,7 @@ "@aws-sdk/client-secrets-manager": "^3.954.0", "@aws-sdk/client-ssm": "^3.968.0", "@aws-sdk/credential-providers": "^3.969.0", + "@aws-sdk/s3-request-presigner": "^3.986.0", "@energiz3r/icon-library": "^0.0.6", "@hookform/resolvers": "^5.2.2", "@monaco-editor/react": "^4.7.0", @@ -53,6 +55,7 @@ "drizzle-kit": "^0.31.8", "drizzle-orm": "^0.45.1", "lucide-react": "^0.544.0", + "mammoth": "^1.11.0", "next": "^15.5.10", "next-auth": "^4.24.11", "postgres": "^3.4.7", @@ -62,6 +65,7 @@ "react-hook-form": "^7.64.0", "react-markdown": "^10.1.0", "remark": "^15.0.1", + "resend": "^6.9.3", "sharp": "^0.33.5", "strip-markdown": "^6.0.0", "stripe": "^19.3.1", @@ -77,10 +81,11 @@ "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", "eslint": "^9.37.0", - "eslint-config-next": "15.5.4", + "eslint-config-next": "^16.1.6", "prettier": "^3.8.0", "tailwindcss": "^4.1.14", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "typescript-eslint": "^8.56.1" }, "packageManager": "pnpm@10.16.1+sha512.0e155aa2629db8672b49e8475da6226aa4bdea85fdcdfdc15350874946d4f3c91faaf64cbdc4a5d1ab8002f473d5c3fcedcd197989cf0390f9badd3c04678706" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f468d01..504822b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@aws-sdk/credential-providers': specifier: ^3.969.0 version: 3.985.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.986.0 + version: 3.986.0 '@energiz3r/icon-library': specifier: ^0.0.6 version: 0.0.6(@types/react@19.2.13) @@ -110,12 +113,15 @@ importers: lucide-react: specifier: ^0.544.0 version: 0.544.0(react@19.2.0) + mammoth: + specifier: ^1.11.0 + version: 1.11.0 next: specifier: ^15.5.10 - version: 15.5.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 15.5.12(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) next-auth: specifier: ^4.24.11 - version: 4.24.13(next@15.5.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 4.24.13(next@15.5.12(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(nodemailer@7.0.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) postgres: specifier: ^3.4.7 version: 3.4.8 @@ -137,6 +143,9 @@ importers: remark: specifier: ^15.0.1 version: 15.0.1 + resend: + specifier: ^6.9.3 + version: 6.9.3 sharp: specifier: ^0.33.5 version: 0.33.5 @@ -164,7 +173,7 @@ importers: version: 4.1.18 '@tanstack/eslint-plugin-query': specifier: ^5.91.0 - version: 5.91.4(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + version: 5.91.4(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) '@types/node': specifier: ^24.6.2 version: 24.10.12 @@ -176,10 +185,10 @@ importers: version: 19.2.3(@types/react@19.2.13) eslint: specifier: ^9.37.0 - version: 9.39.2(jiti@1.21.7) + version: 9.39.3(jiti@1.21.7) eslint-config-next: - specifier: 15.5.4 - version: 15.5.4(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + specifier: ^16.1.6 + version: 16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) prettier: specifier: ^3.8.0 version: 3.8.1 @@ -189,6 +198,9 @@ importers: typescript: specifier: ^5.9.3 version: 5.9.3 + typescript-eslint: + specifier: ^8.56.1 + version: 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) packages: @@ -259,6 +271,10 @@ packages: resolution: {integrity: sha512-hFiezao0lCEddPhSQEF6vCu+TepUN3edKxWYbswMoH87XpUvHJmFVX5+zttj4qi33saGiuOaJciswWcN6YSA9g==} engines: {node: '>=20.0.0'} + '@aws-sdk/core@3.973.16': + resolution: {integrity: sha512-Nasoyb5K4jfvncTKQyA13q55xHoz9as01NVYP05B0Kzux/X5UhMn3qXsZDyWOSXkfSCAIrMBKmVVWbI0vUapdQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/core@3.973.7': resolution: {integrity: sha512-wNZZQQNlJ+hzD49cKdo+PY6rsTDElO8yDImnrI69p2PLBa7QomeUKAJWYp9xnaR38nlHqWhMHZuYLCQ3oSX+xg==} engines: {node: '>=20.0.0'} @@ -379,6 +395,10 @@ packages: resolution: {integrity: sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==} engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-sdk-s3@3.972.16': + resolution: {integrity: sha512-U4K1rqyJYvT/zgTI3+rN+MToa51dFnnq1VSsVJuJWPNEKcEnuZVqf7yTpkJJMkYixVW5TTi1dgupd+nmJ0JyWw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-sdk-s3@3.972.7': resolution: {integrity: sha512-VtZ7tMIw18VzjG+I6D6rh2eLkJfTtByiFoCIauGDtTTPBEUMQUiGaJ/zZrPlCY6BsvLLeFKz3+E5mntgiOWmIg==} engines: {node: '>=20.0.0'} @@ -407,6 +427,10 @@ packages: resolution: {integrity: sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==} engines: {node: '>=20.0.0'} + '@aws-sdk/s3-request-presigner@3.986.0': + resolution: {integrity: sha512-+yopxtoXwRXZ2Ai9H4GzkN+T2D07sGrURYcm7Eh2OQe3p+Ys/3VrR6UrzILssaJGYtR2vQqVKnGJBHVYqaM1EQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/signature-v4-multi-region@3.986.0': resolution: {integrity: sha512-Upw+rw7wCH93E6QWxqpAqJLrUmJYVUAWrk4tCOBnkeuwzGERZvJFL5UQ6TAJFj9T18Ih+vNFaACh8J5aP4oTBw==} engines: {node: '>=20.0.0'} @@ -423,6 +447,10 @@ packages: resolution: {integrity: sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==} engines: {node: '>=20.0.0'} + '@aws-sdk/types@3.973.4': + resolution: {integrity: sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-arn-parser@3.972.2': resolution: {integrity: sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==} engines: {node: '>=20.0.0'} @@ -443,6 +471,10 @@ packages: resolution: {integrity: sha512-EhSBGWSGQ6Jcbt6jRyX1/0EV7rf+6RGbIIskN0MTtHk0k8uj5FAa1FZhLf+1ETfnDTy/BT39t5IUOQiZL5X1jQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/util-format-url@3.972.6': + resolution: {integrity: sha512-0YNVNgFyziCejXJx0rzxPiD2rkxTWco4c9wiMF6n37Tb9aQvIF8+t7GyEyIFCwQHZ0VMQaAl+nCZHOYz5I5EKw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-locate-window@3.965.4': resolution: {integrity: sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==} engines: {node: '>=20.0.0'} @@ -476,14 +508,85 @@ packages: resolution: {integrity: sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA==} engines: {node: '>=20.0.0'} + '@aws-sdk/xml-builder@3.972.9': + resolution: {integrity: sha512-ItnlMgSqkPrUfJs7EsvU/01zw5UeIb2tNPhD09LBLHbg+g+HDiKibSLwpkuz/ZIlz4F2IMn+5XgE4AK/pfPuog==} + engines: {node: '>=20.0.0'} + '@aws/lambda-invoke-store@0.2.3': resolution: {integrity: sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==} engines: {node: '>=18.0.0'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@date-fns/tz@1.4.1': resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} @@ -824,8 +927,8 @@ packages: resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.2': - resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + '@eslint/js@9.39.3': + resolution: {integrity: sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': @@ -1159,8 +1262,8 @@ packages: '@next/env@15.5.12': resolution: {integrity: sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==} - '@next/eslint-plugin-next@15.5.4': - resolution: {integrity: sha512-SR1vhXNNg16T4zffhJ4TS7Xn7eq4NfKfcOsRwea7RIAHrjRpI9ALYbamqIJqkAhowLlERffiwk0FMvTLNdnVtw==} + '@next/eslint-plugin-next@16.1.6': + resolution: {integrity: sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==} '@next/swc-darwin-arm64@15.5.12': resolution: {integrity: sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==} @@ -1675,8 +1778,9 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@rushstack/eslint-patch@1.15.0': - resolution: {integrity: sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==} + '@smithy/abort-controller@4.2.10': + resolution: {integrity: sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g==} + engines: {node: '>=18.0.0'} '@smithy/abort-controller@4.2.8': resolution: {integrity: sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==} @@ -1710,6 +1814,10 @@ packages: resolution: {integrity: sha512-IH7G3hWxUhd2Z6HtvjZ1EiyDBCRYRr2sngOB9KUWf96XQ8JP2O5ascUH6TouW5YCIMFaVnKADEscM/vUfI3TvA==} engines: {node: '>=18.0.0'} + '@smithy/core@3.23.7': + resolution: {integrity: sha512-/+ldRdtiO5Cb26afAZOG1FZM0x7D4AYdjpyOv2OScJw+4C7X+OLdRnNKF5UyUE0VpPgSKr3rnF/kvprRA4h2kg==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.2.8': resolution: {integrity: sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==} engines: {node: '>=18.0.0'} @@ -1742,6 +1850,10 @@ packages: resolution: {integrity: sha512-qF4EcrEtEf2P6f2kGGuSVe1lan26cn7PsWJBC3vZJ6D16Fm5FSN06udOMVoW6hjzQM3W7VDFwtyUG2szQY50dA==} engines: {node: '>=18.0.0'} + '@smithy/fetch-http-handler@5.3.12': + resolution: {integrity: sha512-muS5tFw+A/uo+U+yig06vk1776UFM+aAp9hFM8efI4ZcHhTcgv6NTeK4x7ltHeMPBwnhEjcf0MULTyxNkSNxDw==} + engines: {node: '>=18.0.0'} + '@smithy/fetch-http-handler@5.3.9': resolution: {integrity: sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==} engines: {node: '>=18.0.0'} @@ -1790,6 +1902,10 @@ packages: resolution: {integrity: sha512-4OS3TP3IWZysT8KlSG/UwfKdelJmuQ2CqVNfrkjm2Rsm146/DuSTfXiD1ulgWpp9L6lJmPYfWTp7/m4b4dQSdQ==} engines: {node: '>=18.0.0'} + '@smithy/middleware-endpoint@4.4.21': + resolution: {integrity: sha512-CoVGZaqIC0tEjz0ga3ciwCMA5fd/4lIOwO2wx0fH+cTi1zxSFZnMJbIiIF9G1d4vRSDyTupDrpS3FKBBJGkRZg==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-retry@4.4.30': resolution: {integrity: sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==} engines: {node: '>=18.0.0'} @@ -1802,10 +1918,18 @@ packages: resolution: {integrity: sha512-BQsdoi7ma4siJAzD0S6MedNPhiMcTdTLUqEUjrHeT1TJppBKWnwqySg34Oh/uGRhJeBd1sAH2t5tghBvcyD6tw==} engines: {node: '>=18.0.0'} + '@smithy/middleware-serde@4.2.11': + resolution: {integrity: sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-serde@4.2.9': resolution: {integrity: sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==} engines: {node: '>=18.0.0'} + '@smithy/middleware-stack@4.2.10': + resolution: {integrity: sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-stack@4.2.8': resolution: {integrity: sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==} engines: {node: '>=18.0.0'} @@ -1814,6 +1938,10 @@ packages: resolution: {integrity: sha512-pid7ksBr7nm0X/3paIlGo9Fh3UK1pQ5yH0007tBmdkVvv+AsBZAOzC2dmLhlzDWKkSB+ZCiiyDArjAW3klkbMg==} engines: {node: '>=18.0.0'} + '@smithy/node-config-provider@4.3.10': + resolution: {integrity: sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w==} + engines: {node: '>=18.0.0'} + '@smithy/node-config-provider@4.3.8': resolution: {integrity: sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==} engines: {node: '>=18.0.0'} @@ -1826,10 +1954,18 @@ packages: resolution: {integrity: sha512-kQNJFwzYA9y+Fj3h9t1ToXYOJBobwUVEc6/WX45urJXyErgG0WOsres8Se8BAiFCMe8P06OkzRgakv7bQ5S+6Q==} engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.4.13': + resolution: {integrity: sha512-o8CP8w6tlUA0lk+Qfwm6Ed0jCWk3bEY6iBOJjdBaowbXKCSClk8zIHQvUL6RUZMvuNafF27cbRCMYqw6O1v4aA==} + engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.4.9': resolution: {integrity: sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==} engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.2.10': + resolution: {integrity: sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw==} + engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.2.8': resolution: {integrity: sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==} engines: {node: '>=18.0.0'} @@ -1838,6 +1974,10 @@ packages: resolution: {integrity: sha512-ibHwLxq4KlbfueoNxMNrZkG+O7V/5XKrewhDGYn0p9DYKCsdsofuWHKdX3QW4zHlAUfLStqdCUSDi/q/9WSjwA==} engines: {node: '>=18.0.0'} + '@smithy/protocol-http@5.3.10': + resolution: {integrity: sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A==} + engines: {node: '>=18.0.0'} + '@smithy/protocol-http@5.3.8': resolution: {integrity: sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==} engines: {node: '>=18.0.0'} @@ -1846,6 +1986,10 @@ packages: resolution: {integrity: sha512-PRy4yZqsKI3Eab8TLc16Dj2NzC4dnw/8E95+++Jc+wwlkjBpAq3tNLqkLHMmSvDfxKQ+X5PmmCYt+rM/GcMKPA==} engines: {node: '>=18.0.0'} + '@smithy/querystring-builder@4.2.10': + resolution: {integrity: sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA==} + engines: {node: '>=18.0.0'} + '@smithy/querystring-builder@4.2.8': resolution: {integrity: sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==} engines: {node: '>=18.0.0'} @@ -1854,6 +1998,10 @@ packages: resolution: {integrity: sha512-/AIDaq0+ehv+QfeyAjCUFShwHIt+FA1IodsV/2AZE5h4PUZcQYv5sjmy9V67UWfsBoTjOPKUFYSRfGoNW9T2UQ==} engines: {node: '>=18.0.0'} + '@smithy/querystring-parser@4.2.10': + resolution: {integrity: sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q==} + engines: {node: '>=18.0.0'} + '@smithy/querystring-parser@4.2.8': resolution: {integrity: sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==} engines: {node: '>=18.0.0'} @@ -1878,6 +2026,14 @@ packages: resolution: {integrity: sha512-tA5Cm11BHQCk/67y6VPIWydLh/pMY90jqOEWIr/2VAzTOoDwGpwp0C/AuHBc3/xWSOA5m5PXLN+lIOrsnTm/PQ==} engines: {node: '>=18.0.0'} + '@smithy/shared-ini-file-loader@4.4.5': + resolution: {integrity: sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.10': + resolution: {integrity: sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA==} + engines: {node: '>=18.0.0'} + '@smithy/signature-v4@5.3.8': resolution: {integrity: sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==} engines: {node: '>=18.0.0'} @@ -1890,6 +2046,10 @@ packages: resolution: {integrity: sha512-gQP2J3qB/Wmc26gdmB8gA6zq2o2spG5sEU3o7TaTATBJEk29sYGWdEFoGEy91BczSpifTo0DQhVYjZXBEVcrpA==} engines: {node: '>=18.0.0'} + '@smithy/smithy-client@4.12.1': + resolution: {integrity: sha512-Xf9UFHlAihewfkmLNZ6I/Ek6kcYBKoU3cbRS9Z4q++9GWoW0YFbAHs7wMbuXm+nGuKHZ5OKheZMuDdaWPv8DJw==} + engines: {node: '>=18.0.0'} + '@smithy/types@4.12.0': resolution: {integrity: sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==} engines: {node: '>=18.0.0'} @@ -1898,6 +2058,14 @@ packages: resolution: {integrity: sha512-ow30Ze/DD02KH2p0eMyIF2+qJzGyNb0kFrnTRtPpuOkQ4hrgvLdaU4YC6r/K8aOrCML4FH0Cmm0aI4503L1Hwg==} engines: {node: '>=18.0.0'} + '@smithy/types@4.13.0': + resolution: {integrity: sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.10': + resolution: {integrity: sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A==} + engines: {node: '>=18.0.0'} + '@smithy/url-parser@4.2.8': resolution: {integrity: sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==} engines: {node: '>=18.0.0'} @@ -1978,6 +2146,10 @@ packages: resolution: {integrity: sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA==} engines: {node: '>=18.0.0'} + '@smithy/util-middleware@4.2.10': + resolution: {integrity: sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA==} + engines: {node: '>=18.0.0'} + '@smithy/util-middleware@4.2.8': resolution: {integrity: sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==} engines: {node: '>=18.0.0'} @@ -2002,6 +2174,10 @@ packages: resolution: {integrity: sha512-IOBEiJTOltSx6MAfwkx/GSVM8/UCJxdtw13haP5OEL543lb1DN6TAypsxv+qcj4l/rKcpapbS6zK9MQGBOhoaA==} engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.5.16': + resolution: {integrity: sha512-c7awZV6cxY0czgDDSr+Bz0XfRtg8AwW2BWhrHhLJISrpmwv8QzA2qzTllWyMVNdy1+UJr9vCm29hzuh3l8TTFw==} + engines: {node: '>=18.0.0'} + '@smithy/util-uri-escape@4.2.0': resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} engines: {node: '>=18.0.0'} @@ -2034,6 +2210,9 @@ packages: resolution: {integrity: sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw==} engines: {node: '>=18.0.0'} + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -2266,19 +2445,19 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@typescript-eslint/eslint-plugin@8.54.0': - resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} + '@typescript-eslint/eslint-plugin@8.56.1': + resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.54.0 - eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/parser': ^8.56.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.54.0': - resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/project-service@8.54.0': @@ -2287,33 +2466,59 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@8.54.0': resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.54.0': resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.54.0': - resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.56.1': + resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} + 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.0.0' '@typescript-eslint/types@8.54.0': resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.54.0': resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.54.0': resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2321,10 +2526,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.56.1': + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + 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.0.0' + '@typescript-eslint/visitor-keys@8.54.0': resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -2423,6 +2639,10 @@ packages: cpu: [x64] os: [win32] + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + engines: {node: '>=10.0.0'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2436,6 +2656,9 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -2450,6 +2673,9 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2518,6 +2744,18 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + bcryptjs@3.0.3: resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} hasBin: true @@ -2526,6 +2764,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} @@ -2535,10 +2776,19 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2625,6 +2875,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} @@ -2636,6 +2889,9 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2725,6 +2981,9 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dingbat-to-unicode@1.0.1: + resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -2848,10 +3107,16 @@ packages: sqlite3: optional: true + duck@0.1.12: + resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + electron-to-chromium@1.5.307: + resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -2914,14 +3179,18 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@15.5.4: - resolution: {integrity: sha512-BzgVVuT3kfJes8i2GHenC1SRJ+W3BTML11lAOYFOOPzrk2xp66jBOAGEFRw+3LkYCln5UzvFsLhojrshb5Zfaw==} + eslint-config-next@16.1.6: + resolution: {integrity: sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==} peerDependencies: - eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + eslint: '>=9.0.0' typescript: '>=3.3.1' peerDependenciesMeta: typescript: @@ -2980,9 +3249,9 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + 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 @@ -3004,8 +3273,12 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.39.2: - resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.3: + resolution: {integrity: sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -3060,6 +3333,12 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + + fast-xml-builder@1.0.0: + resolution: {integrity: sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==} + fast-xml-parser@5.3.4: resolution: {integrity: sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==} hasBin: true @@ -3068,6 +3347,10 @@ packages: resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==} hasBin: true + fast-xml-parser@5.4.1: + resolution: {integrity: sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==} + hasBin: true + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -3155,6 +3438,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -3186,6 +3473,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'} @@ -3230,6 +3521,12 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + html-dom-parser@5.1.8: resolution: {integrity: sha512-MCIUng//mF2qTtGHXJWr6OLfHWmg3Pm8ezpfiltF83tizPWY17JxT4dRLE8lykJ5bChJELoY3onQKPbufJHxYA==} @@ -3256,6 +3553,9 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -3264,6 +3564,9 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -3400,6 +3703,9 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -3432,6 +3738,11 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -3445,10 +3756,18 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3463,6 +3782,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -3560,6 +3882,12 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lop@0.4.2: + resolution: {integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -3572,6 +3900,11 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + mammoth@1.11.0: + resolution: {integrity: sha512-BcEqqY/BOwIcI1iR5tqyVlqc3KIaMRa4egSoK83YAVrBf6+yqdAAbtUcFDCWX8Zef8/fgNZ6rl4VUv+vVX8ddQ==} + engines: {node: '>=12.0.0'} + hasBin: true + marked@14.0.0: resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} engines: {node: '>= 18'} @@ -3680,6 +4013,10 @@ packages: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -3756,6 +4093,13 @@ packages: sass: optional: true + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + + nodemailer@7.0.13: + resolution: {integrity: sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==} + engines: {node: '>=6.0.0'} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -3813,6 +4157,9 @@ packages: openid-client@5.7.1: resolution: {integrity: sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==} + option@0.2.4: + resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3829,6 +4176,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -3840,6 +4190,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3881,6 +4235,9 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postal-mime@2.7.3: + resolution: {integrity: sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw==} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -3972,6 +4329,9 @@ packages: pretty-format@3.8.0: resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -4069,6 +4429,9 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -4093,6 +4456,15 @@ packages: remark@15.0.1: resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + resend@6.9.3: + resolution: {integrity: sha512-GRXjH9XZBJA+daH7bBVDuTShr22iWCxXA8P7t495G4dM/RC+d+3gHBK/6bz9K6Vpcq11zRQKmD+B+jECwQlyGQ==} + engines: {node: '>=20'} + peerDependencies: + '@react-email/render': '*' + peerDependenciesMeta: + '@react-email/render': + optional: true + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -4120,6 +4492,9 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -4165,6 +4540,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -4214,9 +4592,15 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} @@ -4247,6 +4631,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -4305,6 +4692,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svix@1.84.1: + resolution: {integrity: sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==} + tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} @@ -4396,6 +4786,13 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typescript-eslint@8.56.1: + resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==} + 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.0.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -4405,6 +4802,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + underscore@1.13.8: + resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -4432,6 +4832,12 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -4463,6 +4869,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -4498,10 +4908,17 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + xmlbuilder@10.1.1: + resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==} + engines: {node: '>=4.0'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -4509,6 +4926,12 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + 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@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -4996,6 +5419,22 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@aws-sdk/core@3.973.16': + dependencies: + '@aws-sdk/types': 3.973.4 + '@aws-sdk/xml-builder': 3.972.9 + '@smithy/core': 3.23.7 + '@smithy/node-config-provider': 4.3.10 + '@smithy/property-provider': 4.2.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/signature-v4': 5.3.10 + '@smithy/smithy-client': 4.12.1 + '@smithy/types': 4.13.0 + '@smithy/util-base64': 4.3.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + '@aws-sdk/core@3.973.7': dependencies: '@aws-sdk/types': 3.973.1 @@ -5344,6 +5783,23 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 + '@aws-sdk/middleware-sdk-s3@3.972.16': + dependencies: + '@aws-sdk/core': 3.973.16 + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-arn-parser': 3.972.2 + '@smithy/core': 3.23.7 + '@smithy/node-config-provider': 4.3.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/signature-v4': 5.3.10 + '@smithy/smithy-client': 4.12.1 + '@smithy/types': 4.13.0 + '@smithy/util-config-provider': 4.2.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-stream': 4.5.16 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + '@aws-sdk/middleware-sdk-s3@3.972.7': dependencies: '@aws-sdk/core': 3.973.7 @@ -5481,13 +5937,24 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 + '@aws-sdk/s3-request-presigner@3.986.0': + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.986.0 + '@aws-sdk/types': 3.973.4 + '@aws-sdk/util-format-url': 3.972.6 + '@smithy/middleware-endpoint': 4.4.21 + '@smithy/protocol-http': 5.3.10 + '@smithy/smithy-client': 4.12.1 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.986.0': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.972.7 - '@aws-sdk/types': 3.973.1 - '@smithy/protocol-http': 5.3.8 - '@smithy/signature-v4': 5.3.8 - '@smithy/types': 4.12.0 + '@aws-sdk/middleware-sdk-s3': 3.972.16 + '@aws-sdk/types': 3.973.4 + '@smithy/protocol-http': 5.3.10 + '@smithy/signature-v4': 5.3.10 + '@smithy/types': 4.13.0 tslib: 2.8.1 '@aws-sdk/token-providers@3.985.0': @@ -5519,6 +5986,11 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 + '@aws-sdk/types@3.973.4': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@aws-sdk/util-arn-parser@3.972.2': dependencies: tslib: 2.8.1 @@ -5555,6 +6027,13 @@ snapshots: '@smithy/util-endpoints': 3.2.8 tslib: 2.8.1 + '@aws-sdk/util-format-url@3.972.6': + dependencies: + '@aws-sdk/types': 3.973.4 + '@smithy/querystring-builder': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@aws-sdk/util-locate-window@3.965.4': dependencies: tslib: 2.8.1 @@ -5594,10 +6073,116 @@ snapshots: fast-xml-parser: 5.3.6 tslib: 2.8.1 + '@aws-sdk/xml-builder@3.972.9': + dependencies: + '@smithy/types': 4.13.0 + fast-xml-parser: 5.4.1 + tslib: 2.8.1 + '@aws/lambda-invoke-store@0.2.3': {} + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + '@babel/runtime@7.28.6': {} + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@date-fns/tz@1.4.1': {} '@drizzle-team/brocli@0.10.2': {} @@ -5790,9 +6375,9 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@1.21.7))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.3(jiti@1.21.7))': dependencies: - eslint: 9.39.2(jiti@1.21.7) + eslint: 9.39.3(jiti@1.21.7) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -5827,7 +6412,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.39.2': {} + '@eslint/js@9.39.3': {} '@eslint/object-schema@2.1.7': {} @@ -6103,7 +6688,7 @@ snapshots: '@next/env@15.5.12': {} - '@next/eslint-plugin-next@15.5.4': + '@next/eslint-plugin-next@16.1.6': dependencies: fast-glob: 3.3.1 @@ -6565,7 +7150,10 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@rushstack/eslint-patch@1.15.0': {} + '@smithy/abort-controller@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 '@smithy/abort-controller@4.2.8': dependencies: @@ -6630,6 +7218,19 @@ snapshots: '@smithy/uuid': 1.1.1 tslib: 2.8.1 + '@smithy/core@3.23.7': + dependencies: + '@smithy/middleware-serde': 4.2.11 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + '@smithy/util-base64': 4.3.1 + '@smithy/util-body-length-browser': 4.2.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-stream': 4.5.16 + '@smithy/util-utf8': 4.2.1 + '@smithy/uuid': 1.1.1 + tslib: 2.8.1 + '@smithy/credential-provider-imds@4.2.8': dependencies: '@smithy/node-config-provider': 4.3.8 @@ -6684,6 +7285,14 @@ snapshots: '@smithy/util-base64': 4.3.1 tslib: 2.8.1 + '@smithy/fetch-http-handler@5.3.12': + dependencies: + '@smithy/protocol-http': 5.3.10 + '@smithy/querystring-builder': 4.2.10 + '@smithy/types': 4.13.0 + '@smithy/util-base64': 4.3.1 + tslib: 2.8.1 + '@smithy/fetch-http-handler@5.3.9': dependencies: '@smithy/protocol-http': 5.3.8 @@ -6763,6 +7372,17 @@ snapshots: '@smithy/util-middleware': 4.2.9 tslib: 2.8.1 + '@smithy/middleware-endpoint@4.4.21': + dependencies: + '@smithy/core': 3.23.7 + '@smithy/middleware-serde': 4.2.11 + '@smithy/node-config-provider': 4.3.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + '@smithy/url-parser': 4.2.10 + '@smithy/util-middleware': 4.2.10 + tslib: 2.8.1 + '@smithy/middleware-retry@4.4.30': dependencies: '@smithy/node-config-provider': 4.3.8 @@ -6793,12 +7413,23 @@ snapshots: '@smithy/types': 4.12.1 tslib: 2.8.1 + '@smithy/middleware-serde@4.2.11': + dependencies: + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@smithy/middleware-serde@4.2.9': dependencies: '@smithy/protocol-http': 5.3.8 '@smithy/types': 4.12.0 tslib: 2.8.1 + '@smithy/middleware-stack@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@smithy/middleware-stack@4.2.8': dependencies: '@smithy/types': 4.12.0 @@ -6809,6 +7440,13 @@ snapshots: '@smithy/types': 4.12.1 tslib: 2.8.1 + '@smithy/node-config-provider@4.3.10': + dependencies: + '@smithy/property-provider': 4.2.10 + '@smithy/shared-ini-file-loader': 4.4.5 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@smithy/node-config-provider@4.3.8': dependencies: '@smithy/property-provider': 4.2.8 @@ -6831,6 +7469,14 @@ snapshots: '@smithy/types': 4.12.1 tslib: 2.8.1 + '@smithy/node-http-handler@4.4.13': + dependencies: + '@smithy/abort-controller': 4.2.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/querystring-builder': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@smithy/node-http-handler@4.4.9': dependencies: '@smithy/abort-controller': 4.2.8 @@ -6839,6 +7485,11 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 + '@smithy/property-provider@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@smithy/property-provider@4.2.8': dependencies: '@smithy/types': 4.12.0 @@ -6849,6 +7500,11 @@ snapshots: '@smithy/types': 4.12.1 tslib: 2.8.1 + '@smithy/protocol-http@5.3.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@smithy/protocol-http@5.3.8': dependencies: '@smithy/types': 4.12.0 @@ -6859,6 +7515,12 @@ snapshots: '@smithy/types': 4.12.1 tslib: 2.8.1 + '@smithy/querystring-builder@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + '@smithy/util-uri-escape': 4.2.1 + tslib: 2.8.1 + '@smithy/querystring-builder@4.2.8': dependencies: '@smithy/types': 4.12.0 @@ -6871,6 +7533,11 @@ snapshots: '@smithy/util-uri-escape': 4.2.1 tslib: 2.8.1 + '@smithy/querystring-parser@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@smithy/querystring-parser@4.2.8': dependencies: '@smithy/types': 4.12.0 @@ -6899,6 +7566,22 @@ snapshots: '@smithy/types': 4.12.1 tslib: 2.8.1 + '@smithy/shared-ini-file-loader@4.4.5': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.10': + dependencies: + '@smithy/is-array-buffer': 4.2.1 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + '@smithy/util-hex-encoding': 4.2.1 + '@smithy/util-middleware': 4.2.10 + '@smithy/util-uri-escape': 4.2.1 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + '@smithy/signature-v4@5.3.8': dependencies: '@smithy/is-array-buffer': 4.2.0 @@ -6930,6 +7613,16 @@ snapshots: '@smithy/util-stream': 4.5.14 tslib: 2.8.1 + '@smithy/smithy-client@4.12.1': + dependencies: + '@smithy/core': 3.23.7 + '@smithy/middleware-endpoint': 4.4.21 + '@smithy/middleware-stack': 4.2.10 + '@smithy/protocol-http': 5.3.10 + '@smithy/types': 4.13.0 + '@smithy/util-stream': 4.5.16 + tslib: 2.8.1 + '@smithy/types@4.12.0': dependencies: tslib: 2.8.1 @@ -6938,6 +7631,16 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/types@4.13.0': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.10': + dependencies: + '@smithy/querystring-parser': 4.2.10 + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@smithy/url-parser@4.2.8': dependencies: '@smithy/querystring-parser': 4.2.8 @@ -7051,6 +7754,11 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/util-middleware@4.2.10': + dependencies: + '@smithy/types': 4.13.0 + tslib: 2.8.1 + '@smithy/util-middleware@4.2.8': dependencies: '@smithy/types': 4.12.0 @@ -7095,6 +7803,17 @@ snapshots: '@smithy/util-utf8': 4.2.1 tslib: 2.8.1 + '@smithy/util-stream@4.5.16': + dependencies: + '@smithy/fetch-http-handler': 5.3.12 + '@smithy/node-http-handler': 4.4.13 + '@smithy/types': 4.13.0 + '@smithy/util-base64': 4.3.1 + '@smithy/util-buffer-from': 4.2.1 + '@smithy/util-hex-encoding': 4.2.1 + '@smithy/util-utf8': 4.2.1 + tslib: 2.8.1 + '@smithy/util-uri-escape@4.2.0': dependencies: tslib: 2.8.1 @@ -7132,6 +7851,8 @@ snapshots: dependencies: tslib: 2.8.1 + '@stablelib/base64@1.0.1': {} + '@standard-schema/utils@0.3.0': {} '@stripe/react-stripe-js@5.6.0(@stripe/stripe-js@8.7.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': @@ -7218,10 +7939,10 @@ snapshots: '@tanstack/devtools-event-client@0.4.0': {} - '@tanstack/eslint-plugin-query@5.91.4(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + '@tanstack/eslint-plugin-query@5.91.4(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.2(jiti@1.21.7) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.3(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -7349,15 +8070,15 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 - eslint: 9.39.2(jiti@1.21.7) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + eslint: 9.39.3(jiti@1.21.7) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -7365,14 +8086,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 debug: 4.4.3 - eslint: 9.39.2(jiti@1.21.7) + eslint: 9.39.3(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -7386,22 +8107,40 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.54.0': dependencies: '@typescript-eslint/types': 8.54.0 '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.2(jiti@1.21.7) + eslint: 9.39.3(jiti@1.21.7) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -7409,6 +8148,8 @@ snapshots: '@typescript-eslint/types@8.54.0': {} + '@typescript-eslint/types@8.56.1': {} + '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) @@ -7424,13 +8165,39 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.54.0(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@1.21.7)) '@typescript-eslint/scope-manager': 8.54.0 '@typescript-eslint/types': 8.54.0 '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - eslint: 9.39.2(jiti@1.21.7) + eslint: 9.39.3(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + eslint: 9.39.3(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -7440,6 +8207,11 @@ snapshots: '@typescript-eslint/types': 8.54.0 eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.0': {} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -7501,6 +8273,8 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@xmldom/xmldom@0.8.11': {} + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -7514,6 +8288,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -7527,6 +8308,10 @@ snapshots: arg@5.0.2: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -7618,10 +8403,18 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.0: {} + bcryptjs@3.0.3: {} binary-extensions@2.3.0: {} + bluebird@3.4.7: {} + bowser@2.14.1: {} brace-expansion@1.1.12: @@ -7633,10 +8426,22 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001769 + electron-to-chromium: 1.5.307 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + buffer-from@1.1.2: {} call-bind-apply-helpers@1.0.2: @@ -7721,12 +8526,16 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + cookie-es@2.0.0: {} cookie@0.7.2: {} cookie@1.1.1: {} + core-util-is@1.0.3: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -7803,6 +8612,8 @@ snapshots: didyoumean@1.2.2: {} + dingbat-to-unicode@1.0.1: {} + dlv@1.1.3: {} doctrine@2.1.0: @@ -7848,12 +8659,18 @@ snapshots: '@types/pg': 8.16.0 postgres: 3.4.8 + duck@0.1.12: + dependencies: + underscore: 1.13.8 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 + electron-to-chromium@1.5.307: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.19.0: @@ -8027,24 +8844,26 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} - eslint-config-next@15.5.4(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): + eslint-config-next@16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@next/eslint-plugin-next': 15.5.4 - '@rushstack/eslint-patch': 1.15.0 - '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.2(jiti@1.21.7) + '@next/eslint-plugin-next': 16.1.6 + eslint: 9.39.3(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.3(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.3(jiti@1.21.7)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.3(jiti@1.21.7)) + eslint-plugin-react: 7.37.5(eslint@9.39.3(jiti@1.21.7)) + eslint-plugin-react-hooks: 7.0.1(eslint@9.39.3(jiti@1.21.7)) + globals: 16.4.0 + typescript-eslint: 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: + - '@typescript-eslint/parser' - eslint-import-resolver-webpack - eslint-plugin-import-x - supports-color @@ -8057,33 +8876,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.3(jiti@1.21.7)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 - eslint: 9.39.2(jiti@1.21.7) + eslint: 9.39.3(jiti@1.21.7) get-tsconfig: 4.13.6 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.3(jiti@1.21.7)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.3(jiti@1.21.7)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.2(jiti@1.21.7) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.3(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.3(jiti@1.21.7)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.3(jiti@1.21.7)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -8092,9 +8911,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.2(jiti@1.21.7) + eslint: 9.39.3(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.3(jiti@1.21.7)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -8106,13 +8925,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2(jiti@1.21.7)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.3(jiti@1.21.7)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -8122,7 +8941,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.39.2(jiti@1.21.7) + eslint: 9.39.3(jiti@1.21.7) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -8131,11 +8950,18 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.2.0(eslint@9.39.2(jiti@1.21.7)): + eslint-plugin-react-hooks@7.0.1(eslint@9.39.3(jiti@1.21.7)): dependencies: - eslint: 9.39.2(jiti@1.21.7) + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + eslint: 9.39.3(jiti@1.21.7) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color - eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@1.21.7)): + eslint-plugin-react@7.37.5(eslint@9.39.3(jiti@1.21.7)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -8143,7 +8969,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.2 - eslint: 9.39.2(jiti@1.21.7) + eslint: 9.39.3(jiti@1.21.7) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -8166,21 +8992,23 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.2(jiti@1.21.7): + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.3(jiti@1.21.7): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.2 + '@eslint/js': 9.39.3 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.12.6 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -8253,6 +9081,10 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-sha256@1.3.0: {} + + fast-xml-builder@1.0.0: {} + fast-xml-parser@5.3.4: dependencies: strnum: 2.1.2 @@ -8261,6 +9093,11 @@ snapshots: dependencies: strnum: 2.1.2 + fast-xml-parser@5.4.1: + dependencies: + fast-xml-builder: 1.0.0 + strnum: 2.1.2 + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -8359,6 +9196,8 @@ snapshots: generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -8399,6 +9238,8 @@ snapshots: globals@14.0.0: {} + globals@16.4.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -8454,6 +9295,12 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + html-dom-parser@5.1.8: dependencies: domhandler: 5.0.3 @@ -8482,6 +9329,8 @@ snapshots: ignore@7.0.5: {} + immediate@3.0.6: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -8489,6 +9338,8 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + inline-style-parser@0.2.7: {} internal-slot@1.1.0: @@ -8630,6 +9481,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + isarray@1.0.0: {} + isarray@2.0.5: {} isbot@5.1.34: {} @@ -8657,6 +9510,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -8667,6 +9522,8 @@ snapshots: dependencies: minimist: 1.2.8 + json5@2.2.3: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -8674,6 +9531,13 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -8689,6 +9553,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lightningcss-android-arm64@1.30.2: optional: true @@ -8758,6 +9626,16 @@ snapshots: dependencies: js-tokens: 4.0.0 + lop@0.4.2: + dependencies: + duck: 0.1.12 + option: 0.2.4 + underscore: 1.13.8 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -8770,6 +9648,19 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + mammoth@1.11.0: + dependencies: + '@xmldom/xmldom': 0.8.11 + argparse: 1.0.10 + base64-js: 1.5.1 + bluebird: 3.4.7 + dingbat-to-unicode: 1.0.1 + jszip: 3.10.1 + lop: 0.4.2 + path-is-absolute: 1.0.1 + underscore: 1.13.8 + xmlbuilder: 10.1.1 + marked@14.0.0: {} math-intrinsics@1.1.0: {} @@ -9005,6 +9896,10 @@ snapshots: mini-svg-data-uri@1.4.4: {} + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -9044,13 +9939,13 @@ snapshots: natural-compare@1.4.0: {} - next-auth@4.24.13(next@15.5.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next-auth@4.24.13(next@15.5.12(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(nodemailer@7.0.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@babel/runtime': 7.28.6 '@panva/hkdf': 1.2.1 cookie: 0.7.2 jose: 4.15.9 - next: 15.5.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next: 15.5.12(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) oauth: 0.9.15 openid-client: 5.7.1 preact: 10.28.3 @@ -9058,8 +9953,10 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) uuid: 8.3.2 + optionalDependencies: + nodemailer: 7.0.13 - next@15.5.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next@15.5.12(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 15.5.12 '@swc/helpers': 0.5.15 @@ -9067,7 +9964,7 @@ snapshots: postcss: 8.4.31 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - styled-jsx: 5.1.6(react@19.2.0) + styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.0) optionalDependencies: '@next/swc-darwin-arm64': 15.5.12 '@next/swc-darwin-x64': 15.5.12 @@ -9082,6 +9979,11 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-releases@2.0.36: {} + + nodemailer@7.0.13: + optional: true + normalize-path@3.0.0: {} oauth@0.9.15: {} @@ -9143,6 +10045,8 @@ snapshots: object-hash: 2.2.0 oidc-token-hash: 5.2.0 + option@0.2.4: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -9166,6 +10070,8 @@ snapshots: dependencies: p-limit: 3.1.0 + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -9182,6 +10088,8 @@ snapshots: path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -9210,6 +10118,8 @@ snapshots: possible-typed-array-names@1.1.0: {} + postal-mime@2.7.3: {} + postcss-import@15.1.0(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -9278,6 +10188,8 @@ snapshots: pretty-format@3.8.0: {} + process-nextick-args@2.0.1: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -9379,6 +10291,16 @@ snapshots: dependencies: pify: 2.3.0 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -9435,6 +10357,11 @@ snapshots: transitivePeerDependencies: - supports-color + resend@6.9.3: + dependencies: + postal-mime: 2.7.3 + svix: 1.84.1 + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -9465,6 +10392,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.1.2: {} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -9514,6 +10443,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setimmediate@1.0.5: {} + sharp@0.33.5: dependencies: color: 4.2.3 @@ -9621,8 +10552,15 @@ snapshots: space-separated-tokens@2.0.2: {} + sprintf-js@1.0.3: {} + stable-hash@0.0.5: {} + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + state-local@1.0.7: {} stop-iteration-iterator@1.1.0: @@ -9680,6 +10618,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -9709,10 +10651,12 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(react@19.2.0): + styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.0): dependencies: client-only: 0.0.1 react: 19.2.0 + optionalDependencies: + '@babel/core': 7.29.0 sucrase@3.35.1: dependencies: @@ -9730,6 +10674,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svix@1.84.1: + dependencies: + standardwebhooks: 1.0.0 + uuid: 10.0.0 + tabbable@6.4.0: {} tailwind-merge@2.2.2: @@ -9853,6 +10802,17 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typescript-eslint@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.3(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} unbox-primitive@1.1.0: @@ -9862,6 +10822,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + underscore@1.13.8: {} + undici-types@6.21.0: {} undici-types@7.16.0: {} @@ -9923,6 +10885,12 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -9948,6 +10916,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@10.0.0: {} + uuid@8.3.2: {} vfile-message@4.0.3: @@ -10007,12 +10977,20 @@ snapshots: word-wrap@1.2.5: {} + xmlbuilder@10.1.1: {} + xtend@4.0.2: {} + yallist@3.1.1: {} + yallist@4.0.0: {} yocto-queue@0.1.0: {} + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + zod@4.3.6: {} zwitch@2.0.4: {} diff --git a/src/app/admin/settings/pg-dump/page.tsx b/src/app/admin/database/page.tsx similarity index 62% rename from src/app/admin/settings/pg-dump/page.tsx rename to src/app/admin/database/page.tsx index 4dd8bec..5f5dab4 100644 --- a/src/app/admin/settings/pg-dump/page.tsx +++ b/src/app/admin/database/page.tsx @@ -3,9 +3,9 @@ import { redirect } from "next/navigation"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/lib/auth"; import { isAdminUser } from "@/lib/metadata-store"; -import AdminPgDumpImport from "@/components/admin-pg-dump-import"; +import AdminDatabase from "@/components/admin-database"; -export default async function AdminSettingsPgDumpPage() { +export default async function AdminDatabasePage() { const session = await getServerSession(authOptions); const userId = (session?.user as { id?: string } | undefined)?.id; if (!userId) { @@ -20,14 +20,14 @@ export default async function AdminSettingsPgDumpPage() { return (
- - Back to settings + + Back to admin -

Import pg_dump

-

One-off data migration helper.

+

Database Maintenance

+

Database import, export, and migration helpers.

- +
); } diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index e4fafce..5081475 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -59,6 +59,9 @@ export default async function AdminHomePage() { Patch notes + + Database + diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx index ef64551..5a57ffb 100644 --- a/src/app/admin/settings/page.tsx +++ b/src/app/admin/settings/page.tsx @@ -27,9 +27,6 @@ export default async function AdminSettingsPage() {

Settings

Configure site messaging and funding.

- - Import pg_dump - diff --git a/src/app/api/admin/settings/db-backup/route.ts b/src/app/api/admin/settings/db-backup/route.ts new file mode 100644 index 0000000..4e183df --- /dev/null +++ b/src/app/api/admin/settings/db-backup/route.ts @@ -0,0 +1,34 @@ +import { NextResponse } from "next/server"; +import { getSessionUserId } from "@/lib/auth"; +import { isAdminUser } from "@/lib/metadata-store"; +import { createSqlBackupAndStore } from "@/lib/db-sql-backup"; + +export const runtime = "nodejs"; + +const IS_ENABLED = true; + +export async function POST(): Promise { + if (!IS_ENABLED) { + return NextResponse.json({ error: "Not found." }, { status: 404 }); + } + + const userId = await getSessionUserId(); + if (!userId) { + return NextResponse.json({ error: "Unauthorized." }, { status: 401 }); + } + const isAdmin = await isAdminUser(userId); + if (!isAdmin) { + return NextResponse.json({ error: "Forbidden." }, { status: 403 }); + } + + try { + const backup = await createSqlBackupAndStore(); + return NextResponse.json({ backup }); + } catch (error) { + return NextResponse.json( + { error: error instanceof Error ? error.message : "Backup failed." }, + { status: 500 }, + ); + } +} + diff --git a/src/app/api/admin/settings/pg-dump-import/route.ts b/src/app/api/admin/settings/db-import/route.ts similarity index 99% rename from src/app/api/admin/settings/pg-dump-import/route.ts rename to src/app/api/admin/settings/db-import/route.ts index 5aa45f3..9957748 100644 --- a/src/app/api/admin/settings/pg-dump-import/route.ts +++ b/src/app/api/admin/settings/db-import/route.ts @@ -8,7 +8,7 @@ import { isAdminUser } from "@/lib/metadata-store"; export const runtime = "nodejs"; -const IS_ENABLED = false; +const IS_ENABLED = true; const MAX_DUMP_BYTES = 1024 * 1024 * 1024; const s3Region = process.env.S3_REGION; diff --git a/src/app/api/admin/settings/route.ts b/src/app/api/admin/settings/route.ts index 8557e53..f179cb1 100644 --- a/src/app/api/admin/settings/route.ts +++ b/src/app/api/admin/settings/route.ts @@ -38,6 +38,7 @@ export async function POST(request: Request): Promise { supportEnabled?: boolean; signupsEnabled?: boolean; uploadsEnabled?: boolean; + shareHtmlNavigationEnabled?: boolean; resumableThresholdBytes?: number; }; @@ -74,6 +75,7 @@ export async function POST(request: Request): Promise { supportEnabled: payload.supportEnabled, signupsEnabled: payload.signupsEnabled, uploadsEnabled: payload.uploadsEnabled, + shareHtmlNavigationEnabled: payload.shareHtmlNavigationEnabled, resumableThresholdBytes: payload.resumableThresholdBytes, }); diff --git a/src/app/api/auth/forgot-password/route.ts b/src/app/api/auth/forgot-password/route.ts new file mode 100644 index 0000000..0e4653f --- /dev/null +++ b/src/app/api/auth/forgot-password/route.ts @@ -0,0 +1,62 @@ +import { eq } from "drizzle-orm"; +import { NextResponse } from "next/server"; +import { db } from "@/db"; +import { users } from "@/db/schema"; +import { + buildPasswordResetUrl, + createPasswordResetToken, + getPasswordResetExpiryDate, + sendPasswordResetEmail, +} from "@/lib/password-reset"; + +export const runtime = "nodejs"; + +const GENERIC_SUCCESS_MESSAGE = + "If an account with that email exists, a password reset link has been sent."; +const EMAIL_REGEX = + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + +export async function POST(request: Request): Promise { + const payload = (await request.json()) as { email?: string }; + const email = payload?.email?.trim().toLowerCase(); + + if (!email) { + return NextResponse.json({ error: "Email is required." }, { status: 400 }); + } + if (!EMAIL_REGEX.test(email)) { + return NextResponse.json({ error: "Email format is invalid." }, { status: 400 }); + } + + const [user] = await db.select().from(users).where(eq(users.email, email)).limit(1); + if (!user) { + return NextResponse.json({ ok: true, message: GENERIC_SUCCESS_MESSAGE }); + } + + const { token, tokenHash } = createPasswordResetToken(); + const tokenExpiresAt = getPasswordResetExpiryDate(); + + await db + .update(users) + .set({ + passwordResetTokenHash: tokenHash, + passwordResetTokenExpiresAt: tokenExpiresAt, + }) + .where(eq(users.id, user.id)); + + const resetUrl = buildPasswordResetUrl(request, token); + let emailSent = false; + try { + emailSent = await sendPasswordResetEmail({ + to: user.email, + resetUrl, + }); + } catch (error) { + console.error("[password-reset] Failed to send reset email.", error); + } + + if (!emailSent && process.env.NODE_ENV !== "production") { + console.info("[password-reset] Resend is not configured. Reset link:", resetUrl); + } + + return NextResponse.json({ ok: true, message: GENERIC_SUCCESS_MESSAGE, emailSent }); +} diff --git a/src/app/api/auth/reset-password/route.ts b/src/app/api/auth/reset-password/route.ts new file mode 100644 index 0000000..ed14dd3 --- /dev/null +++ b/src/app/api/auth/reset-password/route.ts @@ -0,0 +1,72 @@ +import bcrypt from "bcryptjs"; +import { and, eq, gt } from "drizzle-orm"; +import { NextResponse } from "next/server"; +import { db } from "@/db"; +import { users } from "@/db/schema"; +import { hashPasswordResetToken } from "@/lib/password-reset"; + +export const runtime = "nodejs"; + +function isValidPassword(value: string): boolean { + return value.length > 6 && /[a-zA-Z]/.test(value) && /[0-9]/.test(value); +} + +export async function POST(request: Request): Promise { + const payload = (await request.json()) as { + token?: string; + password?: string; + confirmPassword?: string; + }; + + const token = payload?.token?.trim(); + const password = payload?.password; + const confirmPassword = payload?.confirmPassword; + + if (!token || !password || !confirmPassword) { + return NextResponse.json( + { error: "Reset token, password, and confirm password are required." }, + { status: 400 }, + ); + } + + if (!isValidPassword(password)) { + return NextResponse.json( + { error: "Password must be >6 chars and include letters and numbers." }, + { status: 400 }, + ); + } + + if (password !== confirmPassword) { + return NextResponse.json({ error: "Passwords do not match." }, { status: 400 }); + } + + const tokenHash = hashPasswordResetToken(token); + const now = new Date(); + + const [user] = await db + .select({ id: users.id }) + .from(users) + .where( + and( + eq(users.passwordResetTokenHash, tokenHash), + gt(users.passwordResetTokenExpiresAt, now), + ), + ) + .limit(1); + + if (!user) { + return NextResponse.json({ error: "Reset token is invalid or has expired." }, { status: 400 }); + } + + const passwordHash = await bcrypt.hash(password, 12); + await db + .update(users) + .set({ + passwordHash, + passwordResetTokenHash: null, + passwordResetTokenExpiresAt: null, + }) + .where(eq(users.id, user.id)); + + return NextResponse.json({ ok: true }); +} diff --git a/src/app/error.tsx b/src/app/error.tsx index e7c88d3..e0b6052 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -10,7 +10,6 @@ export default function GlobalAppError({ reset: () => void; }) { useEffect(() => { - // eslint-disable-next-line no-console console.error(error); }, [error]); diff --git a/src/app/gallery/page.tsx b/src/app/gallery/page.tsx index f7db872..42383b1 100644 --- a/src/app/gallery/page.tsx +++ b/src/app/gallery/page.tsx @@ -33,6 +33,8 @@ export default async function GalleryPage({ getUserLastPatchNoteDismissed(userId), getAppSettings(), ]); + + console.log("isAdmin", isAdmin); const resolvedSearchParams = searchParams ? await searchParams : undefined; const initialTab = resolvedSearchParams?.tab === "albums" ? "albums" : "files"; const pageTitle = initialTab === "albums" ? "ur albums" : "ur gallery"; @@ -75,6 +77,7 @@ export default async function GalleryPage({ albums={albums.map((album) => ({ id: album.id, name: album.name }))} media={media} resumableThresholdBytes={settings.resumableThresholdBytes} + isAdmin={isAdmin} /> ); diff --git a/src/app/media/[kind]/[mediaId]/[fileName]/route.ts b/src/app/media/[kind]/[mediaId]/[fileName]/route.ts index 43ee298..5672fb3 100644 --- a/src/app/media/[kind]/[mediaId]/[fileName]/route.ts +++ b/src/app/media/[kind]/[mediaId]/[fileName]/route.ts @@ -4,10 +4,11 @@ import { authOptions } from "@/lib/auth"; import { getMediaForUser, type MediaKind } from "@/lib/media-store"; import { contentTypeForExt } from "@/lib/media-types"; import { - getMediaBuffer, - getMediaBufferRange, getMediaBufferSize, - pendingVideoPreviewPng, + getMediaSignedUrl, + getMediaRangeStream, + getMediaStream, + usesS3StorageBackend, } from "@/lib/media-storage"; export const runtime = "nodejs"; @@ -87,21 +88,25 @@ export async function GET( ? "original" : parsed.size; if (parsedKind === "video" && parsed.size !== "original" && media.previewStatus !== "ready") { - const fallback = await pendingVideoPreviewPng(parsed.size); - return new Response(new Uint8Array(fallback), { - headers: { - "Content-Type": "image/png", - "Cache-Control": "private, no-store, max-age=0, must-revalidate", - Pragma: "no-cache", - Expires: "0", - Vary: "Cookie, Authorization", - }, - }); + return new Response("Not found", { status: 404 }); } const isRangeStreamableOriginal = requestedSize === "original" && (parsedKind === "video" || (parsedKind === "other" && (media.mimeType ?? "").toLowerCase().startsWith("audio/"))); + if (usesS3StorageBackend()) { + const responseExt = + requestedSize === "original" ? media.ext : parsedKind === "image" ? media.ext : "png"; + const signedUrl = await getMediaSignedUrl({ + kind: parsedKind, + baseName: media.baseName, + ext: media.ext, + size: requestedSize, + uploadedAt: new Date(media.uploadedAt), + responseContentType: contentTypeForExt(responseExt), + }); + return Response.redirect(signedUrl, 307); + } if (isRangeStreamableOriginal) { const uploadedAt = new Date(media.uploadedAt); const total = await getMediaBufferSize({ @@ -123,7 +128,7 @@ export async function GET( }, }); } - const data = await getMediaBufferRange({ + const stream = await getMediaRangeStream({ kind: parsedKind, baseName: media.baseName, ext: media.ext, @@ -132,7 +137,7 @@ export async function GET( start: byteRange.start, end: byteRange.end, }); - return new Response(new Uint8Array(data), { + return new Response(stream, { status: 206, headers: { "Content-Type": contentTypeForExt(media.ext), @@ -147,7 +152,7 @@ export async function GET( }); } } - const data = await getMediaBuffer({ + const stream = await getMediaStream({ kind: parsedKind, baseName: media.baseName, ext: media.ext, @@ -156,7 +161,7 @@ export async function GET( }); const responseExt = requestedSize === "original" ? media.ext : parsedKind === "image" ? media.ext : "png"; - return new Response(new Uint8Array(data), { + return new Response(stream, { headers: { "Content-Type": contentTypeForExt(responseExt), ...(isRangeStreamableOriginal ? { "Accept-Ranges": "bytes" } : {}), diff --git a/src/app/page.tsx b/src/app/page.tsx index 54a37dd..2ccbc2c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -37,6 +37,7 @@ export default async function Home() {

latex img_srv

+ {/* eslint-disable-next-line @next/next/no-img-element */} latex
@@ -90,7 +91,7 @@ export default async function Home() { {/*

I C U AGAIN

*/}

- u r {session?.user?.email ?? session?.user?.name ?? "user"}. wb <3 + u r {session?.user?.name ?? session?.user?.email ?? "...who r u?"}. wb <3

u hav {userStats?.imageCount ?? 0} imgs uploaded using{" "} diff --git a/src/app/reset-password/page.tsx b/src/app/reset-password/page.tsx new file mode 100644 index 0000000..90edf90 --- /dev/null +++ b/src/app/reset-password/page.tsx @@ -0,0 +1,109 @@ +"use client"; + +import { useMemo, useState } from "react"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; + +function isValidPassword(value: string): boolean { + return value.length > 6 && /[a-zA-Z]/.test(value) && /[0-9]/.test(value); +} + +function getFormString(formData: FormData, key: string): string { + const value = formData.get(key); + return typeof value === "string" ? value : ""; +} + +export default function ResetPasswordPage() { + const params = useSearchParams(); + const token = useMemo(() => params.get("token")?.trim() ?? "", [params]); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + const router = useRouter(); + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + setError(null); + setSuccess(null); + + if (!token) { + setError("Reset token is missing. Use the full link from your email."); + return; + } + + const formData = new FormData(event.currentTarget); + const password = getFormString(formData, "password"); + const confirmPassword = getFormString(formData, "confirmPassword"); + + if (!isValidPassword(password)) { + setError("Password must be >6 chars and include letters and numbers."); + return; + } + + if (password !== confirmPassword) { + setError("Passwords do not match."); + return; + } + + setIsSubmitting(true); + try { + const response = await fetch("/api/auth/reset-password", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ token, password, confirmPassword }), + }); + if (!response.ok) { + const payload = (await response.json()) as { error?: string }; + setError(payload.error ?? "Unable to reset password."); + return; + } + setSuccess("Password updated. Redirecting to login..."); + setTimeout(() => { + router.push("/"); + }, 1200); + } finally { + setIsSubmitting(false); + } + } + + return ( +

+
+

reset ur password

+

+ enter a new password with letters and numbers. minimum length is 7. +

+
+ + + +
+ {error ?

{error}

: null} + {success ?

{success}

: null} + + back to login + +
+
+ ); +} diff --git a/src/app/share/[fileName]/route.ts b/src/app/share/[fileName]/route.ts index ea4efc0..91797f2 100644 --- a/src/app/share/[fileName]/route.ts +++ b/src/app/share/[fileName]/route.ts @@ -1,10 +1,11 @@ import type { NextRequest } from "next/server"; -import { getAlbumShareByCode, getImage, getShareByCode } from "@/lib/metadata-store"; +import { getAlbumShareByCode, getAppSettings, getImage, getShareByCode } from "@/lib/metadata-store"; import { - getMediaBuffer, - getMediaBufferRange, getMediaBufferSize, - pendingVideoPreviewPng, + getMediaSignedUrl, + getMediaRangeStream, + getMediaStream, + usesS3StorageBackend, } from "@/lib/media-storage"; import { getSharedMediaByCode, getSharedMediaByCodeAndExt } from "@/lib/media-store"; import { contentTypeForExt } from "@/lib/media-types"; @@ -56,6 +57,60 @@ function publicCacheHeaders(ext: string): Headers { }); } +function isDocumentNavigation(request: NextRequest): boolean { + const destination = request.headers.get("sec-fetch-dest"); + const mode = request.headers.get("sec-fetch-mode"); + const accept = request.headers.get("accept") ?? ""; + return destination === "document" || mode === "navigate" || accept.includes("text/html"); +} + +function escapeHtml(value: string): string { + return value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function signedMediaViewerHtml(input: { src: string; mimeType: string; fileName: string }): string { + const safeSrc = escapeHtml(input.src); + const safeName = escapeHtml(input.fileName); + const type = input.mimeType.toLowerCase(); + const body = type.startsWith("image/") + ? `${safeName}` + : type.startsWith("video/") + ? `` + : type.startsWith("audio/") + ? `` + : `

Open file

`; + + return ` + + + + + ${safeName} + + + + ${body} + +`; +} + function parseByteRange(rangeHeader: string, total: number): { start: number; end: number } | null { const match = /^bytes=(\d*)-(\d*)$/i.exec(rangeHeader.trim()); if (!match) { @@ -89,6 +144,9 @@ export async function GET( ): Promise { const { fileName } = await params; const parsed = parseFileName(fileName); + const allowHtmlNavigationMode = usesS3StorageBackend() + ? (await getAppSettings()).shareHtmlNavigationEnabled + : false; try { if (!parsed && /^[A-Za-z0-9]+$/.test(fileName)) { const albumShare = await getAlbumShareByCode(fileName); @@ -126,14 +184,42 @@ export async function GET( : parsed.size === "x640" ? "lg" : parsed.size; - const data = await getMediaBuffer({ + if (usesS3StorageBackend()) { + const responseExt = imageRequestedSize === "original" ? image.ext : "png"; + const mimeType = contentTypeForExt(responseExt); + const signedUrl = await getMediaSignedUrl({ + kind: "image", + baseName: image.baseName, + ext: image.ext, + size: imageRequestedSize, + uploadedAt: new Date(image.uploadedAt), + responseContentType: mimeType, + }); + if (allowHtmlNavigationMode && isDocumentNavigation(request)) { + const html = signedMediaViewerHtml({ + src: signedUrl, + mimeType, + fileName, + }); + return withPublicImageCors( + new Response(html, { + headers: { + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-store", + }, + }), + ); + } + return withPublicImageCors(Response.redirect(signedUrl, 307)); + } + const stream = await getMediaStream({ kind: "image", baseName: image.baseName, ext: image.ext, size: imageRequestedSize, uploadedAt: new Date(image.uploadedAt), }); - return withPublicImageCors(new Response(new Uint8Array(data), { headers: publicCacheHeaders(image.ext) })); + return withPublicImageCors(new Response(stream, { headers: publicCacheHeaders(image.ext) })); } } @@ -145,10 +231,7 @@ export async function GET( return withPublicImageCors(await unavailableImageResponse(parsed.ext)); } if (media.kind === "video" && media.previewStatus !== "ready" && parsed.size !== "original") { - const fallback = await pendingVideoPreviewPng(parsed.size === "sm" ? "sm" : "lg"); - return withPublicImageCors( - new Response(new Uint8Array(fallback), { headers: publicCacheHeaders("png") }), - ); + return withPublicImageCors(new Response("Not found", { status: 404 })); } const requestedSize = media.kind === "image" && media.ext.toLowerCase() === "svg" && parsed.size !== "original" @@ -160,6 +243,35 @@ export async function GET( requestedSize === "original" && (media.kind === "video" || (media.kind === "other" && (media.mimeType ?? "").toLowerCase().startsWith("audio/"))); + if (usesS3StorageBackend()) { + const responseExt = + requestedSize === "original" ? media.ext : media.kind === "image" ? media.ext : "png"; + const mimeType = contentTypeForExt(responseExt); + const signedUrl = await getMediaSignedUrl({ + kind: media.kind, + baseName: media.baseName, + ext: media.ext, + size: requestedSize, + uploadedAt: new Date(media.uploadedAt), + responseContentType: mimeType, + }); + if (allowHtmlNavigationMode && isDocumentNavigation(request)) { + const html = signedMediaViewerHtml({ + src: signedUrl, + mimeType, + fileName, + }); + return withPublicImageCors( + new Response(html, { + headers: { + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-store", + }, + }), + ); + } + return withPublicImageCors(Response.redirect(signedUrl, 307)); + } if (isRangeStreamableOriginal) { const uploadedAt = new Date(media.uploadedAt); const total = await getMediaBufferSize({ @@ -183,7 +295,7 @@ export async function GET( }), ); } - const data = await getMediaBufferRange({ + const stream = await getMediaRangeStream({ kind: media.kind, baseName: media.baseName, ext: media.ext, @@ -196,11 +308,11 @@ export async function GET( headers.set("Content-Range", `bytes ${byteRange.start}-${byteRange.end}/${total}`); headers.set("Content-Length", String(byteRange.end - byteRange.start + 1)); headers.set("Accept-Ranges", "bytes"); - return withPublicImageCors(new Response(new Uint8Array(data), { status: 206, headers })); + return withPublicImageCors(new Response(stream, { status: 206, headers })); } } - const data = await getMediaBuffer({ + const stream = await getMediaStream({ kind: media.kind, baseName: media.baseName, ext: media.ext, @@ -213,7 +325,7 @@ export async function GET( if (isRangeStreamableOriginal) { headers.set("Accept-Ranges", "bytes"); } - return withPublicImageCors(new Response(new Uint8Array(data), { headers })); + return withPublicImageCors(new Response(stream, { headers })); } catch { if (!parsed) { return withPublicImageCors(new Response("Service temporarily unavailable.", { status: 503 })); diff --git a/src/app/share/album/[shareId]/image/[imageId]/[fileName]/route.ts b/src/app/share/album/[shareId]/image/[imageId]/[fileName]/route.ts index 0308341..711ed0a 100644 --- a/src/app/share/album/[shareId]/image/[imageId]/[fileName]/route.ts +++ b/src/app/share/album/[shareId]/image/[imageId]/[fileName]/route.ts @@ -1,6 +1,6 @@ import type { NextRequest } from "next/server"; import { getAlbumShareById, getImage } from "@/lib/metadata-store"; -import { getMediaBuffer } from "@/lib/media-storage"; +import { getMediaSignedUrl, getMediaStream, usesS3StorageBackend } from "@/lib/media-storage"; import { unavailableImageResponse } from "@/lib/unavailable-image"; export const runtime = "nodejs"; @@ -65,14 +65,25 @@ export async function GET( return unavailableImageResponse(parsed.ext); } - const data = await getMediaBuffer({ + if (usesS3StorageBackend()) { + const signedUrl = await getMediaSignedUrl({ + kind: "image", + baseName: image.baseName, + ext: image.ext, + size: parsed.size === "x640" ? "lg" : parsed.size, + uploadedAt: new Date(image.uploadedAt), + responseContentType: contentTypeForExt(image.ext), + }); + return Response.redirect(signedUrl, 307); + } + const data = await getMediaStream({ kind: "image", baseName: image.baseName, ext: image.ext, size: parsed.size === "x640" ? "lg" : parsed.size, uploadedAt: new Date(image.uploadedAt), }); - return new Response(new Uint8Array(data), { + return new Response(data, { headers: publicCacheHeaders(image.ext), }); } catch { diff --git a/src/app/share/album/[shareId]/media/[kind]/[mediaId]/[fileName]/route.ts b/src/app/share/album/[shareId]/media/[kind]/[mediaId]/[fileName]/route.ts index a30dcf8..92dccc1 100644 --- a/src/app/share/album/[shareId]/media/[kind]/[mediaId]/[fileName]/route.ts +++ b/src/app/share/album/[shareId]/media/[kind]/[mediaId]/[fileName]/route.ts @@ -3,10 +3,11 @@ import { getAlbumShareById } from "@/lib/metadata-store"; import { getMedia, type MediaKind } from "@/lib/media-store"; import { contentTypeForExt } from "@/lib/media-types"; import { - getMediaBuffer, - getMediaBufferRange, getMediaBufferSize, - pendingVideoPreviewPng, + getMediaSignedUrl, + getMediaRangeStream, + getMediaStream, + usesS3StorageBackend, } from "@/lib/media-storage"; import { unavailableImageResponse } from "@/lib/unavailable-image"; @@ -102,8 +103,7 @@ export async function GET( } if (media.kind === "video" && media.previewStatus !== "ready" && parsed.size !== "original") { - const fallback = await pendingVideoPreviewPng(parsed.size); - return withPublicCors(new Response(new Uint8Array(fallback), { headers: publicCacheHeaders("png") })); + return withPublicCors(new Response("Not found", { status: 404 })); } const requestedSize = @@ -115,6 +115,19 @@ export async function GET( requestedSize === "original" && (media.kind === "video" || (media.kind === "other" && (media.mimeType ?? "").toLowerCase().startsWith("audio/"))); + if (usesS3StorageBackend()) { + const responseExt = + requestedSize === "original" ? media.ext : media.kind === "image" ? media.ext : "png"; + const signedUrl = await getMediaSignedUrl({ + kind: media.kind, + baseName: media.baseName, + ext: media.ext, + size: requestedSize, + uploadedAt: new Date(media.uploadedAt), + responseContentType: contentTypeForExt(responseExt), + }); + return withPublicCors(Response.redirect(signedUrl, 307)); + } if (isRangeStreamableOriginal) { const uploadedAt = new Date(media.uploadedAt); @@ -139,7 +152,7 @@ export async function GET( }), ); } - const data = await getMediaBufferRange({ + const stream = await getMediaRangeStream({ kind: media.kind, baseName: media.baseName, ext: media.ext, @@ -152,11 +165,11 @@ export async function GET( headers.set("Content-Range", `bytes ${byteRange.start}-${byteRange.end}/${total}`); headers.set("Content-Length", String(byteRange.end - byteRange.start + 1)); headers.set("Accept-Ranges", "bytes"); - return withPublicCors(new Response(new Uint8Array(data), { status: 206, headers })); + return withPublicCors(new Response(stream, { status: 206, headers })); } } - const data = await getMediaBuffer({ + const stream = await getMediaStream({ kind: media.kind, baseName: media.baseName, ext: media.ext, @@ -169,7 +182,7 @@ export async function GET( if (isRangeStreamableOriginal) { headers.set("Accept-Ranges", "bytes"); } - return withPublicCors(new Response(new Uint8Array(data), { headers })); + return withPublicCors(new Response(stream, { headers })); } catch { return withPublicCors(await unavailableImageResponse(parsed.ext)); } diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx index 114309e..70f2517 100644 --- a/src/app/upload/page.tsx +++ b/src/app/upload/page.tsx @@ -5,7 +5,6 @@ import { getAppSettings } from "@/lib/metadata-store"; import UploadDropzone from "@/components/upload-dropzone"; import AlertBanner from "@/components/ui/alert-banner"; import PageHeader from "@/components/ui/page-header"; -import TextLink from "@/components/ui/text-link"; export default async function UploadPage() { const session = await getServerSession(authOptions); diff --git a/src/components/admin-database.tsx b/src/components/admin-database.tsx new file mode 100644 index 0000000..7907daa --- /dev/null +++ b/src/components/admin-database.tsx @@ -0,0 +1,255 @@ +"use client"; + +import { useState } from "react"; + +type ImportResponse = { + message?: string; + error?: string; +}; + +type LegacyMigrationReport = { + backend: "local" | "s3"; + checkedImages: number; + migrated: number; + skippedAlreadyMigrated: number; + missingLegacySource: number; + errors: number; + migratedExamples: string[]; + skippedExamples: string[]; + missingExamples: string[]; + errorExamples: string[]; +}; + +type DbBackupReport = { + fileName: string; + backend: "local" | "s3"; + storagePath: string; + tableCount: number; + totalRows: number; + tables: Array<{ tableName: string; rowCount: number }>; +}; + +export default function AdminDatabase() { + const [file, setFile] = useState(null); + const [s3Key, setS3Key] = useState(""); + const [isUploading, setIsUploading] = useState(false); + const [error, setError] = useState(null); + const [message, setMessage] = useState(null); + + const [backupBusy, setBackupBusy] = useState(false); + const [backupError, setBackupError] = useState(null); + const [backupReport, setBackupReport] = useState(null); + + const [migrationBusy, setMigrationBusy] = useState(false); + const [migrationError, setMigrationError] = useState(null); + const [migrationReport, setMigrationReport] = useState(null); + + async function runLegacyMigration() { + setMigrationBusy(true); + setMigrationError(null); + setMigrationReport(null); + const response = await fetch("/api/admin/settings/migrate-legacy-images", { + method: "POST", + }); + if (!response.ok) { + const payload = (await response.json()) as { error?: string }; + setMigrationError(payload.error ?? "Unable to run migration."); + setMigrationBusy(false); + return; + } + const payload = (await response.json()) as { report?: LegacyMigrationReport }; + setMigrationReport(payload.report ?? null); + setMigrationBusy(false); + } + + async function runDatabaseBackup() { + setBackupBusy(true); + setBackupError(null); + setBackupReport(null); + const response = await fetch("/api/admin/settings/db-backup", { + method: "POST", + }); + if (!response.ok) { + const payload = (await response.json()) as { error?: string }; + setBackupError(payload.error ?? "Unable to create backup."); + setBackupBusy(false); + return; + } + const payload = (await response.json()) as { backup?: DbBackupReport }; + setBackupReport(payload.backup ?? null); + setBackupBusy(false); + } + + async function runImport() { + const trimmedKey = s3Key.trim(); + if (!file && !trimmedKey) { + setError("Choose a .sql/.dump file or enter an S3 object key."); + return; + } + + setError(null); + setMessage(null); + setIsUploading(true); + try { + const response = file + ? await (async () => { + const formData = new FormData(); + formData.append("dump", file); + if (trimmedKey) { + formData.append("s3Key", trimmedKey); + } + return fetch("/api/admin/settings/db-import", { + method: "POST", + body: formData, + }); + })() + : await fetch("/api/admin/settings/db-import", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ s3Key: trimmedKey }), + }); + + const payload = (await response.json()) as ImportResponse; + if (!response.ok) { + setError(payload.error ?? "Import failed."); + return; + } + setMessage(payload.message ?? "Import completed."); + } catch { + setError("Import failed."); + } finally { + setIsUploading(false); + } + } + + return ( + <> +
+

Database Import

+

+ Upload a `.sql` or `.dump` PostgreSQL dump file to import. The server runs `psql` or `pg_restore` + against the current database to restore the data. +

+

NOTE: `IS_ENABLED = true;` must be set in `/src/app/api/admin/settings/db-import/route.ts` to enable this unsafe feature.

+ + +
+ + {message ? {message} : null} + {error ? {error} : null} +
+
+ +
+
+

Create DB backup

+

+ Generates a data-only `.sql` backup by reading all public tables and saves it to storage root. +

+

NOTE: `IS_ENABLED = true;` must be set in `/src/app/api/admin/settings/db-backup/route.ts` to enable this unsafe feature.

+
+ + {backupError ? {backupError} : null} +
+ {backupReport ? ( +
+
+
backend: {backupReport.backend}
+
file: {backupReport.fileName}
+
storage path: {backupReport.storagePath}
+
tables: {backupReport.tableCount}
+
total rows: {backupReport.totalRows}
+
+
+ Show table row counts +
+                  {backupReport.tables.map((entry) => `${entry.tableName}: ${entry.rowCount}`).join("\n") || "(none)"}
+                
+
+
+ ) : null} +
+
+ +
+
+

Legacy image storage migration

+

+ Moves pre-media images from legacy storage paths into the new `/image/...` folder layout. +

+
+ + {migrationError ? {migrationError} : null} +
+ {migrationReport ? ( +
+
+
backend: {migrationReport.backend}
+
images checked: {migrationReport.checkedImages}
+
files migrated: {migrationReport.migrated}
+
already migrated: {migrationReport.skippedAlreadyMigrated}
+
missing legacy source: {migrationReport.missingLegacySource}
+
errors: {migrationReport.errors}
+
+
+ Show detailed output +
+                  {`Migrated examples:
+${migrationReport.migratedExamples.join("\n") || "(none)"}
+
+Skipped examples:
+${migrationReport.skippedExamples.join("\n") || "(none)"}
+
+Missing examples:
+${migrationReport.missingExamples.join("\n") || "(none)"}
+
+Error examples:
+${migrationReport.errorExamples.join("\n") || "(none)"}`}
+                
+
+
+ ) : null} +
+
+ + + ); +} diff --git a/src/components/admin-pg-dump-import.tsx b/src/components/admin-pg-dump-import.tsx deleted file mode 100644 index f4837e5..0000000 --- a/src/components/admin-pg-dump-import.tsx +++ /dev/null @@ -1,99 +0,0 @@ -"use client"; - -import { useState } from "react"; - -type ImportResponse = { - message?: string; - error?: string; -}; - -export default function AdminPgDumpImport() { - const [file, setFile] = useState(null); - const [s3Key, setS3Key] = useState(""); - const [isUploading, setIsUploading] = useState(false); - const [error, setError] = useState(null); - const [message, setMessage] = useState(null); - - async function runImport() { - const trimmedKey = s3Key.trim(); - if (!file && !trimmedKey) { - setError("Choose a .sql/.dump file or enter an S3 object key."); - return; - } - - setError(null); - setMessage(null); - setIsUploading(true); - try { - const response = file - ? await (async () => { - const formData = new FormData(); - formData.append("dump", file); - if (trimmedKey) { - formData.append("s3Key", trimmedKey); - } - return fetch("/api/admin/settings/pg-dump-import", { - method: "POST", - body: formData, - }); - })() - : await fetch("/api/admin/settings/pg-dump-import", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ s3Key: trimmedKey }), - }); - - const payload = (await response.json()) as ImportResponse; - if (!response.ok) { - setError(payload.error ?? "Import failed."); - return; - } - setMessage(payload.message ?? "Import completed."); - } catch { - setError("Import failed."); - } finally { - setIsUploading(false); - } - } - - return ( -
-

Import PostgreSQL dump

-

- Upload a `.sql` or `.dump` PostgreSQL dump file. The server runs `psql` or `pg_restore` - against the current database. -

- - -
- - {message ? {message} : null} - {error ? {error} : null} -
-
- ); -} diff --git a/src/components/admin-settings.tsx b/src/components/admin-settings.tsx index 4259fa5..941d186 100644 --- a/src/components/admin-settings.tsx +++ b/src/components/admin-settings.tsx @@ -10,21 +10,11 @@ type AppSettings = { supportEnabled: boolean; signupsEnabled: boolean; uploadsEnabled: boolean; + shareHtmlNavigationEnabled: boolean; resumableThresholdBytes: number; }; -type LegacyMigrationReport = { - backend: "local" | "s3"; - checkedImages: number; - migrated: number; - skippedAlreadyMigrated: number; - missingLegacySource: number; - errors: number; - migratedExamples: string[]; - skippedExamples: string[]; - missingExamples: string[]; - errorExamples: string[]; -}; + export default function AdminSettings({ initial }: { initial: AppSettings }) { const [motd, setMotd] = useState(initial.motd); @@ -34,14 +24,14 @@ export default function AdminSettings({ initial }: { initial: AppSettings }) { const [supportEnabled, setSupportEnabled] = useState(initial.supportEnabled); const [signupsEnabled, setSignupsEnabled] = useState(initial.signupsEnabled); const [uploadsEnabled, setUploadsEnabled] = useState(initial.uploadsEnabled); + const [shareHtmlNavigationEnabled, setShareHtmlNavigationEnabled] = useState( + initial.shareHtmlNavigationEnabled, + ); const [resumableThresholdMb, setResumableThresholdMb] = useState( Math.max(1, Math.round(initial.resumableThresholdBytes / (1024 * 1024))), ); const [error, setError] = useState(null); const [saved, setSaved] = useState(false); - const [migrationBusy, setMigrationBusy] = useState(false); - const [migrationError, setMigrationError] = useState(null); - const [migrationReport, setMigrationReport] = useState(null); async function save() { setError(null); @@ -57,6 +47,7 @@ export default function AdminSettings({ initial }: { initial: AppSettings }) { supportEnabled, signupsEnabled, uploadsEnabled, + shareHtmlNavigationEnabled, resumableThresholdBytes: Math.max(1024 * 1024, Number(resumableThresholdMb) * 1024 * 1024), }), }); @@ -70,23 +61,7 @@ export default function AdminSettings({ initial }: { initial: AppSettings }) { setSaved(true); } - async function runLegacyMigration() { - setMigrationBusy(true); - setMigrationError(null); - setMigrationReport(null); - const response = await fetch("/api/admin/settings/migrate-legacy-images", { - method: "POST", - }); - if (!response.ok) { - const payload = (await response.json()) as { error?: string }; - setMigrationError(payload.error ?? "Unable to run migration."); - setMigrationBusy(false); - return; - } - const payload = (await response.json()) as { report?: LegacyMigrationReport }; - setMigrationReport(payload.report ?? null); - setMigrationBusy(false); - } + return (
@@ -143,7 +118,7 @@ export default function AdminSettings({ initial }: { initial: AppSettings }) { /> -
+
+
-
-

Legacy image storage migration

-

- Moves pre-media images from legacy storage paths into the new `/image/...` folder layout. -

-
- - {migrationError ? {migrationError} : null} -
- {migrationReport ? ( -
-
-
backend: {migrationReport.backend}
-
images checked: {migrationReport.checkedImages}
-
files migrated: {migrationReport.migrated}
-
already migrated: {migrationReport.skippedAlreadyMigrated}
-
missing legacy source: {migrationReport.missingLegacySource}
-
errors: {migrationReport.errors}
-
-
- Show detailed output -
-{`Migrated examples:
-${migrationReport.migratedExamples.join("\n") || "(none)"}
-
-Skipped examples:
-${migrationReport.skippedExamples.join("\n") || "(none)"}
-
-Missing examples:
-${migrationReport.missingExamples.join("\n") || "(none)"}
-
-Error examples:
-${migrationReport.errorExamples.join("\n") || "(none)"}`}
-              
-
-
- ) : null} -
-
); } diff --git a/src/components/album-share-view.tsx b/src/components/album-share-view.tsx index 906a4ca..b866d58 100644 --- a/src/components/album-share-view.tsx +++ b/src/components/album-share-view.tsx @@ -45,6 +45,7 @@ export default function AlbumShareView({ return (
{item.kind === "image" ? ( + /* eslint-disable-next-line @next/next/no-img-element */ Shared album file("login"); + const [mode, setMode] = useState<"login" | "signup" | "forgot">("login"); const [signUpError, setSignUpError] = useState(null); const [signInError, setSignInError] = useState(null); + const [forgotError, setForgotError] = useState(null); + const [forgotSuccess, setForgotSuccess] = useState(null); const router = useRouter(); async function handleSignUp(event: React.FormEvent) { @@ -15,10 +22,10 @@ export default function AuthForms({ signupsEnabled }: { signupsEnabled: boolean setSignUpError(null); const formData = new FormData(event.currentTarget); - const username = formData.get("signupUsername")?.toString().trim(); - const email = formData.get("signupEmail")?.toString().trim(); - const password = formData.get("signupPassword")?.toString(); - const confirmPassword = formData.get("signupConfirmPassword")?.toString(); + const username = getFormString(formData, "signupUsername")?.trim(); + const email = getFormString(formData, "signupEmail")?.trim(); + const password = getFormString(formData, "signupPassword") ?? undefined; + const confirmPassword = getFormString(formData, "signupConfirmPassword") ?? undefined; if (!username || !email || !password || !confirmPassword) { setSignUpError("bruh"); @@ -71,8 +78,8 @@ export default function AuthForms({ signupsEnabled }: { signupsEnabled: boolean setSignInError(null); const formData = new FormData(event.currentTarget); - const email = formData.get("email")?.toString().trim(); - const password = formData.get("password")?.toString(); + const email = getFormString(formData, "email")?.trim(); + const password = getFormString(formData, "password") ?? undefined; if (!email || !password) { setSignInError("Email and password are required."); @@ -93,6 +100,39 @@ export default function AuthForms({ signupsEnabled }: { signupsEnabled: boolean router.push("/gallery"); } + async function handleForgotPassword(event: React.FormEvent) { + event.preventDefault(); + setForgotError(null); + setForgotSuccess(null); + + const formData = new FormData(event.currentTarget); + const email = getFormString(formData, "forgotEmail")?.trim(); + + if (!email) { + setForgotError("Email is required."); + return; + } + + const response = await fetch("/api/auth/forgot-password", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }), + }); + + if (!response.ok) { + const payload = (await response.json()) as { error?: string }; + setForgotError(payload.error ?? "Unable to process password reset request."); + return; + } + + const payload = (await response.json()) as { message?: string; emailSent?: boolean }; + const suffix = + payload.emailSent === false + ? " Resend is not configured, so for local dev check server logs for the reset link." + : ""; + setForgotSuccess((payload.message ?? "If that account exists, a reset link has been sent.") + suffix); + } + return (
@@ -101,6 +141,8 @@ export default function AuthForms({ signupsEnabled }: { signupsEnabled: boolean onClick={() => { setMode("login"); setSignUpError(null); + setForgotError(null); + setForgotSuccess(null); }} className={`rounded px-3 py-1 ${ mode === "login" ? "bg-black text-white" : "border border-neutral-200" @@ -113,6 +155,8 @@ export default function AuthForms({ signupsEnabled }: { signupsEnabled: boolean onClick={() => { setMode("signup"); setSignInError(null); + setForgotError(null); + setForgotSuccess(null); }} className={`rounded px-3 py-1 ${ mode === "signup" ? "bg-black text-white" : "border border-neutral-200" @@ -179,6 +223,35 @@ export default function AuthForms({ signupsEnabled }: { signupsEnabled: boolean ) : null}
+ ) : mode === "forgot" ? ( +
+

forgot ur password?

+
+ + + + {forgotError ?

{forgotError}

: null} + {forgotSuccess ?

{forgotSuccess}

: null} +
+
) : (

the legend returns

@@ -200,6 +273,16 @@ export default function AuthForms({ signupsEnabled }: { signupsEnabled: boolean + {signInError ? (

{signInError}

) : null} diff --git a/src/components/gallery-client.tsx b/src/components/gallery-client.tsx index be94d53..3d3dac3 100644 --- a/src/components/gallery-client.tsx +++ b/src/components/gallery-client.tsx @@ -78,6 +78,7 @@ export default function GalleryClient({ hideImagesInAlbums = false, kindFilter = "all", resumableThresholdBytes = DEFAULT_RESUMABLE_THRESHOLD, + isAdmin = false, }: { media: GalleryImage[]; onImagesChange?: (next: GalleryImage[]) => void; @@ -86,6 +87,7 @@ export default function GalleryClient({ hideImagesInAlbums?: boolean; kindFilter?: "all" | "image" | "video" | "document" | "other"; resumableThresholdBytes?: number; + isAdmin?: boolean; }) { const [items, setItems] = useState(media); const [active, setActive] = useState(null); @@ -237,6 +239,7 @@ export default function GalleryClient({ window.removeEventListener("dragleave", handleDragLeave); window.removeEventListener("drop", handleDrop); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const filteredItems = useMemo( @@ -1128,6 +1131,7 @@ export default function GalleryClient({ window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [active, activeIndex, displayItems, hasNext, hasPrevious]); return ( @@ -1300,6 +1304,7 @@ export default function GalleryClient({
) : (
+ {/* eslint-disable-next-line @next/next/no-img-element */} Uploaded ) : ( (null); const [deleteError, setDeleteError] = useState(null); - const [hideAlbumImages, setHideAlbumImages] = useState(false); + let storedSetting = ""; + try { + storedSetting = window.localStorage.getItem(HIDE_ALBUM_IMAGES_STORAGE_KEY) ?? "0"; + } catch {} // ignore storage errors + const [hideAlbumImages, setHideAlbumImages] = useState(storedSetting === "1"); const [delBtnLabel, setDelBtnLabel] = useState("del album"); - useEffect(() => { - try { - const stored = window.localStorage.getItem(HIDE_ALBUM_IMAGES_STORAGE_KEY); - if (stored === "1") { - setHideAlbumImages(true); - } - if (stored === "0") { - setHideAlbumImages(false); - } - } catch { - // ignore storage errors - } - }, []); - useEffect(() => { try { window.localStorage.setItem(HIDE_ALBUM_IMAGES_STORAGE_KEY, hideAlbumImages ? "1" : "0"); - } catch { - // ignore storage errors - } + } catch {} // ignore storage errors }, [hideAlbumImages]); const albumPreviews = albumItems.map((album) => { @@ -250,6 +240,7 @@ export default function GalleryTabs({
{album.previews.length > 0 ? ( album.previews.map((image) => ( + /* eslint-disable-next-line @next/next/no-img-element */ )}
diff --git a/src/components/theme/floating-logo.tsx b/src/components/theme/floating-logo.tsx index 42c9dbe..7d950b9 100644 --- a/src/components/theme/floating-logo.tsx +++ b/src/components/theme/floating-logo.tsx @@ -4,22 +4,20 @@ import { useEffect, useState } from "react"; export const FloatingLogo = () => { const [numClicks, setNumClicks] = useState(0); - const [easterEggActivated, setEasterEggActivated] = useState(false); useEffect(() => { - if (numClicks == 5) { - setEasterEggActivated(true); - setNumClicks(0); + if (numClicks == 5) { setTimeout(() => { - setEasterEggActivated(false); + setNumClicks(0); }, 1000); } }, [numClicks]); return ( -
4 ? "easter-egg" : ""}`} onClick={() => { setNumClicks(numClicks + 1); }}> - LaTeX logo + {/* eslint-disable-next-line @next/next/no-img-element */} + LaTeX logo 4 ? "easter-egg" : ""}`} />
); }; \ No newline at end of file diff --git a/src/components/theme/theme-provider.tsx b/src/components/theme/theme-provider.tsx index 00be461..6c3d0eb 100644 --- a/src/components/theme/theme-provider.tsx +++ b/src/components/theme/theme-provider.tsx @@ -99,6 +99,7 @@ export function ThemeProvider({ isSaving, setTheme, }), + // eslint-disable-next-line react-hooks/exhaustive-deps [theme, isSaving], ); @@ -111,6 +112,7 @@ export function useTheme(): ThemeContextValue { return { theme: "dark", isSaving: false, + // eslint-disable-next-line @typescript-eslint/require-await setTheme: async () => undefined, }; } diff --git a/src/components/theme/themes.tsx b/src/components/theme/themes.tsx index f348b80..436d5cf 100644 --- a/src/components/theme/themes.tsx +++ b/src/components/theme/themes.tsx @@ -6,19 +6,18 @@ import { LightGamepad } from '@energiz3r/icon-library/Icons/Light/LightGamepad'; import { LightRadiation } from '@energiz3r/icon-library/Icons/Light/LightRadiation'; // neon-green theme import { LightMicrochip } from '@energiz3r/icon-library/Icons/Light/LightMicrochip'; // cyber theme import { LightRaindrops } from '@energiz3r/icon-library/Icons/Light/LightRaindrops'; // blood theme - -import { LightSeedling } from '@energiz3r/icon-library/Icons/Light/LightSeedling'; // forest (greens) theme -import { LightCrow } from '@energiz3r/icon-library/Icons/Light/LightCrow'; // sky (light blues) theme -import { LightRainbow } from '@energiz3r/icon-library/Icons/Light/LightRainbow'; // rainbow colors theme -import { LightTransgenderAlt } from '@energiz3r/icon-library/Icons/Light/LightTransgenderAlt'; // trans flag colors theme -import { LightHeart } from '@energiz3r/icon-library/Icons/Light/LightHeart'; // pinks theme -import { LightJackOLantern } from '@energiz3r/icon-library/Icons/Light/LightJackOLantern'; // 'pumpkin' theme import { LightRobot } from '@energiz3r/icon-library/Icons/Light/LightRobot'; // cyan theme - import { LightPager } from '@energiz3r/icon-library/Icons/Light/LightPager'; -import { LightGhost } from '@energiz3r/icon-library/Icons/Light/LightGhost'; -import { LightBiohazard } from '@energiz3r/icon-library/Icons/Light/LightBiohazard'; -import { LightSkull } from '@energiz3r/icon-library/Icons/Light/LightSkull'; + +// import { LightSeedling } from '@energiz3r/icon-library/Icons/Light/LightSeedling'; // forest (greens) theme +// import { LightCrow } from '@energiz3r/icon-library/Icons/Light/LightCrow'; // sky (light blues) theme +// import { LightRainbow } from '@energiz3r/icon-library/Icons/Light/LightRainbow'; // rainbow colors theme +// import { LightTransgenderAlt } from '@energiz3r/icon-library/Icons/Light/LightTransgenderAlt'; // trans flag colors theme +// import { LightHeart } from '@energiz3r/icon-library/Icons/Light/LightHeart'; // pinks theme +// import { LightJackOLantern } from '@energiz3r/icon-library/Icons/Light/LightJackOLantern'; // 'pumpkin' theme +// import { LightGhost } from '@energiz3r/icon-library/Icons/Light/LightGhost'; +// import { LightBiohazard } from '@energiz3r/icon-library/Icons/Light/LightBiohazard'; +// import { LightSkull } from '@energiz3r/icon-library/Icons/Light/LightSkull'; export const THEMES = ["dark", "light", "author", "depth", "neon-green", "retro", "cyber", "blood", "robot", "crt"] as const; export const THEME_SET = new Set(THEMES); diff --git a/src/components/upload-dropzone.tsx b/src/components/upload-dropzone.tsx index 74c6a47..bef8c61 100644 --- a/src/components/upload-dropzone.tsx +++ b/src/components/upload-dropzone.tsx @@ -679,6 +679,7 @@ export default function UploadDropzone({ })()}
) : ( + /* eslint-disable-next-line @next/next/no-img-element */ Uploaded thumbnail void; isRegeneratingThumbnail?: boolean; + isAdmin?: boolean; }) { + + const iconClass = "h-12 w-12 text-neutral-500"; + if (kind === "video") { return (
@@ -35,9 +41,12 @@ export function FileViewerContent({
- {previewStatus === "failed" ? "preview failed" : "preview pending"} + preview pending
- {onRegenerateThumbnail ? ( + +
+ ) : null} + {isAdmin && onRegenerateThumbnail ? (
- ) : null}
); } if (kind === "document") { if (previewStatus !== "ready") { - const Icon = getFileIconForExtension(ext); return (
- + {renderFileIconForExtension(ext, { className: iconClass, fill: "currentColor" })}
); } return ( + {/* eslint-disable-next-line @next/next/no-img-element */} Document preview
- {(() => { - const Icon = getFileIconForExtension(ext); - return ; - })()} + {renderFileIconForExtension(ext, { className: iconClass, fill: "currentColor" })}

Reset your password

If you did not request this, you can ignore this email.

`, + }); + return true; +} diff --git a/src/lib/upload-sessions.ts b/src/lib/upload-sessions.ts index cfb4d4f..0c968fc 100644 --- a/src/lib/upload-sessions.ts +++ b/src/lib/upload-sessions.ts @@ -68,7 +68,7 @@ type InitInput = { function mapSession(row: typeof uploadSessions.$inferSelect): UploadSessionEntry { let uploadedParts: Record = {}; try { - uploadedParts = JSON.parse(row.uploadedPartsJson); + uploadedParts = JSON.parse(row.uploadedPartsJson) as Record; } catch { uploadedParts = {}; }