diff --git a/bun.lock b/bun.lock
index 548bb8ebd..d4a888e8d 100644
--- a/bun.lock
+++ b/bun.lock
@@ -7,21 +7,11 @@
"dependencies": {
"@better-auth/core": "1.4.12",
"@better-auth/oauth-provider": "1.4.12",
- "@wooorm/starry-night": "^3.8.0",
"ag-charts-react": "^12.3.0",
"better-auth": "1.4.12",
- "chalk": "^5.6.2",
- "drizzle-orm": "catalog:",
- "posthog-node": "^5.24.1",
- "tailwind-scrollbar-hide": "^4.0.0",
},
"devDependencies": {
- "@better-auth/cli": "^1.4.12",
- "@biomejs/biome": "^2.2.7",
"@types/node": "^24.9.1",
- "concurrently": "^9.2.1",
- "dotenv": "^16.6.1",
- "inquirer": "^12.10.0",
"knip": "^5.82.1",
},
},
@@ -30,19 +20,14 @@
"version": "1.0.0",
"dependencies": {
"@autumn/shared": "workspace:*",
- "@aws-sdk/client-iam": "^3.958.0",
"@clickhouse/client": "catalog:",
"chalk": "^5.3.0",
"dotenv": "^16.5.0",
"drizzle-orm": "catalog:",
- "ink": "^6.6.0",
"inquirer": "^12.6.3",
- "ora": "^9.0.0",
"p-limit": "^7.2.0",
- "react": "^19.2.3",
},
"devDependencies": {
- "@types/inquirer": "^9.0.7",
"tsx": "^4.19.2",
"typescript": "^5.7.3",
},
@@ -52,117 +37,79 @@
"version": "1.0.0",
"dependencies": {
"@ai-sdk/anthropic": "^3.0.9",
- "@anthropic-ai/sdk": "^0.32.1",
"@autumn/shared": "workspace:*",
- "@aws-sdk/client-scheduler": "^3.958.0",
"@aws-sdk/client-sqs": "^3.926.0",
- "@axiomhq/pino": "^1.3.1",
"@better-auth/dash": "catalog:",
"@better-auth/oauth-provider": "^1.4.12",
"@clickhouse/client": "^1.11.2",
- "@date-fns/tz": "^1.2.0",
"@date-fns/utc": "^2.1.0",
"@hatchet-dev/typescript-sdk": "^1.10.5",
- "@hono-rate-limiter/redis": "^0.1.4",
"@hono/node-server": "^1.19.5",
"@hono/zod-validator": "^0.7.3",
- "@hyperbrowser/sdk": "^0.54.0",
"@infisical/sdk": "^4.0.6",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.60.1",
"@opentelemetry/exporter-trace-otlp-proto": "^0.202.0",
- "@opentelemetry/instrumentation-ioredis": "^0.56.0",
"@opentelemetry/resources": "^2.0.1",
- "@opentelemetry/sdk-metrics": "^2.0.1",
"@opentelemetry/sdk-node": "^0.202.0",
"@opentelemetry/sdk-trace-base": "^2.0.1",
- "@opentelemetry/sdk-trace-node": "^2.0.1",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@posthog/ai": "^7.4.2",
"@puzzmo/revenue-cat-webhook-types": "^1.1.0",
- "@react-email/components": "^0.0.42",
"@sentry/bun": "catalog:",
"@supabase/supabase-js": "^2.46.2",
"@types/qs": "^6.14.0",
- "@types/semver": "^7.7.1",
"@typescript/native-preview": "^7.0.0-dev.20251114.1",
- "@upstash/ratelimit": "^2.0.7",
- "@upstash/redis": "^1.35.6",
"@vercel/sdk": "^1.17.0",
"ai": "^6.0.24",
- "arctic": "^3.7.0",
"autumn-js": "^0.1.8",
"axios": "^1.8.3",
- "better-auth": "^1.4.12",
- "body-parser": "^1.20.3",
+ "better-auth": "catalog:",
"bullmq": "^5.56.2",
"chai": "^5.1.2",
- "chai-http": "^5.1.1",
"chalk": "^5.3.0",
"cors": "^2.8.5",
"cron": "^3.5.0",
- "csv-parse": "^5.6.0",
- "csv-parser": "^3.2.0",
- "currency-symbol-map": "^5.1.0",
"date-fns": "^4.1.0",
"decimal.js": "^10.5.0",
- "detect-content-type": "^1.2.0",
"dotenv": "^16.5.0",
"drizzle-orm": "catalog:",
"express": "^4.21.1",
- "express-rate-limit": "^7.5.1",
- "fetch-retry": "^6.0.0",
"hono": "4.9.9",
"hono-rate-limiter": "^0.4.2",
"http-status-codes": "^2.3.0",
- "ink": "^6.3.1",
- "ink-spinner": "^5.0.0",
"ioredis": "^5.5.0",
"jose": "^6.1.0",
"ksuid": "^3.0.0",
- "lodash-es": "^4.17.21",
"loops": "^5.0.1",
- "mime-detect": "^1.3.0",
"nanoid": "^5.1.6",
- "openai": "^4.85.2",
- "p-limit": "^7.2.0",
"pg": "^8.13.1",
"pino": "^9.6.0",
- "pino-pretty": "^13.0.0",
"postgres": "^3.4.7",
"posthog-node": "^5.20.0",
"puppeteer-core": "^24.14.0",
"qs": "^6.14.0",
"react": "^18.2.0",
"resend": "^4.1.1",
- "semver": "^7.7.2",
"stripe": "catalog:",
"svix": "^1.45.1",
- "tsc-alias": "^1.8.16",
- "ws": "^8.18.0",
"zod": "^3.25.23",
},
"devDependencies": {
"@types/bun": "^1.3.1",
"@types/chai": "^5.0.1",
- "@types/chai-http": "^3.0.5",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
- "@types/lodash-es": "^4.17.12",
"@types/mocha": "^10.0.10",
"@types/node": "^25.0.7",
"@types/pg": "^8.11.10",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
- "@types/ws": "^8.18.1",
"cross-env": "^7.0.3",
"drizzle-kit": "catalog:",
"mocha": "^11.1.0",
"nodemon": "^3.1.10",
"react-email": "4.0.16",
- "ts-node": "^10.9.2",
- "tsconfig-paths": "^4.2.0",
- "tsx": "^4.21.0",
"typescript": "^5.7.3",
},
},
@@ -176,7 +123,6 @@
"dotenv": "^16.5.0",
"drizzle-kit": "catalog:",
"drizzle-orm": "catalog:",
- "drizzle-zod": "^0.8.2",
"stripe": "catalog:",
"yaml": "^2.8.1",
"zod-openapi": "^5.4.1",
@@ -185,8 +131,6 @@
"@types/bun": "latest",
"@types/node": "^24.0.3",
"cross-env": "^7.0.3",
- "nodemon": "^3.1.7",
- "tsx": "^4.19.4",
"typescript": "^5.7.2",
},
"peerDependencies": {
@@ -198,26 +142,18 @@
"version": "0.0.0",
"dependencies": {
"@ai-sdk/react": "^3.0.25",
- "@amplitude/unified": "^1.0.0-beta.9",
"@autumn/shared": "workspace:*",
"@better-auth/dash": "catalog:",
- "@better-auth/stripe": "^1.2.12",
- "@date-fns/utc": "^2.1.1",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
- "@heroicons/react": "^2.2.0",
- "@jsx-tool/jsx-tool": "^0.0.21",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-checkbox": "^1.1.4",
- "@radix-ui/react-collapsible": "^1.1.12",
- "@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-popover": "^1.1.14",
- "@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
@@ -226,53 +162,36 @@
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-tooltip": "^1.2.8",
- "@radix-ui/react-use-controllable-state": "^1.2.2",
"@sentry/react": "^10.25.0",
"@sentry/vite-plugin": "^4.6.0",
"@squircle/core": "^1.0.6",
"@squircle/paint-polyfill": "^1.0.6",
- "@squircle/tailwindcss": "^1.0.6",
- "@supabase/ssr": "^0.5.2",
"@tailwindcss/vite": "^4.0.13",
"@tanstack/react-form": "^1.23.8",
"@tanstack/react-query": "^5.85.6",
- "@tanstack/react-query-devtools": "^5.85.6",
"@tanstack/react-table": "^8.21.3",
- "@types/recharts": "^2.0.1",
- "@wooorm/starry-night": "^3.6.0",
- "@xyflow/react": "^12.10.0",
"ag-charts-community": "^12.0.2",
"ag-grid-community": "^34.0.2",
"ag-grid-react": "^34.0.2",
"ai": "^6.0.5",
"autumn-js": "^0.1.8",
"axios": "^1.8.3",
- "better-auth": "1.4.12",
- "buffer": "^6.0.3",
+ "better-auth": "catalog:",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^3.6.0",
"decimal.js": "^10.5.0",
- "embla-carousel-react": "^8.6.0",
- "hast-util-to-html": "^9.0.5",
"input-otp": "^1.4.2",
"lodash": "^4.17.21",
"lucide-react": "^0.562.0",
"motion": "^12.26.1",
"nanoid": "^5.1.6",
- "next-themes": "^0.4.6",
"nuqs": "^2.4.3",
"posthog-js": "^1.230.4",
- "prism-themes": "^1.9.0",
- "prismjs": "^1.29.0",
"react": "^18.2.0",
- "react-cmdk": "^1.3.9",
- "react-confetti-explosion": "^2.1.2",
"react-day-picker": "^8.10.1",
"react-dom": "^18.2.0",
- "react-dropzone": "^14.3.8",
- "react-hot-toast": "^2.5.2",
"react-hotkeys-hook": "^4.6.1",
"react-router": "^7.3.0",
"react-router-dom": "^7.6.2",
@@ -284,11 +203,7 @@
"svix-react": "^1.13.3",
"swr": "^2.3.3",
"tailwind-merge": "^3.0.2",
- "tailwindcss": "^4.0.13",
- "tailwindcss-animate": "^1.0.7",
- "tokenlens": "^1.3.1",
"use-stick-to-bottom": "^1.1.1",
- "vaul": "^1.1.2",
"zod": "^3.25.23",
"zustand": "^5.0.8",
},
@@ -296,11 +211,8 @@
"@eslint/js": "^9.21.0",
"@types/lodash": "^4.17.16",
"@types/node": "^22.13.10",
- "@types/prismjs": "^1.26.5",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
- "@types/react-router-dom": "^5.3.3",
- "@typescript/native-preview": "^7.0.0-dev.20251111.1",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
@@ -309,7 +221,6 @@
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0",
- "vite-plugin-node-polyfills": "^0.24.0",
"vite-tsconfig-paths": "^5.1.4",
},
},
@@ -323,6 +234,7 @@
"@clickhouse/client": "1.11.2",
"@date-fns/utc": "2.1.0",
"@sentry/bun": "10.25.0",
+ "better-auth": "catalog:",
"drizzle-kit": "^0.31.1",
"drizzle-orm": "0.43.1",
"stripe": "19.3.0-beta.1",
@@ -338,65 +250,9 @@
"@ai-sdk/react": ["@ai-sdk/react@3.0.59", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.10", "ai": "6.0.57", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-oUzalId/oEd2E1/3wkrVUDp3VFTBJlBzr2PaKbr6DxGzsUSftFGSAQVaG/Xv+DId+sCfhzhRzVo4XpL4vKgQqw=="],
- "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.3", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-jsElTJ0sQ4wHRz+C45tfect76BwbTbgkgKByOzpCN9xG61N5V6u/glvg1CsNJhq2xJIFpKHSwG3D2wPPuEYOrQ=="],
-
- "@amplitude/analytics-browser": ["@amplitude/analytics-browser@2.31.0-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "@amplitude/plugin-autocapture-browser": "1.18.0-zen-plus-zoning.2", "@amplitude/plugin-network-capture-browser": "1.6.14-zen-plus-zoning.2", "@amplitude/plugin-page-url-enrichment-browser": "0.5.3-zen-plus-zoning.2", "@amplitude/plugin-page-view-tracking-browser": "2.6.0-zen-plus-zoning.2", "@amplitude/plugin-web-vitals-browser": "1.0.1-zen-plus-zoning.2", "tslib": "^2.4.1" } }, "sha512-wkN7XBV2OUKkE3yxuyK8EIhB1I1Ee4D1HLZ7dn4ffEuWSO+o7rT3PuecXA7I8Anl3dsvIG2RiQtYa04cuWmTPQ=="],
-
- "@amplitude/analytics-client-common": ["@amplitude/analytics-client-common@2.4.13-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-connector": "^1.4.8", "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "@amplitude/analytics-types": "^2.11.0", "tslib": "^2.4.1" } }, "sha512-AUpvhHe3dkzvAm6cgnwmY/v99t3AX67PbqkDZtWgXPJD47te3nUkFXELtqwBeD3aBtAtS4m41emU4fSQIFN23A=="],
-
- "@amplitude/analytics-connector": ["@amplitude/analytics-connector@1.6.4", "", {}, "sha512-SpIv0IQMNIq6SH3UqFGiaZyGSc7PBZwRdq7lvP0pBxW8i4Ny+8zwI0pV+VMfMHQwWY3wdIbWw5WQphNjpdq1/Q=="],
-
- "@amplitude/analytics-core": ["@amplitude/analytics-core@2.32.0-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-connector": "^1.6.4", "tslib": "^2.4.1", "zen-observable-ts": "^1.1.0" } }, "sha512-hkQ7GQ+HSvtrqnJrXBJf0AhlI5StWE0XDYy8uYMcNWV35tKBYFIaoGEV8PjEcuVLbM9BjP7owhDAqko2jNfqrg=="],
-
- "@amplitude/analytics-types": ["@amplitude/analytics-types@1.4.0", "", {}, "sha512-RiMPHBqdrJ8ktTqG+Wzj2htnN/PCG9jGZG0SXtTFnWwVvcAJYbYm55/nrP1TTyrx1OlLhvF2VG3lVUP/xGAU8w=="],
-
- "@amplitude/engagement-browser": ["@amplitude/engagement-browser@1.0.6", "", { "dependencies": { "@amplitude/analytics-types": "^1.0.0" } }, "sha512-g689fpbJ8tEgnUQI1bNZj2nVx9WDa+qTWf8H4Q2nHSYpHZP+PMBcI+dRFo73pSdprZgAec4+8Kd4cn8xezFoVw=="],
-
- "@amplitude/experiment-core": ["@amplitude/experiment-core@0.12.0", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-EiLLxcyJD8T3GFsMPxBfWx9n9fBw6rC0RJwccPXLzResE0HnGZZpVWF86ZndnYmEMD1lUUjWi41N1ymEzodI5w=="],
-
- "@amplitude/experiment-js-client": ["@amplitude/experiment-js-client@1.20.3", "", { "dependencies": { "@amplitude/analytics-connector": "^1.6.4", "@amplitude/experiment-core": "^0.12.0", "@amplitude/ua-parser-js": "^0.7.31", "base64-js": "1.5.1", "unfetch": "4.1.0" } }, "sha512-rv778CbjisEaYcHW9UyFcSQu1TqJPqR+lo8yYPZ50UKDGQfK6hKtWY4zPKRdwnr49ZF0Z+Bm/haYBNNP0aAW8Q=="],
-
- "@amplitude/plugin-autocapture-browser": ["@amplitude/plugin-autocapture-browser@1.18.0-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "tslib": "^2.4.1" } }, "sha512-68pbfyR7SxEvCrCnu4E8KSkr8sWXRxA3O3eFOyatmqSXqCJmyR7Pu7MtKZJy0nsfFaWuRg+vDL9jBiPxSR18OQ=="],
-
- "@amplitude/plugin-experiment-browser": ["@amplitude/plugin-experiment-browser@1.0.0-beta.4", "", { "dependencies": { "@amplitude/analytics-core": "2.37.0", "@amplitude/experiment-js-client": "^1.15.5" } }, "sha512-p7f9oi5+rtTEwC0OYohrjlFr/JjLEp20sZM2p+ZAMuE8XGLzx4TZyZXcryXgMnTbaa7TkXJrPAzwXGHNkCgM3A=="],
-
- "@amplitude/plugin-network-capture-browser": ["@amplitude/plugin-network-capture-browser@1.6.14-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "tslib": "^2.4.1" } }, "sha512-P3Ky7XF4XLciCdXlnJw0UVNBX2JqPXYRGryQZ8CJ/sg5dRzIkX2e5k16x5PeFPfI8puG+RcCid0AohFfSNx+GA=="],
-
- "@amplitude/plugin-page-url-enrichment-browser": ["@amplitude/plugin-page-url-enrichment-browser@0.5.3-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "tslib": "^2.4.1" } }, "sha512-Vey2ZqSi/7jBqnVkOw9odShmtfnkzSqTKZsQOmum4TlaDnVMy3M4EvDPQ6RzeOgXa26NXoi9pqFb/mkXq/SAHA=="],
-
- "@amplitude/plugin-page-view-tracking-browser": ["@amplitude/plugin-page-view-tracking-browser@2.6.0-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "tslib": "^2.4.1" } }, "sha512-9gU24zDd+wAnehyBsk0H9bP3MCLWhGHacsCYak1hKLNPSlS22a/6i/1AqKilb/cobZBm2PmagPO2IdUq11ERVA=="],
-
- "@amplitude/plugin-session-replay-browser": ["@amplitude/plugin-session-replay-browser@1.23.3-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-client-common": "2.4.13-zen-plus-zoning.2", "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "@amplitude/analytics-types": "^2.11.0", "@amplitude/session-replay-browser": "1.29.5-zen-plus-zoning.2", "idb-keyval": "^6.2.1", "tslib": "^2.4.1" } }, "sha512-icb4/8gxEoQJaHI0kt9lpx6gzsUu0wXTi5kwelGEok9qyj0F4zq65jLPiCb0ROqWlFYfiT3QVAMG6uWyT4aRAA=="],
-
- "@amplitude/plugin-web-vitals-browser": ["@amplitude/plugin-web-vitals-browser@1.0.1-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "tslib": "^2.4.1", "web-vitals": "^5.0.1" } }, "sha512-V9KAbJTmbSlLaMeIejJC2Pz1QyM58gT4B16x7JSpVpbqTZ82jKT024oEfWH8vwiqY9dVdpVpor3J2RVQryMQgQ=="],
-
- "@amplitude/rrdom": ["@amplitude/rrdom@2.0.0-alpha.35", "", { "dependencies": { "@amplitude/rrweb-snapshot": "^2.0.0-alpha.35" } }, "sha512-W9ImCKtgFB8oBKd7td0TH7JKkQ/3iwu5bfLXcOvzxLj7+RSD1k1gfDyncooyobwBV8j4FMiTyj2N53tJ6rFgaw=="],
-
- "@amplitude/rrweb": ["@amplitude/rrweb@2.0.0-alpha.35", "", { "dependencies": { "@amplitude/rrdom": "^2.0.0-alpha.35", "@amplitude/rrweb-snapshot": "^2.0.0-alpha.35", "@amplitude/rrweb-types": "^2.0.0-alpha.35", "@amplitude/rrweb-utils": "^2.0.0-alpha.35", "@types/css-font-loading-module": "0.0.7", "@xstate/fsm": "^1.4.0", "base64-arraybuffer": "^1.0.1", "mitt": "^3.0.0" } }, "sha512-qFaZDNMkjolZUVv1OxrWngGl38FH0iF0jtybd/vhuOzvwohJjyKL9Tgoulj8osj21/4BUpGEhWweGeJygjoJJw=="],
-
- "@amplitude/rrweb-packer": ["@amplitude/rrweb-packer@2.0.0-alpha.32", "", { "dependencies": { "@amplitude/rrweb-types": "^2.0.0-alpha.32", "fflate": "^0.4.4" } }, "sha512-vYT0JFzle/FV9jIpEbuumCLh516az6ltAo7mrd06dlGo1tgos7bJbl3kcnvEXmDG7WWsKwip/Qprap7cZ4CmJw=="],
-
- "@amplitude/rrweb-plugin-console-record": ["@amplitude/rrweb-plugin-console-record@2.0.0-alpha.32", "", { "peerDependencies": { "@amplitude/rrweb": "^2.0.0-alpha.32" } }, "sha512-oJuBSNuBnqnrRCneW3b/pMirSz0Ubr2Ebz/t+zJhkGBgrTPNMviv8sSyyGuSn0kL4RAh/9QAG1H1hiYf9cuzgA=="],
-
- "@amplitude/rrweb-record": ["@amplitude/rrweb-record@2.0.0-alpha.32", "", { "dependencies": { "@amplitude/rrweb": "^2.0.0-alpha.32", "@amplitude/rrweb-types": "^2.0.0-alpha.32" } }, "sha512-bs5ItsPfedVNiZyIzYgtey6S6qaU90XcP4/313dcvedzBk9o+eVjBG5DDbStJnwYnSj+lB+oAWw5uc9H9ghKjQ=="],
-
- "@amplitude/rrweb-snapshot": ["@amplitude/rrweb-snapshot@2.0.0-alpha.35", "", { "dependencies": { "postcss": "^8.4.38" } }, "sha512-n55AdmlRNZ7XuOlCRmSjH2kyyHS1oe5haUS+buxqjfQcamUtam+dSnP+6N1E8dLxIDjynJnbrCOC+8xvenpl1A=="],
-
- "@amplitude/rrweb-types": ["@amplitude/rrweb-types@2.0.0-alpha.32", "", {}, "sha512-tDs8uizkG+UwE2GKjXh+gH8WhUz0C3y7WfTwrtWi1TnsVc00sXaKSUo5G2h4YF4PGK6dpnLgJBqTwrqCZ211AQ=="],
-
- "@amplitude/rrweb-utils": ["@amplitude/rrweb-utils@2.0.0-alpha.32", "", {}, "sha512-DCCQjuNACkIMkdY5/KBaEgL4znRHU694ClW3RIjqFXJ6j6pqGyjEhCqtlCes+XwdgwOQKnJGMNka3J9rmrSqHg=="],
-
- "@amplitude/session-replay-browser": ["@amplitude/session-replay-browser@1.29.5-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-client-common": "2.4.13-zen-plus-zoning.2", "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "@amplitude/analytics-types": "^2.11.0", "@amplitude/rrweb-packer": "2.0.0-alpha.32", "@amplitude/rrweb-plugin-console-record": "2.0.0-alpha.32", "@amplitude/rrweb-record": "2.0.0-alpha.32", "@amplitude/rrweb-types": "2.0.0-alpha.32", "@amplitude/rrweb-utils": "2.0.0-alpha.32", "@amplitude/targeting": "0.2.0", "@rollup/plugin-replace": "^6.0.1", "idb": "8.0.0", "tslib": "^2.4.1" } }, "sha512-TdZev1I4X8/rlNZvB9TvQz5QIi14KH5nOpAn4rLLBQu202i1K0thc7KPYkILNCjw/QQNeFxNuhFdreWCJWi9yw=="],
-
- "@amplitude/targeting": ["@amplitude/targeting@0.2.0", "", { "dependencies": { "@amplitude/analytics-client-common": ">=1 <3", "@amplitude/analytics-core": ">=1 <3", "@amplitude/analytics-types": ">=1 <3", "@amplitude/experiment-core": "0.7.2", "idb": "^8.0.0", "tslib": "^2.4.1" } }, "sha512-/50ywTrC4hfcfJVBbh5DFbqMPPfaIOivZeb5Gb+OGM03QrA+lsUqdvtnKLNuWtceD4H6QQ2KFzPJ5aAJLyzVDA=="],
-
- "@amplitude/ua-parser-js": ["@amplitude/ua-parser-js@0.7.33", "", {}, "sha512-wKEtVR4vXuPT9cVEIJkYWnlF++Gx3BdLatPBM+SZ1ztVIvnhdGBZR/mn9x/PzyrMcRlZmyi6L56I2J3doVBnjA=="],
-
- "@amplitude/unified": ["@amplitude/unified@1.0.0-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-browser": "2.31.0-zen-plus-zoning.2", "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "@amplitude/engagement-browser": "^1.0.3", "@amplitude/plugin-experiment-browser": "^1.0.0-beta.0", "@amplitude/plugin-session-replay-browser": "1.23.3-zen-plus-zoning.2" } }, "sha512-/0fu52DLRJcBPplGkQX9CgbjuvpVSJ08O2QQL7UysWsLSYWqUUsfX48OtWG0dSCx8QA00sydins+lmZCGvmnNw=="],
-
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
- "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.32.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg=="],
+ "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.71.2", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ=="],
"@apm-js-collab/code-transformer": ["@apm-js-collab/code-transformer@0.8.2", "", {}, "sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA=="],
@@ -422,10 +278,6 @@
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.600.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/client-sso-oidc": "3.600.0", "@aws-sdk/client-sts": "3.600.0", "@aws-sdk/core": "3.598.0", "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", "@aws-sdk/middleware-user-agent": "3.598.0", "@aws-sdk/region-config-resolver": "3.598.0", "@aws-sdk/types": "3.598.0", "@aws-sdk/util-endpoints": "3.598.0", "@aws-sdk/util-user-agent-browser": "3.598.0", "@aws-sdk/util-user-agent-node": "3.598.0", "@smithy/config-resolver": "^3.0.2", "@smithy/core": "^2.2.1", "@smithy/fetch-http-handler": "^3.0.2", "@smithy/hash-node": "^3.0.1", "@smithy/invalid-dependency": "^3.0.1", "@smithy/middleware-content-length": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", "@smithy/middleware-retry": "^3.0.4", "@smithy/middleware-serde": "^3.0.1", "@smithy/middleware-stack": "^3.0.1", "@smithy/node-config-provider": "^3.1.1", "@smithy/node-http-handler": "^3.0.1", "@smithy/protocol-http": "^4.0.1", "@smithy/smithy-client": "^3.1.2", "@smithy/types": "^3.1.0", "@smithy/url-parser": "^3.0.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.4", "@smithy/util-defaults-mode-node": "^3.0.4", "@smithy/util-endpoints": "^2.0.2", "@smithy/util-middleware": "^3.0.1", "@smithy/util-retry": "^3.0.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-8dYsnDLiD0rjujRiZZl0E57heUkHqMSFZHBi0YMs57SM8ODPxK3tahwDYZtS7bqanvFKZwGy+o9jIcij7jBOlA=="],
- "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.975.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.1", "@aws-sdk/credential-provider-node": "^3.972.1", "@aws-sdk/middleware-host-header": "^3.972.1", "@aws-sdk/middleware-logger": "^3.972.1", "@aws-sdk/middleware-recursion-detection": "^3.972.1", "@aws-sdk/middleware-user-agent": "^3.972.2", "@aws-sdk/region-config-resolver": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "^3.972.1", "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.21.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.11", "@smithy/middleware-retry": "^4.4.27", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.26", "@smithy/util-defaults-mode-node": "^4.2.29", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-CRl7uwKWq16fc2cV4Arh48G9ktvr1SrLj0Hy3/FbvQ1YuACwQfcH+JJ9kPAeRTgpgnllPtPQyeNwbC5MLo+UaQ=="],
-
- "@aws-sdk/client-scheduler": ["@aws-sdk/client-scheduler@3.975.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.1", "@aws-sdk/credential-provider-node": "^3.972.1", "@aws-sdk/middleware-host-header": "^3.972.1", "@aws-sdk/middleware-logger": "^3.972.1", "@aws-sdk/middleware-recursion-detection": "^3.972.1", "@aws-sdk/middleware-user-agent": "^3.972.2", "@aws-sdk/region-config-resolver": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "^3.972.1", "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.21.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.11", "@smithy/middleware-retry": "^4.4.27", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.26", "@smithy/util-defaults-mode-node": "^4.2.29", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wBUcXhiYA78pPDobojgkGWCh0LspGCPe0ijceLW692uW9r1MiqW8EhY4osfKRBqzbGvO9CdYbwD5fitu7kpwDw=="],
-
"@aws-sdk/client-sqs": ["@aws-sdk/client-sqs@3.975.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.1", "@aws-sdk/credential-provider-node": "^3.972.1", "@aws-sdk/middleware-host-header": "^3.972.1", "@aws-sdk/middleware-logger": "^3.972.1", "@aws-sdk/middleware-recursion-detection": "^3.972.1", "@aws-sdk/middleware-sdk-sqs": "^3.972.2", "@aws-sdk/middleware-user-agent": "^3.972.2", "@aws-sdk/region-config-resolver": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "^3.972.1", "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.21.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.11", "@smithy/middleware-retry": "^4.4.27", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.26", "@smithy/util-defaults-mode-node": "^4.2.29", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6KS8T24LkEp2QZ/529SKVHQTgvCDUWXE8NtrILUBxZ9e3BiprjC9JSEdMqgh82BUD8s8yv4nnoa4Faiz7lRFpw=="],
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.598.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.598.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", "@aws-sdk/middleware-user-agent": "3.598.0", "@aws-sdk/region-config-resolver": "3.598.0", "@aws-sdk/types": "3.598.0", "@aws-sdk/util-endpoints": "3.598.0", "@aws-sdk/util-user-agent-browser": "3.598.0", "@aws-sdk/util-user-agent-node": "3.598.0", "@smithy/config-resolver": "^3.0.2", "@smithy/core": "^2.2.1", "@smithy/fetch-http-handler": "^3.0.2", "@smithy/hash-node": "^3.0.1", "@smithy/invalid-dependency": "^3.0.1", "@smithy/middleware-content-length": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", "@smithy/middleware-retry": "^3.0.4", "@smithy/middleware-serde": "^3.0.1", "@smithy/middleware-stack": "^3.0.1", "@smithy/node-config-provider": "^3.1.1", "@smithy/node-http-handler": "^3.0.1", "@smithy/protocol-http": "^4.0.1", "@smithy/smithy-client": "^3.1.2", "@smithy/types": "^3.1.0", "@smithy/url-parser": "^3.0.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.4", "@smithy/util-defaults-mode-node": "^3.0.4", "@smithy/util-endpoints": "^2.0.2", "@smithy/util-middleware": "^3.0.1", "@smithy/util-retry": "^3.0.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-nOI5lqPYa+YZlrrzwAJywJSw3MKVjvu6Ge2fCqQUNYMfxFB0NAaDFnl0EPjXi+sEbtCuz/uWE77poHbqiZ+7Iw=="],
@@ -492,10 +344,6 @@
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
- "@axiomhq/js": ["@axiomhq/js@1.4.0", "", { "dependencies": { "fetch-retry": "^6.0.0" } }, "sha512-wC5x1ud/QJMstrjpicATkyY8+ZVWEl4WlXMtA5EZf7hkj0+b191yv4yynLxLEfr/MveXora9m6CWdJq4DsbcAg=="],
-
- "@axiomhq/pino": ["@axiomhq/pino@1.4.0", "", { "dependencies": { "@axiomhq/js": "1.4.0", "pino-abstract-transport": "^1.2.0" } }, "sha512-7ujZM1kqbA98BWl8ltWdTLSDE7+67nOvR30/hm8P6omISjaQV0xgtdtZtDGe/BH2VmZeQWPGfAI1FnUNYC3CtQ=="],
-
"@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
"@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="],
@@ -504,28 +352,16 @@
"@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
- "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
-
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
- "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="],
-
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
- "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="],
-
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
- "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="],
-
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
- "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="],
-
- "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="],
-
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
@@ -536,30 +372,10 @@
"@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
- "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
-
- "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
-
- "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="],
-
- "@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA=="],
-
- "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-jsx": "^7.28.6", "@babel/types": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow=="],
-
- "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="],
-
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
- "@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA=="],
-
- "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
-
- "@babel/preset-react": ["@babel/preset-react@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ=="],
-
- "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="],
-
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
@@ -568,76 +384,38 @@
"@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
- "@better-auth/cli": ["@better-auth/cli@1.4.17", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/preset-react": "^7.27.1", "@babel/preset-typescript": "^7.27.1", "@better-auth/core": "1.4.17", "@better-auth/telemetry": "1.4.17", "@better-auth/utils": "0.3.0", "@clack/prompts": "^0.11.0", "@mrleebo/prisma-ast": "^0.13.0", "@prisma/client": "^5.22.0", "@types/pg": "^8.15.5", "better-auth": "^1.4.17", "better-sqlite3": "^12.2.0", "c12": "^3.2.0", "chalk": "^5.6.2", "commander": "^12.1.0", "dotenv": "^17.2.2", "drizzle-orm": "^0.41.0", "open": "^10.2.0", "pg": "^8.16.3", "prettier": "^3.6.2", "prompts": "^2.4.2", "semver": "^7.7.2", "yocto-spinner": "^0.2.3", "zod": "^4.3.5" }, "bin": { "better-auth": "dist/index.mjs" } }, "sha512-GT0epRZCRAxwDnDNVsAVXx2W4PNjBfm8NwrtjxfYKRHsnrCHkT0N/cZyKCYG1sReN/wlsyYk8bJ6f1N1P4VT8w=="],
-
"@better-auth/core": ["@better-auth/core@1.4.12", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.7", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-VfqZwMAEl9rnGx092BIZ2Q5z8rt7jjN2OAbvPqehufSKZGmh8JsdtZRBMl/CHQir9bwi2Ev0UF4+7TQp+DXEMg=="],
"@better-auth/dash": ["@better-auth/dash@0.1.6", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.21", "better-call": "^1.1.8", "jose": "^6.1.0", "libphonenumber-js": "^1.12.33", "mailchecker": "^6.0.19" }, "peerDependencies": { "better-auth": ">=1.4.0" } }, "sha512-5FTWeto6oZVhZRHvHiAEM0D6ifQKbjdVTBUidd9gUn5f+SSa1Zxn4qPtW+iWyJPDLWanXZNXoXGvggtj1TQn9w=="],
"@better-auth/oauth-provider": ["@better-auth/oauth-provider@1.4.12", "", { "dependencies": { "jose": "^6.1.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/core": "1.4.12", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-auth": "1.4.12", "better-call": "1.1.7" } }, "sha512-YzyXU7dWUESfui1KVs18GwkQNHfcRHj1ZkXqSLMlWfVZp3LJzLXhVdYQx5WBkj0B/UIY4aJQTX15dNxu5eaKGw=="],
- "@better-auth/stripe": ["@better-auth/stripe@1.4.17", "", { "dependencies": { "defu": "^6.1.4", "zod": "^4.3.5" }, "peerDependencies": { "@better-auth/core": "1.4.17", "better-auth": "1.4.17", "stripe": "^18 || ^19 || ^20" } }, "sha512-BQpd7BYkbusiBf1a5MGUXFFhLe8wZXx5CbKWu7osN2JU7z8KP1ZocQrLDV+dy6n5e5SioQiF5+EqCiDwqTL+7g=="],
-
- "@better-auth/telemetry": ["@better-auth/telemetry@1.4.17", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.17" } }, "sha512-R1BC4e/bNjQbXu7lG6ubpgmsPj7IMqky5DvMlzAtnAJWJhh99pMh/n6w5gOHa0cqDZgEAuj75IPTxv+q3YiInA=="],
+ "@better-auth/telemetry": ["@better-auth/telemetry@1.4.12", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.12" } }, "sha512-4q504Og42PzkUbZjXDt+FyeYaS0WZmAlEOC3nbBCZDObTVCRUnGgJW52B2maJ7BCVvAQgBGLEeQmQzU5+63J0A=="],
"@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="],
"@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],
- "@biomejs/biome": ["@biomejs/biome@2.3.13", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.13", "@biomejs/cli-darwin-x64": "2.3.13", "@biomejs/cli-linux-arm64": "2.3.13", "@biomejs/cli-linux-arm64-musl": "2.3.13", "@biomejs/cli-linux-x64": "2.3.13", "@biomejs/cli-linux-x64-musl": "2.3.13", "@biomejs/cli-win32-arm64": "2.3.13", "@biomejs/cli-win32-x64": "2.3.13" }, "bin": { "biome": "bin/biome" } }, "sha512-Fw7UsV0UAtWIBIm0M7g5CRerpu1eKyKAXIazzxhbXYUyMkwNrkX/KLkGI7b+uVDQ5cLUMfOC9vR60q9IDYDstA=="],
-
- "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0OCwP0/BoKzyJHnFdaTk/i7hIP9JHH9oJJq6hrSCPmJPo8JWcJhprK4gQlhFzrwdTBAW4Bjt/RmCf3ZZe59gwQ=="],
-
- "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-AGr8OoemT/ejynbIu56qeil2+F2WLkIjn2d8jGK1JkchxnMUhYOfnqc9sVzcRxpG9Ycvw4weQ5sprRvtb7Yhcw=="],
-
- "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-xvOiFkrDNu607MPMBUQ6huHmBG1PZLOrqhtK6pXJW3GjfVqJg0Z/qpTdhXfcqWdSZHcT+Nct2fOgewZvytESkw=="],
-
- "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-TUdDCSY+Eo/EHjhJz7P2GnWwfqet+lFxBZzGHldrvULr59AgahamLs/N85SC4+bdF86EhqDuuw9rYLvLFWWlXA=="],
-
- "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.13", "", { "os": "linux", "cpu": "x64" }, "sha512-s+YsZlgiXNq8XkgHs6xdvKDFOj/bwTEevqEY6rC2I3cBHbxXYU1LOZstH3Ffw9hE5tE1sqT7U23C00MzkXztMw=="],
-
- "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.13", "", { "os": "linux", "cpu": "x64" }, "sha512-0bdwFVSbbM//Sds6OjtnmQGp4eUjOTt6kHvR/1P0ieR9GcTUAlPNvPC3DiavTqq302W34Ae2T6u5VVNGuQtGlQ=="],
-
- "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-QweDxY89fq0VvrxME+wS/BXKmqMrOTZlN9SqQ79kQSIc3FrEwvW/PvUegQF6XIVaekncDykB5dzPqjbwSKs9DA=="],
-
- "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.13", "", { "os": "win32", "cpu": "x64" }, "sha512-trDw2ogdM2lyav9WFQsdsfdVy1dvZALymRpgmWsvSez0BJzBjulhOT/t+wyKeh3pZWvwP3VMs1SoOKwO3wecMQ=="],
-
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.1", "", {}, "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw=="],
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="],
"@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="],
- "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@10.5.0", "", { "dependencies": { "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw=="],
+ "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="],
- "@chevrotain/gast": ["@chevrotain/gast@10.5.0", "", { "dependencies": { "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A=="],
+ "@chevrotain/gast": ["@chevrotain/gast@11.0.3", "", { "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q=="],
"@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.0.3", "", {}, "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA=="],
- "@chevrotain/types": ["@chevrotain/types@10.5.0", "", {}, "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A=="],
+ "@chevrotain/types": ["@chevrotain/types@11.0.3", "", {}, "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ=="],
- "@chevrotain/utils": ["@chevrotain/utils@10.5.0", "", {}, "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ=="],
-
- "@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="],
-
- "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="],
+ "@chevrotain/utils": ["@chevrotain/utils@11.0.3", "", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="],
"@clickhouse/client": ["@clickhouse/client@1.11.2", "", { "dependencies": { "@clickhouse/client-common": "1.11.2" } }, "sha512-ZE7Q1qxsDNXCkGPf1zqmhpZpwAKxKT+1s4Z432J1Mb2Gm26Y4tG/sJoug81AfAJTt6s7taO2vzNBAKfSR3SStg=="],
"@clickhouse/client-common": ["@clickhouse/client-common@1.11.2", "", {}, "sha512-H4ECHqaipzMgiZKqpb1Z4N3Ofq+lVTCn8I59XsSynqrsfR4jWZD3PipXVvIzMpDmTMvrlJWrOwAdm0DMNiMQbA=="],
- "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
-
- "@csstools/css-calc": ["@csstools/css-calc@2.1.2", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw=="],
-
- "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.4", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A=="],
-
- "@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.3", "", {}, "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw=="],
-
- "@csstools/media-query-list-parser": ["@csstools/media-query-list-parser@2.0.4", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1" } }, "sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA=="],
-
- "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
-
"@date-fns/utc": ["@date-fns/utc@2.1.1", "", {}, "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
@@ -750,12 +528,6 @@
"@hatchet-dev/typescript-sdk": ["@hatchet-dev/typescript-sdk@1.10.6", "", { "dependencies": { "@bufbuild/protobuf": "^2.2.5", "@types/qs": "^6.9.18", "abort-controller-x": "^0.4.3", "axios": "^1.11.0", "long": "^5.3.1", "nice-grpc": "^2.1.12", "nice-grpc-common": "^2.0.2", "protobufjs": "^7.4.0", "qs": "^6.14.1", "semver": "^7.7.1", "yaml": "^2.7.1", "zod": "^3.24.2" }, "optionalDependencies": { "prom-client": "^15.1.3" } }, "sha512-RrbbcKSlvBKenCqHQQKDKNw0gIN6HHaXgC6JOSdDrRNw+JhXleUNrwNfZk8btaWGA5zoPJpAGUzc3y0KYABSWA=="],
- "@headlessui/react": ["@headlessui/react@1.7.19", "", { "dependencies": { "@tanstack/react-virtual": "^3.0.0-beta.60", "client-only": "^0.0.1" }, "peerDependencies": { "react": "^16 || ^17 || ^18", "react-dom": "^16 || ^17 || ^18" } }, "sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw=="],
-
- "@heroicons/react": ["@heroicons/react@2.2.0", "", { "peerDependencies": { "react": ">= 16 || ^19.0.0-rc" } }, "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ=="],
-
- "@hono-rate-limiter/redis": ["@hono-rate-limiter/redis@0.1.4", "", { "peerDependencies": { "hono-rate-limiter": "^0.2.1" } }, "sha512-RSrVX5N2Oo/xXApskegu667cBVHyr8RXGWnbRDGjU2py8pN4BttEKSHA0iKi3BAwh1xSkENgDRng4tpFD9DbKg=="],
-
"@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="],
"@hono/zod-validator": ["@hono/zod-validator@0.7.6", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="],
@@ -768,8 +540,6 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
- "@hyperbrowser/sdk": ["@hyperbrowser/sdk@0.54.0", "", { "dependencies": { "form-data": "^4.0.1", "node-fetch": "2.7.0", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.1" } }, "sha512-QmbwMG6niqInlS0FfUWWzvv+DfyINHGnF8i/0oNuyyuiIdqSXnp+UG/74Ci+yz8QuyezmuKzBSp6dBXlVG0Glw=="],
-
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
@@ -876,12 +646,10 @@
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
- "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
- "@jsx-tool/jsx-tool": ["@jsx-tool/jsx-tool@0.0.21", "", { "dependencies": { "@inquirer/prompts": "^7.9.0", "@tailwindcss/language-service": "^0.14.29", "@vscode/ripgrep": "^1.17.0", "commander": "^14.0.0", "http-proxy": "^1.18.1", "picocolors": "^1.1.1", "prettier": "^3.6.2", "reflect-metadata": "^0.2.2", "tsyringe": "^4.10.0", "typescript-language-server": "^5.0.1", "vscode-languageserver": "^9.0.1", "vscode-languageserver-textdocument": "^1.0.12", "ws": "^8.18.2" }, "bin": { "jsx-tool": "bin/jsx-tool.js" } }, "sha512-QZbXXfNFbrQTfBTo3Y5qZW0AihM1PAuRDwL0f+eiVzy4r36uxPlpu1sLlkMxFOFOpSTyKshyHaz9TSvIE1egwg=="],
-
"@langchain/core": ["@langchain/core@1.1.17", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.4.0 <1.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "uuid": "^10.0.0", "zod": "^3.25.76 || ^4" } }, "sha512-g7/kcKbKEwNZSyyT7aT0utxn7wTOtKErqz0cL6VjrV4v/aOb9g+dKcfj17YkSm42YQmJp/rB2IXGc17vQPEBqA=="],
"@langchain/langgraph": ["@langchain/langgraph@1.1.2", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^1.0.0", "@langchain/langgraph-sdk": "~1.5.5", "@standard-schema/spec": "1.1.0", "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": "^1.0.1", "zod": "^3.25.32 || ^4.2.0", "zod-to-json-schema": "^3.x" }, "optionalPeers": ["zod-to-json-schema"] }, "sha512-kpZCttZ0N+jHSl5Vh/zVNElD5SxGR4sTjjLiBC00aLGf9JK+Sa/XXO6Bsk3WWXFtA1dY+4tUzUqH0mAHfN0WvA=="],
@@ -894,8 +662,6 @@
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.3", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ=="],
- "@mrleebo/prisma-ast": ["@mrleebo/prisma-ast@0.13.1", "", { "dependencies": { "chevrotain": "^10.5.0", "lilconfig": "^2.1.0" } }, "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw=="],
-
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],
@@ -944,7 +710,7 @@
"@opentelemetry/auto-instrumentations-node": ["@opentelemetry/auto-instrumentations-node@0.60.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.202.0", "@opentelemetry/instrumentation-amqplib": "^0.49.0", "@opentelemetry/instrumentation-aws-lambda": "^0.53.0", "@opentelemetry/instrumentation-aws-sdk": "^0.54.0", "@opentelemetry/instrumentation-bunyan": "^0.48.0", "@opentelemetry/instrumentation-cassandra-driver": "^0.48.0", "@opentelemetry/instrumentation-connect": "^0.46.0", "@opentelemetry/instrumentation-cucumber": "^0.17.0", "@opentelemetry/instrumentation-dataloader": "^0.19.0", "@opentelemetry/instrumentation-dns": "^0.46.0", "@opentelemetry/instrumentation-express": "^0.51.0", "@opentelemetry/instrumentation-fastify": "^0.47.0", "@opentelemetry/instrumentation-fs": "^0.22.0", "@opentelemetry/instrumentation-generic-pool": "^0.46.0", "@opentelemetry/instrumentation-graphql": "^0.50.0", "@opentelemetry/instrumentation-grpc": "^0.202.0", "@opentelemetry/instrumentation-hapi": "^0.49.0", "@opentelemetry/instrumentation-http": "^0.202.0", "@opentelemetry/instrumentation-ioredis": "^0.50.0", "@opentelemetry/instrumentation-kafkajs": "^0.11.0", "@opentelemetry/instrumentation-knex": "^0.47.0", "@opentelemetry/instrumentation-koa": "^0.50.1", "@opentelemetry/instrumentation-lru-memoizer": "^0.47.0", "@opentelemetry/instrumentation-memcached": "^0.46.0", "@opentelemetry/instrumentation-mongodb": "^0.55.1", "@opentelemetry/instrumentation-mongoose": "^0.49.0", "@opentelemetry/instrumentation-mysql": "^0.48.0", "@opentelemetry/instrumentation-mysql2": "^0.48.0", "@opentelemetry/instrumentation-nestjs-core": "^0.48.0", "@opentelemetry/instrumentation-net": "^0.46.1", "@opentelemetry/instrumentation-oracledb": "^0.28.0", "@opentelemetry/instrumentation-pg": "^0.54.0", "@opentelemetry/instrumentation-pino": "^0.49.0", "@opentelemetry/instrumentation-redis": "^0.49.1", "@opentelemetry/instrumentation-redis-4": "^0.49.0", "@opentelemetry/instrumentation-restify": "^0.48.1", "@opentelemetry/instrumentation-router": "^0.47.0", "@opentelemetry/instrumentation-runtime-node": "^0.16.0", "@opentelemetry/instrumentation-socket.io": "^0.49.0", "@opentelemetry/instrumentation-tedious": "^0.21.0", "@opentelemetry/instrumentation-undici": "^0.13.1", "@opentelemetry/instrumentation-winston": "^0.47.0", "@opentelemetry/resource-detector-alibaba-cloud": "^0.31.2", "@opentelemetry/resource-detector-aws": "^2.2.0", "@opentelemetry/resource-detector-azure": "^0.9.0", "@opentelemetry/resource-detector-container": "^0.7.2", "@opentelemetry/resource-detector-gcp": "^0.36.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-node": "^0.202.0" }, "peerDependencies": { "@opentelemetry/api": "^1.4.1", "@opentelemetry/core": "^2.0.0" } }, "sha512-oMBVXiun0qWhj693Y24Ie+75q45YXHRFeH9vX/XBWKRNJIM/02ufjmNvmOdoHY0EPxU9rBmWCW82Uidf54iSPA=="],
- "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.5.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw=="],
+ "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.0.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw=="],
"@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="],
@@ -1006,7 +772,7 @@
"@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.202.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/instrumentation": "0.202.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-oX+jyY2KBg4/nVH3vZhSWDbhywkHgE0fq3YinhUBx0jv+YUWC2UKA7qLkxr/CSzfKsFi/Km0NKV+llH17yYGKw=="],
- "@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.56.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/redis-common": "^0.38.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-XSWeqsd3rKSsT3WBz/JKJDcZD4QYElZEa0xVdX8f9dh4h4QgXhKRLorVsVkK3uXFbC2sZKAS2Ds+YolGwD83Dg=="],
+ "@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.50.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.202.0", "@opentelemetry/redis-common": "^0.38.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HKrWKOM23qNwqNjWfzkw7mePversmcH5ac6T1dUdiRyJVYaLr4qfydyYkgKIGWHOF2TKvQGobXo3CjvxABQWVw=="],
"@opentelemetry/instrumentation-kafkajs": ["@opentelemetry/instrumentation-kafkajs@0.11.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.202.0", "@opentelemetry/semantic-conventions": "^1.30.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+i9VqVEPNObB1tkwcLV6zAafnve72h2Iwo48E11M/kVXMNXlgGhiYckYCmzba8c2u5XD/V98XZDrCIyO8CLCNA=="],
@@ -1082,28 +848,18 @@
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.202.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.202.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-pv8QiQLQzk4X909YKm0lnW4hpuQg4zHwJ4XBd5bZiXcd9urvrJNoNVKnxGHPiDVX/GiLFvr5DMYsDBQbZCypRQ=="],
- "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA=="],
+ "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
"@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.202.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.202.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/exporter-logs-otlp-grpc": "0.202.0", "@opentelemetry/exporter-logs-otlp-http": "0.202.0", "@opentelemetry/exporter-logs-otlp-proto": "0.202.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.202.0", "@opentelemetry/exporter-metrics-otlp-http": "0.202.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.202.0", "@opentelemetry/exporter-prometheus": "0.202.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.202.0", "@opentelemetry/exporter-trace-otlp-http": "0.202.0", "@opentelemetry/exporter-trace-otlp-proto": "0.202.0", "@opentelemetry/exporter-zipkin": "2.0.1", "@opentelemetry/instrumentation": "0.202.0", "@opentelemetry/propagator-b3": "2.0.1", "@opentelemetry/propagator-jaeger": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.202.0", "@opentelemetry/sdk-metrics": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "@opentelemetry/sdk-trace-node": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-SF9vXWVd9I5CZ69mW3GfwfLI2SHgyvEqntcg0en5y8kRp5+2PPoa3Mkgj0WzFLrbSgTw4PsXn7c7H6eSdrtV0w=="],
"@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ=="],
- "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.5.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.5.0", "@opentelemetry/core": "2.5.0", "@opentelemetry/sdk-trace-base": "2.5.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-O6N/ejzburFm2C84aKNrwJVPpt6HSTSq8T0ZUMq3xT2XmqT4cwxUItcL5UWGThYuq8RTcbH8u1sfj6dmRci0Ow=="],
+ "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.0.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.0.1", "@opentelemetry/core": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-UhdbPF19pMpBtCWYP5lHbTogLWx9N0EBxtdagvkn5YtsAnCBZzL7SjktG+ZmupRgifsHMjwUaCCaVmqGfSADmA=="],
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="],
"@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.2", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="],
- "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
-
- "@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
-
- "@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="],
-
- "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
-
- "@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="],
-
"@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.16.4", "", { "os": "android", "cpu": "arm" }, "sha512-6XUHilmj8D6Ggus+sTBp64x/DUQ7LgC/dvTDdUOt4iMQnDdSep6N1mnvVLIiG+qM5tRnNHravNzBJnUlYwRQoA=="],
"@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.16.4", "", { "os": "android", "cpu": "arm64" }, "sha512-5ODwd1F5mdkm6JIg1CNny9yxIrCzrkKpxmqas7Alw23vE0Ot8D4ykqNBW5Z/nIZkXVEo5VDmnm0sMBBIANcpeQ=="],
@@ -1144,8 +900,6 @@
"@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.16.4", "", { "os": "win32", "cpu": "x64" }, "sha512-qnjQhjHI4TDL3hkidZyEmQRK43w2NHl6TP5Rnt/0XxYuLdEgx/1yzShhYidyqWzdnhGhSPTM/WVP2mK66XLegA=="],
- "@paralleldrive/cuid2": ["@paralleldrive/cuid2@2.3.1", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw=="],
-
"@phosphor-icons/react": ["@phosphor-icons/react@2.1.10", "", { "peerDependencies": { "react": ">= 16.8", "react-dom": ">= 16.8" } }, "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
@@ -1204,8 +958,6 @@
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
- "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="],
-
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
@@ -1236,8 +988,6 @@
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
- "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.8", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA=="],
-
"@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ=="],
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
@@ -1276,58 +1026,12 @@
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
- "@react-email/body": ["@react-email/body@0.0.11", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ZSD2SxVSgUjHGrB0Wi+4tu3MEpB4fYSbezsFNEJk2xCWDBkFiOeEsjTmR5dvi+CxTK691hQTQlHv0XWuP7ENTg=="],
-
- "@react-email/button": ["@react-email/button@0.0.19", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-HYHrhyVGt7rdM/ls6FuuD6XE7fa7bjZTJqB2byn6/oGsfiEZaogY77OtoLL/mrQHjHjZiJadtAMSik9XLcm7+A=="],
-
- "@react-email/code-block": ["@react-email/code-block@0.0.13", "", { "dependencies": { "prismjs": "^1.30.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-4DE4yPSgKEOnZMzcrDvRuD6mxsNxOex0hCYEG9F9q23geYgb2WCCeGBvIUXVzK69l703Dg4Vzrd5qUjl+JfcwA=="],
-
- "@react-email/code-inline": ["@react-email/code-inline@0.0.5", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-MmAsOzdJpzsnY2cZoPHFPk6uDO/Ncpb4Kh1hAt9UZc1xOW3fIzpe1Pi9y9p6wwUmpaeeDalJxAxH6/fnTquinA=="],
-
- "@react-email/column": ["@react-email/column@0.0.13", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Lqq17l7ShzJG/d3b1w/+lVO+gp2FM05ZUo/nW0rjxB8xBICXOVv6PqjDnn3FXKssvhO5qAV20lHM6S+spRhEwQ=="],
-
- "@react-email/components": ["@react-email/components@0.0.42", "", { "dependencies": { "@react-email/body": "0.0.11", "@react-email/button": "0.0.19", "@react-email/code-block": "0.0.13", "@react-email/code-inline": "0.0.5", "@react-email/column": "0.0.13", "@react-email/container": "0.0.15", "@react-email/font": "0.0.9", "@react-email/head": "0.0.12", "@react-email/heading": "0.0.15", "@react-email/hr": "0.0.11", "@react-email/html": "0.0.11", "@react-email/img": "0.0.11", "@react-email/link": "0.0.12", "@react-email/markdown": "0.0.15", "@react-email/preview": "0.0.13", "@react-email/render": "1.1.2", "@react-email/row": "0.0.12", "@react-email/section": "0.0.16", "@react-email/tailwind": "1.0.5", "@react-email/text": "0.1.5" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-KZtf7RjCoLgEwa5swrsbEF/liGrmfMlZCDNDXqaAjlgF3iRq0h5KY/HNMs6LbLmW7fEneRhnynrwApwbkEwe+A=="],
-
- "@react-email/container": ["@react-email/container@0.0.15", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Qo2IQo0ru2kZq47REmHW3iXjAQaKu4tpeq/M8m1zHIVwKduL2vYOBQWbC2oDnMtWPmkBjej6XxgtZByxM6cCFg=="],
-
- "@react-email/font": ["@react-email/font@0.0.9", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-4zjq23oT9APXkerqeslPH3OZWuh5X4crHK6nx82mVHV2SrLba8+8dPEnWbaACWTNjOCbcLIzaC9unk7Wq2MIXw=="],
-
- "@react-email/head": ["@react-email/head@0.0.12", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-X2Ii6dDFMF+D4niNwMAHbTkeCjlYYnMsd7edXOsi0JByxt9wNyZ9EnhFiBoQdqkE+SMDcu8TlNNttMrf5sJeMA=="],
-
- "@react-email/heading": ["@react-email/heading@0.0.15", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-xF2GqsvBrp/HbRHWEfOgSfRFX+Q8I5KBEIG5+Lv3Vb2R/NYr0s8A5JhHHGf2pWBMJdbP4B2WHgj/VUrhy8dkIg=="],
-
- "@react-email/hr": ["@react-email/hr@0.0.11", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-S1gZHVhwOsd1Iad5IFhpfICwNPMGPJidG/Uysy1AwmspyoAP5a4Iw3OWEpINFdgh9MHladbxcLKO2AJO+cA9Lw=="],
-
- "@react-email/html": ["@react-email/html@0.0.11", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-qJhbOQy5VW5qzU74AimjAR9FRFQfrMa7dn4gkEXKMB/S9xZN8e1yC1uA9C15jkXI/PzmJ0muDIWmFwatm5/+VA=="],
-
- "@react-email/img": ["@react-email/img@0.0.11", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aGc8Y6U5C3igoMaqAJKsCpkbm1XjguQ09Acd+YcTKwjnC2+0w3yGUJkjWB2vTx4tN8dCqQCXO8FmdJpMfOA9EQ=="],
-
- "@react-email/link": ["@react-email/link@0.0.12", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-vF+xxQk2fGS1CN7UPQDbzvcBGfffr+GjTPNiWM38fhBfsLv6A/YUfaqxWlmL7zLzVmo0K2cvvV9wxlSyNba1aQ=="],
-
- "@react-email/markdown": ["@react-email/markdown@0.0.15", "", { "dependencies": { "md-to-react-email": "^5.0.5" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-UQA9pVm5sbflgtg3EX3FquUP4aMBzmLReLbGJ6DZQZnAskBF36aI56cRykDq1o+1jT+CKIK1CducPYziaXliag=="],
-
- "@react-email/preview": ["@react-email/preview@0.0.13", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-F7j9FJ0JN/A4d7yr+aw28p4uX7VLWs7hTHtLo7WRyw4G+Lit6Zucq4UWKRxJC8lpsUdzVmG7aBJnKOT+urqs/w=="],
-
"@react-email/render": ["@react-email/render@1.1.2", "", { "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3", "react-promise-suspense": "^0.3.4" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw=="],
- "@react-email/row": ["@react-email/row@0.0.12", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-HkCdnEjvK3o+n0y0tZKXYhIXUNPDx+2vq1dJTmqappVHXS5tXS6W5JOPZr5j+eoZ8gY3PShI2LWj5rWF7ZEtIQ=="],
-
- "@react-email/section": ["@react-email/section@0.0.16", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-FjqF9xQ8FoeUZYKSdt8sMIKvoT9XF8BrzhT3xiFKdEMwYNbsDflcjfErJe3jb7Wj/es/lKTbV5QR1dnLzGpL3w=="],
-
- "@react-email/tailwind": ["@react-email/tailwind@1.0.5", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-BH00cZSeFfP9HiDASl+sPHi7Hh77W5nzDgdnxtsVr/m3uQD9g180UwxcE3PhOfx0vRdLzQUU8PtmvvDfbztKQg=="],
-
- "@react-email/text": ["@react-email/text@0.1.5", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-o5PNHFSE085VMXayxH+SJ1LSOtGsTv+RpNKnTiJDrJUwoBu77G3PlKOsZZQHCNyD28WsQpl9v2WcJLbQudqwPg=="],
-
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
- "@rollup/plugin-inject": ["@rollup/plugin-inject@5.0.5", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "estree-walker": "^2.0.2", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg=="],
-
- "@rollup/plugin-replace": ["@rollup/plugin-replace@6.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA=="],
-
- "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
-
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.0", "", { "os": "android", "cpu": "arm" }, "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.0", "", { "os": "android", "cpu": "arm64" }, "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg=="],
@@ -1522,8 +1226,6 @@
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
- "@smithy/util-waiter": ["@smithy/util-waiter@4.2.8", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg=="],
-
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
@@ -1532,8 +1234,6 @@
"@squircle/paint-polyfill": ["@squircle/paint-polyfill@1.0.6", "", {}, "sha512-w3FotA1v53MG+R6eGM37wv1aVoW3jwymwGteGCGXEM10Gq7zYeCus5dTgcyW6fhc9piRqsFLyU8MIl+EbtM9RA=="],
- "@squircle/tailwindcss": ["@squircle/tailwindcss@1.0.6", "", {}, "sha512-tmOWdpX7lzBAJzuvajn4NVBtC7mVORmzf/jKREYMw8iJ806Xq4FCUkrtbxkd9blzOx7TG2JwLF3avh5It/Z6TQ=="],
-
"@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
@@ -1548,16 +1248,12 @@
"@supabase/realtime-js": ["@supabase/realtime-js@2.93.2", "", { "dependencies": { "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-YpAmJn7DLbMeYfQilcf3f0DKoY8O8TRbTF2oRpWFzHXTlEA+YWms8fBqM13Mf7RE72ouSNKDYyf5K2pWRSHvFw=="],
- "@supabase/ssr": ["@supabase/ssr@0.5.2", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.7.0" }, "peerDependencies": { "@supabase/supabase-js": "^2.43.4" } }, "sha512-n3plRhr2Bs8Xun1o4S3k1CDv17iH5QY9YcoEvXX3bxV1/5XSasA0mNXYycFmADIdtdE6BG9MRjP5CGIs8qxC8A=="],
-
"@supabase/storage-js": ["@supabase/storage-js@2.93.2", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-abRSVClfIQn+SqpdqL7S7b3VeyS8270/o0gqmGFtiidb7Lu0COsIV6Mor/mK9xE99KYWzyd37vwYYwv/jaANhw=="],
"@supabase/supabase-js": ["@supabase/supabase-js@2.93.2", "", { "dependencies": { "@supabase/auth-js": "2.93.2", "@supabase/functions-js": "2.93.2", "@supabase/postgrest-js": "2.93.2", "@supabase/realtime-js": "2.93.2", "@supabase/storage-js": "2.93.2" } }, "sha512-G3bZZi6rPwXcPtyHLXQTeHKa5ADZ2UW/+hv8YhwZFwngz4TlPnR4+TeO37EwU5+d/reD02qXozOZgz+QHv1Jtg=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
- "@tailwindcss/language-service": ["@tailwindcss/language-service@0.14.29", "", { "dependencies": { "@csstools/css-calc": "2.1.2", "@csstools/css-parser-algorithms": "3.0.4", "@csstools/css-tokenizer": "3.0.3", "@csstools/media-query-list-parser": "2.0.4", "@types/culori": "^2.1.0", "@types/moo": "0.5.3", "@types/semver": "7.3.10", "braces": "3.0.3", "color-name": "1.1.4", "css.escape": "1.5.1", "culori": "^4.0.1", "detect-indent": "6.0.0", "dlv": "1.1.3", "dset": "3.1.4", "line-column": "1.0.2", "moo": "0.5.1", "postcss": "8.5.4", "postcss-selector-parser": "6.0.2", "postcss-value-parser": "4.2.0", "semver": "7.7.1", "sift-string": "0.0.2", "stringify-object": "3.3.0", "tmp-cache": "1.1.0", "vscode-emmet-helper-bundled": "0.0.1", "vscode-languageserver": "8.1.0", "vscode-languageserver-textdocument": "1.0.12" } }, "sha512-XRrlouKG6Jwl35ad3UiUS/7tyx54gJPPj82CbNIqe/+RUydNkrgNHyZztz1Cr9yAJDiwBqgg85qy4lTRSW1BJQ=="],
-
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
@@ -1596,44 +1292,20 @@
"@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="],
- "@tanstack/query-devtools": ["@tanstack/query-devtools@5.92.0", "", {}, "sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ=="],
-
"@tanstack/react-form": ["@tanstack/react-form@1.28.0", "", { "dependencies": { "@tanstack/form-core": "1.28.0", "@tanstack/react-store": "^0.8.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-ibLcf5QkTogV0Ly944CuqGxWTpHyreNA4Cy8Wtky7zE9wtE3HVapQt4/hUuXo51zihfTkv5URiXpoTSKF5Xosg=="],
"@tanstack/react-query": ["@tanstack/react-query@5.90.20", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw=="],
- "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.91.2", "", { "dependencies": { "@tanstack/query-devtools": "5.92.0" }, "peerDependencies": { "@tanstack/react-query": "^5.90.14", "react": "^18 || ^19" } }, "sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg=="],
-
"@tanstack/react-store": ["@tanstack/react-store@0.8.0", "", { "dependencies": { "@tanstack/store": "0.8.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow=="],
"@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="],
- "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.18", "", { "dependencies": { "@tanstack/virtual-core": "3.13.18" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A=="],
-
"@tanstack/store": ["@tanstack/store@0.7.7", "", {}, "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ=="],
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
- "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.18", "", {}, "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg=="],
-
- "@tokenlens/core": ["@tokenlens/core@1.3.0", "", {}, "sha512-d8YNHNC+q10bVpi95fELJwJyPVf1HfvBEI18eFQxRSZTdByXrP+f/ZtlhSzkx0Jl0aEmYVeBA5tPeeYRioLViQ=="],
-
- "@tokenlens/fetch": ["@tokenlens/fetch@1.3.0", "", { "dependencies": { "@tokenlens/core": "1.3.0" } }, "sha512-RONDRmETYly9xO8XMKblmrZjKSwCva4s5ebJwQNfNlChZoA5kplPoCgnWceHnn1J1iRjLVlrCNB43ichfmGBKQ=="],
-
- "@tokenlens/helpers": ["@tokenlens/helpers@1.3.1", "", { "dependencies": { "@tokenlens/core": "1.3.0", "@tokenlens/fetch": "1.3.0" } }, "sha512-t6yL8N6ES8337E6eVSeH4hCKnPdWkZRFpupy9w5E66Q9IeqQ9IO7XQ6gh12JKjvWiRHuyyJ8MBP5I549Cr41EQ=="],
-
- "@tokenlens/models": ["@tokenlens/models@1.3.0", "", { "dependencies": { "@tokenlens/core": "1.3.0" } }, "sha512-9mx7ZGeewW4ndXAiD7AT1bbCk4OpJeortbjHHyNkgap+pMPPn1chY6R5zqe1ggXIUzZ2l8VOAKfPqOvpcrisJw=="],
-
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
- "@tsconfig/node10": ["@tsconfig/node10@1.0.12", "", {}, "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ=="],
-
- "@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="],
-
- "@tsconfig/node14": ["@tsconfig/node14@1.0.3", "", {}, "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="],
-
- "@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="],
-
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/aws-lambda": ["@types/aws-lambda@8.10.150", "", {}, "sha512-AX+AbjH/rH5ezX1fbK8onC/a+HyQHo7QGmvoxAE42n22OsciAxvZoZNEr22tbXs8WfP1nIsBjKDpgPm3HjOZbA=="],
@@ -1654,20 +1326,10 @@
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
- "@types/chai-http": ["@types/chai-http@3.0.5", "", { "dependencies": { "@types/chai": "*", "@types/node": "*", "@types/superagent": "*" } }, "sha512-nJ/oIvYley9+1Fec8xzDHsWrWSu5VTuhuF8iLabnRTQwRfSbAke6VuI7qBXvBN9Qqo/+gwsSaGyYndofTpiJ9A=="],
-
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
- "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
-
- "@types/cookiejar": ["@types/cookiejar@2.1.5", "", {}, "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q=="],
-
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
- "@types/css-font-loading-module": ["@types/css-font-loading-module@0.0.7", "", {}, "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q=="],
-
- "@types/culori": ["@types/culori@2.1.1", "", {}, "sha512-NzLYD0vNHLxTdPp8+RlvGbR2NfOZkwxcYGFwxNtm+WH2NuUNV8785zv1h0sulFQ5aFQ9n/jNDUuJeo3Bh7+oFA=="],
-
"@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="],
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
@@ -1746,42 +1408,28 @@
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
- "@types/history": ["@types/history@4.7.11", "", {}, "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA=="],
-
- "@types/html-minifier-terser": ["@types/html-minifier-terser@6.1.0", "", {}, "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg=="],
-
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
- "@types/inquirer": ["@types/inquirer@9.0.9", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw=="],
-
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/katex": ["@types/katex@0.16.8", "", {}, "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg=="],
"@types/lodash": ["@types/lodash@4.17.23", "", {}, "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA=="],
- "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="],
-
"@types/luxon": ["@types/luxon@3.4.2", "", {}, "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA=="],
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
"@types/memcached": ["@types/memcached@2.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg=="],
- "@types/methods": ["@types/methods@1.1.4", "", {}, "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ=="],
-
"@types/mocha": ["@types/mocha@10.0.10", "", {}, "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q=="],
- "@types/moo": ["@types/moo@0.5.3", "", {}, "sha512-PJJ/jvb5Gor8DWvXN3e75njfQyYNRz0PaFSZ3br9GfHM9N2FxvuJ/E/ytcQePJOLzHlvgFSsIJIvfUMUxWTbnA=="],
-
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="],
"@types/node": ["@types/node@24.10.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw=="],
- "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
-
"@types/oracledb": ["@types/oracledb@6.5.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ=="],
"@types/pg": ["@types/pg@8.16.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="],
@@ -1790,8 +1438,6 @@
"@types/phoenix": ["@types/phoenix@1.6.7", "", {}, "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q=="],
- "@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="],
-
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
"@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
@@ -1802,26 +1448,14 @@
"@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
- "@types/react-router": ["@types/react-router@5.1.20", "", { "dependencies": { "@types/history": "^4.7.11", "@types/react": "*" } }, "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q=="],
-
- "@types/react-router-dom": ["@types/react-router-dom@5.3.3", "", { "dependencies": { "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router": "*" } }, "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw=="],
-
- "@types/recharts": ["@types/recharts@2.0.1", "", { "dependencies": { "recharts": "*" } }, "sha512-/cFs7oiafzByUwBSWA1IzE6FW+ppPwQAWsDTadSgVOwzveY9MESpyLHyyHY0SfPPKLW4+4qVNYHPXd0rFiC8vg=="],
-
- "@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
-
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
"@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="],
"@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="],
- "@types/superagent": ["@types/superagent@8.1.9", "", { "dependencies": { "@types/cookiejar": "^2.1.5", "@types/methods": "^1.1.4", "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ=="],
-
"@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="],
- "@types/through": ["@types/through@0.0.33", "", { "dependencies": { "@types/node": "*" } }, "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ=="],
-
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
@@ -1834,8 +1468,6 @@
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
- "@types/zen-observable": ["@types/zen-observable@0.8.3", "", {}, "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw=="],
-
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.54.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/type-utils": "8.54.0", "@typescript-eslint/utils": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.54.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA=="],
@@ -1874,30 +1506,12 @@
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
- "@upstash/core-analytics": ["@upstash/core-analytics@0.0.10", "", { "dependencies": { "@upstash/redis": "^1.28.3" } }, "sha512-7qJHGxpQgQr9/vmeS1PktEwvNAF7TI4iJDi8Pu2CFZ9YUGHZH4fOP5TfYlZ4aVxfopnELiE4BS4FBjyK7V1/xQ=="],
-
- "@upstash/ratelimit": ["@upstash/ratelimit@2.0.8", "", { "dependencies": { "@upstash/core-analytics": "^0.0.10" }, "peerDependencies": { "@upstash/redis": "^1.34.3" } }, "sha512-YSTMBJ1YIxsoPkUMX/P4DDks/xV5YYCswWMamU8ZIfK9ly6ppjRnVOyBhMDXBmzjODm4UQKcxsJPvaeFAijp5w=="],
-
- "@upstash/redis": ["@upstash/redis@1.36.1", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-N6SjDcgXdOcTAF+7uNoY69o7hCspe9BcA7YjQdxVu5d25avljTwyLaHBW3krWjrP0FfocgMk94qyVtQbeDp39A=="],
-
"@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
"@vercel/sdk": ["@vercel/sdk@1.18.8", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.25.2", "zod": "^3.25.0 || ^4.0.0" }, "bin": { "mcp": "bin/mcp-server.js" } }, "sha512-oWNb8Hnw3aWw8r3xySsikmm8B6KiKkDo0D3Md9y74CFnDoUBre/Y9v2DAgAzVyvMTBmX9KZJmaEMwHdOfG6p0w=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
- "@vscode/ripgrep": ["@vscode/ripgrep@1.17.0", "", { "dependencies": { "https-proxy-agent": "^7.0.2", "proxy-from-env": "^1.1.0", "yauzl": "^2.9.2" } }, "sha512-mBRKm+ASPkUcw4o9aAgfbusIu6H4Sdhw09bjeP1YOBFTJEZAnrnk6WZwzv8NEjgC82f7ILvhmb1WIElSugea6g=="],
-
- "@wooorm/starry-night": ["@wooorm/starry-night@3.9.0", "", { "dependencies": { "@types/hast": "^3.0.0", "import-meta-resolve": "^4.0.0", "vscode-oniguruma": "^2.0.0", "vscode-textmate": "^9.0.0" } }, "sha512-LXVGKfYhTuFhoRuPAHz2XolS/J45L4lI/lCSIBugDpklXYDUPrJyA8tk17u7G32fcFaGODJXH/YDwQDfUXdPRw=="],
-
- "@xstate/fsm": ["@xstate/fsm@1.6.5", "", {}, "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw=="],
-
- "@xyflow/react": ["@xyflow/react@12.10.0", "", { "dependencies": { "@xyflow/system": "0.0.74", "classcat": "^5.0.3", "zustand": "^4.4.0" }, "peerDependencies": { "react": ">=17", "react-dom": ">=17" } }, "sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw=="],
-
- "@xyflow/system": ["@xyflow/system@0.0.74", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q=="],
-
- "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
-
"abort-controller-x": ["abort-controller-x@0.4.3", "", {}, "sha512-VtUwTNU8fpMwvWGn4xE93ywbogTYsuT+AUxAXOeelbXuQVIwNmC5YLeho9sH4vZ4ITW8414TTAOG1nW6uIVHCA=="],
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
@@ -1908,8 +1522,6 @@
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
- "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="],
-
"ag-charts-community": ["ag-charts-community@12.3.1", "", { "dependencies": { "ag-charts-core": "12.3.1", "ag-charts-locale": "12.3.1", "ag-charts-types": "12.3.1" } }, "sha512-uRaUFmCl8e0Y3KxjaHUYlkCPS5OtwtfTChkdpoZuBKDXqerCPTjPu+uvlun3rYUSYxScwVf2LZCI0Hfw4Vc+WQ=="],
"ag-charts-core": ["ag-charts-core@12.3.1", "", { "dependencies": { "ag-charts-types": "12.3.1" } }, "sha512-711UJ0fXengb8+4PEW4nlzWDowmbYymPcjW2eJWHRzzvttUf14hnh+wP/l/s3EGVgYkEHe9vkXFwmeOJUlkC0Q=="],
@@ -1926,40 +1538,24 @@
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
- "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
-
"ai": ["ai@6.0.57", "", { "dependencies": { "@ai-sdk/gateway": "3.0.25", "@ai-sdk/provider": "3.0.5", "@ai-sdk/provider-utils": "4.0.10", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5wYcMQmOaNU71wGv4XX1db3zvn4uLjLbTKIo6cQZPWOJElA0882XI7Eawx6TCd5jbjOvKMIP+KLWbpVomAFT2g=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
- "ansi-escapes": ["ansi-escapes@7.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="],
-
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
- "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
+ "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
- "arctic": ["arctic@3.7.0", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-ZMQ+f6VazDgUJOd+qNV+H7GohNSYal1mVjm5kEaZfE2Ifb7Ss70w+Q7xpJC87qZDkMZIXYf0pTIYZA0OPasSbw=="],
-
- "arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="],
-
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
"array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="],
- "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
-
- "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
-
- "asn1.js": ["asn1.js@4.10.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw=="],
-
- "assert": ["assert@2.1.0", "", { "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", "object-is": "^1.1.5", "object.assign": "^4.1.4", "util": "^0.12.5" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="],
-
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
@@ -1968,14 +1564,8 @@
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
- "attr-accept": ["attr-accept@2.2.5", "", {}, "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ=="],
-
- "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
-
"autumn-js": ["autumn-js@0.1.74", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4" }, "optionalPeers": ["better-auth", "better-call", "convex"] }, "sha512-U5t88gSGEcT/syMZYOruftNE+Ie09eIyjTemgXEiaPw9du0YRDEZJQYDs1nnwacDQbhlhGxYiMlAlzTzVB0c+g=="],
- "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
-
"axios": ["axios@1.13.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="],
"b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="],
@@ -1998,8 +1588,6 @@
"base-convert-int-array": ["base-convert-int-array@1.0.1", "", {}, "sha512-NWqzaoXx8L/SS32R+WmKqnQkVXVYl2PwNJ68QV3RAlRRL1uV+yxJT66abXI1cAvqCXQTyXr7/9NN4Af90/zDVw=="],
- "base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="],
-
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
@@ -2024,39 +1612,19 @@
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
- "bn.js": ["bn.js@5.2.2", "", {}, "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw=="],
-
"body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="],
- "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
-
"bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="],
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
- "brorand": ["brorand@1.1.0", "", {}, "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="],
-
- "browser-resolve": ["browser-resolve@2.0.0", "", { "dependencies": { "resolve": "^1.17.0" } }, "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ=="],
-
"browser-stdout": ["browser-stdout@1.3.1", "", {}, "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="],
- "browserify-aes": ["browserify-aes@1.2.0", "", { "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.3", "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA=="],
-
- "browserify-cipher": ["browserify-cipher@1.0.1", "", { "dependencies": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", "evp_bytestokey": "^1.0.0" } }, "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w=="],
-
- "browserify-des": ["browserify-des@1.0.2", "", { "dependencies": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A=="],
-
- "browserify-rsa": ["browserify-rsa@4.1.1", "", { "dependencies": { "bn.js": "^5.2.1", "randombytes": "^2.1.0", "safe-buffer": "^5.2.1" } }, "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ=="],
-
- "browserify-sign": ["browserify-sign@4.2.5", "", { "dependencies": { "bn.js": "^5.2.2", "browserify-rsa": "^4.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "elliptic": "^6.6.1", "inherits": "^2.0.4", "parse-asn1": "^5.1.9", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1" } }, "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw=="],
-
- "browserify-zlib": ["browserify-zlib@0.2.0", "", { "dependencies": { "pako": "~1.0.5" } }, "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA=="],
-
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
- "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
+ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
@@ -2064,30 +1632,18 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
- "buffer-xor": ["buffer-xor@1.0.3", "", {}, "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="],
-
- "builtin-status-codes": ["builtin-status-codes@3.0.0", "", {}, "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ=="],
-
"bullmq": ["bullmq@5.67.2", "", { "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.9.2", "msgpackr": "1.11.5", "node-abort-controller": "3.1.1", "semver": "7.7.3", "tslib": "2.8.1", "uuid": "11.1.0" } }, "sha512-3KYqNqQptKcgksACO1li4YW9/jxEh6XWa1lUg4OFrHa80Pf0C7H9zeb6ssbQQDfQab/K3QCXopbZ40vrvcyrLw=="],
"bun-types": ["bun-types@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-qyschsA03Qz+gou+apt6HNl6HnI+sJJLL4wLDke4iugsE6584CMupOtTY1n+2YC9nGVrEKUlTs99jjRLKgWnjQ=="],
- "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
-
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
- "c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
-
- "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
-
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
- "camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="],
-
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001766", "", {}, "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="],
@@ -2096,8 +1652,6 @@
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
- "chai-http": ["chai-http@5.1.2", "", { "dependencies": { "@types/superagent": "^8.1.7", "charset": "^1.0.1", "cookiejar": "^2.1.4", "is-ip": "^5.0.1", "methods": "^1.1.2", "qs": "^6.12.1", "superagent": "^10.0.0" } }, "sha512-UFup7mUGkkjmi9bGA7F6vfp3lzGQZjtL//CEd+a4C+vlynSv756XHDUK8PoYk/UpTBBXqSghjQaJOUMUxJXNaA=="],
-
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
@@ -2110,11 +1664,9 @@
"chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="],
- "charset": ["charset@1.0.1", "", {}, "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg=="],
-
"check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
- "chevrotain": ["chevrotain@10.5.0", "", { "dependencies": { "@chevrotain/cst-dts-gen": "10.5.0", "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", "@chevrotain/utils": "10.5.0", "lodash": "4.17.21", "regexp-to-ast": "0.5.0" } }, "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A=="],
+ "chevrotain": ["chevrotain@11.0.3", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", "@chevrotain/regexp-to-ast": "11.0.3", "@chevrotain/types": "11.0.3", "@chevrotain/utils": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="],
"chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="],
@@ -2124,25 +1676,13 @@
"chromium-bidi": ["chromium-bidi@13.0.1", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-c+RLxH0Vg2x2syS9wPw378oJgiJNXtYXUvnVAldUlt5uaHekn0CCU7gPksNgHjrH1qFhmjVXQj4esvuthuC7OQ=="],
- "cipher-base": ["cipher-base@1.0.7", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.2" } }, "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA=="],
-
- "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
-
"cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
- "classcat": ["classcat@5.0.5", "", {}, "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="],
+ "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
- "clean-css": ["clean-css@5.3.3", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg=="],
-
- "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
-
- "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="],
-
- "cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="],
-
- "cli-truncate": ["cli-truncate@5.1.1", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A=="],
+ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
@@ -2150,76 +1690,44 @@
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
- "clone-regexp": ["clone-regexp@3.0.0", "", { "dependencies": { "is-regexp": "^3.0.0" } }, "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw=="],
-
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
"cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="],
- "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
-
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
- "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
-
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
- "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
-
- "component-emitter": ["component-emitter@1.3.1", "", {}, "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ=="],
+ "commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
- "concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="],
-
- "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
-
- "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
-
- "console-browserify": ["console-browserify@1.2.0", "", {}, "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA=="],
+ "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
"console-table-printer": ["console-table-printer@2.15.0", "", { "dependencies": { "simple-wcswidth": "^1.1.2" } }, "sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw=="],
- "constants-browserify": ["constants-browserify@1.0.0", "", {}, "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ=="],
-
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
- "convert-hrtime": ["convert-hrtime@5.0.0", "", {}, "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg=="],
-
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
- "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
-
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
- "cookiejar": ["cookiejar@2.1.4", "", {}, "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw=="],
-
"core-js": ["core-js@3.48.0", "", {}, "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ=="],
- "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
-
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
"cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="],
- "create-ecdh": ["create-ecdh@4.0.4", "", { "dependencies": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" } }, "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A=="],
-
- "create-hash": ["create-hash@1.2.0", "", { "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "md5.js": "^1.3.4", "ripemd160": "^2.0.1", "sha.js": "^2.4.0" } }, "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg=="],
-
- "create-hmac": ["create-hmac@1.1.7", "", { "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", "inherits": "^2.0.1", "ripemd160": "^2.0.0", "safe-buffer": "^5.0.1", "sha.js": "^2.4.8" } }, "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg=="],
-
- "create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="],
-
"cron": ["cron@3.5.0", "", { "dependencies": { "@types/luxon": "~3.4.0", "luxon": "~3.5.0" } }, "sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A=="],
"cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="],
@@ -2228,30 +1736,8 @@
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
- "crypto-browserify": ["crypto-browserify@3.12.1", "", { "dependencies": { "browserify-cipher": "^1.0.1", "browserify-sign": "^4.2.3", "create-ecdh": "^4.0.4", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "diffie-hellman": "^5.0.3", "hash-base": "~3.0.4", "inherits": "^2.0.4", "pbkdf2": "^3.1.2", "public-encrypt": "^4.0.3", "randombytes": "^2.1.0", "randomfill": "^1.0.4" } }, "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ=="],
-
- "css-jss": ["css-jss@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "^10.10.0", "jss-preset-default": "^10.10.0" } }, "sha512-YyMIS/LsSKEGXEaVJdjonWe18p4vXLo8CMA4FrW/kcaEyqdIGKCFXao31gbJddXEdIxSXFFURWrenBJPlKTgAA=="],
-
- "css-select": ["css-select@4.3.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", "domhandler": "^4.3.1", "domutils": "^2.8.0", "nth-check": "^2.0.1" } }, "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ=="],
-
- "css-vendor": ["css-vendor@2.0.8", "", { "dependencies": { "@babel/runtime": "^7.8.3", "is-in-browser": "^1.0.2" } }, "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ=="],
-
- "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
-
- "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
-
- "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
-
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
- "csv-parse": ["csv-parse@5.6.0", "", {}, "sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q=="],
-
- "csv-parser": ["csv-parser@3.2.0", "", { "bin": { "csv-parser": "bin/csv-parser" } }, "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA=="],
-
- "culori": ["culori@4.0.2", "", {}, "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw=="],
-
- "currency-symbol-map": ["currency-symbol-map@5.1.0", "", {}, "sha512-LO/lzYRw134LMDVnLyAf1dHE5tyO6axEFkR3TXjQIOmMkAM9YL6QsiUwuXzZAmFnuDJcs4hayOgyIYtViXFrLw=="],
-
"cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="],
"cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="],
@@ -2328,8 +1814,6 @@
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
- "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
-
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
"debounce": ["debounce@2.2.0", "", {}, "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw=="],
@@ -2356,16 +1840,6 @@
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
- "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="],
-
- "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
-
- "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
-
- "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
-
- "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
-
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
@@ -2380,16 +1854,8 @@
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
- "des.js": ["des.js@1.1.0", "", { "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg=="],
-
- "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
-
"destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
- "detect-content-type": ["detect-content-type@1.2.0", "", {}, "sha512-YCBxuqJLY9rMxV44Ict2kNgjYFN3v1dnsn6sJvd6sUwwU1TWP3D+K2dr/S9AF/fio2/RsAKYdRiEOtNoRbmiag=="],
-
- "detect-indent": ["detect-indent@6.0.0", "", {}, "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA=="],
-
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
@@ -2398,22 +1864,10 @@
"devtools-protocol": ["devtools-protocol@0.0.1551306", "", {}, "sha512-CFx8QdSim8iIv+2ZcEOclBKTQY6BI1IEDa7Tm9YkwAXzEWFndTEzpTo5jAUhSnq24IC7xaDw0wvGcm96+Y3PEg=="],
- "dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="],
-
"diff": ["diff@7.0.0", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="],
- "diffie-hellman": ["diffie-hellman@5.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg=="],
-
- "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
-
- "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
-
- "dom-converter": ["dom-converter@0.2.0", "", { "dependencies": { "utila": "~0.4" } }, "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA=="],
-
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
- "domain-browser": ["domain-browser@4.22.0", "", {}, "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw=="],
-
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
@@ -2422,18 +1876,12 @@
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
- "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
-
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
"drizzle-kit": ["drizzle-kit@0.31.8", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="],
"drizzle-orm": ["drizzle-orm@0.43.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="],
- "drizzle-zod": ["drizzle-zod@0.8.3", "", { "peerDependencies": { "drizzle-orm": ">=0.36.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww=="],
-
- "dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
-
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
@@ -2444,14 +1892,6 @@
"electron-to-chromium": ["electron-to-chromium@1.5.279", "", {}, "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg=="],
- "elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="],
-
- "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
-
- "embla-carousel-react": ["embla-carousel-react@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA=="],
-
- "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="],
-
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
@@ -2466,8 +1906,6 @@
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
- "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
-
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
@@ -2512,40 +1950,28 @@
"estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
- "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
-
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
- "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
-
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
- "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
-
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
"eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
- "evp_bytestokey": ["evp_bytestokey@1.0.3", "", { "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA=="],
-
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
"express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="],
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
- "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
-
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
- "fast-copy": ["fast-copy@4.0.2", "", {}, "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw=="],
-
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
@@ -2556,8 +1982,6 @@
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
- "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
-
"fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
@@ -2574,14 +1998,10 @@
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
- "fetch-retry": ["fetch-retry@6.0.0", "", {}, "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag=="],
-
"fflate": ["fflate@0.4.8", "", {}, "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
- "file-selector": ["file-selector@2.1.2", "", { "dependencies": { "tslib": "^2.7.0" } }, "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig=="],
-
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@@ -2600,22 +2020,14 @@
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
- "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
-
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
- "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
-
"formatly": ["formatly@0.3.0", "", { "dependencies": { "fd-package-json": "^2.0.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w=="],
- "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
-
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
- "formidable": ["formidable@3.5.4", "", { "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", "once": "^1.4.0" } }, "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug=="],
-
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="],
@@ -2630,14 +2042,10 @@
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
- "function-timeout": ["function-timeout@0.1.1", "", {}, "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg=="],
-
"gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
"gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
- "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
-
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
@@ -2648,8 +2056,6 @@
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
- "get-own-enumerable-property-symbols": ["get-own-enumerable-property-symbols@3.0.2", "", {}, "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g=="],
-
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
@@ -2658,8 +2064,6 @@
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
- "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
-
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
@@ -2668,12 +2072,8 @@
"globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
- "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
-
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
- "goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="],
-
"google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
"google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
@@ -2688,16 +2088,10 @@
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
- "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
-
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
- "hash-base": ["hash-base@3.0.5", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" } }, "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg=="],
-
- "hash.js": ["hash.js@1.1.7", "", { "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA=="],
-
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hast": ["hast@1.0.0", "", {}, "sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA=="],
@@ -2732,52 +2126,30 @@
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
- "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="],
-
- "hmac-drbg": ["hmac-drbg@1.0.1", "", { "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg=="],
-
- "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
-
"hono": ["hono@4.9.9", "", {}, "sha512-Hxw4wT6zjJGZJdkJzAx9PyBdf7ZpxaTSA0NfxqjLghwMrLBX8p33hJBzoETRakF3UJu6OdNQBZAlNSkGqKFukw=="],
"hono-rate-limiter": ["hono-rate-limiter@0.4.2", "", { "peerDependencies": { "hono": "^4.1.1" } }, "sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw=="],
- "html-minifier-terser": ["html-minifier-terser@6.1.0", "", { "dependencies": { "camel-case": "^4.1.2", "clean-css": "^5.2.2", "commander": "^8.3.0", "he": "^1.2.0", "param-case": "^3.0.4", "relateurl": "^0.2.7", "terser": "^5.10.0" }, "bin": { "html-minifier-terser": "cli.js" } }, "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw=="],
-
"html-to-text": ["html-to-text@9.0.5", "", { "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", "dom-serializer": "^2.0.0", "htmlparser2": "^8.0.2", "selderee": "^0.11.0" } }, "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg=="],
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
- "html-webpack-plugin": ["html-webpack-plugin@5.6.6", "", { "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", "lodash": "^4.17.21", "pretty-error": "^4.0.0", "tapable": "^2.0.0" }, "peerDependencies": { "@rspack/core": "0.x || 1.x", "webpack": "^5.20.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw=="],
-
"htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
- "http-proxy": ["http-proxy@1.18.1", "", { "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ=="],
-
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
"http-status-codes": ["http-status-codes@2.3.0", "", {}, "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="],
- "https-browserify": ["https-browserify@1.0.0", "", {}, "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg=="],
-
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
- "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
-
- "hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="],
-
"iceberg-js": ["iceberg-js@0.8.1", "", {}, "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
- "idb": ["idb@8.0.0", "", {}, "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw=="],
-
- "idb-keyval": ["idb-keyval@6.2.2", "", {}, "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg=="],
-
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -2790,22 +2162,12 @@
"import-in-the-middle": ["import-in-the-middle@1.15.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA=="],
- "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
-
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
- "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
-
- "indexes-of": ["indexes-of@1.0.1", "", {}, "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA=="],
-
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
- "ink": ["ink@6.6.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.1", "ansi-escapes": "^7.2.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^8.1.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-QDt6FgJxgmSxAelcOvOHUvFxbIUjVpCH5bx+Slvc5m7IEcpGt3dYwbz/L+oRnqEGeRvwy1tineKK4ect3nW1vQ=="],
-
- "ink-spinner": ["ink-spinner@5.0.0", "", { "dependencies": { "cli-spinners": "^2.7.0" }, "peerDependencies": { "ink": ">=4.0.0", "react": ">=18.0.0" } }, "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA=="],
-
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
"input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="],
@@ -2818,90 +2180,50 @@
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
- "ip-regex": ["ip-regex@5.0.0", "", {}, "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw=="],
-
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
- "is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="],
-
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
- "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
-
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
- "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
-
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
- "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
-
- "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
- "is-in-browser": ["is-in-browser@1.1.3", "", {}, "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g=="],
-
- "is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="],
-
- "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
-
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
- "is-ip": ["is-ip@5.0.1", "", { "dependencies": { "ip-regex": "^5.0.0", "super-regex": "^0.2.0" } }, "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw=="],
-
- "is-nan": ["is-nan@1.3.2", "", { "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" } }, "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w=="],
-
"is-network-error": ["is-network-error@1.3.0", "", {}, "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
- "is-obj": ["is-obj@1.0.1", "", {}, "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="],
-
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
"is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
- "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
-
- "is-regexp": ["is-regexp@1.0.0", "", {}, "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA=="],
-
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
- "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
-
- "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
-
- "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
-
- "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
+ "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
- "isobject": ["isobject@2.1.0", "", { "dependencies": { "isarray": "1.0.0" } }, "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA=="],
-
- "isomorphic-timers-promises": ["isomorphic-timers-promises@1.0.1", "", {}, "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ=="],
-
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
- "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
-
- "js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
-
"js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
@@ -2926,34 +2248,6 @@
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
- "jss": ["jss@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "csstype": "^3.0.2", "is-in-browser": "^1.1.3", "tiny-warning": "^1.0.2" } }, "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw=="],
-
- "jss-plugin-camel-case": ["jss-plugin-camel-case@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "hyphenate-style-name": "^1.0.3", "jss": "10.10.0" } }, "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw=="],
-
- "jss-plugin-compose": ["jss-plugin-compose@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0", "tiny-warning": "^1.0.2" } }, "sha512-F5kgtWpI2XfZ3Z8eP78tZEYFdgTIbpA/TMuX3a8vwrNolYtN1N4qJR/Ob0LAsqIwCMLojtxN7c7Oo/+Vz6THow=="],
-
- "jss-plugin-default-unit": ["jss-plugin-default-unit@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0" } }, "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ=="],
-
- "jss-plugin-expand": ["jss-plugin-expand@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0" } }, "sha512-ymT62W2OyDxBxr7A6JR87vVX9vTq2ep5jZLIdUSusfBIEENLdkkc0lL/Xaq8W9s3opUq7R0sZQpzRWELrfVYzA=="],
-
- "jss-plugin-extend": ["jss-plugin-extend@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0", "tiny-warning": "^1.0.2" } }, "sha512-sKYrcMfr4xxigmIwqTjxNcHwXJIfvhvjTNxF+Tbc1NmNdyspGW47Ey6sGH8BcQ4FFQhLXctpWCQSpDwdNmXSwg=="],
-
- "jss-plugin-global": ["jss-plugin-global@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0" } }, "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A=="],
-
- "jss-plugin-nested": ["jss-plugin-nested@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0", "tiny-warning": "^1.0.2" } }, "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA=="],
-
- "jss-plugin-props-sort": ["jss-plugin-props-sort@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0" } }, "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg=="],
-
- "jss-plugin-rule-value-function": ["jss-plugin-rule-value-function@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0", "tiny-warning": "^1.0.2" } }, "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g=="],
-
- "jss-plugin-rule-value-observable": ["jss-plugin-rule-value-observable@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0", "symbol-observable": "^1.2.0" } }, "sha512-ZLMaYrR3QE+vD7nl3oNXuj79VZl9Kp8/u6A1IbTPDcuOu8b56cFdWRZNZ0vNr8jHewooEeq2doy8Oxtymr2ZPA=="],
-
- "jss-plugin-template": ["jss-plugin-template@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0", "tiny-warning": "^1.0.2" } }, "sha512-ocXZBIOJOA+jISPdsgkTs8wwpK6UbsvtZK5JI7VUggTD6LWKbtoxUzadd2TpfF+lEtlhUmMsCkTRNkITdPKa6w=="],
-
- "jss-plugin-vendor-prefixer": ["jss-plugin-vendor-prefixer@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "css-vendor": "^2.0.8", "jss": "10.10.0" } }, "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg=="],
-
- "jss-preset-default": ["jss-preset-default@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "jss": "10.10.0", "jss-plugin-camel-case": "10.10.0", "jss-plugin-compose": "10.10.0", "jss-plugin-default-unit": "10.10.0", "jss-plugin-expand": "10.10.0", "jss-plugin-extend": "10.10.0", "jss-plugin-global": "10.10.0", "jss-plugin-nested": "10.10.0", "jss-plugin-props-sort": "10.10.0", "jss-plugin-rule-value-function": "10.10.0", "jss-plugin-rule-value-observable": "10.10.0", "jss-plugin-template": "10.10.0", "jss-plugin-vendor-prefixer": "10.10.0" } }, "sha512-GL175Wt2FGhjE+f+Y3aWh+JioL06/QWFgZp53CbNNq6ZkVU0TDplD8Bxm9KnkotAYn3FlplNqoW5CjyLXcoJ7Q=="],
-
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
@@ -2964,8 +2258,6 @@
"khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="],
- "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
-
"knip": ["knip@5.82.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "js-yaml": "^4.1.1", "minimist": "^1.2.8", "oxc-resolver": "^11.15.0", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-1nQk+5AcnkqL40kGQXfouzAEXkTR+eSrgo/8m1d0BMei4eAzFwghoXC4gOKbACgBiCof7hE8wkBVDsEvznf85w=="],
"ksuid": ["ksuid@3.0.0", "", { "dependencies": { "base-convert-int-array": "^1.0.1" } }, "sha512-81CkBGn/06ZVAjGvFZi6fVG8VcPeMH0JpJ4V1Z9VwrMMaGIeAjY4jrVdrIcxhL9I2ZUU6t5uiyswcmkk+KZegA=="],
@@ -3010,10 +2302,6 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
- "lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
-
- "line-column": ["line-column@1.0.2", "", { "dependencies": { "isarray": "^1.0.0", "isobject": "^2.0.0" } }, "sha512-Ktrjk5noGYlHsVnYWh62FLVs4hTb8A3e+vucNZMgPeAOITdshMSgv4cCZQeRDjm7+goqmo6+liZwTXo+U3sVww=="],
-
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
@@ -3028,7 +2316,7 @@
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
- "log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
+ "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
@@ -3040,30 +2328,22 @@
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
- "lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="],
-
- "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="],
"luxon": ["luxon@3.5.0", "", {}, "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ=="],
- "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+ "magic-string": ["magic-string@0.30.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ=="],
"mailchecker": ["mailchecker@6.0.19", "", {}, "sha512-a7yghUa0IC1n6OkSJIqCSw2HS8ujKJGBJcRkjhHiKvHqPU3JB28Yze9o90L52r6lA/sp/EBveDXOWbozcdmxYg=="],
- "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
-
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
"marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
- "md-to-react-email": ["md-to-react-email@5.0.5", "", { "dependencies": { "marked": "7.0.4" }, "peerDependencies": { "react": "^18.0 || ^19.0" } }, "sha512-OvAXqwq57uOk+WZqFFNCMZz8yDp8BD3WazW1wAKHUrPbbdr89K9DWS6JXY09vd9xNdPNeurI8DU/X4flcfaD8A=="],
-
- "md5.js": ["md5.js@1.3.5", "", { "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg=="],
-
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
@@ -3172,26 +2452,16 @@
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
- "miller-rabin": ["miller-rabin@4.0.1", "", { "dependencies": { "bn.js": "^4.0.0", "brorand": "^1.0.1" }, "bin": { "miller-rabin": "bin/miller-rabin" } }, "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA=="],
-
- "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="],
+ "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
- "mime-detect": ["mime-detect@1.3.0", "", { "dependencies": { "mime-db": "^1.52.0" } }, "sha512-SFFkIWONYQdDoiA3iq2abd68kCy+ifzsgWyytMDa/v8Jiu5UhVhgb4upoKDVKTlCz663jqM9PksyM99m+MnSaQ=="],
-
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
- "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
-
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
- "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="],
-
- "minimalistic-crypto-utils": ["minimalistic-crypto-utils@1.0.1", "", {}, "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="],
-
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -3208,8 +2478,6 @@
"module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
- "moo": ["moo@0.5.1", "", {}, "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w=="],
-
"motion": ["motion@12.29.2", "", { "dependencies": { "framer-motion": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-jMpHdAzEDF1QQ055cB+1lOBLdJ6ialVWl6QQzpJI2OvmHequ7zFVHM2mx0HNAy+Tu4omUlApfC+4vnkX0geEOg=="],
"motion-dom": ["motion-dom@12.29.2", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA=="],
@@ -3226,8 +2494,6 @@
"mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="],
- "mylas": ["mylas@2.1.14", "", {}, "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog=="],
-
"nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
"nanostores": ["nanostores@1.1.0", "", {}, "sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA=="],
@@ -3242,14 +2508,10 @@
"next": ["next@15.5.10", "", { "dependencies": { "@next/env": "15.5.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.7", "@next/swc-darwin-x64": "15.5.7", "@next/swc-linux-arm64-gnu": "15.5.7", "@next/swc-linux-arm64-musl": "15.5.7", "@next/swc-linux-x64-gnu": "15.5.7", "@next/swc-linux-x64-musl": "15.5.7", "@next/swc-win32-arm64-msvc": "15.5.7", "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-r0X65PNwyDDyOrWNKpQoZvOatw7BcsTPRKdwEqtc9cj3wv7mbBIk9tKed4klRaFXJdX0rugpuMTHslDrAU1bBg=="],
- "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
-
"nice-grpc": ["nice-grpc@2.1.14", "", { "dependencies": { "@grpc/grpc-js": "^1.14.0", "abort-controller-x": "^0.4.0", "nice-grpc-common": "^2.0.2" } }, "sha512-GK9pKNxlvnU5FAdaw7i2FFuR9CqBspcE+if2tqnKXBcE0R8525wj4BZvfcwj7FjvqbssqKxRHt2nwedalbJlww=="],
"nice-grpc-common": ["nice-grpc-common@2.0.2", "", { "dependencies": { "ts-error": "^1.0.6" } }, "sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ=="],
- "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
-
"node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="],
"node-abort-controller": ["node-abort-controller@3.1.1", "", {}, "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="],
@@ -3258,57 +2520,37 @@
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
- "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
-
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
- "node-stdlib-browser": ["node-stdlib-browser@1.3.1", "", { "dependencies": { "assert": "^2.0.0", "browser-resolve": "^2.0.0", "browserify-zlib": "^0.2.0", "buffer": "^5.7.1", "console-browserify": "^1.1.0", "constants-browserify": "^1.0.0", "create-require": "^1.1.1", "crypto-browserify": "^3.12.1", "domain-browser": "4.22.0", "events": "^3.0.0", "https-browserify": "^1.0.0", "isomorphic-timers-promises": "^1.0.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "pkg-dir": "^5.0.0", "process": "^0.11.10", "punycode": "^1.4.1", "querystring-es3": "^0.2.1", "readable-stream": "^3.6.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "string_decoder": "^1.0.0", "timers-browserify": "^2.0.4", "tty-browserify": "0.0.1", "url": "^0.11.4", "util": "^0.12.4", "vm-browserify": "^1.0.1" } }, "sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw=="],
-
"nodemon": ["nodemon@3.1.11", "", { "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" } }, "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
- "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
-
"nuqs": ["nuqs@2.8.6", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^5 || ^6 || ^7", "react-router-dom": "^5 || ^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-aRxeX68b4ULmhio8AADL2be1FWDy0EPqaByPvIYWrA7Pm07UjlrICp/VPlSnXJNAG0+3MQwv3OporO2sOXMVGA=="],
- "nypm": ["nypm@0.6.4", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw=="],
-
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
- "object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="],
-
- "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
-
- "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
-
- "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
-
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
- "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
+ "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="],
"oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="],
- "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
-
- "openai": ["openai@4.104.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA=="],
+ "openai": ["openai@6.16.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
- "ora": ["ora@9.1.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.2.2", "string-width": "^8.1.0" } }, "sha512-53uuLsXHOAJl5zLrUrzY9/kE+uIFEx7iaH4g2BIJQK4LZjY4LpCCYZVKDWIkL+F01wAaCg93duQ1whnK/AmY1A=="],
-
- "os-browserify": ["os-browserify@0.3.0", "", {}, "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A=="],
+ "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
"oxc-resolver": ["oxc-resolver@11.16.4", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.16.4", "@oxc-resolver/binding-android-arm64": "11.16.4", "@oxc-resolver/binding-darwin-arm64": "11.16.4", "@oxc-resolver/binding-darwin-x64": "11.16.4", "@oxc-resolver/binding-freebsd-x64": "11.16.4", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.16.4", "@oxc-resolver/binding-linux-arm-musleabihf": "11.16.4", "@oxc-resolver/binding-linux-arm64-gnu": "11.16.4", "@oxc-resolver/binding-linux-arm64-musl": "11.16.4", "@oxc-resolver/binding-linux-ppc64-gnu": "11.16.4", "@oxc-resolver/binding-linux-riscv64-gnu": "11.16.4", "@oxc-resolver/binding-linux-riscv64-musl": "11.16.4", "@oxc-resolver/binding-linux-s390x-gnu": "11.16.4", "@oxc-resolver/binding-linux-x64-gnu": "11.16.4", "@oxc-resolver/binding-linux-x64-musl": "11.16.4", "@oxc-resolver/binding-openharmony-arm64": "11.16.4", "@oxc-resolver/binding-wasm32-wasi": "11.16.4", "@oxc-resolver/binding-win32-arm64-msvc": "11.16.4", "@oxc-resolver/binding-win32-ia32-msvc": "11.16.4", "@oxc-resolver/binding-win32-x64-msvc": "11.16.4" } }, "sha512-nvJr3orFz1wNaBA4neRw7CAn0SsjgVaEw1UHpgO/lzVW12w+nsFnvU/S6vVX3kYyFaZdxZheTExi/fa8R8PrZA=="],
@@ -3332,14 +2574,8 @@
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
- "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
-
- "param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="],
-
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
- "parse-asn1": ["parse-asn1@5.1.9", "", { "dependencies": { "asn1.js": "^4.10.1", "browserify-aes": "^1.2.0", "evp_bytestokey": "^1.0.3", "pbkdf2": "^3.1.5", "safe-buffer": "^5.2.1" } }, "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg=="],
-
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
@@ -3348,12 +2584,6 @@
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
- "pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="],
-
- "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
-
- "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
-
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
@@ -3366,20 +2596,14 @@
"path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="],
- "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
-
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
- "pbkdf2": ["pbkdf2@3.1.5", "", { "dependencies": { "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "ripemd160": "^2.0.3", "safe-buffer": "^5.2.1", "sha.js": "^2.4.12", "to-buffer": "^1.2.1" } }, "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ=="],
-
"peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
- "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="],
-
"pg": ["pg@8.17.2", "", { "dependencies": { "pg-connection-string": "^2.10.1", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw=="],
"pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="],
@@ -3402,32 +2626,20 @@
"pino": ["pino@9.14.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w=="],
- "pino-abstract-transport": ["pino-abstract-transport@1.2.0", "", { "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" } }, "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q=="],
-
- "pino-pretty": ["pino-pretty@13.1.3", "", { "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pump": "^3.0.0", "secure-json-parse": "^4.0.0", "sonic-boom": "^4.0.1", "strip-json-comments": "^5.0.2" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg=="],
+ "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
"pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
- "pkg-dir": ["pkg-dir@5.0.0", "", { "dependencies": { "find-up": "^5.0.0" } }, "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA=="],
-
- "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
-
- "plimit-lit": ["plimit-lit@1.6.1", "", { "dependencies": { "queue-lit": "^1.5.1" } }, "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA=="],
+ "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
"points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="],
- "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
-
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
- "postcss-selector-parser": ["postcss-selector-parser@6.0.2", "", { "dependencies": { "cssesc": "^3.0.0", "indexes-of": "^1.0.1", "uniq": "^1.0.1" } }, "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg=="],
-
- "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
-
"postgres": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="],
"postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
@@ -3450,24 +2662,12 @@
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
- "pretty-error": ["pretty-error@4.0.0", "", { "dependencies": { "lodash": "^4.17.20", "renderkid": "^3.0.0" } }, "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw=="],
-
- "prism-themes": ["prism-themes@1.9.0", "", {}, "sha512-tX2AYsehKDw1EORwBps+WhBFKc2kxfoFpQAjxBndbZKr4fRmMkv47XN0BghC/K1qwodB1otbe4oF23vUTFDokw=="],
-
- "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
-
- "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
-
- "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
-
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
"prom-client": ["prom-client@15.1.3", "", { "dependencies": { "@opentelemetry/api": "^1.4.0", "tdigest": "^0.1.1" } }, "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g=="],
- "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
-
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
@@ -3482,11 +2682,9 @@
"pstree.remy": ["pstree.remy@1.1.8", "", {}, "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="],
- "public-encrypt": ["public-encrypt@4.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q=="],
-
"pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
- "punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="],
+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"puppeteer-core": ["puppeteer-core@24.36.1", "", { "dependencies": { "@puppeteer/browsers": "2.11.2", "chromium-bidi": "13.0.1", "debug": "^4.4.3", "devtools-protocol": "0.0.1551306", "typed-query-selector": "^2.12.0", "webdriver-bidi-protocol": "0.4.0", "ws": "^8.19.0" } }, "sha512-L7ykMWc3lQf3HS7ME3PSjp7wMIjJeW6+bKfH/RSTz5l6VUDGubnrC2BKj3UvM28Y5PMDFW0xniJOZHBZPpW1dQ=="],
@@ -3496,54 +2694,32 @@
"query-string": ["query-string@9.3.1", "", { "dependencies": { "decode-uri-component": "^0.4.1", "filter-obj": "^5.1.0", "split-on-first": "^3.0.0" } }, "sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw=="],
- "querystring-es3": ["querystring-es3@0.2.1", "", {}, "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA=="],
-
- "queue-lit": ["queue-lit@1.5.2", "", {}, "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw=="],
-
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
- "randomfill": ["randomfill@1.0.4", "", { "dependencies": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" } }, "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw=="],
-
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
- "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
+ "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
- "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
-
- "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
-
- "react-cmdk": ["react-cmdk@1.3.9", "", { "dependencies": { "@headlessui/react": "^1.6.4", "@heroicons/react": "^2.0.13", "html-webpack-plugin": "^5.5.0" }, "peerDependencies": { "react": "^16.x || ^17.x || ^18.x", "react-dom": "^16.x || ^17.x || ^18.x" } }, "sha512-MSVmAQZ9iqY7hO3r++XP6yWSHzGfMDGMvY3qlDT8k5RiWoRFwO1CGPlsWzhvcUbPilErzsMKK7uB4McEcX4B6g=="],
-
- "react-confetti-explosion": ["react-confetti-explosion@2.1.2", "", { "dependencies": { "lodash": "^4.17.21", "react-jss": "^10.9.2" }, "peerDependencies": { "react": "^18.x", "react-dom": "^18.x" } }, "sha512-4UzDFBajAGXmF9TSJoRMO2QOBCIXc66idTxH8l7Mkul48HLGtk+tMzK9HYDYsy7Zmw5sEGchi2fbn4AJUuLrZw=="],
+ "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
"react-day-picker": ["react-day-picker@8.10.1", "", { "peerDependencies": { "date-fns": "^2.28.0 || ^3.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA=="],
- "react-display-name": ["react-display-name@0.2.5", "", {}, "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg=="],
-
"react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
- "react-dropzone": ["react-dropzone@14.3.8", "", { "dependencies": { "attr-accept": "^2.2.4", "file-selector": "^2.1.0", "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">= 16.8 || 18.0.0" } }, "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug=="],
-
"react-email": ["react-email@4.0.16", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/traverse": "^7.27.0", "chalk": "^5.0.0", "chokidar": "^4.0.3", "commander": "^13.0.0", "debounce": "^2.0.0", "esbuild": "^0.25.0", "glob": "^11.0.0", "log-symbols": "^7.0.0", "mime-types": "^3.0.0", "next": "^15.3.1", "normalize-path": "^3.0.0", "ora": "^8.0.0", "socket.io": "^4.8.1" }, "bin": { "email": "dist/cli/index.mjs" } }, "sha512-auhFU+nQxAkKkP6lQhPyGsa9exwfUEzp2BwZnjHokCwphZlg30tu4t1LgdKRwGPYsi7XNGy6asbVLAUhOVpzzg=="],
- "react-hot-toast": ["react-hot-toast@2.6.0", "", { "dependencies": { "csstype": "^3.1.3", "goober": "^2.1.16" }, "peerDependencies": { "react": ">=16", "react-dom": ">=16" } }, "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg=="],
-
"react-hotkeys-hook": ["react-hotkeys-hook@4.6.2", "", { "peerDependencies": { "react": ">=16.8.1", "react-dom": ">=16.8.1" } }, "sha512-FmP+ZriY3EG59Ug/lxNfrObCnW9xQShgk7Nb83+CkpfkcCpfS95ydv+E9JuXA5cp8KtskU7LGlIARpkc92X22Q=="],
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
- "react-jss": ["react-jss@10.10.0", "", { "dependencies": { "@babel/runtime": "^7.3.1", "@emotion/is-prop-valid": "^0.7.3", "css-jss": "10.10.0", "hoist-non-react-statics": "^3.2.0", "is-in-browser": "^1.1.3", "jss": "10.10.0", "jss-preset-default": "10.10.0", "prop-types": "^15.6.0", "shallow-equal": "^1.2.0", "theming": "^3.3.0", "tiny-warning": "^1.0.2" }, "peerDependencies": { "react": ">=16.8.6" } }, "sha512-WLiq84UYWqNBF6579/uprcIUnM1TSywYq6AIjKTTTG5ziJl9Uy+pwuvpN3apuyVwflMbD60PraeTKT7uWH9XEQ=="],
-
"react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="],
- "react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="],
-
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
@@ -3558,7 +2734,7 @@
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
- "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
+ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
@@ -3574,16 +2750,12 @@
"redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="],
- "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
-
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
"regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
"regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="],
- "regexp-to-ast": ["regexp-to-ast@0.5.0", "", {}, "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw=="],
-
"rehype-harden": ["rehype-harden@1.1.7", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-j5DY0YSK2YavvNGV+qBHma15J9m0WZmRe8posT5AtKDS6TNWtMVTo6RiqF8SidfcASYz8f3k2J/1RWmq5zTXUw=="],
"rehype-katex": ["rehype-katex@7.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/katex": "^0.16.0", "hast-util-from-html-isomorphic": "^2.0.0", "hast-util-to-text": "^4.0.0", "katex": "^0.16.0", "unist-util-visit-parents": "^6.0.0", "vfile": "^6.0.0" } }, "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA=="],
@@ -3592,8 +2764,6 @@
"rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="],
- "relateurl": ["relateurl@0.2.7", "", {}, "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog=="],
-
"remark-cjk-friendly": ["remark-cjk-friendly@1.2.3", "", { "dependencies": { "micromark-extension-cjk-friendly": "1.2.3" }, "peerDependencies": { "@types/mdast": "^4.0.0", "unified": "^11.0.0" }, "optionalPeers": ["@types/mdast"] }, "sha512-UvAgxwlNk+l9Oqgl/9MWK2eWRS7zgBW/nXX9AthV7nd/3lNejF138E7Xbmk9Zs4WjTJGs721r7fAEc7tNFoH7g=="],
"remark-cjk-friendly-gfm-strikethrough": ["remark-cjk-friendly-gfm-strikethrough@1.2.3", "", { "dependencies": { "micromark-extension-cjk-friendly-gfm-strikethrough": "1.2.3" }, "peerDependencies": { "@types/mdast": "^4.0.0", "unified": "^11.0.0" }, "optionalPeers": ["@types/mdast"] }, "sha512-bXfMZtsaomK6ysNN/UGRIcasQAYkC10NtPmP0oOHOV8YOhA2TXmwRXCku4qOzjIFxAPfish5+XS0eIug2PzNZA=="],
@@ -3610,16 +2780,12 @@
"remend": ["remend@1.0.1", "", {}, "sha512-152puVH0qMoRJQFnaMG+rVDdf01Jq/CaED+MBuXExurJgdbkLp0c3TIe4R12o28Klx8uyGsjvFNG05aFG69G9w=="],
- "renderkid": ["renderkid@3.0.0", "", { "dependencies": { "css-select": "^4.1.3", "dom-converter": "^0.2.0", "htmlparser2": "^6.1.0", "lodash": "^4.17.21", "strip-ansi": "^6.0.1" } }, "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg=="],
-
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="],
- "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
-
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
"resend": ["resend@4.8.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-R8eBOFQDO6dzRTDmaMEdpqrkmgSjPpVXt4nGfWsZdYOet0kqra0xgbvTES6HmCriZEXbmGk3e0DiGIaLFTFSHA=="],
@@ -3630,14 +2796,12 @@
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
- "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
+ "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
- "ripemd160": ["ripemd160@2.0.3", "", { "dependencies": { "hash-base": "^3.1.2", "inherits": "^2.0.4" } }, "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA=="],
-
"robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
"rollup": ["rollup@4.57.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.0", "@rollup/rollup-android-arm64": "4.57.0", "@rollup/rollup-darwin-arm64": "4.57.0", "@rollup/rollup-darwin-x64": "4.57.0", "@rollup/rollup-freebsd-arm64": "4.57.0", "@rollup/rollup-freebsd-x64": "4.57.0", "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", "@rollup/rollup-linux-arm-musleabihf": "4.57.0", "@rollup/rollup-linux-arm64-gnu": "4.57.0", "@rollup/rollup-linux-arm64-musl": "4.57.0", "@rollup/rollup-linux-loong64-gnu": "4.57.0", "@rollup/rollup-linux-loong64-musl": "4.57.0", "@rollup/rollup-linux-ppc64-gnu": "4.57.0", "@rollup/rollup-linux-ppc64-musl": "4.57.0", "@rollup/rollup-linux-riscv64-gnu": "4.57.0", "@rollup/rollup-linux-riscv64-musl": "4.57.0", "@rollup/rollup-linux-s390x-gnu": "4.57.0", "@rollup/rollup-linux-x64-gnu": "4.57.0", "@rollup/rollup-linux-x64-musl": "4.57.0", "@rollup/rollup-openbsd-x64": "4.57.0", "@rollup/rollup-openharmony-arm64": "4.57.0", "@rollup/rollup-win32-arm64-msvc": "4.57.0", "@rollup/rollup-win32-ia32-msvc": "4.57.0", "@rollup/rollup-win32-x64-gnu": "4.57.0", "@rollup/rollup-win32-x64-msvc": "4.57.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA=="],
@@ -3648,8 +2812,6 @@
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
- "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
-
"run-async": ["run-async@4.0.6", "", {}, "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
@@ -3660,16 +2822,12 @@
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
- "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
-
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
- "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="],
-
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
@@ -3682,24 +2840,14 @@
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
- "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
-
- "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
-
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
- "sha.js": ["sha.js@2.4.12", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" } }, "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w=="],
-
- "shallow-equal": ["shallow-equal@1.2.1", "", {}, "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="],
-
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
- "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
-
"shiki": ["shiki@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/engine-javascript": "3.21.0", "@shikijs/engine-oniguruma": "3.21.0", "@shikijs/langs": "3.21.0", "@shikijs/themes": "3.21.0", "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w=="],
"shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="],
@@ -3712,9 +2860,7 @@
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
- "sift-string": ["sift-string@0.0.2", "", {}, "sha512-cOWdhRSgl6neRIgTCLdqsKgQQGnkF92+PzVKpRJEnVVZvFs/mLipRanroOO+2Lp6Kv4vWk/3wMnRylTne0jksQ=="],
-
- "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
@@ -3724,12 +2870,6 @@
"simple-wcswidth": ["simple-wcswidth@1.1.2", "", {}, "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw=="],
- "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
-
- "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
-
- "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
-
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
"smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="],
@@ -3760,8 +2900,6 @@
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
- "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
-
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
"standardwebhooks": ["standardwebhooks@1.0.0", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg=="],
@@ -3770,15 +2908,11 @@
"stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
- "stream-browserify": ["stream-browserify@3.0.0", "", { "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" } }, "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA=="],
-
- "stream-http": ["stream-http@3.2.0", "", { "dependencies": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.4", "readable-stream": "^3.6.0", "xtend": "^4.0.2" } }, "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A=="],
-
"streamdown": ["streamdown@1.6.11", "", { "dependencies": { "clsx": "^2.1.1", "hast": "^1.0.0", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "katex": "^0.16.22", "lucide-react": "^0.542.0", "marked": "^16.2.1", "mermaid": "^11.11.0", "rehype-harden": "^1.1.6", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-cjk-friendly": "^1.2.3", "remark-cjk-friendly-gfm-strikethrough": "^1.2.3", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.0.1", "shiki": "^3.12.2", "tailwind-merge": "^3.3.1", "unified": "^11.0.5", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-Y38fwRx5kCKTluwM+Gf27jbbi9q6Qy+WC9YrC1YbCpMkktT3PsRBJHMWiqYeF8y/JzLpB1IzDoeaB6qkQEDnAA=="],
"streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
- "string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="],
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -3786,14 +2920,10 @@
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
- "stringify-object": ["stringify-object@3.3.0", "", { "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" } }, "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw=="],
-
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
- "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
-
"strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="],
"stripe": ["stripe@19.3.0-beta.1", "", { "dependencies": { "qs": "^6.11.0" }, "peerDependencies": { "@types/node": ">=16" }, "optionalPeers": ["@types/node"] }, "sha512-tZLZYj2LPBt+FvYKQYRcop9IAvkDxIsqealtgG2kJ9zVK0DAy8yqC5aXJZxJ+81I5mRWXXpyLKGu298/owkX8Q=="],
@@ -3808,10 +2938,6 @@
"stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="],
- "super-regex": ["super-regex@0.2.0", "", { "dependencies": { "clone-regexp": "^3.0.0", "function-timeout": "^0.1.0", "time-span": "^5.1.0" } }, "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw=="],
-
- "superagent": ["superagent@10.3.0", "", { "dependencies": { "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.5", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", "qs": "^6.14.1" } }, "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ=="],
-
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
@@ -3822,16 +2948,10 @@
"swr": ["swr@2.3.8", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w=="],
- "symbol-observable": ["symbol-observable@1.2.0", "", {}, "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="],
-
"tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="],
- "tailwind-scrollbar-hide": ["tailwind-scrollbar-hide@4.0.0", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20" } }, "sha512-gobtvVcThB2Dxhy0EeYSS1RKQJ5baDFkamkhwBvzvevwX6L4XQfpZ3me9s25Ss1ecFVT5jPYJ50n+7xTBJG9WQ=="],
-
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
- "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
-
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
"tar-fs": ["tar-fs@3.1.1", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg=="],
@@ -3844,40 +2964,24 @@
"text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
- "theming": ["theming@3.3.0", "", { "dependencies": { "hoist-non-react-statics": "^3.3.0", "prop-types": "^15.5.8", "react-display-name": "^0.2.4", "tiny-warning": "^1.0.2" }, "peerDependencies": { "react": ">=16.3" } }, "sha512-u6l4qTJRDaWZsqa8JugaNt7Xd8PPl9+gonZaIe28vAhqgHMIG/DOyFPqiKN/gQLQYj05tHv+YQdNILL4zoiAVA=="],
-
"thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="],
"throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="],
- "time-span": ["time-span@5.1.0", "", { "dependencies": { "convert-hrtime": "^5.0.0" } }, "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA=="],
-
- "timers-browserify": ["timers-browserify@2.0.12", "", { "dependencies": { "setimmediate": "^1.0.4" } }, "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ=="],
-
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
- "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="],
-
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
- "tmp-cache": ["tmp-cache@1.1.0", "", {}, "sha512-j040fkL/x+XAZQ9K3bKGEPwgYhOZNBQLa3NXEADUiuno9C+3N2JJA4bVPDREixp604G3/vTXWA3DIPpA9lu1RQ=="],
-
- "to-buffer": ["to-buffer@1.2.2", "", { "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", "typed-array-buffer": "^1.0.3" } }, "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw=="],
-
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
- "tokenlens": ["tokenlens@1.3.1", "", { "dependencies": { "@tokenlens/core": "1.3.0", "@tokenlens/fetch": "1.3.0", "@tokenlens/helpers": "1.3.1", "@tokenlens/models": "1.3.0" } }, "sha512-7oxmsS5PNCX3z+b+z07hL5vCzlgHKkCGrEQjQmWl5l+v5cUrtL7S1cuST4XThaL1XyjbTX8J5hfP0cjDJRkaLA=="],
-
"touch": ["touch@3.1.1", "", { "bin": { "nodetouch": "bin/nodetouch.js" } }, "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
- "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
-
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
@@ -3890,54 +2994,32 @@
"ts-error": ["ts-error@1.0.6", "", {}, "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA=="],
- "ts-node": ["ts-node@10.9.2", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-script": "dist/bin-script-deprecated.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js" } }, "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ=="],
-
- "tsc-alias": ["tsc-alias@1.8.16", "", { "dependencies": { "chokidar": "^3.5.3", "commander": "^9.0.0", "get-tsconfig": "^4.10.0", "globby": "^11.0.4", "mylas": "^2.1.9", "normalize-path": "^3.0.0", "plimit-lit": "^1.2.6" }, "bin": { "tsc-alias": "dist/bin/index.js" } }, "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g=="],
-
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
- "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="],
-
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
- "tsyringe": ["tsyringe@4.10.0", "", { "dependencies": { "tslib": "^1.9.3" } }, "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw=="],
-
- "tty-browserify": ["tty-browserify@0.0.1", "", {}, "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw=="],
-
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
- "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
-
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
- "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
-
"typed-query-selector": ["typed-query-selector@2.12.0", "", {}, "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript-eslint": ["typescript-eslint@8.54.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ=="],
- "typescript-language-server": ["typescript-language-server@5.1.3", "", { "bin": { "typescript-language-server": "lib/cli.mjs" } }, "sha512-r+pAcYtWdN8tKlYZPwiiHNA2QPjXnI02NrW5Sf2cVM3TRtuQ3V9EKKwOxqwaQ0krsaEXk/CbN90I5erBuf84Vg=="],
-
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
- "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
-
"undefsafe": ["undefsafe@2.0.5", "", {}, "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
- "unfetch": ["unfetch@4.1.0", "", {}, "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg=="],
-
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
- "uniq": ["uniq@1.0.1", "", {}, "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA=="],
-
"unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="],
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
@@ -3960,8 +3042,6 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
- "url": ["url@0.11.4", "", { "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" } }, "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg=="],
-
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
@@ -3970,22 +3050,14 @@
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
- "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
-
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
- "utila": ["utila@0.4.0", "", {}, "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA=="],
-
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
- "v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="],
-
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
- "vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
-
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
@@ -3996,14 +3068,8 @@
"vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
- "vite-plugin-node-polyfills": ["vite-plugin-node-polyfills@0.24.0", "", { "dependencies": { "@rollup/plugin-inject": "^5.0.5", "node-stdlib-browser": "^1.2.0" }, "peerDependencies": { "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-GA9QKLH+vIM8NPaGA+o2t8PDfFUl32J8rUp1zQfMKVJQiNkOX4unE51tR6ppl6iKw5yOrDAdSH7r/UIFLCVhLw=="],
-
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
- "vm-browserify": ["vm-browserify@1.1.2", "", {}, "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="],
-
- "vscode-emmet-helper-bundled": ["vscode-emmet-helper-bundled@0.0.1", "", {}, "sha512-EhZ0Wt8MbdrKF3NUMfaUDhFPTdRnl1tyqYS7KOcNtsSNTV285IV+XPDtNQyw5rwYsULEfb6n+fK1DRufJQlPYw=="],
-
"vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
"vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="],
@@ -4014,17 +3080,13 @@
"vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
- "vscode-oniguruma": ["vscode-oniguruma@2.0.1", "", {}, "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ=="],
-
- "vscode-textmate": ["vscode-textmate@9.3.2", "", {}, "sha512-n2uGbUcrjhUEBH16uGA0TvUfhWwliFZ1e3+pTjrkim1Mt7ydB41lV08aUvsi70OlzDWp6X7Bx3w/x3fAXIsN0Q=="],
-
"vscode-uri": ["vscode-uri@3.0.8", "", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="],
"walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="],
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
- "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
+ "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"web-vitals": ["web-vitals@5.1.0", "", {}, "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg=="],
@@ -4040,15 +3102,11 @@
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
- "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="],
-
- "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
-
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"workerpool": ["workerpool@9.3.4", "", {}, "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg=="],
- "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
+ "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
@@ -4056,8 +3114,6 @@
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
- "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
-
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
@@ -4074,22 +3130,12 @@
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
- "yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="],
-
"yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
- "yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="],
-
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
"yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="],
- "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
-
- "zen-observable": ["zen-observable@0.8.15", "", {}, "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="],
-
- "zen-observable-ts": ["zen-observable-ts@1.1.0", "", { "dependencies": { "@types/zen-observable": "0.8.3", "zen-observable": "0.8.15" } }, "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA=="],
-
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"zod-openapi": ["zod-openapi@5.4.6", "", { "peerDependencies": { "zod": "^3.25.74 || ^4.0.0" } }, "sha512-P2jsOOBAq/6hCwUsMCjUATZ8szkMsV5VAwZENfyxp2Hc/XPJQpVwAgevWZc65xZauCwWB9LAn7zYeiCJFAEL+A=="],
@@ -4100,40 +3146,14 @@
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
- "@amplitude/analytics-client-common/@amplitude/analytics-types": ["@amplitude/analytics-types@2.11.1", "", {}, "sha512-wFEgb0t99ly2uJKm5oZ28Lti0Kh5RecR5XBkwfUpDzn84IoCIZ8GJTsMw/nThu8FZFc7xFDA4UAt76zhZKrs9A=="],
-
- "@amplitude/plugin-experiment-browser/@amplitude/analytics-core": ["@amplitude/analytics-core@2.37.0", "", { "dependencies": { "@amplitude/analytics-connector": "^1.6.4", "tslib": "^2.4.1", "zen-observable-ts": "^1.1.0" } }, "sha512-/2vIyquLMSA29MMM901d5DOhBZ5bc6Qf4s0KVfRk8Avn90mC7KlE5SQiHOAW8+63o5aT1NtB0djrY4cq8Y8Opw=="],
-
- "@amplitude/plugin-session-replay-browser/@amplitude/analytics-types": ["@amplitude/analytics-types@2.11.1", "", {}, "sha512-wFEgb0t99ly2uJKm5oZ28Lti0Kh5RecR5XBkwfUpDzn84IoCIZ8GJTsMw/nThu8FZFc7xFDA4UAt76zhZKrs9A=="],
-
- "@amplitude/rrweb/@amplitude/rrweb-types": ["@amplitude/rrweb-types@2.0.0-alpha.35", "", {}, "sha512-cR/xlN5fu7Cw6Zh9O6iEgNleqT92wJ3HO2mV19yQE6SRqLGKXXeDeTrUBd5FKCZnXvRsv3JtK+VR4u9vmZze3g=="],
-
- "@amplitude/rrweb/@amplitude/rrweb-utils": ["@amplitude/rrweb-utils@2.0.0-alpha.35", "", {}, "sha512-/OpyKKHYGwoy2fvWDg5jiH1LzWag4wlFTQjd2DUgndxlXccQF1+yxYljCDdM+J1GBeZ7DaLZa9qe2JUUtoNOOw=="],
-
- "@amplitude/session-replay-browser/@amplitude/analytics-types": ["@amplitude/analytics-types@2.11.1", "", {}, "sha512-wFEgb0t99ly2uJKm5oZ28Lti0Kh5RecR5XBkwfUpDzn84IoCIZ8GJTsMw/nThu8FZFc7xFDA4UAt76zhZKrs9A=="],
-
- "@amplitude/targeting/@amplitude/analytics-client-common": ["@amplitude/analytics-client-common@2.4.24", "", { "dependencies": { "@amplitude/analytics-connector": "^1.4.8", "@amplitude/analytics-core": "2.37.0", "@amplitude/analytics-types": "2.11.1", "tslib": "^2.4.1" } }, "sha512-8kdSSdc5p95rbavBHXJV4WIxHFbuh9eTDg4cWHKoLbZSw9bspzxrcORcfMbCZrQr017YpxXiTgJUrybhv/Z2FA=="],
-
- "@amplitude/targeting/@amplitude/analytics-core": ["@amplitude/analytics-core@2.37.0", "", { "dependencies": { "@amplitude/analytics-connector": "^1.6.4", "tslib": "^2.4.1", "zen-observable-ts": "^1.1.0" } }, "sha512-/2vIyquLMSA29MMM901d5DOhBZ5bc6Qf4s0KVfRk8Avn90mC7KlE5SQiHOAW8+63o5aT1NtB0djrY4cq8Y8Opw=="],
-
- "@amplitude/targeting/@amplitude/analytics-types": ["@amplitude/analytics-types@2.11.1", "", {}, "sha512-wFEgb0t99ly2uJKm5oZ28Lti0Kh5RecR5XBkwfUpDzn84IoCIZ8GJTsMw/nThu8FZFc7xFDA4UAt76zhZKrs9A=="],
-
- "@amplitude/targeting/@amplitude/experiment-core": ["@amplitude/experiment-core@0.7.2", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-Wc2NWvgQ+bLJLeF0A9wBSPIaw0XuqqgkPKsoNFQrmS7r5Djd56um75In05tqmVntPJZRvGKU46pAp8o5tdf4mA=="],
-
- "@anthropic-ai/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
-
"@autumn/server/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
- "@autumn/server/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
-
"@autumn/shared/@date-fns/utc": ["@date-fns/utc@2.1.0", "", {}, "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA=="],
"@autumn/vite/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="],
"@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
- "@autumn/vite/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
-
"@autumn/vite/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
"@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@3.0.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w=="],
@@ -4468,29 +3488,19 @@
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
- "@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+ "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
- "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
- "@better-auth/cli/dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
-
- "@better-auth/cli/drizzle-orm": ["drizzle-orm@0.41.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-7A4ZxhHk9gdlXmTdPj/lREtP+3u8KvZ4yEN6MYVxBzZGex5Wtdc+CWSbu7btgF6TB0N+MNPrvW7RKBbxJchs/Q=="],
-
- "@better-auth/cli/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
-
"@better-auth/core/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@better-auth/dash/better-call": ["better-call@1.2.0", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-7msprrikJah2Pq+OPJOUkqqlDOpVQysBPfxLfXQZr1XeIPqUD3O5z6qxh0vsAU8m/GDFbJD0n1a4vvTnekM7Kw=="],
"@better-auth/oauth-provider/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
- "@better-auth/stripe/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
+ "@chevrotain/cst-dts-gen/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
- "@chevrotain/cst-dts-gen/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
-
- "@chevrotain/gast/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
+ "@chevrotain/gast/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
@@ -4506,28 +3516,12 @@
"@fortawesome/fontawesome-svg-core/@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@7.1.0", "", {}, "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA=="],
- "@headlessui/react/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
-
- "@inquirer/core/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
-
- "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
-
"@inquirer/external-editor/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
- "@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
-
- "@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
-
- "@jridgewell/source-map/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
-
- "@jsx-tool/jsx-tool/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="],
-
- "@langchain/core/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
-
"@langchain/core/decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
"@langchain/core/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
@@ -4546,12 +3540,8 @@
"@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
- "@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
-
"@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
- "@opentelemetry/auto-instrumentations-node/@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.50.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.202.0", "@opentelemetry/redis-common": "^0.38.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HKrWKOM23qNwqNjWfzkw7mePversmcH5ac6T1dUdiRyJVYaLr4qfydyYkgKIGWHOF2TKvQGobXo3CjvxABQWVw=="],
-
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
@@ -4566,26 +3556,18 @@
"@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
- "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
-
"@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
"@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
- "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
-
"@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
"@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
- "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
-
"@opentelemetry/exporter-prometheus/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
"@opentelemetry/exporter-prometheus/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
- "@opentelemetry/exporter-prometheus/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
-
"@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
"@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
@@ -4612,8 +3594,6 @@
"@opentelemetry/instrumentation-http/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
- "@opentelemetry/instrumentation-ioredis/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA=="],
-
"@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.15.4", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg=="],
"@opentelemetry/instrumentation-redis/@opentelemetry/redis-common": ["@opentelemetry/redis-common@0.37.0", "", {}, "sha512-tJwgE6jt32bLs/9J6jhQRKU2EZnsD8qaO13aoFyXwF6s4LhpT7YFHf3Z03MqdILk6BA2BFUhoyh7k9fj9i032A=="],
@@ -4628,8 +3608,6 @@
"@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
- "@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
-
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ=="],
"@opentelemetry/propagator-b3/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
@@ -4640,23 +3618,19 @@
"@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
+ "@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
+
+ "@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
+
"@opentelemetry/sdk-node/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
"@opentelemetry/sdk-node/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
- "@opentelemetry/sdk-node/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
-
"@opentelemetry/sdk-node/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ=="],
- "@opentelemetry/sdk-node/@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.0.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.0.1", "@opentelemetry/core": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-UhdbPF19pMpBtCWYP5lHbTogLWx9N0EBxtdagvkn5YtsAnCBZzL7SjktG+ZmupRgifsHMjwUaCCaVmqGfSADmA=="],
+ "@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
- "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
-
- "@paralleldrive/cuid2/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
-
- "@posthog/ai/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.71.2", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ=="],
-
- "@posthog/ai/openai": ["openai@6.16.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg=="],
+ "@opentelemetry/sdk-trace-node/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ=="],
"@posthog/ai/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
@@ -4674,10 +3648,6 @@
"@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
- "@radix-ui/react-progress/@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="],
-
- "@radix-ui/react-progress/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
-
"@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
@@ -4696,10 +3666,10 @@
"@sentry/browser/@sentry/core": ["@sentry/core@10.37.0", "", {}, "sha512-hkRz7S4gkKLgPf+p3XgVjVm7tAfvcEPZxeACCC6jmoeKhGkzN44nXwLiqqshJ25RMcSrhfFvJa/FlBg6zupz7g=="],
- "@sentry/bundler-plugin-core/magic-string": ["magic-string@0.30.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ=="],
-
"@sentry/cli/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
+ "@sentry/node/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.5.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw=="],
+
"@sentry/node/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.204.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.204.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-vV5+WSxktzoMP8JoYWKeopChy6G3HKk4UQ2hESCRDUUTZqQ3+nM3u8noVG0LmNfRWwcFBnbZ71GKC7vaYYdJ1g=="],
"@sentry/node/@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.51.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.204.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-XGmjYwjVRktD4agFnWBWQXo9SiYHKBxR6Ag3MLXwtLE4R99N3a08kGKM5SC1qOFKIELcQDGFEFT9ydXMH00Luw=="],
@@ -4746,19 +3716,17 @@
"@sentry/node/@opentelemetry/instrumentation-undici": ["@opentelemetry/instrumentation-undici@0.15.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.204.0" }, "peerDependencies": { "@opentelemetry/api": "^1.7.0" } }, "sha512-sNFGA/iCDlVkNjzTzPRcudmI11vT/WAfAguRdZY9IspCw02N4WSC72zTuQhSMheh2a1gdeM9my1imnKRvEEvEg=="],
+ "@sentry/node-core/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.5.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw=="],
+
+ "@sentry/opentelemetry/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.5.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw=="],
+
"@sentry/react/@sentry/core": ["@sentry/core@10.37.0", "", {}, "sha512-hkRz7S4gkKLgPf+p3XgVjVm7tAfvcEPZxeACCC6jmoeKhGkzN44nXwLiqqshJ25RMcSrhfFvJa/FlBg6zupz7g=="],
"@smithy/eventstream-codec/@smithy/types": ["@smithy/types@1.2.0", "", { "dependencies": { "tslib": "^2.5.0" } }, "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA=="],
"@smithy/eventstream-codec/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@1.1.0", "", { "dependencies": { "tslib": "^2.5.0" } }, "sha512-7UtIE9eH0u41zpB60Jzr0oNCQ3hMJUabMcKRUVjmyHTXiWDE4vjSqN6qlih7rCNeKGbioS7f/y2Jgym4QZcKFg=="],
- "@tailwindcss/language-service/@types/semver": ["@types/semver@7.3.10", "", {}, "sha512-zsv3fsC7S84NN6nPK06u79oWgrPVd0NvOyqgghV1haPaFcVxIrP4DLomRwGAXk0ui4HZA7mOcSFL98sMVW9viw=="],
-
- "@tailwindcss/language-service/postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="],
-
- "@tailwindcss/language-service/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
-
- "@tailwindcss/language-service/vscode-languageserver": ["vscode-languageserver@8.1.0", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.3" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw=="],
+ "@tailwindcss/node/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
@@ -4778,8 +3746,6 @@
"@types/bunyan/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
- "@types/chai-http/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
-
"@types/connect/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
"@types/cors/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
@@ -4790,8 +3756,6 @@
"@types/mysql/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
- "@types/node-fetch/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
-
"@types/oracledb/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
"@types/pg/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
@@ -4800,12 +3764,8 @@
"@types/serve-static/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
- "@types/superagent/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
-
"@types/tedious/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
- "@types/through/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
-
"@types/ws/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
"@types/yauzl/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
@@ -4814,60 +3774,32 @@
"@vercel/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
- "@xyflow/react/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="],
-
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
- "asn1.js/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
-
"autumn-js/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
- "better-auth/@better-auth/telemetry": ["@better-auth/telemetry@1.4.12", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.12" } }, "sha512-4q504Og42PzkUbZjXDt+FyeYaS0WZmAlEOC3nbBCZDObTVCRUnGgJW52B2maJ7BCVvAQgBGLEeQmQzU5+63J0A=="],
-
"better-auth/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
- "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
-
- "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
-
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
- "browserify-sign/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
+ "body-parser/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
"bun-types/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
- "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
-
- "c12/dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
-
- "chevrotain/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
-
- "chevrotain-allstar/chevrotain": ["chevrotain@11.0.3", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", "@chevrotain/regexp-to-ast": "11.0.3", "@chevrotain/types": "11.0.3", "@chevrotain/utils": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="],
-
- "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+ "chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
- "clone-regexp/is-regexp": ["is-regexp@3.1.0", "", {}, "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA=="],
-
"cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
- "concurrently/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
-
- "create-ecdh/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
-
- "css-select/domhandler": ["domhandler@4.3.1", "", { "dependencies": { "domelementtype": "^2.2.0" } }, "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ=="],
-
- "css-select/domutils": ["domutils@2.8.0", "", { "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" } }, "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A=="],
-
"cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="],
"d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
@@ -4878,12 +3810,8 @@
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
- "diffie-hellman/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
-
"drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
- "elliptic/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
-
"engine.io/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
"engine.io/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
@@ -4896,12 +3824,8 @@
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
- "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
-
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
- "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
-
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
@@ -4916,12 +3840,6 @@
"gtoken/gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
- "html-minifier-terser/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
-
- "http-proxy/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
-
- "ink-spinner/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
-
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"knip/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
@@ -4930,34 +3848,20 @@
"langchain/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
- "langium/chevrotain": ["chevrotain@11.0.3", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", "@chevrotain/regexp-to-ast": "11.0.3", "@chevrotain/types": "11.0.3", "@chevrotain/utils": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="],
-
"langsmith/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"langsmith/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
- "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="],
-
- "md5.js/hash-base": ["hash-base@3.1.2", "", { "dependencies": { "inherits": "^2.0.4", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.1" } }, "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg=="],
+ "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
- "miller-rabin/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
-
- "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
-
- "mocha/log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
-
"mocha/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
- "node-stdlib-browser/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
-
- "node-stdlib-browser/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
-
"nodemon/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"nodemon/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
@@ -4966,11 +3870,11 @@
"nuqs/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
- "nypm/citty": ["citty@0.2.0", "", {}, "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA=="],
+ "ora/is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
- "openai/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
+ "ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
- "ora/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
+ "ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
@@ -4980,12 +3884,6 @@
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
- "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
-
- "pino/pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
-
- "pino-pretty/pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
-
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"posthog-js/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg=="],
@@ -5000,57 +3898,31 @@
"proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
- "public-encrypt/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
+ "raw-body/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
- "react-cmdk/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
-
- "react-confetti-explosion/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
-
"react-day-picker/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
- "react-day-picker/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
-
- "react-dom/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
-
- "react-email/commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="],
-
"react-email/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"react-email/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
- "react-email/ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
+ "react-email/log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
"react-promise-suspense/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="],
- "react-reconciler/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
-
"react-router/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
- "renderkid/htmlparser2": ["htmlparser2@6.1.0", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", "domutils": "^2.5.2", "entities": "^2.0.0" } }, "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A=="],
-
- "renderkid/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
-
- "ripemd160/hash-base": ["hash-base@3.1.2", "", { "dependencies": { "inherits": "^2.0.4", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.1" } }, "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg=="],
-
"router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
- "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
-
"socket.io-adapter/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
- "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
-
- "stream-browserify/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
-
- "stream-http/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
-
"streamdown/lucide-react": ["lucide-react@0.542.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw=="],
- "string-width-cjs/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+ "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -5060,40 +3932,22 @@
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
- "to-buffer/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
-
- "ts-node/diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="],
-
- "tsc-alias/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
-
- "tsc-alias/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
-
- "tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
-
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"unified/is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"unplugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
- "uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
-
"vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
- "widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
+ "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
- "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
+ "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
- "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
-
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
- "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
-
- "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
-
"@autumn/vite/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
@@ -5418,14 +4272,10 @@
"@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
- "@inquirer/core/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
-
- "@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
-
- "@inquirer/core/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
-
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
+ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
+
"@langchain/langgraph-sdk/p-queue/p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="],
"@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
@@ -5450,17 +4300,9 @@
"@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
- "@modelcontextprotocol/sdk/raw-body/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
-
- "@opentelemetry/instrumentation-ioredis/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg=="],
-
- "@opentelemetry/instrumentation-ioredis/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="],
-
- "@opentelemetry/instrumentation-ioredis/@opentelemetry/instrumentation/require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="],
-
"@opentelemetry/instrumentation-pg/@types/pg/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
- "@opentelemetry/sdk-node/@opentelemetry/sdk-trace-node/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.0.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw=="],
+ "@opentelemetry/sdk-trace-node/@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
"@prisma/instrumentation/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.57.2", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A=="],
@@ -5472,44 +4314,16 @@
"@sentry/node/@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.15.5", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ=="],
- "@tailwindcss/language-service/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
-
- "@tailwindcss/language-service/vscode-languageserver/vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.3", "", { "dependencies": { "vscode-jsonrpc": "8.1.0", "vscode-languageserver-types": "3.17.3" } }, "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA=="],
-
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
- "browserify-sign/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
-
- "browserify-sign/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
-
- "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
-
- "chevrotain-allstar/chevrotain/@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="],
-
- "chevrotain-allstar/chevrotain/@chevrotain/gast": ["@chevrotain/gast@11.0.3", "", { "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q=="],
-
- "chevrotain-allstar/chevrotain/@chevrotain/types": ["@chevrotain/types@11.0.3", "", {}, "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ=="],
-
- "chevrotain-allstar/chevrotain/@chevrotain/utils": ["@chevrotain/utils@11.0.3", "", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="],
-
- "chevrotain-allstar/chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
-
- "cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
-
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
- "concurrently/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
-
- "concurrently/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
-
- "css-select/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="],
-
"cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="],
"d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
@@ -5584,27 +4398,13 @@
"gtoken/gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
- "langium/chevrotain/@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="],
-
- "langium/chevrotain/@chevrotain/gast": ["@chevrotain/gast@11.0.3", "", { "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q=="],
-
- "langium/chevrotain/@chevrotain/types": ["@chevrotain/types@11.0.3", "", {}, "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ=="],
-
- "langium/chevrotain/@chevrotain/utils": ["@chevrotain/utils@11.0.3", "", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="],
-
- "langium/chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
-
"langsmith/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"langsmith/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
- "md5.js/hash-base/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
+ "log-symbols/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
- "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
-
- "mocha/log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
-
- "mocha/log-symbols/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
+ "log-symbols/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
@@ -5616,9 +4416,9 @@
"nodemon/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
- "openai/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+ "ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
- "ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
+ "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
@@ -5692,31 +4492,13 @@
"react-email/glob/path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
- "react-email/ora/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
-
- "react-email/ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
-
- "react-email/ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
-
- "react-email/ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
-
- "renderkid/htmlparser2/domhandler": ["domhandler@4.3.1", "", { "dependencies": { "domelementtype": "^2.2.0" } }, "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ=="],
-
- "renderkid/htmlparser2/domutils": ["domutils@2.8.0", "", { "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" } }, "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A=="],
-
- "renderkid/htmlparser2/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="],
-
- "renderkid/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
-
- "ripemd160/hash-base/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
+ "react-email/log-symbols/is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
- "tsc-alias/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
-
- "tsc-alias/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
+ "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
@@ -5776,17 +4558,9 @@
"vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
- "widest-line/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
-
- "wrap-ansi-cjs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
-
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
- "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
-
- "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
-
- "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
@@ -5954,68 +4728,26 @@
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@1.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw=="],
- "@inquirer/core/wrap-ansi/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
-
- "@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
-
"@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"@modelcontextprotocol/sdk/express/body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
- "@opentelemetry/instrumentation-ioredis/@opentelemetry/instrumentation/import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="],
-
"@sentry/node/@opentelemetry/instrumentation-pg/@types/pg/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
- "@tailwindcss/language-service/vscode-languageserver/vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@8.1.0", "", {}, "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw=="],
-
- "@tailwindcss/language-service/vscode-languageserver/vscode-languageserver-protocol/vscode-languageserver-types": ["vscode-languageserver-types@3.17.3", "", {}, "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="],
-
- "css-select/domutils/dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="],
-
- "md5.js/hash-base/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
-
- "md5.js/hash-base/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
-
- "mocha/log-symbols/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
-
- "mocha/log-symbols/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
-
"nodemon/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
- "ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
-
- "ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
-
"posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="],
"posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw=="],
"posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="],
- "prebuild-install/tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
-
"react-email/glob/path-scurry/lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
- "react-email/ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
-
- "react-email/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
-
- "react-email/ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
-
- "renderkid/htmlparser2/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="],
-
- "ripemd160/hash-base/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
-
- "ripemd160/hash-base/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
-
- "tsc-alias/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
-
"unplugin/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
- "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
-
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@4.1.3", "", { "dependencies": { "@smithy/protocol-http": "^4.1.8", "@smithy/querystring-builder": "^3.0.11", "@smithy/types": "^3.7.2", "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-6SxNltSncI8s689nvnzZQc/dPXcpHQ34KUj6gR/HBroytKOd/isMG3gJF/zBE1TBmTT18TXyzhg3O3SOOqGEhA=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@3.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA=="],
@@ -6096,10 +4828,6 @@
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="],
- "react-email/ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
-
- "react-email/ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
-
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@3.0.11", "", { "dependencies": { "@smithy/types": "^3.7.2", "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="],
diff --git a/server/src/cron/cleanupPreviewOrgs/cleanupPreviewOrgsCron.ts b/server/src/cron/cleanupPreviewOrgs/cleanupPreviewOrgsCron.ts
deleted file mode 100644
index af0868654..000000000
--- a/server/src/cron/cleanupPreviewOrgs/cleanupPreviewOrgsCron.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import * as Sentry from "@sentry/bun";
-import { subDays } from "date-fns";
-import { db } from "@/db/initDrizzle.js";
-import { hatchet } from "@/external/hatchet/initHatchet.js";
-import { logger } from "@/external/logtail/logtailUtils";
-import { deleteOrg } from "../../internal/orgs/deleteOrg/deleteOrg";
-import { OrgService } from "../../internal/orgs/OrgService";
-
-export const cleanupPreviewOrgsWorkflow = hatchet?.workflow({
- name: "cleanup-preview-orgs",
- onCrons: ["0 8 * * *"], // Run daily at 3 AM UTC
-});
-
-cleanupPreviewOrgsWorkflow?.task({
- name: "cleanup-preview-orgs-task",
- executionTimeout: "300s",
- fn: async () => {
- // 1. Find all preview orgs with no memberships
- const orgsToDelete = await OrgService.listPreviewOrgsForDeletion({ db });
-
- logger.info(`Found ${orgsToDelete.length} preview orgs to delete`);
-
- // SAFETY: Alert if we're trying to delete too many orgs (possible bug)
- if (orgsToDelete.length > 20) {
- Sentry.captureException(
- `Found ${orgsToDelete.length} preview orgs with no members, too many to run cleanup.`,
- );
- return { deletedCount: 0, totalFound: orgsToDelete.length, errors: 0 };
- }
-
- let deletedCount = 0;
-
- for (const previewOrg of orgsToDelete) {
- // SAFETY: Double-check this is actually a preview org
- if (!previewOrg.slug.startsWith("preview|")) {
- logger.error(
- `Attempted to delete non-preview org in cleanup: ${previewOrg.slug}`,
- );
- Sentry.captureException(
- `Attempted to delete non-preview org in cleanup: ${previewOrg.slug}`,
- );
- continue;
- }
-
- // SAFETY: Don't delete orgs older than 10 days (might be important)
- const tenDaysAgo = subDays(new Date(), 10);
- if (new Date(previewOrg.createdAt).getTime() < tenDaysAgo.getTime()) {
- logger.error(
- `Preview org older than 10 days found in cleanup: ${previewOrg.slug} (created: ${previewOrg.createdAt})`,
- );
- Sentry.captureException(
- `Preview org older than 10 days found in cleanup: ${previewOrg.slug} (created: ${previewOrg.createdAt})`,
- );
- continue;
- }
-
- logger.info(
- `Deleting preview org: ${previewOrg.id} (${previewOrg.slug})`,
- );
-
- await deleteOrg({
- org: previewOrg,
- db,
- logger,
- deleteOrgFromDb: true,
- });
-
- deletedCount++;
- logger.info(
- `Successfully deleted preview org: ${previewOrg.id} (${previewOrg.slug})`,
- );
- }
-
- return { deletedCount, totalFound: orgsToDelete.length };
- },
-});
diff --git a/server/src/db/cteUtils/sqlGenerators.ts b/server/src/db/cteUtils/sqlGenerators.ts
deleted file mode 100644
index 9fa4d166d..000000000
--- a/server/src/db/cteUtils/sqlGenerators.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import { type SQL, sql } from "drizzle-orm";
-import type { PgTable } from "drizzle-orm/pg-core";
-
-export interface ArrayAggregationConfig {
- table: PgTable;
- alias?: string;
- filter?: SQL;
- orderBy?: SQL[];
- limit?: number;
- distinct?: boolean;
-}
-
-export interface RowSubqueryConfig {
- table: PgTable;
- alias?: string;
- where?: SQL;
-}
-
-export interface JunctionJoinConfig {
- junctionTable: PgTable;
- fromField: string;
- toField: string;
- fromTable: PgTable;
- toTable: PgTable;
- fromId: SQL;
-}
-
-/**
- * Generate json_agg SQL for array aggregation with COALESCE to empty array
- * Example: COALESCE(json_agg(row_to_json(e) ORDER BY e.id) FILTER (WHERE e.id IS NOT NULL), '[]'::json)
- */
-export function generateArrayAggSQL({
- table,
- alias,
- filter,
- orderBy,
- distinct = false,
-}: ArrayAggregationConfig): SQL {
- const tableAlias = alias || getTableAlias(table);
- const distinctKeyword = distinct ? sql`DISTINCT ` : sql``;
-
- let aggExpression = sql`json_agg(${distinctKeyword}row_to_json(${sql.identifier(tableAlias)})`;
-
- // Add ORDER BY if provided
- if (orderBy && orderBy.length > 0) {
- aggExpression = sql`${aggExpression} ORDER BY ${sql.join(orderBy, sql`, `)}`;
- }
-
- aggExpression = sql`${aggExpression})`;
-
- // Add FILTER clause if provided
- if (filter) {
- aggExpression = sql`${aggExpression} FILTER (WHERE ${filter})`;
- }
-
- // Wrap with COALESCE to handle NULL → empty array
- return sql`COALESCE(${aggExpression}, '[]'::json)`;
-}
-
-/**
- * Generate row_to_json SQL for single row subquery
- * Example: (SELECT row_to_json(p) FROM products p WHERE p.id = ${parentId})
- */
-export function generateRowSubquerySQL({
- table,
- alias,
- where,
-}: RowSubqueryConfig): SQL {
- const tableAlias = alias || getTableAlias(table);
- const tableName = getTableName(table);
-
- let query = sql`(SELECT row_to_json(${sql.identifier(tableAlias)}) FROM ${sql.identifier(tableName)} ${sql.identifier(tableAlias)}`;
-
- if (where) {
- query = sql`${query} WHERE ${where}`;
- }
-
- query = sql`${query})`;
-
- return query;
-}
-
-// /**
-// * Generate SQL for many-to-many join through junction table
-// * Example:
-// * SELECT json_agg(o)
-// * FROM member m
-// * INNER JOIN organizations o ON o.id = m.organization_id
-// * WHERE m.user_id = ${userId}
-// */
-// export function generateJunctionJoinSQL({
-// junctionTable,
-// fromField,
-// toField,
-// toTable,
-// fromId,
-// }: JunctionJoinConfig): SQL {
-// const junctionAlias = getTableAlias(junctionTable);
-// const toAlias = getTableAlias(toTable);
-// const junctionTableName = getTableName(junctionTable);
-// const toTableName = getTableName(toTable);
-
-// return sql`
-// FROM ${sql.identifier(junctionTableName)} ${sql.identifier(junctionAlias)}
-// INNER JOIN ${sql.identifier(toTableName)} ${sql.identifier(toAlias)}
-// ON ${sql.identifier(toAlias)}.${sql.identifier("id")} = ${sql.identifier(junctionAlias)}.${sql.identifier(toField)}
-// WHERE ${sql.identifier(junctionAlias)}.${sql.identifier(fromField)} = ${fromId}
-// `;
-// }
-
-/**
- * Generate SQL for limiting results per parent using window functions
- * Example: row_number() OVER (PARTITION BY user_id ORDER BY created_at)
- */
-export function generateRowNumberSQL({
- partitionBy,
- orderBy,
-}: {
- partitionBy: SQL;
- orderBy?: SQL[];
-}): SQL {
- let windowSQL = sql`row_number() OVER (PARTITION BY ${partitionBy}`;
-
- if (orderBy && orderBy.length > 0) {
- windowSQL = sql`${windowSQL} ORDER BY ${sql.join(orderBy, sql`, `)}`;
- }
-
- windowSQL = sql`${windowSQL})`;
-
- return windowSQL;
-}
-
-/**
- * Extract table name from Drizzle table object
- */
-function getTableName(table: PgTable): string {
- return (table as any)[Symbol.for("drizzle:Name")] || String(table);
-}
-
-/**
- * Generate a short alias for a table (first letter of table name)
- */
-function getTableAlias(table: PgTable): string {
- const tableName = getTableName(table);
- return tableName.charAt(0);
-}
diff --git a/server/src/db/initializeDatabaseFunctions.ts b/server/src/db/initializeDatabaseFunctions.ts
deleted file mode 100644
index 811397c14..000000000
--- a/server/src/db/initializeDatabaseFunctions.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { readFileSync } from "node:fs";
-import { dirname, join } from "node:path";
-import { fileURLToPath } from "node:url";
-import { sql } from "drizzle-orm";
-import { db } from "./initDrizzle.js";
-
-const __dirname = dirname(fileURLToPath(import.meta.url));
-
-/**
- * Initialize all database functions (stored procedures)
- * Loads SQL files in dependency order: helpers first, then main functions
- */
-export const initializeDatabaseFunctions = async () => {
- try {
- console.log("Initializing database functions...");
-
- const sqlPath = join(__dirname, "../internal/balances/utils/sql");
-
- // Load SQL files in dependency order:
- // 1. Helper functions (used by main functions)
- // 2. Main functions (depend on helpers)
- const sqlFiles = [
- // Helper functions
- "deductFromRollovers.sql",
- "deductFromMainBalance.sql",
- "getTotalBalance.sql",
- "deductFromAdditionalBalance.sql",
- "performDeduction.sql",
- "syncBalances.sql",
- "syncBalancesV2.sql",
- ];
-
- for (const file of sqlFiles) {
- const sqlContent = readFileSync(join(sqlPath, file), "utf-8");
- await db.execute(sql.raw(sqlContent));
- console.log(` ✓ Loaded ${file}`);
- }
-
- console.log("Database functions initialized successfully");
- } catch (error) {
- console.error("Failed to initialize database functions:", error);
- throw error;
- }
-};
diff --git a/server/src/db/validateDbSchema.ts b/server/src/db/validateDbSchema.ts
deleted file mode 100644
index 935266a6b..000000000
--- a/server/src/db/validateDbSchema.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as schema from "@autumn/shared";
-import { is } from "drizzle-orm";
-import { PgTable } from "drizzle-orm/pg-core";
-import { logger } from "../external/logtail/logtailUtils";
-import type { DrizzleCli } from "./initDrizzle";
-
-const SKIP_TABLES = ["migrationErrors"];
-
-export const validateDbSchema = async ({ db }: { db: DrizzleCli }) => {
- // Dynamically get all tables from schema (exclude relations)
-
- const tableEntries = Object.entries(schema)
- .filter(([name, table]) => {
- // Filter out relations and non-table exports
- if (name.includes("Relations")) return false;
- // Skip migrationErrors table (known issue)
- if (SKIP_TABLES.includes(name)) return false;
- return is(table, PgTable);
- })
- .map(([name, table]) => ({ name, table: table as PgTable }));
-
- // Validate all tables by selecting all columns to ensure schema matches
- // If schema mismatches, Drizzle will throw an error
- const start = Date.now();
- const results = await Promise.allSettled(
- tableEntries.map(({ name, table }) =>
- db
- .select()
- .from(table)
- .limit(1)
- .then(() => ({ name, success: true as const }))
- .catch((err: Error) => ({
- name,
- success: false as const,
- error: err.message,
- })),
- ),
- );
- const elapsed = Date.now() - start;
-
- // Check for any failures
- const failures = results
- .map((r) => (r.status === "fulfilled" ? r.value : null))
- .filter(
- (v): v is { name: string; success: false; error: string } =>
- v !== null && !v.success,
- );
-
- if (failures.length > 0) {
- const failureDetails = failures
- .map((f) => `Table '${f.name}': ${f.error}`)
- .join("; ");
- logger.error(
- `Health check failed - DB schema validation error: ${failureDetails}`,
- );
- throw new Error(
- `Health check failed - DB schema validation error: ${failureDetails}`,
- );
- }
-
- logger.info(
- `Health check passed - DB schema validated for ${tableEntries.length} tables in ${elapsed}ms`,
- );
- return true;
-};
diff --git a/server/src/db/validateSqlFunctions.ts b/server/src/db/validateSqlFunctions.ts
deleted file mode 100644
index d622c720b..000000000
--- a/server/src/db/validateSqlFunctions.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { createHash } from "node:crypto";
-import { readdirSync, readFileSync } from "node:fs";
-import { join } from "node:path";
-import { fileURLToPath } from "node:url";
-import { sql } from "drizzle-orm";
-import { logger } from "../external/logtail/logtailUtils";
-import type { DrizzleCli } from "./initDrizzle";
-
-type SqlFunction = {
- name: string;
- sourceFile: string;
- content: string;
- contentHash: string;
-};
-
-/**
- * Dynamically discover SQL functions from the deductRpc folder
- */
-const discoverSqlFunctions = (): SqlFunction[] => {
- const __filename = fileURLToPath(import.meta.url);
- const deductRpcPath = join(
- __filename,
- "../../internal/balances/track/trackUtils/deductRpc",
- );
-
- const sqlFiles = readdirSync(deductRpcPath).filter((file) =>
- file.endsWith(".sql"),
- );
-
- return sqlFiles.map((file) => {
- const filePath = join(deductRpcPath, file);
- const content = readFileSync(filePath, "utf-8");
-
- // Extract function name from CREATE FUNCTION statement
- const functionNameMatch = content.match(
- /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+(\w+)/i,
- );
- const functionName = functionNameMatch?.[1];
-
- if (!functionName) {
- throw new Error(`Could not extract function name from ${file}`);
- }
-
- // Create hash of normalized content (ignore whitespace differences)
- const normalizedContent = content
- .replace(/--.*$/gm, "") // Remove comments
- .replace(/\s+/g, " ") // Normalize whitespace
- .trim();
-
- const contentHash = createHash("sha256")
- .update(normalizedContent)
- .digest("hex")
- .substring(0, 16);
-
- return {
- name: functionName,
- sourceFile: file,
- content,
- contentHash,
- };
- });
-};
-
-export const validateSqlFunctions = async ({
- db,
- validateContent = false,
-}: {
- db: DrizzleCli;
- validateContent?: boolean;
-}) => {
- const start = Date.now();
-
- // Dynamically discover SQL functions from source files
- const requiredFunctions = discoverSqlFunctions();
- logger.info(
- `Discovered ${requiredFunctions.length} SQL functions from source files`,
- );
-
- // Query database for existing functions and their definitions
- const result = await db.execute<{
- function_name: string;
- definition: string;
- }>(sql`
- SELECT
- p.proname as function_name,
- pg_get_functiondef(p.oid) as definition
- FROM pg_proc p
- JOIN pg_namespace n ON p.pronamespace = n.oid
- WHERE n.nspname = 'public'
- AND p.prokind = 'f'
- ORDER BY p.proname;
- `);
-
- const dbFunctions = new Map(
- result.map((row) => [row.function_name, row.definition]),
- );
-
- // Check for missing functions
- const missingFunctions = requiredFunctions.filter(
- (fn) => !dbFunctions.has(fn.name),
- );
-
- if (missingFunctions.length > 0) {
- const missingDetails = missingFunctions
- .map((fn) => `'${fn.name}' (from ${fn.sourceFile})`)
- .join(", ");
-
- logger.error(
- `SQL function validation failed: Missing functions: ${missingDetails}`,
- );
- throw new Error(`Missing SQL functions: ${missingDetails}`);
- }
-
- // Optionally validate function content
- const mismatchedFunctions: Array<{
- name: string;
- sourceFile: string;
- reason: string;
- }> = [];
-
- if (validateContent) {
- for (const fn of requiredFunctions) {
- const dbDefinition = dbFunctions.get(fn.name);
- if (!dbDefinition) continue;
-
- // Normalize both definitions for comparison
- const normalizeFunc = (str: string) =>
- str
- .replace(/--.*$/gm, "") // Remove comments
- .replace(/\s+/g, " ") // Normalize whitespace
- .toLowerCase()
- .trim();
-
- const normalizedSource = normalizeFunc(fn.content);
- const normalizedDb = normalizeFunc(dbDefinition);
-
- // Create hashes for comparison
- const sourceHash = createHash("sha256")
- .update(normalizedSource)
- .digest("hex")
- .substring(0, 16);
-
- const dbHash = createHash("sha256")
- .update(normalizedDb)
- .digest("hex")
- .substring(0, 16);
-
- if (sourceHash !== dbHash) {
- mismatchedFunctions.push({
- name: fn.name,
- sourceFile: fn.sourceFile,
- reason: `Source hash: ${sourceHash}, DB hash: ${dbHash}`,
- });
- }
- }
-
- if (mismatchedFunctions.length > 0) {
- const mismatchDetails = mismatchedFunctions
- .map((fn) => `'${fn.name}' (${fn.sourceFile}): ${fn.reason}`)
- .join("; ");
-
- logger.warn(`SQL function content mismatch detected: ${mismatchDetails}`);
- logger.warn(
- "Functions exist but their content differs from source files. Run migrations to update.",
- );
- }
- }
-
- const elapsed = Date.now() - start;
-
- logger.info(
- `SQL function validation passed - ${requiredFunctions.length} functions verified in ${elapsed}ms${validateContent ? " (content validated)" : ""}`,
- );
-
- if (mismatchedFunctions.length > 0) {
- logger.info(
- `⚠️ ${mismatchedFunctions.length} function(s) have content differences`,
- );
- }
-
- return true;
-};
diff --git a/server/src/external/redis/initUpstash.ts b/server/src/external/redis/initUpstash.ts
deleted file mode 100644
index 0a6a7aeb0..000000000
--- a/server/src/external/redis/initUpstash.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Redis } from "@upstash/redis";
-
-const upstash = new Redis({
- url: process.env.CLOUD_UPSTASH_REDIS_REST_URL,
- token: process.env.CLOUD_UPSTASH_REDIS_REST_TOKEN,
-});
-
-export { upstash };
diff --git a/server/src/external/stripe/createStripePrice/createStripePrepaidPriceV2.ts b/server/src/external/stripe/createStripePrice/createStripePrepaidPriceV2.ts
deleted file mode 100644
index ec8628273..000000000
--- a/server/src/external/stripe/createStripePrice/createStripePrepaidPriceV2.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import {
- atmnToStripeAmountDecimal,
- BillingInterval,
- type EntitlementWithFeature,
- InternalError,
- type Organization,
- type Price,
- type Product,
- type UsagePriceConfig,
-} from "@autumn/shared";
-import { Decimal } from "decimal.js";
-import type Stripe from "stripe";
-import type { DrizzleCli } from "../../../db/initDrizzle.js";
-import { orgToCurrency } from "../../../internal/orgs/orgUtils.js";
-import { PriceService } from "../../../internal/products/prices/PriceService.js";
-import { billingIntervalToStripe } from "../stripePriceUtils.js";
-
-// 1. Product name
-const prepaidToStripeTiers = ({
- ent,
- price,
- org,
-}: {
- ent: EntitlementWithFeature;
- price: Price;
- org: Organization;
-}) => {
- const usageTiers = price.config.usage_tiers;
- if (!usageTiers) {
- throw new InternalError({
- message:
- "[Internal Error] Converting prepaid price to tiers, but `usage_tiers` field is missing",
- });
- }
- // Create paid tiers first
- const paidTiers: Stripe.PriceCreateParams.Tier[] = usageTiers.map(
- (tier, index) => {
- const atmnUnitAmount = new Decimal(tier.amount).div(
- price.config.billing_units ?? 1,
- );
-
- const stripeUnitAmountDecimal = atmnToStripeAmountDecimal({
- amount: atmnUnitAmount,
- currency: orgToCurrency({ org }),
- });
-
- return {
- unit_amount_decimal: stripeUnitAmountDecimal,
- up_to: index === usageTiers.length - 1 ? "inf" : (tier.to ?? 0),
- };
- },
- );
-
- // 1. Get included usage
- const includedUsage = ent.allowance;
-
- if (includedUsage && includedUsage > 0) {
- paidTiers.forEach((tier) => {
- if (tier.up_to === "inf") {
- return;
- }
- tier.up_to = new Decimal(tier.up_to as number)
- .plus(includedUsage)
- .toNumber();
- });
-
- paidTiers.unshift({
- unit_amount_decimal: "0",
- up_to: includedUsage,
- });
- }
-
- return paidTiers;
-};
-
-export const createStripePrepaidPriceV2 = async ({
- org,
- stripeCli,
- db,
- price,
- ent,
- product,
- curStripeProd,
-}: {
- org: Organization;
- stripeCli: Stripe;
- db: DrizzleCli;
- price: Price;
- ent: EntitlementWithFeature;
- product: Product;
- curStripeProd: Stripe.Product | null;
-}) => {
- let recurringData;
- if (price.config!.interval !== BillingInterval.OneOff) {
- recurringData = billingIntervalToStripe({
- interval: price.config!.interval,
- intervalCount: price.config!.interval_count,
- });
- }
-
- const config = price.config as UsagePriceConfig;
- const productName = `${product.name} - ${ent.feature.name}`;
-
- const productData = curStripeProd
- ? { product: curStripeProd.id }
- : {
- product_data: {
- name: productName,
- },
- };
-
- // 2. If billing interval is one off
- let stripePrice = null;
- if (price.config!.interval === BillingInterval.OneOff) {
- const amount = config.usage_tiers[0].amount;
-
- const unitAmountDecimalStr = atmnToStripeAmountDecimal({
- amount,
- currency: orgToCurrency({ org }),
- });
-
- stripePrice = await stripeCli.prices.create({
- ...productData,
- unit_amount_decimal: unitAmountDecimalStr,
- currency: orgToCurrency({ org }),
- });
-
- config.stripe_product_id = stripePrice.product as string;
- config.stripe_price_id = stripePrice.id;
- } else {
- const priceConfigTiers = price.config.usage_tiers;
- let priceAmountData: Partial;
- if (priceConfigTiers?.length === 1) {
- priceAmountData = {
- unit_amount_decimal: atmnToStripeAmountDecimal({
- amount: priceConfigTiers[0].amount,
- currency: orgToCurrency({ org }),
- }),
- };
- } else {
- priceAmountData = {
- billing_scheme: "tiered",
- tiers_mode: "graduated",
- tiers: prepaidToStripeTiers({ ent, price, org }),
- };
- }
- stripePrice = await stripeCli.prices.create({
- ...productData,
- currency: orgToCurrency({ org }),
- ...priceAmountData,
- recurring: {
- ...(recurringData as any),
- },
- nickname: `Autumn Price (${ent.feature.name})`,
- });
- config.stripe_price_id = stripePrice.id;
- config.stripe_product_id = stripePrice.product as string;
- }
-
- // New config
- price.config = config;
- await PriceService.update({
- db,
- id: price.id!,
- update: { config },
- });
-};
diff --git a/server/src/external/stripe/invoices/utils/logStripeInvoice.ts b/server/src/external/stripe/invoices/utils/logStripeInvoice.ts
deleted file mode 100644
index 7182f1880..000000000
--- a/server/src/external/stripe/invoices/utils/logStripeInvoice.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import type Stripe from "stripe";
-import type { Logger } from "@/external/logtail/logtailUtils";
-
-/**
- * Logs core information from a Stripe invoice for debugging.
- */
-export const logStripeInvoice = ({
- logger,
- stripeInvoice,
- prefix,
-}: {
- logger: Logger;
- stripeInvoice: Stripe.Invoice;
- prefix?: string;
-}) => {
- const tag = prefix ? `[${prefix}]` : "";
-
- logger.info(`${tag} Stripe Invoice`, {
- id: stripeInvoice.id,
- status: stripeInvoice.status,
- total: stripeInvoice.total,
- currency: stripeInvoice.currency,
- hosted_invoice_url: stripeInvoice.hosted_invoice_url,
- lines: stripeInvoice.lines.data.map((line) => ({
- description: line.description,
- amount: line.amount,
- })),
- });
-};
diff --git a/server/src/external/stripe/stripeCouponUtils/extendCouponDuration.ts b/server/src/external/stripe/stripeCouponUtils/extendCouponDuration.ts
deleted file mode 100644
index 53af611d0..000000000
--- a/server/src/external/stripe/stripeCouponUtils/extendCouponDuration.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-// import Stripe from "stripe";
-
-// /**
-// * Extends an existing coupon's duration by replacing it with a new coupon that has
-// * the remaining duration + additional months. This avoids concurrent stacking issues.
-// */
-// export const extendCouponDuration = async ({
-// stripeCli,
-// sub,
-// existingCouponId,
-// additionalMonths,
-// logger,
-// }: {
-// stripeCli: Stripe;
-// sub: Stripe.Subscription;
-// existingCouponId: string;
-// additionalMonths: number;
-// logger: any;
-// }): Promise<{
-// success: boolean;
-// newCouponId?: string;
-// error?: string;
-// }> => {
-// try {
-// logger.info(
-// `Extending coupon ${existingCouponId} by ${additionalMonths} months`
-// );
-
-// // Get current subscription and coupon details
-// const existingCoupon = await stripeCli.coupons.retrieve(existingCouponId);
-// const currentDiscounts = (sub.discounts as Stripe.Discount[]) || [];
-// const existingDiscount = currentDiscounts.find((d: any) =>
-// d.coupon?.id.startsWith(existingCouponId)
-// );
-
-// if (!existingDiscount) {
-// return {
-// success: false,
-// error: "Original coupon not found on subscription",
-// };
-// }
-
-// // Calculate remaining months using the discount's actual start and end times
-// const originalDurationMonths = existingCoupon.duration_in_months || 0;
-
-// // Use discount start and end times for accurate calculation
-// const discountStart = new Date(existingDiscount.start * 1000);
-// const discountEnd = existingDiscount.end
-// ? new Date(existingDiscount.end * 1000)
-// : null;
-// const now = new Date();
-
-// let remainingMonths: number;
-
-// if (discountEnd) {
-// // Calculate remaining time in months
-// const remainingTimeMs = Math.max(
-// 0,
-// discountEnd.getTime() - now.getTime()
-// );
-// remainingMonths = Math.ceil(remainingTimeMs / (30 * 24 * 60 * 60 * 1000));
-// } else {
-// // If no end date (shouldn't happen for repeating coupons), fall back to original duration
-// remainingMonths = originalDurationMonths;
-// }
-
-// const totalNewDurationMonths = remainingMonths + additionalMonths;
-
-// logger.info(
-// `Discount period: ${discountStart.toISOString()} to ${discountEnd?.toISOString() || "forever"}`
-// );
-// logger.info(
-// `Original: ${originalDurationMonths}m, Remaining: ${remainingMonths}m, Adding: ${additionalMonths}m, Total: ${totalNewDurationMonths}m`
-// );
-
-// // Create a new coupon with the extended duration
-// const extendedCouponId = `${existingCouponId}_${Date.now()}`;
-// const couponCreateParams: Stripe.CouponCreateParams = {
-// id: extendedCouponId,
-// duration: "repeating",
-// duration_in_months: totalNewDurationMonths,
-// name: `Extended ${existingCoupon.name || "Coupon"}`,
-// // metadata: {
-// // original_coupon_id: existingCouponId,
-// // original_duration: originalDurationMonths.toString(),
-// // remaining_months: remainingMonths.toString(),
-// // additional_months: additionalMonths.toString(),
-// // total_duration: totalNewDurationMonths.toString(),
-// // extended_at: Date.now().toString(),
-// // },
-// };
-
-// // Copy discount value from the existing coupon
-// if (existingCoupon.percent_off) {
-// couponCreateParams.percent_off = existingCoupon.percent_off;
-// } else if (existingCoupon.amount_off) {
-// couponCreateParams.amount_off = existingCoupon.amount_off;
-// if (existingCoupon.currency) {
-// couponCreateParams.currency = existingCoupon.currency;
-// }
-// }
-
-// // Copy applies_to if it exists
-// if (existingCoupon.applies_to) {
-// couponCreateParams.applies_to = existingCoupon.applies_to;
-// }
-
-// const extendedCoupon = await stripeCli.coupons.create(couponCreateParams);
-
-// // Replace the existing coupon with the extended one
-// const otherDiscounts = currentDiscounts
-// .filter((d: any) => d.coupon?.id !== existingCouponId)
-// .map((d: Stripe.Discount) => ({ discount: d.id }));
-
-// await stripeCli.subscriptions.update(sub.id, {
-// discounts: [...otherDiscounts, { coupon: extendedCoupon.id }],
-// });
-
-// logger.info(
-// `Successfully extended coupon duration to ${totalNewDurationMonths} months. New coupon ID: ${extendedCouponId}`
-// );
-
-// await stripeCli.coupons.del(existingCouponId);
-
-// return { success: true, newCouponId: extendedCouponId };
-// } catch (error: any) {
-// logger.error(`Failed to extend coupon duration: ${error.message}`, error);
-// return { success: false, error: error.message };
-// }
-// };
diff --git a/server/src/external/stripe/stripeProductUtils.ts b/server/src/external/stripe/stripeProductUtils.ts
deleted file mode 100644
index 28e70d0da..000000000
--- a/server/src/external/stripe/stripeProductUtils.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-import {
- AppEnv,
- ErrCode,
- type Organization,
- type Product,
-} from "@autumn/shared";
-import { StatusCodes } from "http-status-codes";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import RecaseError from "@/utils/errorUtils.js";
-
-export const createStripeProduct = async (
- org: Organization,
- env: AppEnv,
- product: Product,
-) => {
- try {
- const stripe = createStripeCli({ org, env });
-
- const stripeProduct = await stripe.products.create({
- name: product.name,
- metadata: {
- autumn_id: product.id,
- autumn_internal_id: product.internal_id,
- },
- });
-
- return stripeProduct;
- } catch (error: any) {
- throw new RecaseError({
- message: `Error creating product in Stripe. ${error.message}`,
- code: ErrCode.CreateStripeProductFailed,
- statusCode: 500,
- });
- }
-};
-
-export const deleteStripeProduct = async (
- org: Organization,
- env: AppEnv,
- product: Product,
-) => {
- const stripe = createStripeCli({ org, env });
-
- if (
- !product.processor ||
- !product.processor.id ||
- product.env === AppEnv.Live
- ) {
- // Don't delete live products
- return;
- }
-
- try {
- await stripe.products.del(product.processor.id);
- } catch (_error) {
- throw new RecaseError({
- message: "Failed to delete stripe product",
- code: ErrCode.DeleteStripeProductFailed,
- statusCode: 500,
- });
- }
-};
-
-export const deactivateStripeMeters = async ({
- org,
- env,
-}: {
- org: Organization;
- env: AppEnv;
-}) => {
- const stripeCli = createStripeCli({ org, env });
-
- const allStripeMeters = [];
- let hasMore = true;
- let startingAfter;
-
- while (hasMore) {
- const response: any = await stripeCli.billing.meters.list({
- limit: 100,
- status: "active",
- starting_after: startingAfter,
- });
-
- allStripeMeters.push(...response.data);
- hasMore = response.has_more;
-
- if (hasMore && response.data.length > 0) {
- startingAfter = response.data[response.data.length - 1].id;
- }
- }
-
- const batchSize = 20;
- for (let i = 0; i < allStripeMeters.length; i += batchSize) {
- const batch = allStripeMeters.slice(i, i + batchSize);
- await Promise.all(
- batch.map((meter) => stripeCli.billing.meters.deactivate(meter.id)),
- );
- console.log(
- `Deactivated ${i + batch.length}/${allStripeMeters.length} meters`,
- );
- await new Promise((resolve) => setTimeout(resolve, 1000));
- }
-};
-
-export const deleteAllStripeProducts = async ({
- org,
- env,
-}: {
- org: Organization;
- env: AppEnv;
-}) => {
- const stripeCli = createStripeCli({ org, env });
-
- const stripeProducts = await stripeCli.products.list({
- limit: 100,
- active: true,
- });
-
- if (stripeProducts.data.length === 0) {
- return;
- }
-
- const firstProduct = stripeProducts.data[0];
- if (firstProduct.livemode) {
- throw new RecaseError({
- message: "Cannot delete livemode products",
- code: ErrCode.DeleteStripeProductFailed,
- statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
- });
- }
-
- const batchSize = 50;
- for (let i = 0; i < stripeProducts.data.length; i += batchSize) {
- const batch = stripeProducts.data.slice(i, i + batchSize);
- await Promise.all(
- batch.map(async (p) => {
- try {
- await stripeCli.products.del(p.id);
- } catch (_error) {
- await stripeCli.products.update(p.id, {
- active: false,
- });
- }
- }),
- );
- console.log(
- `Deleted ${i + batch.length}/${stripeProducts.data.length} products`,
- );
- await new Promise((resolve) => setTimeout(resolve, 1000));
- }
-};
diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleSchedulePhaseChanges/trackCustomerProductUpdate.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleSchedulePhaseChanges/trackCustomerProductUpdate.ts
deleted file mode 100644
index 3a5d6c886..000000000
--- a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleSchedulePhaseChanges/trackCustomerProductUpdate.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { FullCusProduct, InsertCustomerProduct } from "@autumn/shared";
-import type { StripeSubscriptionUpdatedContext } from "../../stripeSubscriptionUpdatedContext";
-
-/**
- * Tracks a customer product update for the subscription updated workflow.
- * - Adds to updatedCustomerProducts list for logging/audit
- * - Updates customerProducts array in place so subsequent tasks see the change
- * - Updates fullCustomer.customer_products so actions can see the change
- */
-export const trackCustomerProductUpdate = ({
- eventContext,
- customerProduct,
- updates,
-}: {
- eventContext: StripeSubscriptionUpdatedContext;
- customerProduct: FullCusProduct;
- updates: Partial;
-}): FullCusProduct => {
- const { customerProducts, fullCustomer, updatedCustomerProducts } =
- eventContext;
-
- // Track the update for logging
- updatedCustomerProducts.push({ customerProduct, updates });
-
- // Create updated product
- const updatedProduct = { ...customerProduct, ...updates } as FullCusProduct;
-
- // Update in customerProducts array
- const idx = customerProducts.findIndex((cp) => cp.id === customerProduct.id);
- if (idx >= 0) {
- customerProducts[idx] = updatedProduct;
- }
-
- // Also update in fullCustomer.customer_products so actions can see the change
- const fullCustomerIdx = fullCustomer.customer_products.findIndex(
- (cp) => cp.id === customerProduct.id,
- );
- if (fullCustomerIdx >= 0) {
- fullCustomer.customer_products[fullCustomerIdx] = updatedProduct;
- }
-
- return updatedProduct;
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoiceActionRequiredCompleted.ts b/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoiceActionRequiredCompleted.ts
deleted file mode 100644
index 43572b578..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoiceActionRequiredCompleted.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { AttachBranch, type Metadata, ProrationBehavior } from "@autumn/shared";
-import { createStripeCli } from "@server/external/connect/createStripeCli";
-import { getCusPaymentMethod } from "@server/external/stripe/stripeCusUtils";
-import type { AutumnContext } from "@server/honoUtils/HonoEnv";
-import { resetUsageBalances } from "@server/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoiceItems";
-import { handleUpgradeFlow } from "@server/internal/customers/attach/attachFunctions/upgradeFlow/handleUpgradeFlow";
-import { attachParamsToCurCusProduct } from "@server/internal/customers/attach/attachUtils/convertAttachParams";
-import { getDefaultAttachConfig } from "@server/internal/customers/attach/attachUtils/getAttachConfig";
-import type { AttachParams } from "@server/internal/customers/cusProducts/AttachParams";
-import { MetadataService } from "@server/internal/metadata/MetadataService";
-import type Stripe from "stripe";
-
-export const handleInvoiceActionRequiredCompleted = async ({
- ctx,
- invoice,
- metadata,
-}: {
- ctx: AutumnContext;
- invoice: Stripe.Invoice;
- metadata: Metadata;
-}) => {
- const { logger, org, env } = ctx;
- logger.info(`invoice.paid, handling action required`);
-
- const stripeCli = createStripeCli({ org, env });
-
- const paymentMethod = await getCusPaymentMethod({
- stripeCli,
- stripeId: invoice.customer as string,
- });
-
- const attachParams = {
- ...(metadata.data as AttachParams),
- stripeCli,
- req: ctx,
- paymentMethod,
- } as AttachParams;
-
- const attachConfig = {
- ...getDefaultAttachConfig(),
- proration: ProrationBehavior.None,
- };
-
- ctx.logger.info(`handling upgrade flow for invoice ${invoice.id}`);
-
- await handleUpgradeFlow({
- ctx,
- attachParams,
- config: attachConfig,
- branch: AttachBranch.Upgrade,
- });
-
- const curCusProduct = attachParamsToCurCusProduct({ attachParams });
- if (attachParams.cusEntIds && curCusProduct) {
- await resetUsageBalances({
- db: ctx.db,
- cusEntIds: attachParams.cusEntIds,
- cusProduct: curCusProduct,
- });
- }
-
- await MetadataService.delete({
- db: ctx.db,
- id: metadata.id,
- });
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoiceCheckoutPaid.ts b/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoiceCheckoutPaid.ts
deleted file mode 100644
index ac5ed67f3..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoiceCheckoutPaid.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { Metadata } from "@autumn/shared";
-import { AttachScenario } from "@autumn/shared";
-import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
-import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
-import { attachToInsertParams } from "@/internal/products/productUtils.js";
-import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
-import { CusService } from "../../../../../internal/customers/CusService.js";
-import { MetadataService } from "../../../../../internal/metadata/MetadataService.js";
-
-export const handleInvoiceCheckoutPaid = async ({
- ctx,
- metadata,
-}: {
- ctx: AutumnContext;
- metadata: Metadata;
-}) => {
- const { logger, org, env, db } = ctx;
- logger.info(
- `invoice.paid, handling invoice checkout paid for metadata: ${metadata.id}`,
- );
-
- const { subId, anchorToUnix, config, ...rest } =
- metadata.data as AttachParams;
-
- const attachParams = rest;
-
- if (!attachParams) return;
-
- const reqMatch =
- attachParams.org.id === org.id && attachParams.customer.env === env;
-
- if (!reqMatch) return;
-
- const batchInsert = [];
- for (const product of attachParams.products) {
- batchInsert.push(
- createFullCusProduct({
- db,
- attachParams: attachToInsertParams(attachParams, product),
- subscriptionIds: subId ? [subId] : undefined,
- anchorToUnix,
- carryExistingUsages: config?.carryUsage,
- scenario: AttachScenario.New,
- logger: logger,
- }),
- );
- }
-
- await Promise.all(batchInsert);
-
- logger.info(
- `✅ invoice.paid, successfully inserted cus products: ${attachParams.products.map((p) => p.id).join(", ")}`,
- );
-
- await MetadataService.delete({
- db: ctx.db,
- id: metadata.id,
- });
-
- // Fetch customer by internal ID
- let customerId = attachParams.customer.id;
-
- if (!customerId) {
- const customer = await CusService.get({
- db,
- idOrInternalId: attachParams.customer.internal_id,
- orgId: org.id,
- env,
- });
-
- customerId = customer?.id;
- }
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoicePaid.ts b/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoicePaid.ts
deleted file mode 100644
index 6eba55e92..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoicePaid.ts
+++ /dev/null
@@ -1,280 +0,0 @@
-import type {
- AppEnv,
- FullCusProduct,
- FullCustomerPrice,
- InvoiceStatus,
- Organization,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
-import { InvoiceService } from "@/internal/invoices/InvoiceService.js";
-import { getInvoiceItems } from "@/internal/invoices/invoiceUtils.js";
-import { JobName } from "@/queue/JobName.js";
-import { addTaskToQueue } from "@/queue/queueUtils.js";
-import { nullish } from "@/utils/genUtils.js";
-import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
-import type { Logger } from "../../../../logtail/logtailUtils.js";
-import { stripeInvoiceToStripeSubscriptionId } from "../../../invoices/utils/convertStripeInvoice.js";
-import {
- getFullStripeInvoice,
- getInvoiceDiscounts,
- updateInvoiceIfExists,
-} from "../../../stripeInvoiceUtils.js";
-import { lineItemInCusProduct } from "../../../stripeSubUtils/stripeSubItemUtils.js";
-import { getStripeSubs } from "../../../stripeSubUtils.js";
-import { handleInvoicePaidDiscount } from "./handleInvoicePaidDiscount.js";
-import { handleInvoicePaidMetadata } from "./handleInvoicePaidMetadata.js";
-
-const handleOneOffInvoicePaid = async ({
- db,
- stripeInvoice,
-}: {
- db: DrizzleCli;
- stripeInvoice: Stripe.Invoice;
- event: Stripe.Event;
-}) => {
- // Search for invoice
- const invoice = await InvoiceService.getByStripeId({
- db,
- stripeId: stripeInvoice.id!,
- });
-
- if (!invoice) return;
-
- // Update invoice status
- await InvoiceService.updateByStripeId({
- db,
- stripeId: stripeInvoice.id!,
- updates: {
- status: stripeInvoice.status as InvoiceStatus,
- hosted_invoice_url: stripeInvoice.hosted_invoice_url,
- discounts: getInvoiceDiscounts({
- expandedInvoice: stripeInvoice,
- }),
- },
- });
-
- console.log(`Updated one off invoice status to ${stripeInvoice.status}`);
-};
-
-const convertToChargeAutomatically = async ({
- org,
- env,
- invoice,
- activeCusProducts,
- logger,
-}: {
- org: Organization;
- env: AppEnv;
- invoice: Stripe.Invoice;
- activeCusProducts: FullCusProduct[];
- logger: Logger;
-}) => {
- const stripeCli = createStripeCli({ org, env });
-
- const subs = await getStripeSubs({
- stripeCli,
- subIds: activeCusProducts.flatMap((p) => p.subscription_ids || []),
- });
-
- const payments = invoice.payments;
- const firstPayment = payments?.data?.[0];
- const paymentIntentId = firstPayment?.payment?.payment_intent as string;
-
- if (
- subs.every((s) => s.collection_method === "charge_automatically") ||
- nullish(paymentIntentId)
- ) {
- return;
- }
-
- // Get payment intent...
-
- // Try to attach payment method to subscription
- try {
- logger.info(`Converting to charge automatically`);
- // 1. Get payment intent
- const paymentIntent =
- await stripeCli.paymentIntents.retrieve(paymentIntentId);
-
- // 2. Get payment method
- const paymentMethod = await stripeCli.paymentMethods.retrieve(
- paymentIntent.payment_method as string,
- );
-
- await stripeCli.paymentMethods.attach(paymentMethod.id, {
- customer: invoice.customer as string,
- });
-
- const batchUpdateSubs = [];
- const updateSub = async (sub: Stripe.Subscription) => {
- try {
- await stripeCli.subscriptions.update(sub.id, {
- collection_method: "charge_automatically",
- default_payment_method: paymentMethod.id,
- });
- } catch (error) {
- logger.warn(
- `Convert to charge automatically: error updating subscription ${sub.id}`,
- );
- logger.warn(error);
- }
- };
-
- for (const sub of subs) {
- batchUpdateSubs.push(updateSub(sub));
- }
-
- await Promise.all(batchUpdateSubs);
-
- logger.info("Convert to charge automatically successful!");
- } catch (error) {
- logger.warn(`Convert to charge automatically failed: ${error}`);
- }
-};
-
-export const handleInvoicePaid = async ({
- ctx,
- invoiceData,
- event,
-}: {
- ctx: AutumnContext;
- invoiceData: Stripe.Invoice;
- event: Stripe.Event;
-}) => {
- const { logger, org, env, db } = ctx;
- const stripeCli = createStripeCli({ org, env });
- const invoice = await getFullStripeInvoice({
- stripeCli,
- stripeId: invoiceData.id!,
- expand: ["payments"],
- });
-
- if (invoice.metadata?.autumn_metadata_id) {
- await handleInvoicePaidMetadata({
- ctx,
- invoice,
- });
- }
-
- await handleInvoicePaidDiscount({
- db,
- expandedInvoice: invoice,
- org,
- env,
- logger,
- });
-
- const subId = stripeInvoiceToStripeSubscriptionId(invoice);
- if (subId) {
- // Get customer product
- const activeCusProducts = await CusProductService.getByStripeSubId({
- db,
- stripeSubId: subId,
- orgId: org.id,
- env,
- });
-
- if (!activeCusProducts || activeCusProducts.length === 0) {
- // TODO: Send alert
- if (invoice.livemode) {
- logger.warn(
- `invoice.paid: customer product not found for invoice ${invoice.id}`,
- );
- }
- return;
- }
-
- if (org.config.convert_to_charge_automatically) {
- await convertToChargeAutomatically({
- org,
- env,
- invoice,
- activeCusProducts,
- logger,
- });
- }
-
- const updated = await updateInvoiceIfExists({
- db,
- invoice,
- });
-
- if (!updated) {
- const invoiceItems = await getInvoiceItems({
- stripeInvoice: invoice,
- prices: activeCusProducts.flatMap((p) =>
- p.customer_prices.map((cpr: FullCustomerPrice) => cpr.price),
- ),
- logger,
- });
-
- const invoiceLines = invoice.lines.data;
- let cusProducts: FullCusProduct[] = activeCusProducts;
- try {
- cusProducts = activeCusProducts.filter((cp) =>
- invoiceLines.some((l) =>
- lineItemInCusProduct({ cusProduct: cp, lineItem: l }),
- ),
- );
-
- if (cusProducts.length === 0) {
- cusProducts = activeCusProducts;
- }
- } catch (error) {
- logger.error("Failed to filter cus products for invoice");
- logger.error({ error });
- }
-
- const internalEntityId = new Set(
- cusProducts.map((cp) => cp.internal_entity_id),
- );
-
- await InvoiceService.createInvoiceFromStripe({
- db,
- stripeInvoice: invoice,
- internalCustomerId: activeCusProducts[0].internal_customer_id,
- internalEntityId:
- internalEntityId.size > 1
- ? undefined
- : internalEntityId.values().next().value,
-
- productIds: [...new Set(cusProducts.map((p) => p.product_id))],
- internalProductIds: [
- ...new Set(cusProducts.map((p) => p.internal_product_id)),
- ],
- org: org,
- items: invoiceItems,
- });
- }
-
- for (const cusProd of activeCusProducts) {
- try {
- await addTaskToQueue({
- jobName: JobName.TriggerCheckoutReward,
- payload: {
- // For createWorkerContext
- orgId: org.id,
- env: cusProd.customer!.env,
- customerId: cusProd.customer!.id,
- // For triggerCheckoutReward
- customer: cusProd.customer,
- product: cusProd.product,
- subId: cusProd.subscription_ids?.[0],
- },
- });
- } catch (error) {
- logger.error(`invoice.paid: failed to trigger checkout reward check`);
- logger.error(error);
- }
- }
- } else {
- await handleOneOffInvoicePaid({
- db,
- stripeInvoice: invoice,
- event,
- });
- }
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoicePaidDiscount.ts b/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoicePaidDiscount.ts
deleted file mode 100644
index 8dd16ee6e..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoicePaidDiscount.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-import {
- type AppEnv,
- CouponDurationType,
- type Organization,
- type Reward,
- RewardType,
-} from "@autumn/shared";
-import { addMonths } from "date-fns";
-import { Decimal } from "decimal.js";
-
-import type Stripe from "stripe";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import { stripeCustomerToNowMs } from "@/external/stripe/customers/index.js";
-import { RewardService } from "@/internal/rewards/RewardService.js";
-import { generateId } from "@/utils/genUtils.js";
-import { stripeInvoiceToStripeSubscriptionId } from "../../../invoices/utils/convertStripeInvoice.js";
-import {
- deleteCouponFromCus,
- deleteCouponFromSub,
-} from "../../../stripeCouponUtils/deleteCouponFromCus.js";
-
-export const handleInvoicePaidDiscount = async ({
- db,
- expandedInvoice,
- org,
- env,
- logger,
-}: {
- db: DrizzleCli;
- expandedInvoice: Stripe.Invoice;
- org: Organization;
- env: AppEnv;
- logger: any;
-}) => {
- // Handle coupon
- const stripeCli = createStripeCli({ org, env, legacyVersion: true });
- if (expandedInvoice.discounts.length === 0) return;
-
- const stripeCus = await stripeCli.customers.retrieve(
- expandedInvoice.customer as string,
- );
-
- const legacyInvoice = await stripeCli.invoices.retrieve(expandedInvoice.id, {
- expand: ["total_discount_amounts", "discounts.coupon"],
- });
-
- try {
- const totalDiscountAmounts = expandedInvoice.total_discount_amounts;
-
- // Log coupon information for debugging
- for (const discount of legacyInvoice.discounts) {
- if (typeof discount === "string" || !("coupon" in discount)) continue;
-
- const curCoupon = discount.coupon as Stripe.Coupon;
-
- if (!curCoupon || typeof curCoupon === "string" || !curCoupon.amount_off)
- continue;
-
- const rollSuffixIndex = curCoupon.id.indexOf("_roll_");
- const couponId =
- rollSuffixIndex !== -1
- ? curCoupon.id.substring(0, rollSuffixIndex)
- : curCoupon.id;
-
- const autumnReward: Reward | null = await RewardService.get({
- db,
- idOrInternalId: couponId,
- orgId: org.id,
- env,
- });
-
- const shouldRollover =
- autumnReward &&
- (autumnReward.type === RewardType.InvoiceCredits ||
- autumnReward.type === RewardType.FreeProduct);
-
- if (!shouldRollover) continue;
-
- // Get ID of coupon
- const originalCoupon = await stripeCli.coupons.retrieve(couponId, {
- expand: ["applies_to"],
- });
-
- const curAmount = curCoupon.amount_off;
-
- const amountUsed = totalDiscountAmounts?.find(
- (item) => item.discount === discount.id,
- )?.amount;
-
- const newAmount = new Decimal(curAmount!).sub(amountUsed!).toNumber();
-
- const curExpiresAt = curCoupon.metadata?.expires_at
- ? Number(curCoupon.metadata.expires_at)
- : null;
-
- const discountFinished = newAmount <= 0;
-
- const now = await stripeCustomerToNowMs({
- stripeCli,
- stripeCustomer: stripeCus as Stripe.Customer,
- });
-
- const expired = curExpiresAt && curExpiresAt < now;
- const subId = stripeInvoiceToStripeSubscriptionId(expandedInvoice);
-
- if (discountFinished || expired) {
- logger.info(
- `Coupon ${couponId}, stripeCus: ${stripeCus.id}: credits used up or expired. discountFinished: ${discountFinished}, expired: ${expired}`,
- );
-
- if (subId) {
- await deleteCouponFromCus({
- stripeCli,
- stripeSubId: subId,
- stripeCusId: expandedInvoice.customer as string,
- discountId: discount.id,
- logger,
- });
- }
-
- continue;
- }
-
- logger.info(
- `Coupon ${couponId}, stripeCus: ${stripeCus.id}, updating amount from ${curAmount} to ${newAmount}`,
- );
-
- // Set expiry date
- let expiresAt = curCoupon.metadata?.expires_at || null;
- const discountConfig = autumnReward?.discount_config;
- if (discountConfig?.duration_type === CouponDurationType.Months) {
- expiresAt = addMonths(new Date(), discountConfig.duration_value)
- .getTime()
- .toString();
- }
-
- const newCoupon = await stripeCli.coupons.create({
- id: `${couponId}_${generateId("roll")}`,
- name: curCoupon.name as string,
- amount_off: newAmount,
- currency: expandedInvoice.currency,
- duration: "once",
- applies_to: originalCoupon.applies_to,
- metadata: {
- expires_at: expiresAt,
- },
- });
-
- await stripeCli.rawRequest(
- "POST",
- `/v1/customers/${expandedInvoice.customer}`,
- {
- coupon: newCoupon.id,
- },
- );
-
- await stripeCli.coupons.del(newCoupon.id);
-
- if (subId) {
- await deleteCouponFromSub({
- stripeCli,
- stripeSubId: subId,
- discountId: discount.id,
- logger,
- });
- }
- }
- } catch (error) {
- logger.error("invoice.paid: error updating coupon");
- logger.error(error);
- }
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoicePaidMetadata.ts b/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoicePaidMetadata.ts
deleted file mode 100644
index 5e7ed081a..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleInvoicePaid/handleInvoicePaidMetadata.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { MetadataType } from "@autumn/shared";
-import type Stripe from "stripe";
-import { executeDeferredBillingPlan } from "@/internal/billing/v2/execute/executeDeferredBillingPlan";
-import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
-import type { AttachParams } from "../../../../../internal/customers/cusProducts/AttachParams.js";
-import { MetadataService } from "../../../../../internal/metadata/MetadataService.js";
-import { handleInvoiceActionRequiredCompleted } from "./handleInvoiceActionRequiredCompleted";
-import { handleInvoiceCheckoutPaid } from "./handleInvoiceCheckoutPaid";
-
-export const handleInvoicePaidMetadata = async ({
- ctx,
- invoice,
-}: {
- ctx: AutumnContext;
- invoice: Stripe.Invoice;
-}) => {
- const metadataId = invoice.metadata?.autumn_metadata_id;
-
- if (!metadataId) return;
-
- const metadata = await MetadataService.get({
- db: ctx.db,
- id: metadataId,
- });
-
- if (!metadata) return;
-
- // Handle deferred billing plan (v2 flow)
- if (metadata.type === MetadataType.DeferredInvoice) {
- await executeDeferredBillingPlan({ ctx, metadata });
- return;
- }
-
- // Legacy v1 flows below
- const data = metadata.data as unknown as AttachParams;
- const reqMatch =
- data.org?.id === ctx.org.id && data.customer?.env === ctx.env;
-
- if (!reqMatch) return;
-
- if (metadata.type === MetadataType.InvoiceActionRequired) {
- await handleInvoiceActionRequiredCompleted({
- ctx,
- invoice,
- metadata,
- });
-
- return;
- }
-
- await handleInvoiceCheckoutPaid({
- ctx,
- metadata,
- });
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleSubDeleted.ts b/server/src/external/stripe/webhookHandlers/legacy/handleSubDeleted.ts
deleted file mode 100644
index 4f127d900..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleSubDeleted.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import type Stripe from "stripe";
-import {
- getFullStripeSub,
- subIsPrematurelyCanceled,
-} from "@/external/stripe/stripeSubUtils.js";
-import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
-import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
-import { handleCusProductDeleted } from "./handleSubDeleted/handleCusProductDeleted.js";
-
-export const handleSubDeleted = async ({
- ctx,
- stripeCli,
- data,
-}: {
- ctx: AutumnContext;
- stripeCli: Stripe;
- data: Stripe.Subscription;
-}) => {
- const { db, org, env, logger } = ctx;
-
- const activeCusProducts = await CusProductService.getByStripeSubId({
- db,
- stripeSubId: data.id,
- orgId: org.id,
- env,
- });
-
- if (activeCusProducts.length === 0) {
- if (data.livemode) {
- logger.warn(
- `subscription.deleted: ${data.id} - no customer products found`,
- );
- return;
- }
- }
-
- const subscription = await getFullStripeSub({
- stripeCli,
- stripeId: data.id,
- });
-
- const cancellationComment = subscription.cancellation_details?.comment;
- if (
- cancellationComment === "autumn_upgrade" ||
- cancellationComment === "autumn_cancel"
- ) {
- logger.info(
- `sub.deleted: ${subscription.id} from ${cancellationComment}, skipping`,
- );
- return;
- }
-
- if (cancellationComment?.includes("trial_canceled")) {
- logger.info(
- `sub.deleted: ${subscription.id} from trial canceled, skipping`,
- );
- return;
- }
-
- // Prematurely canceled if cancel_at_period_end is false or cancel_at is more than 20 seconds apart from current_period_end
- const prematurelyCanceled = subIsPrematurelyCanceled(subscription);
-
- // const batchUpdate = [];
- for (const cusProduct of activeCusProducts) {
- await handleCusProductDeleted({
- ctx,
- db,
- stripeCli,
- cusProduct,
- subscription,
- prematurelyCanceled,
- });
- }
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleSubDeleted/handleCusProductDeleted.ts b/server/src/external/stripe/webhookHandlers/legacy/handleSubDeleted/handleCusProductDeleted.ts
deleted file mode 100644
index 860e3cdb2..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleSubDeleted/handleCusProductDeleted.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import {
- ApiVersion,
- AttachScenario,
- BillingType,
- CusProductStatus,
- cusProductToPrices,
- customerProductHasActiveStatus,
- type FullCusProduct,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { getCusPaymentMethod } from "@/external/stripe/stripeCusUtils.js";
-import { webhookToAttachParams } from "@/external/stripe/webhookUtils/webhookUtils.js";
-import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
-import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
-import { createUsageInvoice } from "@/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoice.js";
-import { CusService } from "@/internal/customers/CusService.js";
-import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
-import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
-import {
- activateDefaultProduct,
- activateFutureProduct,
-} from "@/internal/customers/cusProducts/cusProductUtils.js";
-
-export const handleCusProductDeleted = async ({
- ctx,
- db,
- stripeCli,
- cusProduct,
- subscription,
- prematurelyCanceled,
-}: {
- ctx: AutumnContext;
- db: DrizzleCli;
- stripeCli: Stripe;
- cusProduct: FullCusProduct;
- subscription: Stripe.Subscription;
- prematurelyCanceled: boolean;
-}) => {
- const { org, env, logger } = ctx;
- const { scheduled_ids } = cusProduct;
- const fullCus = await CusService.getFull({
- db,
- idOrInternalId: cusProduct.internal_customer_id,
- orgId: org.id,
- env,
- withEntities: true,
- });
-
- const paymentMethod = await getCusPaymentMethod({
- stripeCli,
- stripeId: fullCus.processor?.id,
- });
-
- const isV4Usage = cusProduct.api_semver === ApiVersion.V1_Beta;
-
- // refer to handleUpgradeFlow.ts, when cancel immediately through API / dashboard, this happens...?
- const isAutumnCancel =
- subscription.cancellation_details?.comment === "autumn_cancel";
-
- if (
- (cusProduct.internal_entity_id || isV4Usage) &&
- !isAutumnCancel &&
- customerProductHasActiveStatus(cusProduct)
- ) {
- const usagePrices = cusProductToPrices({
- cusProduct,
- billingType: BillingType.UsageInArrear,
- });
-
- if (usagePrices.length > 0) {
- logger.info(
- `sub.deleted, submitting usage for ${fullCus.id}, ${cusProduct.product.name}`,
- );
-
- await createUsageInvoice({
- db,
- attachParams: webhookToAttachParams({
- ctx,
- stripeCli,
- paymentMethod,
- cusProduct,
- fullCus,
- }),
- cusProduct,
- sub: subscription,
- logger,
- });
- }
- }
-
- if (scheduled_ids && scheduled_ids.length > 0 && !prematurelyCanceled) {
- logger.info(
- `sub.deleted: removing sub_id from cus product ${cusProduct.id}`,
- );
- await CusProductService.update({
- db,
- cusProductId: cusProduct.id,
- updates: {
- subscription_ids: cusProduct.subscription_ids?.filter(
- (id) => id !== subscription.id,
- ),
- },
- });
-
- return;
- }
-
- logger.info(`sub.deleted: expiring cus product ${cusProduct.id}`);
- await CusProductService.update({
- db,
- cusProductId: cusProduct.id,
- updates: {
- status: CusProductStatus.Expired,
- ended_at: subscription.ended_at ? subscription.ended_at * 1000 : null,
- },
- });
-
- await addProductsUpdatedWebhookTask({
- ctx,
- internalCustomerId: cusProduct.internal_customer_id,
- org,
- env,
- customerId: null,
- scenario: AttachScenario.Expired,
- cusProduct,
- });
-
- if (cusProduct.product.is_add_on) return;
-
- const activatedFuture = await activateFutureProduct({
- ctx,
- cusProduct,
- });
-
- if (activatedFuture) {
- logger.info(`✅ sub.deleted: activated scheduled product`);
- return;
- }
-
- const cusProducts = await CusProductService.list({
- db,
- internalCustomerId: cusProduct.internal_customer_id,
- inStatuses: [CusProductStatus.Active, CusProductStatus.PastDue],
- });
-
- const { curMainProduct } = getExistingCusProducts({
- product: cusProduct.product,
- cusProducts,
- });
-
- await activateDefaultProduct({
- ctx,
- productGroup: cusProduct.product.group,
- fullCus,
- curCusProduct: curMainProduct || undefined,
- });
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSchedulePhaseCompleted.ts b/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSchedulePhaseCompleted.ts
deleted file mode 100644
index 75adba428..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSchedulePhaseCompleted.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import {
- AttachScenario,
- CusProductStatus,
- type FullCustomer,
- formatMs,
- hasCustomerProductEnded,
-} from "@autumn/shared";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import { stripeCustomerToNowMs } from "@/external/stripe/customers/index";
-import { isStripeSubscriptionScheduleInLastPhase } from "@/external/stripe/subscriptionSchedules/utils/classifyStripeSubscriptionScheduleUtils";
-import { stripeSubscriptionScheduleToPhaseIndex } from "@/external/stripe/subscriptionSchedules/utils/convertStripeSubscriptionScheduleUtils";
-import type { ExpandedStripeSubscription } from "@/external/stripe/subscriptions/operations/getExpandedStripeSubscription.js";
-import { getStripeSubscriptionLock } from "@/external/stripe/subscriptions/utils/lockStripeSubscriptionUtils";
-import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
-import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
-import { notNullish } from "@/utils/genUtils.js";
-import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
-
-export const handleSchedulePhaseCompleted = async ({
- ctx,
- stripeSubscription,
- prevAttributes,
- fullCustomer,
-}: {
- ctx: AutumnContext;
- fullCustomer: FullCustomer;
- stripeSubscription: ExpandedStripeSubscription;
- // biome-ignore lint/suspicious/noExplicitAny: Don't know the type of prevAttributes
- prevAttributes: any;
-}) => {
- if (
- await getStripeSubscriptionLock({
- stripeSubscriptionId: stripeSubscription.id,
- })
- ) {
- ctx.logger.info(
- `[handleSchedulePhaseCompleted] SKIP: subscription is locked`,
- );
- return;
- }
-
- const { db, org, env, logger } = ctx;
-
- const phasePossiblyChanged =
- notNullish(prevAttributes?.items) &&
- notNullish(stripeSubscription.schedule);
-
- if (!phasePossiblyChanged) return;
-
- const stripeSubscriptionSchedule = stripeSubscription.schedule;
- const stripeCli = createStripeCli({ org, env });
- const customerProducts = fullCustomer.customer_products;
-
- const nowMs = await stripeCustomerToNowMs({
- stripeCli,
- stripeCustomer: stripeSubscription.customer,
- });
-
- const currentPhaseIndex = stripeSubscriptionScheduleToPhaseIndex({
- stripeSubscriptionSchedule,
- nowMs,
- });
-
- logger.info(
- `[handleSchedulePhaseCompleted] sub: ${stripeSubscription.id}, now: ${formatMs(nowMs)}, currentPhase: ${currentPhaseIndex + 1}/${stripeSubscriptionSchedule.phases.length}`,
- );
-
- for (const cusProduct of customerProducts) {
- const shouldExpire = hasCustomerProductEnded(cusProduct, { nowMs });
-
- if (shouldExpire) {
- logger.info(
- `[handleSchedulePhaseCompleted] ❌ expiring: ${cusProduct.product.name}${cusProduct.entity_id ? `@${cusProduct.entity_id}` : ""}`,
- );
- await CusProductService.update({
- db,
- cusProductId: cusProduct.id,
- updates: { status: CusProductStatus.Expired },
- });
-
- await addProductsUpdatedWebhookTask({
- ctx,
- internalCustomerId: cusProduct.internal_customer_id,
- org,
- env,
- customerId: null,
- scenario: AttachScenario.Expired,
- cusProduct: cusProduct,
- });
- }
-
- const shouldActivateCustomerProduct = () => {
- if (cusProduct.status !== CusProductStatus.Scheduled) return false;
- return cusProduct.starts_at <= nowMs;
- };
-
- if (shouldActivateCustomerProduct()) {
- logger.info(
- `[handleSchedulePhaseCompleted] ✅ activating: ${cusProduct.product.name}${cusProduct.entity_id ? `@${cusProduct.entity_id}` : ""}`,
- );
-
- await CusProductService.update({
- db,
- cusProductId: cusProduct.id,
- updates: {
- status: CusProductStatus.Active,
- subscription_ids: [stripeSubscription.id],
- scheduled_ids: [stripeSubscriptionSchedule.id],
- },
- });
- }
- }
-
- if (
- isStripeSubscriptionScheduleInLastPhase({
- stripeSubscriptionSchedule,
- nowMs,
- })
- ) {
- logger.debug(
- `[handleSchedulePhaseCompleted] releasing schedule (last phase reached)`,
- );
- try {
- await stripeCli.subscriptionSchedules.release(
- stripeSubscriptionSchedule.id,
- );
- await CusProductService.updateByStripeScheduledId({
- db,
- stripeScheduledId: stripeSubscriptionSchedule.id,
- updates: {
- scheduled_ids: [],
- },
- });
- } catch (error: unknown) {
- if (error instanceof Error) {
- if (process.env.NODE_ENV === "development") {
- logger.warn(
- `[handleSchedulePhaseCompleted] failed to release schedule: ${error.message}`,
- );
- } else {
- logger.error(
- `[handleSchedulePhaseCompleted] failed to release schedule: ${error.message}`,
- );
- }
- }
- }
- }
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubCanceled.ts b/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubCanceled.ts
deleted file mode 100644
index 8813d81ea..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubCanceled.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-import {
- AttachScenario,
- CusProductStatus,
- type FullCusProduct,
- type Organization,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
-import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
-import { productToInsertParams } from "@/internal/customers/attach/attachUtils/attachParams/convertToParams.js";
-import { CusService } from "@/internal/customers/CusService.js";
-import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
-import { ProductService } from "@/internal/products/ProductService.js";
-import { formatUnixToDateTime, nullish } from "@/utils/genUtils.js";
-import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
-import {
- getLatestPeriodEnd,
- subToPeriodStartEnd,
-} from "../../../stripeSubUtils/convertSubUtils.js";
-
-export const isSubCanceled = ({
- previousAttributes,
- sub,
-}: {
- previousAttributes: unknown;
- sub: Stripe.Subscription;
-}) => {
- const prevAttrs = previousAttributes as Record;
-
- if (!sub.cancel_at && !sub.cancel_at_period_end) {
- return {
- canceled: false,
- canceledAt: null,
- };
- }
- const cancelAtPreviousEnd =
- !prevAttrs.cancel_at_period_end && sub.cancel_at_period_end;
-
- const cancelAt = nullish(prevAttrs.cancel_at) && sub.cancel_at;
- const canceledAt = nullish(prevAttrs.canceled_at) && sub.canceled_at;
-
- return {
- canceled: cancelAtPreviousEnd || cancelAt || canceledAt,
- canceledAt: sub.canceled_at ? sub.canceled_at * 1000 : Date.now(),
- };
-};
-
-const updateCusProductCanceled = async ({
- db,
- sub,
- canceledAt,
- logger,
-}: {
- db: DrizzleCli;
- sub: Stripe.Subscription;
- canceledAt?: number | null;
- logger: { info: (msg: string) => void };
-}) => {
- // 1. Check if sub has schedule
- if (sub.schedule) {
- return;
- }
-
- logger.info(
- `sub.updated: updating cus products to canceled, stripeSubId=${sub.id}, canceledAt=${canceledAt}`,
- );
-
- const cancelsAt = sub.cancel_at ? sub.cancel_at * 1000 : undefined;
-
- await CusProductService.updateByStripeSubId({
- db,
- stripeSubId: sub.id,
- updates: {
- canceled_at: canceledAt || Date.now(),
- canceled: true,
- ended_at: cancelsAt,
- },
- });
-};
-
-export const handleSubCanceled = async ({
- ctx,
- previousAttributes,
- org,
- sub,
- updatedCusProducts,
-}: {
- ctx: AutumnContext;
- previousAttributes: unknown;
- sub: Stripe.Subscription;
- org: Organization;
- updatedCusProducts: FullCusProduct[];
-}) => {
- const { canceled, canceledAt } = isSubCanceled({
- previousAttributes,
- sub,
- });
-
- const isAutumnDowngrade =
- sub.cancellation_details?.comment?.includes("autumn_downgrade") ||
- sub.cancellation_details?.comment?.includes("autumn_cancel");
-
- const { db, env, logger } = ctx;
-
- if (!canceled) return;
-
- if (isAutumnDowngrade) {
- logger.info(`sub.canceled SKIP: isAutumnDowngrade`);
- return;
- }
-
- if (updatedCusProducts.length === 0) {
- logger.info(`sub.canceled SKIP: canceled but no updatedCusProducts`);
- return;
- }
-
- await updateCusProductCanceled({
- db,
- sub,
- canceledAt,
- logger,
- });
-
- if (!org.config.sync_status) {
- logger.info(`sub.canceled SKIP webhook: org.config.sync_status=false`);
- return;
- }
-
- const allDefaultProducts = await ProductService.listDefault({
- db,
- orgId: org.id,
- env,
- });
-
- const fullCus = await CusService.getFull({
- db,
- idOrInternalId: updatedCusProducts[0].customer!.id!,
- orgId: org.id,
- env,
- withEntities: true,
- inStatuses: [CusProductStatus.Scheduled],
- });
-
- const cusProducts = fullCus.customer_products;
- const entities = fullCus.entities;
-
- const defaultProducts = allDefaultProducts.filter((p) =>
- updatedCusProducts.some(
- (cp: FullCusProduct) =>
- cp.product.group === p.group && nullish(cp.internal_entity_id),
- ),
- );
-
- if (defaultProducts.length === 0) return;
-
- if (defaultProducts.length > 0) {
- const { end } = subToPeriodStartEnd({ sub });
- const productNames = defaultProducts.map((p) => p.name).join(", ");
- const periodEnd = formatUnixToDateTime(end * 1000);
- logger.info(
- `subscription.updated: canceled -> attempting to schedule default products: ${productNames}, period end: ${periodEnd}`,
- );
- }
-
- const scheduledCusProducts: FullCusProduct[] = [];
- for (const product of defaultProducts) {
- const alreadyScheduled = cusProducts.some(
- (cp: FullCusProduct) => cp.product.group === product.group,
- );
-
- if (alreadyScheduled) {
- continue;
- }
-
- const insertParams = productToInsertParams({
- ctx,
- fullCus,
- newProduct: product,
- entities,
- });
-
- const end = getLatestPeriodEnd({ sub });
- const fullCusProduct = await createFullCusProduct({
- db,
- attachParams: insertParams,
- startsAt: end * 1000,
- sendWebhook: false,
- logger,
- });
-
- if (fullCusProduct) {
- scheduledCusProducts.push(fullCusProduct);
- }
- }
-
- for (const cusProd of updatedCusProducts) {
- try {
- await addProductsUpdatedWebhookTask({
- ctx,
- internalCustomerId: cusProd.internal_customer_id,
- org,
- env,
- customerId: null,
- scenario: AttachScenario.Cancel,
- cusProduct: cusProd,
- scheduledCusProduct: scheduledCusProducts.find(
- (cp) => cp.product.group === cusProd.product.group,
- ),
- });
- logger.info(`sub.canceled ✅ SENT webhook for ${cusProd.product.name}`);
- } catch (error) {
- logger.error(
- `sub.canceled ❌ FAILED webhook for ${cusProd.product.name}`,
- error,
- );
- }
- }
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubPastDue.ts b/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubPastDue.ts
deleted file mode 100644
index 6922c361b..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubPastDue.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import {
- AttachScenario,
- type FullCusProduct,
- type Organization,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
-import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
-
-export const isSubPastDue = ({
- previousAttributes,
- sub,
-}: {
- // biome-ignore lint/suspicious/noExplicitAny: Don't know the type of previousAttributes
- previousAttributes: any;
- sub: Stripe.Subscription;
-}) => {
- const wasPastDue = previousAttributes.status === "past_due";
- const isPastDue = sub.status === "past_due";
-
- return {
- pastDue: !wasPastDue && isPastDue,
- };
-};
-
-export const handleSubPastDue = async ({
- ctx,
- previousAttributes,
- org,
- sub,
- updatedCusProducts,
-}: {
- ctx: AutumnContext;
- // biome-ignore lint/suspicious/noExplicitAny: Don't know the type of previousAttributes
- previousAttributes: any;
- sub: Stripe.Subscription;
- org: Organization;
- updatedCusProducts: FullCusProduct[];
-}) => {
- const { pastDue } = isSubPastDue({
- previousAttributes,
- sub,
- });
-
- const { env, logger } = ctx;
-
- if (!pastDue || updatedCusProducts.length === 0) return;
-
- logger.info(
- `Subscription ${sub.id} is now past due, firing webhooks for ${updatedCusProducts.length} customer product(s)`,
- );
-
- if (!org.config.sync_status) return;
-
- for (const cusProd of updatedCusProducts) {
- try {
- await addProductsUpdatedWebhookTask({
- ctx,
- internalCustomerId: cusProd.internal_customer_id,
- org,
- env,
- customerId: null,
- scenario: AttachScenario.PastDue,
- cusProduct: cusProd,
- });
- } catch (error) {
- logger.error("Failed to add products updated webhook task to queue", {
- error,
- });
- }
- }
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubRenewed.ts b/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubRenewed.ts
deleted file mode 100644
index 0c230ed2b..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubRenewed.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import { AttachScenario, type FullCusProduct } from "@autumn/shared";
-import type Stripe from "stripe";
-import { getStripeSubscriptionLock } from "@/external/stripe/subscriptions/utils/lockStripeSubscriptionUtils";
-import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
-import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
-import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
-import { notNullish, nullish } from "@/utils/genUtils.js";
-import type { AutumnContext } from "../../../../../honoUtils/HonoEnv";
-
-const isSubRenewed = ({
- previousAttributes,
- sub,
-}: {
- previousAttributes: unknown;
- sub: Stripe.Subscription;
-}) => {
- const prevAttrs = previousAttributes as Record;
-
- // 1. If previously canceled
- const uncanceledAtPreviousEnd =
- prevAttrs.cancel_at_period_end && !sub.cancel_at_period_end;
-
- const uncancelAt = notNullish(prevAttrs.cancel_at) && nullish(sub.cancel_at);
-
- const uncanceledAt = notNullish(prevAttrs.canceled_at) && sub.canceled_at;
-
- return {
- renewed: uncanceledAtPreviousEnd || uncancelAt || uncanceledAt,
- renewedAt: Date.now(),
- };
-};
-
-export const handleSubRenewed = async ({
- ctx,
- prevAttributes,
- sub,
- updatedCusProducts,
-}: {
- ctx: AutumnContext;
- prevAttributes: unknown;
- sub: Stripe.Subscription;
- updatedCusProducts: FullCusProduct[];
-}) => {
- const { db, org, env, logger } = ctx;
-
- const { renewed } = isSubRenewed({
- previousAttributes: prevAttributes,
- sub,
- });
-
- logger.info(`sub.renewed: renewed=${renewed}`);
- if (!renewed) return;
-
- if (updatedCusProducts.length === 0) {
- logger.info(`sub.renewed SKIP: renewed but no updatedCusProducts`);
- return;
- }
-
- const subLock = await getStripeSubscriptionLock({
- stripeSubscriptionId: sub.id,
- });
- logger.info(`sub.renewed: renewed=${renewed}, subLock=${!!subLock}`);
- if (subLock) {
- logger.info(`sub.renewed SKIP: already handled by attach`);
- return;
- }
-
- const customer = updatedCusProducts[0].customer;
- const cusProducts = await CusProductService.list({
- db,
- internalCustomerId: customer!.internal_id,
- });
-
- logger.info(`handling sub.renewed!`);
-
- await CusProductService.updateByStripeSubId({
- db,
- stripeSubId: sub.id,
- updates: { canceled_at: null, canceled: false, ended_at: null },
- });
-
- if (!org.config.sync_status) {
- logger.info(`sub.renewed SKIP webhook: org.config.sync_status=false`);
- return;
- }
-
- const curScheduledProductsMap = new Map();
- for (const x of updatedCusProducts) {
- const { curScheduledProduct } = getExistingCusProducts({
- product: x.product,
- cusProducts,
- internalEntityId: x.internal_entity_id,
- });
- if (
- curScheduledProduct &&
- !curScheduledProductsMap.has(curScheduledProduct.id)
- ) {
- curScheduledProductsMap.set(curScheduledProduct.id, curScheduledProduct);
- }
- }
- const curScheduledProducts = Array.from(curScheduledProductsMap.values());
-
- const deletedCusProducts: FullCusProduct[] = [];
-
- for (const curScheduledProduct of curScheduledProducts) {
- if (!curScheduledProduct) continue;
-
- logger.info(
- `sub.updated: renewed -> removing scheduled: ${curScheduledProduct.product.name}, main product: ${updatedCusProducts[0].product.name}`,
- );
-
- await CusProductService.delete({
- db,
- cusProductId: curScheduledProduct.id,
- });
-
- deletedCusProducts.push(curScheduledProduct);
- }
-
- for (const cusProd of updatedCusProducts) {
- try {
- await addProductsUpdatedWebhookTask({
- ctx,
- internalCustomerId: cusProd.internal_customer_id,
- org,
- env,
- customerId: null,
- scenario: AttachScenario.Renew,
- cusProduct: cusProd,
- deletedCusProduct: deletedCusProducts.find(
- (cp) => cp.product.group === cusProd.product.group,
- ),
- });
- logger.info(`sub.renewed ✅ SENT webhook for ${cusProd.product.name}`);
- } catch (error) {
- logger.error(
- `sub.renewed ❌ FAILED webhook for ${cusProd.product.name}`,
- error,
- );
- }
- }
-};
diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubUpdated.ts b/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubUpdated.ts
deleted file mode 100644
index 141bbee13..000000000
--- a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSubUpdated.ts
+++ /dev/null
@@ -1,188 +0,0 @@
-import {
- ACTIVE_STATUSES,
- type CollectionMethod,
- CusProductStatus,
- formatMs,
- InternalError,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import { getExpandedStripeSubscription } from "@/external/stripe/subscriptions/operations/getExpandedStripeSubscription.js";
-import { stripeSubscriptionToTrialEndsAtMs } from "@/external/stripe/subscriptions/utils/convertStripeSubscription";
-import { CusService } from "@/internal/customers/CusService.js";
-import {
- CusProductService,
- RELEVANT_STATUSES,
-} from "@/internal/customers/cusProducts/CusProductService.js";
-import { SubService } from "@/internal/subscriptions/SubService.js";
-import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
-import { handleSchedulePhaseCompleted } from "./handleSchedulePhaseCompleted.js";
-import { handleSubCanceled } from "./handleSubCanceled.js";
-import { handleSubPastDue } from "./handleSubPastDue.js";
-import { handleSubRenewed } from "./handleSubRenewed.js";
-
-export const handleSubscriptionUpdated = async ({
- ctx,
- eventData,
-}: {
- ctx: AutumnContext;
- eventData: Stripe.Event.Data;
-}) => {
- const previousAttributes = eventData.previous_attributes;
- const { db, org, env, logger } = ctx;
-
- const fullCustomer = await CusService.getFull({
- db,
- idOrInternalId: ctx.customerId ?? "",
- orgId: org.id,
- env,
- inStatuses: RELEVANT_STATUSES,
- });
-
- const stripeSubscription = await getExpandedStripeSubscription({
- ctx,
- subscriptionId: (eventData.object as Stripe.Subscription).id,
- });
-
- // handle scheduled updated
- await handleSchedulePhaseCompleted({
- ctx,
- stripeSubscription,
- prevAttributes: previousAttributes,
- fullCustomer,
- });
-
- // Get cus products by stripe sub id
- const cusProducts = await CusProductService.getByStripeSubId({
- db,
- stripeSubId: stripeSubscription.id,
- orgId: org.id,
- env,
- inStatuses: [CusProductStatus.Active, CusProductStatus.PastDue],
- });
-
- if (cusProducts.length === 0) return;
-
- const subStatusMap: {
- [key: string]: CusProductStatus;
- } = {
- trialing: CusProductStatus.Active,
- active: CusProductStatus.Active,
- past_due: CusProductStatus.PastDue,
- incomplete: CusProductStatus.PastDue, // temporary status for incomplete subscription
- };
-
- const trialEndsAtMs = stripeSubscriptionToTrialEndsAtMs({
- stripeSubscription,
- });
- ctx.logger.info(
- `SUB.UPDATED: Setting trial ends to: ${formatMs(trialEndsAtMs)}`,
- );
-
- const updatedCusProducts = await CusProductService.updateByStripeSubId({
- db,
- stripeSubId: stripeSubscription.id,
- inStatuses: ACTIVE_STATUSES,
- updates: {
- status: subStatusMap[stripeSubscription.status] ?? undefined, // don't change status if it's unknown
- collection_method:
- stripeSubscription.collection_method as CollectionMethod,
- trial_ends_at: trialEndsAtMs,
- },
- });
-
- // 2. Update canceled & canceled_at IF sub has no schedule...?
-
- if (updatedCusProducts.length > 0) {
- logger.info(
- `✅ sub.updated: updated ${updatedCusProducts.length} customer product(s)} (${updatedCusProducts[0].status})`,
- );
- }
-
- await handleSubCanceled({
- ctx,
- previousAttributes,
- sub: stripeSubscription,
- updatedCusProducts: cusProducts,
- org,
- });
-
- await handleSubPastDue({
- ctx,
- previousAttributes,
- sub: stripeSubscription,
- updatedCusProducts: cusProducts,
- org,
- });
-
- await handleSubRenewed({
- ctx,
- prevAttributes: previousAttributes,
- sub: stripeSubscription,
- updatedCusProducts: cusProducts,
- });
-
- try {
- await SubService.updateFromStripe({
- db,
- stripeSub: stripeSubscription,
- });
- } catch (error) {
- logger.warn(
- `Failed to update sub from stripe. Stripe sub ID: ${stripeSubscription.id}, org: ${org.slug}, env: ${env}`,
- error,
- );
- }
-
- // Cancel subscription immediately
-
- if (
- stripeSubscription.status === "past_due" &&
- org.config.cancel_on_past_due
- ) {
- if (
- !stripeSubscription.latest_invoice ||
- typeof stripeSubscription.latest_invoice !== "string"
- ) {
- throw new InternalError({
- message: "subscription.latest_invoice is not a string",
- });
- }
-
- const stripeCli = createStripeCli({
- org,
- env,
- });
-
- const latestInvoice = await stripeCli.invoices.retrieve(
- stripeSubscription.latest_invoice,
- );
-
- logger.info(
- `Latest invoice billing reason: ${latestInvoice.billing_reason}`,
- );
-
- const validInvoiceReasons = ["subscription_cycle", "subscription_create"];
- if (!validInvoiceReasons.includes(latestInvoice.billing_reason ?? "")) {
- logger.info(
- `sub.updated, latest invoice billing reason isn't subscription_cycle / subscription_create, past_due not forcing cancel`,
- );
- return;
- }
-
- try {
- logger.info(
- `sub.updated (past_due), cancelling subscription: ${stripeSubscription.id}`,
- );
- await stripeCli.subscriptions.cancel(stripeSubscription.id);
- if (latestInvoice.status === "open") {
- await stripeCli.invoices.voidInvoice(stripeSubscription.latest_invoice);
- }
- } catch (error: unknown) {
- const errMsg = error instanceof Error ? error.message : String(error);
- logger.error(
- `subscription.updated: error cancelling / voiding: ${errMsg}`,
- );
- }
- }
-};
diff --git a/server/src/external/stripe/webhookUtils/webhookUtils.ts b/server/src/external/stripe/webhookUtils/webhookUtils.ts
deleted file mode 100644
index 02778b4e2..000000000
--- a/server/src/external/stripe/webhookUtils/webhookUtils.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import {
- cusProductToEnts,
- cusProductToPrices,
- cusProductToProduct,
- type Entity,
- type FullCusProduct,
- type FullCustomer,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
-import type { AutumnContext } from "../../../honoUtils/HonoEnv";
-
-export const webhookToAttachParams = ({
- ctx,
- stripeCli,
- paymentMethod,
- cusProduct,
- fullCus,
- entities,
-}: {
- ctx: AutumnContext;
- stripeCli: Stripe;
- paymentMethod?: Stripe.PaymentMethod | null;
- cusProduct: FullCusProduct;
- fullCus: FullCustomer;
- entities?: Entity[];
-}): AttachParams => {
- const fullProduct = cusProductToProduct({ cusProduct });
- const { org, features } = ctx;
-
- const params: AttachParams = {
- stripeCli,
- paymentMethod,
- customer: fullCus,
- org,
- products: [fullProduct],
- prices: cusProductToPrices({ cusProduct }),
- entitlements: cusProductToEnts({ cusProduct }),
- features,
- freeTrial: cusProduct.free_trial || null,
- optionsList: cusProduct.options,
- cusProducts: [cusProduct],
-
- internalEntityId: cusProduct.internal_entity_id || undefined,
- entities: entities || [],
- replaceables: [],
- };
-
- return params;
-};
diff --git a/server/src/external/supabase/safeSb.ts b/server/src/external/supabase/safeSb.ts
deleted file mode 100644
index 73c54d391..000000000
--- a/server/src/external/supabase/safeSb.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { logger } from "../logtail/logtailUtils.js";
-
-export function safeSb any>({
- fn,
- action,
-}: {
- fn: T;
- action: string;
-}): (...args: Parameters) => Promise | undefined> {
- return async (...args: Parameters) => {
- if (!process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_KEY) {
- logger.warn(
- `SUPABASE_URL or SUPABASE_SERVICE_KEY is not set, skipping ${action}`,
- );
- return;
- }
- try {
- return await fn(...args);
- } catch (error) {
- logger.error(`Error ${action}: ${error}`);
- }
- };
-}
diff --git a/server/src/external/supabase/subscribeToOrgUpdates.ts b/server/src/external/supabase/subscribeToOrgUpdates.ts
deleted file mode 100644
index ebae4c066..000000000
--- a/server/src/external/supabase/subscribeToOrgUpdates.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { client, type DrizzleCli } from "@/db/initDrizzle.js";
-import { clearOrgCache } from "@/internal/orgs/orgUtils/clearOrgCache.js";
-
-export const subscribeToOrgUpdates = async ({ db }: { db: DrizzleCli }) => {
- try {
- await client.listen("org_updates", async (payload) => {
- try {
- const data = JSON.parse(payload);
- if (data.table === "organizations" && data.operation === "UPDATE") {
- await clearOrgCache({ db, orgId: data.new.id });
- }
- } catch (error) {
- console.warn("Error processing org update notification:", error);
- }
- });
-
- console.log(
- "Successfully subscribed to organization updates via PostgreSQL LISTEN/NOTIFY",
- );
- } catch (error) {
- console.warn("Error subscribing to org updates:", error);
- }
-};
diff --git a/server/src/external/vercel/handlers/installations/handleUpdateInstallation.ts b/server/src/external/vercel/handlers/installations/handleUpdateInstallation.ts
deleted file mode 100644
index 59846dd8e..000000000
--- a/server/src/external/vercel/handlers/installations/handleUpdateInstallation.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-/**
- * Update an existing installation
- *
- * @see handleUpdateBillingPlan
- */
diff --git a/server/src/honoUtils/handleRequestError.ts b/server/src/honoUtils/handleRequestError.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/honoUtils/honoUtils.ts b/server/src/honoUtils/honoUtils.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/init.ts b/server/src/init.ts
index 9619074d7..0cc0dba3f 100644
--- a/server/src/init.ts
+++ b/server/src/init.ts
@@ -17,8 +17,8 @@ import express from "express";
import { addRequestToLogs } from "@/utils/logging/addContextToLogs.js";
import { client, db } from "./db/initDrizzle.js";
import { ClickHouseManager } from "./external/clickhouse/ClickHouseManager.js";
-import { warmupRegionalRedis } from "./external/redis/initRedis.js";
import { logger } from "./external/logtail/logtailUtils.js";
+import { warmupRegionalRedis } from "./external/redis/initRedis.js";
import { redirectToHono } from "./initHono.js";
import { apiRouter } from "./internal/api/apiRouter.js";
import { auth } from "./utils/auth.js";
@@ -98,6 +98,8 @@ const init = async () => {
"app_env",
"x-api-version",
"x-client-type",
+ "x-request-id",
+ "x-visitor-id",
"Authorization",
"Content-Type",
"Accept",
diff --git a/server/src/initHono.ts b/server/src/initHono.ts
index 7d928efe4..88646ddfd 100644
--- a/server/src/initHono.ts
+++ b/server/src/initHono.ts
@@ -9,7 +9,6 @@ import { Hono } from "hono";
import { cors } from "hono/cors";
import { autumnWebhookRouter } from "./external/autumn/autumnWebhookRouter.js";
-import { cliRouter } from "./internal/dev/cli/cliRouter.js";
import { revenuecatWebhookRouter } from "./external/revenueCat/revenuecatWebhookRouter.js";
import { stripeWebhookRouter } from "./external/stripe/stripeWebhookRouter.js";
import { vercelWebhookRouter } from "./external/vercel/vercelWebhookRouter.js";
@@ -18,6 +17,7 @@ import { errorMiddleware } from "./honoMiddlewares/errorMiddleware.js";
import { traceMiddleware } from "./honoMiddlewares/traceMiddleware.js";
import type { HonoEnv } from "./honoUtils/HonoEnv.js";
import { handleHealthCheck } from "./honoUtils/handleHealthCheck.js";
+import { cliRouter } from "./internal/dev/cli/cliRouter.js";
import { handleOAuthCallback } from "./internal/orgs/handlers/stripeHandlers/handleOAuthCallback.js";
import { apiRouter } from "./routers/apiRouter.js";
import { internalRouter } from "./routers/internalRouter.js";
@@ -39,6 +39,8 @@ const ALLOWED_HEADERS = [
"app_env",
"x-api-version",
"x-client-type",
+ "x-request-id",
+ "x-visitor-id",
"Authorization",
"Content-Type",
"Accept",
diff --git a/server/src/internal/admin/handleListOAuthClients.ts b/server/src/internal/admin/handleListOAuthClients.ts
deleted file mode 100644
index 0b4a06593..000000000
--- a/server/src/internal/admin/handleListOAuthClients.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { oauthClient } from "@autumn/shared";
-import { desc } from "drizzle-orm";
-import { createRoute } from "../../honoMiddlewares/routeHandler";
-
-export const handleListOAuthClients = createRoute({
- handler: async (c) => {
- const ctx = c.get("ctx");
- const { db } = ctx;
-
- const clients = await db
- .select({
- id: oauthClient.id,
- clientId: oauthClient.clientId,
- name: oauthClient.name,
- redirectUris: oauthClient.redirectUris,
- public: oauthClient.public,
- disabled: oauthClient.disabled,
- skipConsent: oauthClient.skipConsent,
- scopes: oauthClient.scopes,
- tokenEndpointAuthMethod: oauthClient.tokenEndpointAuthMethod,
- grantTypes: oauthClient.grantTypes,
- responseTypes: oauthClient.responseTypes,
- createdAt: oauthClient.createdAt,
- updatedAt: oauthClient.updatedAt,
- })
- .from(oauthClient)
- .orderBy(desc(oauthClient.createdAt));
-
- return c.json({
- clients: clients.map((client) => ({
- id: client.id,
- client_id: client.clientId,
- client_name: client.name,
- redirect_uris: client.redirectUris,
- public: client.public,
- disabled: client.disabled,
- skip_consent: client.skipConsent,
- scope: client.scopes?.join(" "),
- token_endpoint_auth_method: client.tokenEndpointAuthMethod,
- grant_types: client.grantTypes,
- response_types: client.responseTypes,
- client_id_issued_at: client.createdAt
- ? Math.floor(client.createdAt.getTime() / 1000)
- : undefined,
- })),
- });
- },
-});
diff --git a/server/src/internal/admin/withAdminAuth.ts b/server/src/internal/admin/withAdminAuth.ts
deleted file mode 100644
index 131d777ae..000000000
--- a/server/src/internal/admin/withAdminAuth.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { ErrCode } from "@autumn/shared";
-import type { NextFunction } from "express";
-import { auth } from "@/utils/auth.js";
-import type { ExtendedRequest } from "@/utils/models/Request.js";
-
-export const withAdminAuth = async (req: any, res: any, next: NextFunction) => {
- const { logger } = req as ExtendedRequest;
-
- try {
- const data = await auth.api.getSession({
- headers: req.headers,
- });
-
- // Check if user has admin role
- const isAdmin = data?.user?.role === "admin";
-
- if (!isAdmin) {
- return res.status(403).json({
- error: {
- code: ErrCode.InvalidRequest,
- message: "Forbidden - Admin access required",
- },
- });
- }
-
- next();
- } catch (error: unknown) {
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
- logger.error(`Admin req failed: ${errorMessage}`);
- return res.status(400).json();
- }
-};
diff --git a/server/src/internal/api/check/checkRouter.ts b/server/src/internal/api/check/checkRouter.ts
deleted file mode 100644
index 84f9ef6f2..000000000
--- a/server/src/internal/api/check/checkRouter.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-// import { ApiVersion, ErrCode, type Feature, FeatureType } from "@autumn/shared";
-// import { Router } from "express";
-// import { StatusCodes } from "http-status-codes";
-// import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
-// import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
-// import { notNullish } from "@/utils/genUtils.js";
-// import { handleEventSent } from "../events/eventRouter.js";
-// import { getCheckData } from "./checkUtils/getCheckData.js";
-// import { getV1CheckResponse } from "./checkUtils/getV1CheckResponse.js";
-// import { getV2CheckResponse } from "./checkUtils/getV2CheckResponse.js";
-// import { getBooleanEntitledResult } from "./checkUtils.js";
-// import { getCheckPreview } from "./getCheckPreview.js";
-// import { handleProductCheck } from "./handlers/handleProductCheck.js";
-
-// export const checkRouter: Router = Router();
-
-// checkRouter.post("", async (req: any, res: any) => {
-// try {
-// const {
-// customer_id,
-// feature_id,
-// product_id,
-// required_quantity,
-// required_balance,
-// customer_data,
-// send_event,
-// event_data,
-// entity_id,
-// } = req.body;
-
-// const { logger, db } = req;
-
-// if (!customer_id) {
-// throw new RecaseError({
-// message: "`customer_id` is required",
-// code: ErrCode.InvalidRequest,
-// statusCode: StatusCodes.BAD_REQUEST,
-// });
-// }
-
-// if (!feature_id && !product_id) {
-// throw new RecaseError({
-// message: "`feature_id` or `product_id` is required",
-// code: ErrCode.InvalidRequest,
-// statusCode: StatusCodes.BAD_REQUEST,
-// });
-// }
-
-// if (feature_id && product_id) {
-// throw new RecaseError({
-// message:
-// "Provide either feature_id or product_id. Not allowed to provide both",
-// code: ErrCode.InvalidRequest,
-// statusCode: StatusCodes.BAD_REQUEST,
-// });
-// }
-
-// if (product_id) {
-// const result = await handleProductCheck({
-// ctx: req as AutumnContext,
-// body: req.body,
-// });
-// return res.status(200).json(result);
-// }
-
-// const requiredBalance = notNullish(required_balance)
-// ? required_balance
-// : notNullish(required_quantity)
-// ? required_quantity
-// : null;
-
-// let quantity = 1;
-// if (notNullish(requiredBalance)) {
-// const floatQuantity = parseFloat(requiredBalance);
-
-// if (Number.isNaN(floatQuantity)) {
-// throw new RecaseError({
-// message: "Invalid required_balance",
-// code: ErrCode.InvalidRequest,
-// statusCode: StatusCodes.BAD_REQUEST,
-// });
-// }
-// quantity = floatQuantity;
-// }
-
-// const {
-// fullCus,
-// cusEnts,
-// feature,
-// creditSystems,
-// org,
-// cusProducts,
-// allFeatures,
-// } = await getCheckData({ req });
-
-// // 2. If boolean, return true
-// if (feature.type === FeatureType.Boolean) {
-// return await getBooleanEntitledResult({
-// db,
-// fullCus,
-// res,
-// cusEnts,
-// feature,
-// apiVersion: req.apiVersion,
-// withPreview: req.body.with_preview,
-// cusProducts,
-// allFeatures,
-// });
-// }
-
-// const v1Response = getV1CheckResponse({
-// originalFeature: feature,
-// creditSystems,
-// cusEnts: cusEnts!,
-// quantity,
-// entityId: entity_id,
-// org,
-// });
-
-// const v2Response = await getV2CheckResponse({
-// fullCus,
-// cusEnts,
-// feature,
-// creditSystems,
-// org,
-// cusProducts,
-// requiredBalance,
-// apiVersion: req.apiVersion,
-// });
-
-// const { allowed, balance } = v2Response;
-// const featureToUse = allFeatures.find(
-// (f: Feature) => f.id === v2Response.feature_id,
-// );
-
-// if (allowed && req.isPublic !== true) {
-// if (send_event) {
-// await handleEventSent({
-// req: {
-// ...req,
-// body: {
-// ...req.body,
-// value: quantity,
-// },
-// },
-// customer_id: customer_id,
-// customer_data: customer_data,
-// event_data: {
-// customer_id: customer_id,
-// feature_id: feature_id,
-// value: quantity,
-// entity_id: entity_id,
-// },
-// });
-// } else if (notNullish(event_data)) {
-// await handleEventSent({
-// req,
-// customer_id: customer_id,
-// customer_data: customer_data,
-// event_data: {
-// customer_id: customer_id,
-// feature_id: feature_id,
-// ...event_data,
-// },
-// });
-// }
-// }
-
-// let preview;
-// if (req.body.with_preview) {
-// try {
-// preview = await getCheckPreview({
-// db,
-// allowed,
-// balance: notNullish(balance) ? balance : undefined,
-// feature: featureToUse!,
-// cusProducts,
-// allFeatures,
-// });
-// } catch (error) {
-// logger.error("Failed to get check preview", error);
-// console.error(error);
-// }
-// }
-
-// if (req.apiVersion.gte(ApiVersion.V1_1)) {
-// res.status(200).json({
-// ...v2Response,
-// preview,
-// });
-// } else {
-// res.status(200).json({
-// ...v1Response,
-// preview,
-// });
-// }
-
-// return;
-// } catch (error) {
-// handleRequestError({ req, error, res, action: "Failed to GET entitled" });
-// }
-// });
-
-// // let features = [feature, ...creditSystems];
-// // let balanceObj: any, featureToUse: any;
-// // try {
-// // balanceObj = balances.length > 0 ? balances[0] : null;
-
-// // featureToUse =
-// // notNullish(balanceObj) && balanceObj.feature_id !== feature.id
-// // ? features.find((f) => f.id === balanceObj.feature_id)
-// // : creditSystems.length > 0
-// // ? creditSystems[0]
-// // : feature;
-// // } catch (error) {
-// // logger.error(`/check: failed to get balance & feature to use`, error);
-// // }
-
-// // 3. If with preview, get preview
diff --git a/server/src/internal/api/entities/entityUtils.ts b/server/src/internal/api/entities/entityUtils.ts
deleted file mode 100644
index bee92bb4f..000000000
--- a/server/src/internal/api/entities/entityUtils.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-import {
- type AppEnv,
- BillingType,
- type Customer,
- type Entity,
- EntityExpand,
- ErrCode,
- type Feature,
- type FullCustomerEntitlement,
- type FullCustomerPrice,
- type Organization,
- type UsagePriceConfig,
-} from "@autumn/shared";
-import { StatusCodes } from "http-status-codes";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import { submitUsageToStripe } from "@/external/stripe/stripeMeterUtils.js";
-import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
-import {
- getBillingType,
- roundUsage,
-} from "@/internal/products/prices/priceUtils.js";
-import RecaseError from "@/utils/errorUtils.js";
-import { notNullish } from "@/utils/genUtils.js";
-
-export const getLinkedCusEnt = ({
- linkedFeature,
- cusEnts,
-}: {
- linkedFeature: any;
- cusEnts: any;
-}) => {
- // Get linked cus ent...
- const linkedCusEnt = cusEnts.find(
- (e: any) => e.entitlement.feature.id === linkedFeature.id,
- );
-
- if (!linkedCusEnt) {
- return null;
- }
-
- return linkedCusEnt;
-};
-
-export const entityFeatureIdExists = ({
- cusEnt,
-}: {
- cusEnt: FullCustomerEntitlement;
-}) => {
- const ent = cusEnt.entitlement;
- return notNullish(ent.entity_feature_id);
-};
-
-export const entityMatchesFeature = ({
- feature,
- entity,
-}: {
- feature: Feature;
- entity: Entity;
-}) => {
- return feature.id === entity.feature_id;
-};
-
-export const isLinkedToEntity = ({
- cusEnt,
- entity,
-}: {
- cusEnt: FullCustomerEntitlement;
- entity: Entity;
-}) => {
- return cusEnt.entitlement.entity_feature_id === entity.feature_id;
-};
-
-export const removeEntityFromCusEnt = async ({
- db,
- cusEnt,
- entity,
- logger,
- cusPrice,
- customer,
- org,
- env,
-}: {
- db: DrizzleCli;
- cusEnt: FullCustomerEntitlement;
- entity: Entity;
- logger: any;
- cusPrice?: FullCustomerPrice;
- customer: Customer;
- org: Organization;
- env: AppEnv;
-}) => {
- // isLinked
- const isLinked = isLinkedToEntity({
- cusEnt,
- entity,
- });
-
- if (!isLinked) {
- return;
- }
-
- const entitlement = cusEnt.entitlement;
- console.log(
- `Linked cus ent: ${entitlement.feature.id}, isLinked: ${isLinked}`,
- );
-
- // Delete cus ent ids
- const newEntities = structuredClone(cusEnt.entities!);
-
- // TODO: Send usage to stripe if cus price exists
- const stripeCli = createStripeCli({
- org,
- env,
- });
- if (cusPrice) {
- const config = cusPrice.price.config as UsagePriceConfig;
- const billingType = getBillingType(config);
- if (billingType === BillingType.UsageInArrear) {
- let usage = -newEntities[entity.id]?.balance;
-
- usage = roundUsage({
- usage,
- billingUnits: config.billing_units!,
- });
-
- await submitUsageToStripe({
- price: cusPrice.price,
- usage,
- customer,
- feature: entitlement.feature,
- logger,
- stripeCli,
- });
- }
- }
-
- delete newEntities[entity.id];
-
- await CusEntService.update({
- db,
- id: cusEnt.id,
- updates: {
- entities: newEntities,
- },
- });
-
- logger.info(
- `Feature: ${entitlement.feature.id}, customer: ${cusEnt.customer_id}, deleted entities from cus ent`,
- );
-};
-
-export const parseEntityExpand = (expand: string): EntityExpand[] => {
- if (expand) {
- const options = expand.split(",");
- const result: EntityExpand[] = [];
- for (const option of options) {
- if (!Object.values(EntityExpand).includes(option as EntityExpand)) {
- throw new RecaseError({
- message: `Invalid expand option: ${option}`,
- code: ErrCode.InvalidExpand,
- statusCode: StatusCodes.BAD_REQUEST,
- });
- }
- result.push(option as EntityExpand);
- }
- return result;
- } else {
- return [];
- }
-};
diff --git a/server/src/internal/api/events/eventUtils.ts b/server/src/internal/api/events/eventUtils.ts
deleted file mode 100644
index 776dcb1a8..000000000
--- a/server/src/internal/api/events/eventUtils.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { ErrCode } from "@autumn/shared";
-import { differenceInMonths, differenceInYears } from "date-fns";
-import { StatusCodes } from "http-status-codes";
-import RecaseError from "@/utils/errorUtils.js";
-
-export const getEventTimestamp = (timestamp?: number | null) => {
- // 1. If timestamp is not provided, return now
- if (!timestamp) {
- return new Date();
- }
-
- try {
- const date = new Date(timestamp);
-
- if (differenceInYears(new Date(), date) >= 2) {
- throw new RecaseError({
- message: "Timestamp must be within the last 2 years",
- code: ErrCode.InvalidInputs,
- statusCode: StatusCodes.BAD_REQUEST,
- });
- } else if (differenceInMonths(new Date(), date) <= -1) {
- throw new RecaseError({
- message: "Timestamp can only be up to 1 month in the future",
- code: ErrCode.InvalidInputs,
- statusCode: StatusCodes.BAD_REQUEST,
- });
- }
-
- return date;
- } catch (error) {
- if (error instanceof RecaseError) {
- throw error;
- }
-
- throw new RecaseError({
- message: "Invalid timestamp",
- code: ErrCode.InvalidInputs,
- data: error,
- });
- }
-};
diff --git a/server/src/internal/balances/track/deductUtils/deductFromMainBalance/deductMainBalanceFromAllEntities.ts b/server/src/internal/balances/track/deductUtils/deductFromMainBalance/deductMainBalanceFromAllEntities.ts
deleted file mode 100644
index fe3a0e909..000000000
--- a/server/src/internal/balances/track/deductUtils/deductFromMainBalance/deductMainBalanceFromAllEntities.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-// // =============================================================================
-// // CASE 1: Deduct from ALL entities
-// // =============================================================================
-// const deductFromAllEntities = ({
-// currentBalance,
-// currentEntities,
-// currentAdjustment,
-// amountToDeduct,
-// creditCost,
-// allowNegative,
-// alterGrantedBalance,
-// }: {
-// currentBalance: number;
-// currentEntities: Record | null;
-// currentAdjustment: number;
-// amountToDeduct: number;
-// creditCost: number;
-// allowNegative: boolean;
-// alterGrantedBalance: boolean;
-// }): DeductFromMainBalanceResult => {
-// let remaining = new Decimal(amountToDeduct).mul(creditCost).toNumber();
-// let totalDeducted = 0;
-// const newEntities: Record = structuredClone(
-// currentEntities ?? {},
-// );
-
-// // Sort entity keys for consistency (matches SQL ORDER BY)
-// const sortedEntityKeys = Object.keys(newEntities).sort();
-
-// for (const entityKey of sortedEntityKeys) {
-// if (remaining === 0) break;
-
-// const entityBalance = newEntities[entityKey]?.balance ?? 0;
-// const entityAdjustment = newEntities[entityKey]?.adjustment ?? 0;
-
-// const { deducted, newBalance, newAdjustment } = calculateDeduction({
-// currentBalance: entityBalance,
-// currentAdjustment: entityAdjustment,
-// amountToDeduct: remaining,
-// allowNegative,
-// alterGrantedBalance,
-// });
-
-// if (deducted !== 0) {
-// newEntities[entityKey] = {
-// ...newEntities[entityKey],
-// balance: newBalance,
-// adjustment: newAdjustment,
-// };
-
-// remaining = new Decimal(remaining).sub(deducted).toNumber();
-// totalDeducted = new Decimal(totalDeducted).add(deducted).toNumber();
-// }
-// }
-
-// return {
-// deducted: totalDeducted,
-// newBalance: currentBalance, // Top-level balance unchanged for entity-scoped
-// newEntities,
-// newAdjustment: currentAdjustment, // Top-level adjustment unchanged
-// remaining: new Decimal(remaining).div(creditCost).toNumber(),
-// };
-// };
diff --git a/server/src/internal/balances/track/deductUtils/deductFromMainBalance/deductMainBalanceFromTopLevel.ts b/server/src/internal/balances/track/deductUtils/deductFromMainBalance/deductMainBalanceFromTopLevel.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/internal/balances/utils/sync/SyncBatchingManager.ts b/server/src/internal/balances/utils/sync/SyncBatchingManager.ts
deleted file mode 100644
index fbffc2cc6..000000000
--- a/server/src/internal/balances/utils/sync/SyncBatchingManager.ts
+++ /dev/null
@@ -1,225 +0,0 @@
-import type { AppEnv } from "@autumn/shared";
-import { currentRegion } from "@/external/redis/initRedis.js";
-import { JobName } from "@/queue/JobName.js";
-import { addTaskToQueue } from "@/queue/queueUtils.js";
-import { logger } from "../../../../external/logtail/logtailUtils";
-
-const hashPairKey = (key: string): string => {
- return Bun.hash(key).toString(36);
-};
-
-interface SyncPairContext {
- customerId: string;
- featureId: string;
- orgId: string;
- env: AppEnv;
- entityId?: string;
- region: string;
- timestamp: number;
- breakdownIds: string[];
-}
-
-interface CustomerBatch {
- pairs: Map;
- timer: NodeJS.Timeout | null;
-}
-
-/**
- * Batching manager for syncing Redis balance deductions to PostgreSQL
- * Collects sync items within a time window, then queues each item individually to SQS
- *
- * Benefits:
- * - In-memory deduplication: Same pair only queued once per batch window (500ms)
- * - SQS deduplication: MessageDeduplicationId prevents duplicate processing (5 min window)
- * - Per-customer FIFO ordering: MessageGroupId ensures ordered processing per customer
- * - Non-blocking: Track endpoint returns immediately
- * - Simple: Each sync item is a separate SQS message, easy to retry and monitor
- */
-export class SyncBatchingManager {
- // Map of customerId -> batch
- private customerBatches: Map = new Map();
-
- private readonly BATCH_WINDOW_MS =
- process.env.NODE_ENV === "development" ? 500 : 1000; // 1000ms batching window
- private readonly MAX_BATCH_SIZE_PER_CUSTOMER = 1000; // Max unique pairs per customer batch
-
- /**
- * Add a (customerId, featureId) pair to the sync batch
- * Idempotent - multiple calls for same pair only result in one sync
- */
- addSyncPair({
- customerId,
- featureId,
- orgId,
- env,
- entityId,
- region,
- breakdownIds,
- }: Omit): void {
- // Get or create batch for this customer
- let customerBatch = this.customerBatches.get(customerId);
- if (!customerBatch) {
- customerBatch = {
- pairs: new Map(),
- timer: null,
- };
- this.customerBatches.set(customerId, customerBatch);
- }
-
- // Create unique key for this pair (within customer scope)
- const rawKey = `${orgId}:${env}:${featureId}${entityId ? `:${entityId}` : ""}`;
- const pairKey = hashPairKey(rawKey);
-
- // If this is the first pair for this customer, schedule batch execution
- if (customerBatch.pairs.size === 0) {
- this.scheduleCustomerBatch({ customerId });
- }
-
- // Add or update pair (Map handles deduplication)
- // Use the earliest timestamp if the pair already exists, otherwise use current time
-
- const existingPair = customerBatch.pairs.get(pairKey);
- customerBatch.pairs.set(pairKey, {
- customerId,
- featureId,
- orgId,
- env,
- entityId,
- region: region || currentRegion,
- timestamp: existingPair?.timestamp ?? Date.now(),
- breakdownIds: existingPair
- ? [...new Set([...existingPair.breakdownIds, ...breakdownIds])]
- : breakdownIds,
- });
-
- // Force flush if batch is full
- if (customerBatch.pairs.size >= this.MAX_BATCH_SIZE_PER_CUSTOMER) {
- this.executeCustomerBatch({ customerId });
- }
- }
-
- /**
- * Schedule batch execution for a specific customer after window expires
- */
- private scheduleCustomerBatch({ customerId }: { customerId: string }): void {
- const customerBatch = this.customerBatches.get(customerId);
- if (!customerBatch) return;
-
- customerBatch.timer = setTimeout(() => {
- this.executeCustomerBatch({ customerId });
- }, this.BATCH_WINDOW_MS);
- }
-
- /**
- * Execute the batch for a specific customer - flush to SQS
- * Queues each sync item individually with deduplication
- */
- private async executeCustomerBatch({
- customerId,
- }: {
- customerId: string;
- }): Promise {
- const customerBatch = this.customerBatches.get(customerId);
- if (!customerBatch) return;
-
- // Clear timer
- if (customerBatch.timer) {
- clearTimeout(customerBatch.timer);
- customerBatch.timer = null;
- }
-
- // Snapshot current batch and remove from map
- const currentPairs = customerBatch.pairs;
- this.customerBatches.delete(customerId);
-
- if (currentPairs.size === 0) {
- return;
- }
-
- try {
- // Queue each sync item individually with deduplication
- const items = Array.from(currentPairs.values());
-
- for (const item of items) {
- // Create deterministic deduplication key from sync item fields
- const dedupKey = item.entityId
- ? `${item.orgId}:${item.env}:${item.customerId}:${item.featureId}:${item.entityId}`
- : `${item.orgId}:${item.env}:${item.customerId}:${item.featureId}`;
-
- // Deduplicate messages in a 10ms window
- const dedupTimestamp = Math.floor(Date.now() / 10);
-
- // Hash to create AWS-friendly alphanumeric ID
- const dedupHash = Bun.hash(`${dedupKey}:${dedupTimestamp}`).toString(
- 36,
- );
-
- await addTaskToQueue({
- jobName: JobName.SyncBalanceBatchV2,
- payload: {
- orgId: item.orgId,
- env: item.env,
- customerId: item.customerId,
- item, // Single item
- },
- messageGroupId: customerId, // FIFO ordering per customer
- messageDeduplicationId: dedupHash, // SQS deduplication (1-second window)
- });
- logger.info(
- `Queued sync item for customer ${customerId}, feature: ${item.featureId}${item.entityId ? `, entity: ${item.entityId}` : ""}`,
- );
- }
- } catch (error) {
- logger.error(
- `❌ Failed to queue sync items for customer ${customerId}, error: ${error instanceof Error ? error.message : "unknown"}`,
- {
- error: error instanceof Error ? error : new Error(String(error)),
- customerId,
- },
- );
- console.error(
- `❌ Failed to queue sync items for customer ${customerId}:`,
- error,
- );
- // TODO: Consider retry logic or dead letter queue
- }
- }
-
- /**
- * Get current batch statistics (for monitoring)
- */
- getStats(): {
- totalCustomers: number;
- totalPendingPairs: number;
- activeTimers: number;
- } {
- let totalPairs = 0;
- let activeTimers = 0;
-
- for (const batch of this.customerBatches.values()) {
- totalPairs += batch.pairs.size;
- if (batch.timer !== null) activeTimers++;
- }
-
- return {
- totalCustomers: this.customerBatches.size,
- totalPendingPairs: totalPairs,
- activeTimers,
- };
- }
-
- /**
- * Force flush all customer batches (useful for graceful shutdown)
- */
- async flush(): Promise {
- const customerIds = Array.from(this.customerBatches.keys());
- await Promise.all(
- customerIds.map((customerId) =>
- this.executeCustomerBatch({ customerId }),
- ),
- );
- }
-}
-
-// Singleton instance
-export const globalSyncBatchingManager = new SyncBatchingManager();
diff --git a/server/src/internal/billing/v2/execute/executeAutumnActions/updateCustomerProductOptions.ts b/server/src/internal/billing/v2/execute/executeAutumnActions/updateCustomerProductOptions.ts
deleted file mode 100644
index e073ef33b..000000000
--- a/server/src/internal/billing/v2/execute/executeAutumnActions/updateCustomerProductOptions.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { FeatureOptions } from "@autumn/shared";
-import type { AutumnContext } from "@/honoUtils/HonoEnv";
-import { CusProductService } from "@/internal/customers/cusProducts/CusProductService";
-
-/**
- * Update customer product options with new feature quantities.
- *
- * Extracted from:
- * - updateQuantityFlow.ts:55-59
- */
-export const updateCustomerProductOptions = async ({
- ctx,
- customerProductId,
- updatedFeatureOptions,
-}: {
- ctx: AutumnContext;
- customerProductId: string;
- updatedFeatureOptions: FeatureOptions[];
-}) => {
- const { db } = ctx;
-
- await CusProductService.update({
- db,
- cusProductId: customerProductId,
- updates: { options: updatedFeatureOptions },
- });
-};
diff --git a/server/src/internal/billing/v2/providers/stripe/logs/logStripeExecution.ts b/server/src/internal/billing/v2/providers/stripe/logs/logStripeExecution.ts
deleted file mode 100644
index c9794a927..000000000
--- a/server/src/internal/billing/v2/providers/stripe/logs/logStripeExecution.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { AutumnContext } from "@/honoUtils/HonoEnv";
-import type { StripeBillingPlanResult } from "@/internal/billing/v2/types/billingResult";
-import { addToExtraLogs } from "@/utils/logging/addToExtraLogs";
-
-export const logStripeExecution = ({
- ctx,
- result,
- stage,
-}: {
- ctx: AutumnContext;
- result: StripeBillingPlanResult;
- stage: "invoice" | "subscription" | "complete";
-}) => {
- addToExtraLogs({
- ctx,
- extras: {
- stripeExecution: {
- stage,
- stripeSubscriptionId: result.stripeSubscription?.id ?? "undefined",
- stripeInvoiceId: result.stripeInvoice?.id ?? "undefined",
- stripeInvoiceStatus: result.stripeInvoice?.status ?? "undefined",
- requiredAction: result.requiredAction ?? "undefined",
- deferred: result.deferred ?? false,
- },
- },
- });
-};
diff --git a/server/src/internal/billing/v2/providers/stripe/utils/subscriptions/stripeSubscriptionOps.ts b/server/src/internal/billing/v2/providers/stripe/utils/subscriptions/stripeSubscriptionOps.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/internal/billing/v2/typesOld.ts b/server/src/internal/billing/v2/typesOld.ts
deleted file mode 100644
index d88cd1f12..000000000
--- a/server/src/internal/billing/v2/typesOld.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import type {
- AttachBodyV1,
- FreeTrial,
- FullCusProduct,
- FullCustomer,
- FullCustomerPrice,
- FullProduct,
- LineItem,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import type { StripeInvoiceAction } from "./types/billingPlan";
-
-export type AttachContext = {
- fullCus: FullCustomer;
- products: FullProduct[];
- freeTrial?: FreeTrial;
-
- // Stripe context
- stripeSub?: Stripe.Subscription;
- stripeCus: Stripe.Customer;
- paymentMethod?: Stripe.PaymentMethod;
- testClockFrozenTime?: number;
-
- body: AttachBodyV1;
-};
-
-export type StripeSubAction = {
- type:
- | "create"
- | "update"
- | "cancel_immediately"
- | "cancel_at_period_end"
- | "none";
- subId?: string;
- items?: Stripe.SubscriptionUpdateParams.Item[];
-};
-
-export type StripeCheckoutAction = {
- shouldCreate: boolean;
- reason?: string;
- params: Stripe.Checkout.SessionCreateParams;
-};
-
-export type UpdateOneOffAction = {
- targetCusProduct: FullCusProduct;
-};
-
-export type AttachPlan = {
- lineItems: LineItem[];
-
- // 1. Autumn actions
-
- updateOneOffAction?: UpdateOneOffAction;
- newCusProducts: FullCusProduct[];
-
- // 2. Checkout session?
- stripeCheckoutAction: StripeCheckoutAction;
- stripeSubAction: StripeSubAction;
- stripeInvoiceAction?: StripeInvoiceAction;
-};
-
-export type BillingPlan = {
- intent: "attach" | "update_quantity" | "update_plan" | "cancel" | "one_off";
-};
-
-export type SubscriptionUpdateInvoiceAction = {
- shouldCreateInvoice: boolean;
- invoiceItems: {
- description: string;
- amountDollars: number;
- stripePriceId: string;
- periodStartEpochMs: number;
- periodEndEpochMs: number;
- }[];
- shouldChargeImmediately: boolean;
- paymentMethod?: Stripe.PaymentMethod;
- customerPrices: FullCustomerPrice[];
-};
diff --git a/server/src/internal/billing/v2/updateSubscription/compute/computeOneOffLineItems.ts b/server/src/internal/billing/v2/updateSubscription/compute/computeOneOffLineItems.ts
deleted file mode 100644
index bb580fc8f..000000000
--- a/server/src/internal/billing/v2/updateSubscription/compute/computeOneOffLineItems.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { cp, isCustomerProductFree } from "@autumn/shared";
-import type { UpdateSubscriptionBillingContext } from "@/internal/billing/v2/billingContext";
-import type { AutumnBillingPlan } from "@/internal/billing/v2/types/autumnBillingPlan";
-
-export const computeOneOffLineItems = ({
- billingContext,
- autumnBillingPlan,
-}: {
- billingContext: UpdateSubscriptionBillingContext;
- autumnBillingPlan: AutumnBillingPlan;
-}) => {
- const { insertCustomerProducts } = autumnBillingPlan;
- const newCustomerProduct = insertCustomerProducts?.[0];
- if (!newCustomerProduct) return [];
-
- // Only allow one off items if going from free -> paid
- const currentIsFree = isCustomerProductFree(billingContext.customerProduct);
- const { valid: newIsPaidRecurring } = cp(newCustomerProduct)
- .paid()
- .recurring();
-
- const includeOneOffItems = currentIsFree && newIsPaidRecurring;
-
- if (!includeOneOffItems) return [];
-
- // const newOneOffItems = cusProductToLineItems({
- // cusProduct: newCustomerProduct,
- // nowMs: billingContext.currentEpochMs,
- // billingCycleAnchorMs: billingContext.billingCycleAnchorMs,
- // direction: "charge",
- // org: billingContext.org,
- // logger: billingContext.logger,
- // });
-};
diff --git a/server/src/internal/customers/actions/createWithDefaults/finalize/finalizeCreateCustomer.ts b/server/src/internal/customers/actions/createWithDefaults/finalize/finalizeCreateCustomer.ts
deleted file mode 100644
index 72b85b847..000000000
--- a/server/src/internal/customers/actions/createWithDefaults/finalize/finalizeCreateCustomer.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { FullCustomer } from "@autumn/shared";
-import type Stripe from "stripe";
-import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
-import type { AutumnBillingPlan } from "@/internal/billing/v2/types/autumnBillingPlan.js";
-import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
-import { initSubscriptionFromStripe } from "@/internal/subscriptions/utils/initSubscriptionFromStripe.js";
-import type { CreateCustomerContext } from "../createCustomerContext.js";
-
-/**
- * Finalize customer creation after Stripe subscription is created.
- * Links subscription_ids back to customer products and builds final customer.
- */
-export const finalizeCreateCustomer = async ({
- ctx,
- context,
- autumnBillingPlan,
- stripeSubscription,
-}: {
- ctx: AutumnContext;
- context: CreateCustomerContext;
- autumnBillingPlan: AutumnBillingPlan;
- stripeSubscription: Stripe.Subscription | undefined;
-}): Promise => {
- const { fullCustomer } = context;
-
- if (!stripeSubscription) return fullCustomer;
-
- // Link subscription_ids to customer products
- for (const customerProduct of autumnBillingPlan.insertCustomerProducts) {
- await CusProductService.update({
- db: ctx.db,
- cusProductId: customerProduct.id,
- updates: { subscription_ids: customerProduct.subscription_ids },
- });
- }
-
- // Build final customer with subscription and products
- return {
- ...fullCustomer,
- subscriptions: [initSubscriptionFromStripe({ ctx, stripeSubscription })],
- customer_products: autumnBillingPlan.insertCustomerProducts,
- };
-};
diff --git a/server/src/internal/customers/attach/attachFunctions/scheduleFlow/createSubSchedule.ts b/server/src/internal/customers/attach/attachFunctions/scheduleFlow/createSubSchedule.ts
deleted file mode 100644
index 4cab1afb9..000000000
--- a/server/src/internal/customers/attach/attachFunctions/scheduleFlow/createSubSchedule.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import type Stripe from "stripe";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { sanitizeSubItems } from "@/external/stripe/stripeSubUtils/getStripeSubItems.js";
-import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
-import { SubService } from "@/internal/subscriptions/SubService.js";
-import { generateId } from "@/utils/genUtils.js";
-import type { ItemSet } from "@/utils/models/ItemSet.js";
-
-export const createSubSchedule = async ({
- db,
- attachParams,
- itemSet,
- endOfBillingPeriod,
-}: {
- db: DrizzleCli;
- attachParams: AttachParams;
- itemSet: ItemSet;
- endOfBillingPeriod: number;
-}) => {
- const { org, customer, paymentMethod } = attachParams;
-
- const { stripeCli } = attachParams;
-
- // let subItems = items.filter(
- // (item: any, index: number) =>
- // index >= prices.length ||
- // prices[index].config!.interval !== BillingInterval.OneOff
- // );
- // let oneOffItems = items.filter(
- // (item: any, index: number) =>
- // index < prices.length &&
- // prices[index].config!.interval === BillingInterval.OneOff
- // );
- const { subItems, invoiceItems, usageFeatures } = itemSet;
-
- const newSubscriptionSchedule = await stripeCli.subscriptionSchedules.create({
- customer: customer.processor.id,
- start_date: endOfBillingPeriod,
- billing_mode: { type: "flexible" },
- phases: [
- {
- items: sanitizeSubItems(subItems),
- default_payment_method: paymentMethod?.id,
- add_invoice_items:
- invoiceItems as Stripe.SubscriptionScheduleCreateParams.Phase.AddInvoiceItem[],
- },
- ],
- });
-
- await SubService.createSub({
- db,
- sub: {
- id: generateId("sub"),
- stripe_id: null,
- stripe_schedule_id: newSubscriptionSchedule.id,
- created_at: Date.now(),
- usage_features: usageFeatures,
- org_id: org.id,
- env: customer.env,
- current_period_start: null,
- current_period_end: null,
- },
- });
-
- return newSubscriptionSchedule;
-};
diff --git a/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoice.ts b/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoice.ts
deleted file mode 100644
index 325608744..000000000
--- a/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoice.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import type { FullCusProduct } from "@autumn/shared";
-import type Stripe from "stripe";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { payForInvoice } from "@/external/stripe/stripeInvoiceUtils.js";
-import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
-import { insertInvoiceFromAttach } from "@/internal/invoices/invoiceUtils.js";
-import {
- createUsageInvoiceItems,
- resetUsageBalances,
-} from "./createUsageInvoiceItems.js";
-
-export const createUsageInvoice = async ({
- db,
- attachParams,
- cusProduct,
- sub,
- logger,
-}: {
- db: DrizzleCli;
- attachParams: AttachParams;
- cusProduct: FullCusProduct;
- sub: Stripe.Subscription;
- logger: any;
-}) => {
- const { stripeCli, paymentMethod } = attachParams;
- const customer = cusProduct.customer!;
- const invoice = await stripeCli.invoices.create({
- customer: customer.processor.id,
- auto_advance: false,
- });
-
- const { cusEntIds } = await createUsageInvoiceItems({
- db,
- attachParams,
- cusProduct,
- sub,
- invoiceId: invoice.id,
- logger,
- });
-
- await stripeCli.invoices.finalizeInvoice(invoice.id!, {
- auto_advance: false,
- });
-
- const {
- paid,
- error,
- invoice: latestInvoice,
- } = await payForInvoice({
- stripeCli,
- paymentMethod,
- invoiceId: invoice.id!,
- logger,
- errorOnFail: false,
- });
-
- if (latestInvoice) {
- await resetUsageBalances({
- db,
- cusEntIds,
- cusProduct,
- });
-
- await insertInvoiceFromAttach({
- db,
- invoiceId: latestInvoice.id,
- attachParams,
- logger,
- });
- }
-
- if (!paid) {
- logger.error(`sub.deleted, failed to pay invoice: ${invoice.id}`, {
- error,
- });
- }
-
- return invoice;
-};
diff --git a/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/upgradeUtils.ts b/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/upgradeUtils.ts
deleted file mode 100644
index 4cde37e5a..000000000
--- a/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/upgradeUtils.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type Stripe from "stripe";
-import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
-
-export const getNextCycle = (stripeSubs: Stripe.Subscription[]) => {
- const { end } = subToPeriodStartEnd({ sub: stripeSubs[0] });
- const nextCycle = end * 1000;
- return nextCycle;
-};
diff --git a/server/src/internal/customers/attach/attachFunctions/upgradeSameIntFlow/comparePriceEntIds.ts b/server/src/internal/customers/attach/attachFunctions/upgradeSameIntFlow/comparePriceEntIds.ts
deleted file mode 100644
index 9c08da624..000000000
--- a/server/src/internal/customers/attach/attachFunctions/upgradeSameIntFlow/comparePriceEntIds.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { EntitlementWithFeature, Price } from "@autumn/shared";
-
-export const hasPriceIdsChanged = ({
- oldPrices,
- newPrices,
-}: {
- oldPrices: Price[];
- newPrices: Price[];
-}) => {
- for (const price of oldPrices) {
- if (!newPrices.some((p) => p.id === price.id)) {
- return true;
- }
- }
-
- for (const price of newPrices) {
- if (!oldPrices.some((p) => p.id === price.id)) {
- return true;
- }
- }
-
- return false;
-};
-
-export const hasEntIdsChanged = ({
- oldEntitlements,
- newEntitlements,
-}: {
- oldEntitlements: EntitlementWithFeature[];
- newEntitlements: EntitlementWithFeature[];
-}) => {
- for (const entitlement of oldEntitlements) {
- if (!newEntitlements.some((e) => e.id === entitlement.id)) {
- return true;
- }
- }
-
- for (const entitlement of newEntitlements) {
- if (!oldEntitlements.some((e) => e.id === entitlement.id)) {
- return true;
- }
- }
-
- return false;
-};
diff --git a/server/src/internal/customers/attach/attachPreviewUtils/priceToNewPreviewItem.ts b/server/src/internal/customers/attach/attachPreviewUtils/priceToNewPreviewItem.ts
deleted file mode 100644
index d25fc7f2a..000000000
--- a/server/src/internal/customers/attach/attachPreviewUtils/priceToNewPreviewItem.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import {
- type EntitlementWithFeature,
- type FullProduct,
- formatAmount,
- isFixedPrice,
- isOneOffPrice,
- type Organization,
- type Price,
- priceToInvoiceAmount,
- type Reward,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import { newPriceToInvoiceDescription } from "@/internal/invoices/invoiceFormatUtils.js";
-import { getProration } from "@/internal/invoices/previewItemUtils/getItemsForNewProduct.js";
-import { priceToUsageModel } from "@/internal/products/prices/priceUtils/convertPrice.js";
-import { getPriceEntitlement } from "@/internal/products/prices/priceUtils.js";
-import {
- formatReward,
- getAmountAfterReward,
- getAmountAfterStripeDiscounts,
-} from "@/internal/rewards/rewardUtils.js";
-import { formatUnixToDate } from "@/utils/genUtils.js";
-
-export const priceToNewPreviewItem = ({
- org,
- price,
- entitlements,
- skipOneOff,
- now,
- anchor,
- productQuantity = 1,
- product,
- onTrial,
- rewards,
- subDiscounts,
-}: {
- org: Organization;
- price: Price;
- entitlements: EntitlementWithFeature[];
- skipOneOff?: boolean;
- now?: number;
- anchor?: number;
- productQuantity?: number;
- product: FullProduct;
- onTrial?: boolean;
- rewards?: Reward[];
- subDiscounts?: Stripe.Discount[];
-}) => {
- if (skipOneOff && isOneOffPrice(price)) return;
-
- now = now ?? Date.now();
-
- const ent = getPriceEntitlement(price, entitlements);
-
- const finalProration = getProration({
- anchor,
- now,
- intervalConfig: {
- interval: price.config.interval!,
- intervalCount: price.config.interval_count || 1,
- },
- });
-
- const applyRewards = rewards?.filter(
- (r) =>
- r.discount_config?.price_ids?.includes(price.id) ||
- r.discount_config?.apply_to_all,
- );
-
- for (const reward of applyRewards ?? []) {
- console.log("Apply Reward", formatReward({ reward }));
- }
-
- if (isFixedPrice(price)) {
- let amount = priceToInvoiceAmount({
- price,
- quantity: 1,
- proration: finalProration,
- productQuantity,
- now,
- });
-
- if (onTrial) {
- amount = 0;
- }
-
- for (const reward of applyRewards ?? []) {
- amount = getAmountAfterReward({
- amount,
- reward,
- subDiscounts: subDiscounts ?? [],
- currency: org.default_currency || undefined,
- });
- }
-
- amount = getAmountAfterStripeDiscounts({
- price,
- amount,
- product,
- stripeDiscounts: subDiscounts ?? [],
- currency: org.default_currency || undefined,
- });
-
- let description = newPriceToInvoiceDescription({
- org,
- price,
- product,
- });
-
- if (productQuantity > 1) {
- description = `${description} x ${productQuantity}`;
- }
-
- if (finalProration) {
- description = `${description} (from ${formatUnixToDate(now)})`;
- }
-
- return {
- price_id: price.id,
- price: formatAmount({ org, amount }),
- description,
- amount,
- usage_model: priceToUsageModel(price),
- feature_id: ent?.feature_id,
- };
- }
-};
diff --git a/server/src/internal/customers/attach/handleAttachPreview/attachPreviewUtils.ts b/server/src/internal/customers/attach/handleAttachPreview/attachPreviewUtils.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/internal/customers/attach/handleAttachPreview/getMultiAttachPreview.ts b/server/src/internal/customers/attach/handleAttachPreview/getMultiAttachPreview.ts
deleted file mode 100644
index b4b51fe46..000000000
--- a/server/src/internal/customers/attach/handleAttachPreview/getMultiAttachPreview.ts
+++ /dev/null
@@ -1,159 +0,0 @@
-import {
- type AttachBodyV0,
- type AttachBranch,
- type AttachConfig,
- cusProductsToPrices,
- type PreviewLineItem,
-} from "@autumn/shared";
-import { Decimal } from "decimal.js";
-import type Stripe from "stripe";
-import { getEarliestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
-import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
-import { notNullish } from "@/utils/genUtils.js";
-import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
-import type { AttachParams } from "../../cusProducts/AttachParams.js";
-import { getAddAndRemoveProducts } from "../attachFunctions/multiAttach/getAddAndRemoveProducts.js";
-import { priceToNewPreviewItem } from "../attachPreviewUtils/priceToNewPreviewItem.js";
-import { priceToUnusedPreviewItem } from "../attachPreviewUtils/priceToUnusedPreviewItem.js";
-import { getCustomerSub } from "../attachUtils/convertAttachParams.js";
-import { handleMultiAttachErrors } from "../attachUtils/handleAttachErrors/handleMultiAttachErrors.js";
-
-export const getMultiAttachPreview = async ({
- // biome-ignore lint/correctness/noUnusedFunctionParameters: might be used in the future
- ctx,
- attachBody,
- attachParams,
- config,
- branch,
-}: {
- ctx: AutumnContext;
- attachBody: AttachBodyV0;
- attachParams: AttachParams;
- config: AttachConfig;
- branch: AttachBranch;
-}) => {
- await handleMultiAttachErrors({ attachParams, attachBody, branch });
-
- const { customer } = attachParams;
- const cusProducts = customer.customer_products;
- const { sub } = await getCustomerSub({ attachParams });
-
- const items: PreviewLineItem[] = [];
- const subItems = sub?.items.data || [];
-
- // 1. Get remove cus products...
- const { expireCusProducts } = await getAddAndRemoveProducts({
- attachParams,
- config,
- });
-
- const prices = cusProductsToPrices({ cusProducts: expireCusProducts });
-
- for (const price of prices) {
- const cusProduct = cusProducts.find(
- (cp) => cp.internal_product_id === price.internal_product_id,
- )!;
-
- const previewLineItem = priceToUnusedPreviewItem({
- customer,
- price,
- stripeItems: subItems,
- cusProduct,
- now: attachParams.now!,
- org: attachParams.org,
- latestInvoice: sub?.latest_invoice as Stripe.Invoice,
- subDiscounts: (sub?.discounts ?? []) as Stripe.Discount[],
- });
-
- if (!previewLineItem) continue;
-
- items.push(previewLineItem);
- }
-
- const productList = attachParams.productsList!;
- const newItems: PreviewLineItem[] = [];
- const itemsWithoutTrial: PreviewLineItem[] = [];
-
- for (const productOptions of productList) {
- const product = attachParams.products.find(
- (p) => p.id === productOptions.product_id,
- )!;
-
- // Anchor to unix...
- const anchor = sub ? sub.billing_cycle_anchor * 1000 : undefined;
- if (config.disableTrial) {
- attachParams.freeTrial = null;
- }
-
- const onTrial =
- notNullish(attachParams?.freeTrial) || sub?.status === "trialing";
-
- for (const price of product.prices) {
- const newItem = priceToNewPreviewItem({
- org: attachParams.org,
- price,
- entitlements: product.entitlements,
- skipOneOff: false,
- now: attachParams.now!,
- anchor,
- productQuantity: productOptions.quantity ?? 1,
- product,
- onTrial,
- rewards: attachParams.rewards,
- subDiscounts: (sub?.discounts ?? []) as Stripe.Discount[],
- });
-
- const noTrialItem = priceToNewPreviewItem({
- org: attachParams.org,
- price,
- entitlements: product.entitlements,
- skipOneOff: false,
- now: attachParams.now!,
- // anchorToUnix,
- productQuantity: productOptions.quantity ?? 1,
- product,
- onTrial: false,
- rewards: attachParams.rewards,
- subDiscounts: (sub?.discounts ?? []) as Stripe.Discount[],
- });
-
- if (newItem) {
- newItems.push(newItem);
- }
- if (noTrialItem) {
- itemsWithoutTrial.push(noTrialItem);
- }
- }
- }
-
- const totalDueToday = newItems.reduce(
- (acc, item) => acc + (item.amount ?? 0),
- 0,
- );
-
- const freeTrial = attachParams.freeTrial;
- let dueNextCycle;
- if (freeTrial || sub?.status === "trialing") {
- const nextCycleAt = freeTrial
- ? freeTrialToStripeTimestamp({ freeTrial, now: attachParams.now })! * 1000
- : sub
- ? getEarliestPeriodEnd({ sub }) * 1000
- : undefined;
-
- if (nextCycleAt) {
- dueNextCycle = {
- line_items: itemsWithoutTrial,
- due_at: nextCycleAt,
- };
- }
- }
-
- return {
- // items,
- due_today: {
- line_items: [...items, ...newItems],
- total: new Decimal(totalDueToday).toNumber(),
- },
- due_next_cycle: dueNextCycle,
- };
-};
diff --git a/server/src/internal/customers/attach/models/AttachBranch.ts b/server/src/internal/customers/attach/models/AttachBranch.ts
deleted file mode 100644
index 33110c097..000000000
--- a/server/src/internal/customers/attach/models/AttachBranch.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-// export enum AttachBranch {
-// MultiProduct = "multi_product",
-
-// OneOff = "one_off",
-
-// New = "new",
-// AddOn = "add_on",
-
-// // Same product
-// NewVersion = "new_version",
-// SameCustomEnts = "same_custom_ents",
-// SameCustom = "same_custom",
-// UpdatePrepaidQuantity = "update_prepaid_quantity",
-// Renew = "renew",
-
-// // Handle upgrades / downgrades
-// MainIsFree = "main_is_free",
-// MainIsTrial = "main_is_trial",
-// Upgrade = "upgrade",
-// Downgrade = "downgrade",
-// }
-
-export enum AttachFunction {
- CreateCheckout = "create_checkout",
- AddProduct = "add_product",
- UpdateEnts = "update_ents", // only update entitlements
- UpdateProduct = "update_product", // update product
- ScheduleProduct = "schedule_product",
- UpdatePrepaidQuantity = "update_prepaid_quantity",
- Renew = "renew",
-}
-
-/* Handle checkout / public error:
-1. New version, same custom, same custom ents, renew, update prepaid quantity
-
-2.
-*/
diff --git a/server/src/internal/customers/attach/models/AttachContext.ts b/server/src/internal/customers/attach/models/AttachContext.ts
deleted file mode 100644
index 1c70f0258..000000000
--- a/server/src/internal/customers/attach/models/AttachContext.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// import { ExtendedRequest } from "@/utils/models/Request.js";
-// import {
-// AppEnv,
-// Customer,
-// Feature,
-// FullProduct,
-// Organization,
-// } from "@autumn/shared";
-// import Stripe from "stripe";
-
-// export type AttachContext = {
-// req: ExtendedRequest;
-// customer: Customer;
-// products: FullProduct[];
-// paymentMethod: Stripe.PaymentMethod;
-// stripeCli: Stripe;
-
-// // More...?
-// };
diff --git a/server/src/internal/customers/attach/unifiedAttach/handleUnifiedAttach.ts b/server/src/internal/customers/attach/unifiedAttach/handleUnifiedAttach.ts
deleted file mode 100644
index 0e9842129..000000000
--- a/server/src/internal/customers/attach/unifiedAttach/handleUnifiedAttach.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { type AttachConfig, CusProductStatus } from "@autumn/shared";
-import type { AttachParams } from "../../cusProducts/AttachParams.js";
-
-export const handleUnifiedAttach = async ({
- req,
- res,
- attachParams,
- config,
-}: {
- req: any;
- res: any;
- attachParams: AttachParams;
- config: AttachConfig;
-}) => {
- // 1.
- const cusProducts = attachParams.customer.customer_products;
-
- const scheduledCusProducts = cusProducts.filter(
- (cp) => cp.status === CusProductStatus.Scheduled,
- );
-
- res.status(200).json({
- success: true,
- message: "Unified attach",
- });
-
- return;
-};
diff --git a/server/src/internal/customers/cancel/cancelEndOfCycle.ts b/server/src/internal/customers/cancel/cancelEndOfCycle.ts
deleted file mode 100644
index 681e07b1f..000000000
--- a/server/src/internal/customers/cancel/cancelEndOfCycle.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import {
- CusProductStatus,
- type FullCusProduct,
- type FullCustomer,
-} from "@autumn/shared";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import { getLatestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
-import type { ExtendedRequest } from "@/utils/models/Request.js";
-import { CusProductService } from "../cusProducts/CusProductService.js";
-import { cusProductToSub } from "../cusProducts/cusProductUtils/convertCusProduct.js";
-
-export const cancelEndOfCycle = async ({
- req,
- cusProduct,
- fullCus,
-}: {
- req: ExtendedRequest;
- cusProduct: FullCusProduct;
- fullCus: FullCustomer;
-}) => {
- const { db, org, env, logger } = req;
- const stripeCli = createStripeCli({ org, env });
-
- const sub = await cusProductToSub({ cusProduct, stripeCli });
- if (sub) {
- const latestPeriodEnd = getLatestPeriodEnd({ sub });
- await stripeCli.subscriptions.update(sub.id, {
- cancel_at: latestPeriodEnd,
- });
-
- await CusProductService.update({
- db,
- cusProductId: cusProduct.id,
- updates: { canceled_at: Date.now() },
- });
- } else {
- await CusProductService.update({
- db,
- cusProductId: cusProduct.id,
- updates: {
- status: CusProductStatus.Expired,
- ended_at: Date.now(),
- },
- });
- }
-};
diff --git a/server/src/internal/customers/cancel/cancelImmediately.ts b/server/src/internal/customers/cancel/cancelImmediately.ts
deleted file mode 100644
index 6757e30ab..000000000
--- a/server/src/internal/customers/cancel/cancelImmediately.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import {
- AttachScenario,
- CusProductStatus,
- cusProductToProduct,
- type FullCusProduct,
- type FullCustomer,
-} from "@autumn/shared";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import { setStripeSubscriptionLock } from "@/external/stripe/subscriptions/utils/lockStripeSubscriptionUtils";
-import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
-import { isOneOff } from "@/internal/products/productUtils.js";
-import type { AutumnContext } from "../../../honoUtils/HonoEnv.js";
-import { CusProductService } from "../cusProducts/CusProductService.js";
-import { cusProductToSub } from "../cusProducts/cusProductUtils/convertCusProduct.js";
-import { getExistingCusProducts } from "../cusProducts/cusProductUtils/getExistingCusProducts.js";
-import { activateDefaultProduct } from "../cusProducts/cusProductUtils.js";
-
-export const cancelImmediately = async ({
- ctx,
- cusProduct,
- fullCus,
- prorate,
-}: {
- ctx: AutumnContext;
- cusProduct: FullCusProduct;
- fullCus: FullCustomer;
- prorate: boolean;
-}) => {
- const { db, org, env } = ctx;
- const stripeCli = createStripeCli({ org, env });
-
- const { curScheduledProduct } = getExistingCusProducts({
- product: cusProduct.product,
- cusProducts: fullCus.customer_products,
- internalEntityId: cusProduct.internal_entity_id,
- });
-
- const sub = await cusProductToSub({ cusProduct, stripeCli });
-
- if (sub) {
- // Set lock to prevent webhook handler from processing this cancellation
- await setStripeSubscriptionLock({
- stripeSubscriptionId: sub.id,
- lockedAtMs: Date.now(),
- });
-
- await stripeCli.subscriptions.cancel(sub.id, {
- prorate: prorate,
- cancellation_details: {
- comment: "autumn_cancel",
- },
- });
- }
-
- const isMain = !cusProduct.product.is_add_on;
- const product = cusProductToProduct({ cusProduct });
-
- if (isMain && !isOneOff(product.prices)) {
- // So it doesn't duplicate
- if (curScheduledProduct) {
- await CusProductService.delete({
- db,
- cusProductId: curScheduledProduct.id,
- });
- }
-
- await activateDefaultProduct({
- ctx,
- productGroup: cusProduct.product.group,
- fullCus,
- });
- }
-
- await CusProductService.update({
- db,
- cusProductId: cusProduct.id,
- updates: {
- status: CusProductStatus.Expired,
- ended_at: Date.now(),
- },
- });
-
- console.log("Sending webhook for expired product");
- await addProductsUpdatedWebhookTask({
- ctx,
- internalCustomerId: fullCus.internal_id,
- org,
- env,
- customerId: fullCus.id || null,
- cusProduct,
- scenario: AttachScenario.Expired,
- });
-};
diff --git a/server/src/internal/customers/cancel/cancelScheduledProduct.ts b/server/src/internal/customers/cancel/cancelScheduledProduct.ts
deleted file mode 100644
index 1453250b8..000000000
--- a/server/src/internal/customers/cancel/cancelScheduledProduct.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import {
- CusProductStatus,
- type FullCusProduct,
- type FullCustomer,
-} from "@autumn/shared";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import type { ExtendedRequest } from "@/utils/models/Request.js";
-import { CusProductService } from "../cusProducts/CusProductService.js";
-import { cusProductToSchedule } from "../cusProducts/cusProductUtils/convertCusProduct.js";
-
-export const cancelScheduledProduct = async ({
- req,
- curScheduledProduct,
- fullCus,
- curMainProduct,
-}: {
- req: ExtendedRequest;
- curScheduledProduct?: FullCusProduct;
- fullCus: FullCustomer;
- curMainProduct?: FullCusProduct;
-}) => {
- const { org, env, db, logger } = req;
- const stripeCli = createStripeCli({ org, env });
-
- // 1. Delete subscription schedule if exists
- if (curScheduledProduct) {
- const schedule = await cusProductToSchedule({
- cusProduct: curScheduledProduct,
- stripeCli,
- });
-
- if (schedule) {
- await stripeCli.subscriptionSchedules.cancel(schedule.id);
- }
-
- logger.info(`Deleting scheduled prod (${curScheduledProduct.product.id})`);
- await CusProductService.delete({
- db,
- cusProductId: curScheduledProduct.id,
- });
- }
-
- // 2. Uncancel current main product
- const subId = curMainProduct?.subscription_ids?.[0];
- if (subId) {
- await stripeCli.subscriptions.update(subId, { cancel_at: null });
- }
-
- if (curMainProduct) {
- logger.info(`Updating main prod (${curMainProduct!.product.id}) to active`);
- logger.info(`Cus product ID: ${curMainProduct!.id}`);
- await CusProductService.update({
- db,
- cusProductId: curMainProduct!.id,
- updates: {
- status: CusProductStatus.Active,
- canceled_at: null,
- },
- });
- }
-};
diff --git a/server/src/internal/customers/cancel/handleCancel.ts b/server/src/internal/customers/cancel/handleCancel.ts
deleted file mode 100644
index b91e94eac..000000000
--- a/server/src/internal/customers/cancel/handleCancel.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import {
- CusProductNotFoundError,
- cusProductToProcessorType,
- EntityNotFoundError,
- type FullCusProduct,
- notNullish,
- nullish,
- ProcessorType,
- RELEVANT_STATUSES,
- RecaseError,
-} from "@autumn/shared";
-import { createRoute } from "@/honoMiddlewares/routeHandler";
-import { handleCancelProduct } from "@/internal/customers/cancel/handleCancelProduct.js";
-import { CusService } from "../CusService.js";
-
-export const handleCancel = createRoute({
- // body: CancelBodySchema,
- handler: async (c) => {
- const ctx = c.get("ctx");
- const { db, org, env } = ctx;
- const {
- customer_id,
- product_id,
- entity_id,
- cancel_immediately,
- prorate: bodyProrate,
- customer_product_id,
- } = await c.req.json();
-
- const expireImmediately = cancel_immediately || false;
- const prorate = notNullish(bodyProrate) ? bodyProrate : true;
-
- const fullCus = await CusService.getFull({
- db,
- orgId: org.id,
- idOrInternalId: customer_id,
- env,
- withEntities: true,
- entityId: entity_id,
- inStatuses: RELEVANT_STATUSES,
- allowNotFound: false,
- });
-
- if (entity_id && !fullCus.entity) {
- throw new EntityNotFoundError({ entityId: entity_id });
- }
-
- const cusProducts = fullCus.customer_products;
- const entity = fullCus.entity;
-
- const cusProduct = cusProducts.find((cusProduct: FullCusProduct) => {
- const productIdMatch = cusProduct.product.id === product_id;
- const entityMatch = entity
- ? cusProduct.internal_entity_id === entity.internal_id
- : nullish(cusProduct.internal_entity_id);
-
- const cusProductIdMatch = customer_product_id
- ? cusProduct.id === customer_product_id
- : true;
-
- return productIdMatch && entityMatch && cusProductIdMatch;
- });
-
- if (!cusProduct) {
- throw new CusProductNotFoundError({
- customerId: customer_id,
- productId: product_id,
- entityId: entity_id,
- });
- }
-
- if (cusProductToProcessorType(cusProduct) === ProcessorType.RevenueCat) {
- throw new RecaseError({
- message: `Cannot cancel '${cusProduct.product.name}' because it is managed by RevenueCat.`,
- });
- }
-
- await handleCancelProduct({
- ctx,
- cusProduct,
- fullCus,
- expireImmediately,
- prorate,
- });
-
- return c.json({
- success: true,
- customer_id: customer_id,
- product_id: product_id,
- });
- },
-});
diff --git a/server/src/internal/customers/cancel/handleCancelProduct.ts b/server/src/internal/customers/cancel/handleCancelProduct.ts
deleted file mode 100644
index 8559b2700..000000000
--- a/server/src/internal/customers/cancel/handleCancelProduct.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import {
- AttachBranch,
- CusProductStatus,
- cusProductToProduct,
- type EntitlementWithFeature,
- type FullCusProduct,
- type FullCustomer,
- type Price,
- ProrationBehavior,
-} from "@autumn/shared";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
-import { getCusPaymentMethod } from "../../../external/stripe/stripeCusUtils.js";
-import type { AutumnContext } from "../../../honoUtils/HonoEnv.js";
-import { handleRenewProduct } from "../attach/attachFunctions/handleRenewProduct.js";
-import { handleScheduleFunction2 } from "../attach/attachFunctions/scheduleFlow/handleScheduleFlow2.js";
-import { handleUpgradeFlow } from "../attach/attachFunctions/upgradeFlow/handleUpgradeFlow.js";
-import { getDefaultAttachConfig } from "../attach/attachUtils/getAttachConfig.js";
-import { CusProductService } from "../cusProducts/CusProductService.js";
-import { getExistingCusProducts } from "../cusProducts/cusProductUtils/getExistingCusProducts.js";
-import {
- activateDefaultProduct,
- getFreeDefaultProductByGroup,
-} from "../cusProducts/cusProductUtils.js";
-
-export const handleCancelProduct = async ({
- ctx,
- cusProduct, // cus product to expire
- fullCus,
- expireImmediately = true,
- prorate,
-}: {
- ctx: AutumnContext;
- cusProduct: FullCusProduct;
- fullCus: FullCustomer;
- expireImmediately: boolean;
- prorate: boolean;
-}) => {
- const { org, env, logger, features } = ctx;
- logger.info("--------------------------------");
- logger.info(
- `🔔 Expiring cutomer product (${
- expireImmediately ? "immediately" : "end of cycle"
- })`,
- );
- logger.info(
- `Customer: ${fullCus.id || fullCus.internal_id} (${env}), Org: ${org.id}`,
- );
- logger.info(
- `Product: ${cusProduct.product.name}, Status: ${cusProduct.status}`,
- );
-
- const stripeCli = createStripeCli({ org, env });
-
- // 1. Build attach params
- if (cusProduct.status === CusProductStatus.Scheduled) {
- const { curMainProduct } = getExistingCusProducts({
- product: cusProduct.product,
- cusProducts: fullCus.customer_products,
- internalEntityId: cusProduct.internal_entity_id,
- });
- const product = cusProductToProduct({ cusProduct: curMainProduct! });
-
- await handleRenewProduct({
- ctx,
- attachParams: {
- stripeCli,
- customer: fullCus,
- org,
- cusProducts: fullCus.customer_products,
- products: [product],
- internalEntityId: cusProduct.internal_entity_id || undefined,
- paymentMethod: null,
- prices: product.prices,
- entitlements: product.entitlements,
- freeTrial: product.free_trial || null,
- optionsList: curMainProduct?.options || [],
- replaceables: [],
- entities: fullCus.entities,
- features,
- },
- config: getDefaultAttachConfig(),
- });
- return;
- }
-
- // 2. If there's a scheduled product, throw error?
- const isMain = !cusProduct.product.is_add_on;
- const product = cusProductToProduct({ cusProduct });
- const isFree = isFreeProduct(product.prices || []);
-
- if (isMain) {
- // Delete scheduled product
- const { curScheduledProduct } = getExistingCusProducts({
- product: product,
- cusProducts: fullCus.customer_products,
- internalEntityId: cusProduct.internal_entity_id,
- });
-
- console.log(
- `Current scheduled product: ${curScheduledProduct?.product.name}`,
- );
-
- // Delete scheduled product.
- if (curScheduledProduct) {
- await CusProductService.delete({
- db: ctx.db,
- cusProductId: curScheduledProduct?.id,
- });
- }
- }
-
- // 2. If expire at cycle end, just cancel subscriptions
- if (!expireImmediately && !isFree) {
- const defaultProduct = await getFreeDefaultProductByGroup({
- ctx,
- productGroup: product.group,
- });
-
- let products = [product];
- let prices: Price[] = [];
- let entitlements: EntitlementWithFeature[] = [];
- let skipInsertCusProduct = true;
- if (
- !isFreeProduct(product.prices) &&
- !product.is_add_on &&
- defaultProduct
- ) {
- products = [defaultProduct];
- prices = defaultProduct.prices;
- entitlements = defaultProduct.entitlements;
- skipInsertCusProduct = false;
- }
-
- await handleScheduleFunction2({
- ctx,
- attachParams: {
- stripeCli,
- customer: fullCus,
- org,
- cusProducts: fullCus.customer_products,
- products,
- internalEntityId: cusProduct.internal_entity_id || undefined,
- paymentMethod: null,
- prices,
- entitlements,
- freeTrial: null,
- optionsList: [],
- replaceables: [],
- entities: fullCus.entities,
- features,
- fromCancel: true,
- },
- config: getDefaultAttachConfig(),
- skipInsertCusProduct,
- });
-
- // Schedule default product...
-
- return;
- }
-
- const paymentMethod = await getCusPaymentMethod({
- stripeCli,
- stripeId: fullCus.processor?.id,
- });
-
- // Cancel product immediately
- await handleUpgradeFlow({
- ctx,
- attachParams: {
- stripeCli,
- customer: fullCus,
- org,
- cusProduct,
- cusProducts: fullCus.customer_products,
- products: [],
- internalEntityId: cusProduct.internal_entity_id || undefined,
- paymentMethod,
- prices: [],
- entitlements: [],
- freeTrial: null,
- optionsList: [],
- replaceables: [],
- entities: fullCus.entities,
- features,
- fromCancel: true,
- },
- config: {
- ...getDefaultAttachConfig(),
- proration: prorate
- ? ProrationBehavior.Immediately
- : ProrationBehavior.None,
- requirePaymentMethod: false,
- },
- branch: AttachBranch.Cancel,
- });
-
- // Activate default product
- if (!product.is_add_on && !isOneOff(product.prices)) {
- await activateDefaultProduct({
- ctx,
- productGroup: cusProduct.product.group,
- fullCus,
- curCusProduct: cusProduct,
- });
- }
-};
diff --git a/server/src/internal/customers/cusProducts/cusEnts/cusEntHelpers.ts b/server/src/internal/customers/cusProducts/cusEnts/cusEntHelpers.ts
deleted file mode 100644
index 2163faaee..000000000
--- a/server/src/internal/customers/cusProducts/cusEnts/cusEntHelpers.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// 1. Get min next reset at cus ent
-
-import type { Feature, FullCustomerEntitlement } from "@autumn/shared";
-
-export const getMinNextResetAtCusEnt = ({
- cusEnts,
- feature,
-}: {
- cusEnts: FullCustomerEntitlement[];
- feature: Feature;
-}) => {
- return cusEnts
- .filter(
- (cusEnt) => cusEnt.entitlement.internal_feature_id == feature.internal_id,
- )
- .reduce((min, cusEnt) => {
- return Math.min(min, cusEnt.next_reset_at || Infinity);
- }, Infinity);
-};
diff --git a/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils/cusEntBalanceUtils.ts b/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils/cusEntBalanceUtils.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedApiCustomer.ts b/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedApiCustomer.ts
deleted file mode 100644
index e064b81a7..000000000
--- a/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedApiCustomer.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import {
- type ApiEntityV1,
- addToExpand,
- CusExpand,
- type EntityLegacyData,
- type FullCustomer,
- filterEntityLevelCustomerEntitlementsFromFullCustomer,
- filterOutEntitiesFromFullCustomer,
-} from "@autumn/shared";
-import { redis } from "../../../../external/redis/initRedis.js";
-import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
-import { tryRedisWrite } from "../../../../utils/cacheUtils/cacheUtils.js";
-import { getApiEntityBase } from "../../../entities/entityUtils/apiEntityUtils/getApiEntityBase.js";
-import { getApiCustomerBase } from "../apiCusUtils/getApiCustomerBase.js";
-
-/**
- * Set customer cache in Redis with all entities
- * This function builds the master customer cache (customer-level features only)
- * and individual entity caches (entity-level features only)
- */
-export const setCachedApiCustomer = async ({
- ctx,
- fullCus,
- customerId,
- source,
- fetchTimeMs,
-}: {
- ctx: AutumnContext;
- fullCus: FullCustomer;
- customerId: string;
- source?: string;
- fetchTimeMs: number; // Timestamp when data was fetched from Postgres (for stale write prevention)
-}) => {
- const { org, env, logger } = ctx;
-
- const ctxWithExpand = addToExpand({
- ctx,
- add: [
- CusExpand.BalancesFeature,
- CusExpand.SubscriptionsPlan,
- CusExpand.Invoices,
- CusExpand.ScheduledSubscriptionsPlan,
- ],
- });
-
- // Build master api customer (customer-level features only)
- const { apiCustomer: masterApiCustomer, legacyData } =
- await getApiCustomerBase({
- ctx: ctxWithExpand,
- fullCus: filterOutEntitiesFromFullCustomer({ fullCus }),
- withAutumnId: true,
- });
-
- // Build entity api customers (entity-level features only)
- const filteredFullCus = filterEntityLevelCustomerEntitlementsFromFullCustomer(
- {
- fullCustomer: fullCus,
- },
- );
-
- // Build entities first
- const entityBatch: {
- entityId: string;
- entityData: ApiEntityV1 & { legacyData: EntityLegacyData };
- }[] = [];
- const entityFullCus = {
- ...filteredFullCus,
- customer_products: filteredFullCus.customer_products,
- };
-
- for (const entity of fullCus.entities) {
- const { apiEntity, legacyData: entityLegacyData } = await getApiEntityBase({
- ctx: ctxWithExpand,
- fullCus: entityFullCus,
- entity,
- withAutumnId: true,
- });
-
- entityBatch.push({
- entityId: entity.id,
- entityData: {
- ...apiEntity,
- legacyData: entityLegacyData,
- },
- });
- }
-
- // Then write to Redis
- const masterApiCustomerData = {
- ...masterApiCustomer,
- entities: fullCus.entities.filter((e) => e.id !== null),
- legacyData,
- };
-
- if (masterApiCustomerData.id === null) return;
-
- // console.log(
- // `Setting cached api customer ${customerId}, masterApiCustomerData: `,
- // masterApiCustomerData,
- // );
-
- const result = await tryRedisWrite(async () => {
- return redis.setCustomer(
- JSON.stringify(masterApiCustomerData),
- org.id,
- env,
- customerId,
- fetchTimeMs.toString(),
- );
- });
-
- if (result === "CACHE_EXISTS") {
- logger.info(
- `Cache already exists for customer ${customerId}, source: ${source}`,
- );
- return;
- }
-
- if (result === "STALE_WRITE") {
- logger.info(
- `Stale write blocked for customer ${customerId}, source: ${source}`,
- );
- return;
- }
-
- // Write entity caches (only if customer cache was written)
- const filteredEntityBatch = entityBatch.filter(
- (e) => e.entityData.id !== null,
- );
-
- if (filteredEntityBatch.length > 0) {
- await tryRedisWrite(async () => {
- return redis.setEntitiesBatch(
- JSON.stringify(filteredEntityBatch),
- org.id,
- env,
- );
- });
- }
-
- logger.info(`Set cached api customer ${customerId}, source: ${source}`);
-};
diff --git a/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedApiInvoices.ts b/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedApiInvoices.ts
deleted file mode 100644
index a3af7aa1e..000000000
--- a/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedApiInvoices.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import type { FullCustomer } from "@autumn/shared";
-import { redis } from "../../../../external/redis/initRedis.js";
-import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
-import { tryRedisWrite } from "../../../../utils/cacheUtils/cacheUtils.js";
-import { InvoiceService } from "../../../invoices/InvoiceService.js";
-import { invoicesToResponse } from "../../../invoices/invoiceUtils.js";
-
-/**
- * Set customer invoices cache in Redis with all entities
- * This function updates only the invoices array in the customer cache (customer-level invoices only)
- * and individual entity caches (entity-level invoices only)
- */
-export const setCachedApiInvoices = async ({
- ctx,
- fullCus,
- customerId,
-}: {
- ctx: AutumnContext;
- fullCus: FullCustomer;
- customerId: string;
-}) => {
- const { org, env, logger, db } = ctx;
-
- // Get customer-level invoices (no entity or null entity)
- const invoices = fullCus.invoices
- ? fullCus.invoices
- : await InvoiceService.list({
- db,
- internalCustomerId: fullCus.internal_id,
- limit: 10,
- });
-
- // Build master api customer invoices (customer-level only)
- const masterApiInvoices = invoicesToResponse({
- invoices,
- });
-
- // Then write to Redis
- await tryRedisWrite(async () => {
- await redis.setInvoices(
- JSON.stringify(masterApiInvoices),
- org.id,
- env,
- customerId,
- );
- logger.info(
- `Updated customer invoices cache for customer ${customerId} (${masterApiInvoices.length} invoices)`,
- );
- });
-};
diff --git a/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedApiSubs.ts b/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedApiSubs.ts
deleted file mode 100644
index c1aec4568..000000000
--- a/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedApiSubs.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import {
- addToExpand,
- CusExpand,
- type FullCustomer,
- filterCusProductsByEntity,
- filterOutEntitiesFromFullCustomer,
-} from "@autumn/shared";
-import { redis } from "../../../../external/redis/initRedis.js";
-import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
-import { tryRedisWrite } from "../../../../utils/cacheUtils/cacheUtils.js";
-import { getApiSubscriptions } from "../apiCusUtils/getApiSubscription/getApiSubscriptions.js";
-
-/**
- * Set customer subscriptions cache in Redis with all entities
- * This function updates subscriptions and scheduled_subscriptions arrays in the customer cache (customer-level only)
- * and individual entity caches (entity-level only)
- */
-export const setCachedApiSubs = async ({
- ctx,
- fullCus,
- customerId,
-}: {
- ctx: AutumnContext;
- fullCus: FullCustomer;
- customerId: string;
-}) => {
- const { org, env, logger } = ctx;
-
- // Build master api customer subscriptions (customer-level products only)
- const ctxWithExpand = addToExpand({
- ctx,
- add: [CusExpand.SubscriptionsPlan, CusExpand.ScheduledSubscriptionsPlan],
- });
- const { data: masterApiSubs } = await getApiSubscriptions({
- ctx: ctxWithExpand,
- fullCus: filterOutEntitiesFromFullCustomer({ fullCus }),
- });
-
- // Split subscriptions by status
- const activeSubscriptions = masterApiSubs.filter(
- (s) => s.status === "active",
- );
- const scheduledSubscriptions = masterApiSubs.filter(
- (s) => s.status === "scheduled",
- );
-
- // console.log(`Updating api subs for customer ${customerId}`, masterApiSubs);
-
- // Then write to Redis
- await tryRedisWrite(async () => {
- // Update customer subscriptions and scheduled_subscriptions
- await redis.setSubscriptions(
- JSON.stringify(activeSubscriptions),
- JSON.stringify(scheduledSubscriptions),
- org.id,
- env,
- customerId,
- );
- logger.info(
- `Updated customer subscriptions cache for customer ${customerId} (${activeSubscriptions.length} active, ${scheduledSubscriptions.length} scheduled)`,
- );
-
- // Update entity subscriptions
- for (const entity of fullCus.entities) {
- // Filter customer products for this specific entity
- const entityCusProducts = filterCusProductsByEntity({
- cusProducts: fullCus.customer_products,
- entity,
- org,
- });
-
- const { data: entitySubscriptions } = await getApiSubscriptions({
- ctx: ctxWithExpand,
- fullCus: {
- ...fullCus,
- customer_products: entityCusProducts,
- entity, // Set entity for entity-specific balance calculations
- },
- });
-
- // Split entity subscriptions by status
- const entityActiveSubscriptions = entitySubscriptions.filter(
- (s) => s.status === "active",
- );
- const entityScheduledSubscriptions = entitySubscriptions.filter(
- (s) => s.status === "scheduled",
- );
-
- await redis.setEntityProducts(
- JSON.stringify(entityActiveSubscriptions),
- JSON.stringify(entityScheduledSubscriptions),
- org.id,
- env,
- customerId,
- entity.id,
- );
- logger.info(
- `Updated entity subscriptions cache for entity ${entity.id} (${entityActiveSubscriptions.length} active, ${entityScheduledSubscriptions.length} scheduled)`,
- );
- }
- });
-};
diff --git a/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedGrantedBalance.ts b/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedGrantedBalance.ts
deleted file mode 100644
index 129789e54..000000000
--- a/server/src/internal/customers/cusUtils/apiCusCacheUtils/setCachedGrantedBalance.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import {
- type FullCustomer,
- filterEntityLevelCustomerEntitlementsFromFullCustomer,
- filterOutEntitiesFromFullCustomer,
-} from "@autumn/shared";
-import { redis } from "@/external/redis/initRedis.js";
-import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
-import { tryRedisWrite } from "@/utils/cacheUtils/cacheUtils.js";
-import { getApiBalances } from "../apiCusUtils/getApiBalance/getApiBalances.js";
-
-type BalancesPayload = Record<
- string,
- {
- granted_balance: number;
- breakdown?: Array<{ id: string; granted_balance: number }>;
- }
->;
-
-type EntityBatchItem = {
- entityId: string;
- balances: BalancesPayload;
-};
-
-/**
- * Update granted_balance in Redis cache for all features
- * This is used after updateGrantedBalance to keep Redis in sync without clearing the entire cache
- *
- * Updates both:
- * 1. Customer-level cache (customer-level products only)
- * 2. Entity caches in batch (entity-level products only)
- */
-export const setCachedGrantedBalance = async ({
- ctx,
- fullCus,
-}: {
- ctx: AutumnContext;
- fullCus: FullCustomer;
-}) => {
- const { org, env, logger } = ctx;
- const customerId = fullCus.id;
-
- if (!customerId) {
- logger.debug("[setCachedGrantedBalance] No customer ID, skipping");
- return;
- }
-
- // ============================================================================
- // 1. Build customer-level balances payload (customer-level products only)
- // ============================================================================
- const filteredFullCus = filterOutEntitiesFromFullCustomer({
- fullCus,
- });
-
- const customerLevelCusProducts = filteredFullCus.customer_products;
-
- const { data: customerBalances } = await getApiBalances({
- ctx,
- fullCus: {
- ...fullCus,
- customer_products: customerLevelCusProducts,
- },
- });
-
- const customerBalancesPayload: BalancesPayload = {};
- for (const [featureId, balance] of Object.entries(customerBalances)) {
- customerBalancesPayload[featureId] = {
- granted_balance: balance.granted_balance,
- breakdown: balance.breakdown?.map((bd) => ({
- id: bd.id,
- granted_balance: bd.granted_balance,
- })),
- };
- }
-
- // ============================================================================
- // 2. Build entity balances batch (entity-level products only)
- // ============================================================================
- const entityLevelCusProducts =
- filterEntityLevelCustomerEntitlementsFromFullCustomer({
- fullCustomer: fullCus,
- }).customer_products;
-
- const entityBatch: EntityBatchItem[] = [];
-
- for (const entity of fullCus.entities) {
- const { data: entityBalances } = await getApiBalances({
- ctx,
- fullCus: {
- ...fullCus,
- customer_products: entityLevelCusProducts,
- entity,
- },
- });
-
- const entityBalancesPayload: BalancesPayload = {};
- for (const [featureId, balance] of Object.entries(entityBalances)) {
- entityBalancesPayload[featureId] = {
- granted_balance: balance.granted_balance,
- breakdown: balance.breakdown?.map((bd) => ({
- id: bd.id,
- granted_balance: bd.granted_balance,
- })),
- };
- }
-
- if (Object.keys(entityBalancesPayload).length > 0) {
- entityBatch.push({
- entityId: entity.id,
- balances: entityBalancesPayload,
- });
- }
- }
-
- // ============================================================================
- // 3. Write to Redis in a single call
- // ============================================================================
- await tryRedisWrite(async () => {
- return redis.setGrantedBalance(
- org.id,
- env,
- customerId,
- JSON.stringify(customerBalancesPayload),
- JSON.stringify(entityBatch),
- );
- });
-
- logger.debug(
- `[setCachedGrantedBalance] Updated granted_balance for customer (${Object.keys(customerBalancesPayload).length} features) and ${entityBatch.length} entities`,
- );
-};
diff --git a/server/src/internal/customers/cusUtils/apiCusCacheUtils/testCacheDeleteGuard.ts b/server/src/internal/customers/cusUtils/apiCusCacheUtils/testCacheDeleteGuard.ts
deleted file mode 100644
index 6f98b21dc..000000000
--- a/server/src/internal/customers/cusUtils/apiCusCacheUtils/testCacheDeleteGuard.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { logger } from "@/external/logtail/logtailUtils.js";
-import { redis } from "@/external/redis/initRedis.js";
-import type { AutumnContext } from "../../../../honoUtils/HonoEnv";
-
-/**
- * Builds the test cache delete guard key (matches Lua function)
- */
-const buildTestCacheDeleteGuardKey = ({
- orgId,
- env,
- customerId,
-}: {
- orgId: string;
- env: string;
- customerId: string;
-}) => `{${orgId}}:${env}:test_cache_delete_guard:${customerId}`;
-
-/**
- * Sets a test cache delete guard to prevent cache deletion during testing.
- * When this guard exists, deleteCustomer.lua will skip deletion.
- */
-export const setTestCacheDeleteGuard = async ({
- ctx,
- customerId,
- ttlMs = 60000, // Default 60 seconds
-}: {
- ctx: AutumnContext;
- customerId: string;
- ttlMs?: number;
-}): Promise => {
- const key = buildTestCacheDeleteGuardKey({
- orgId: ctx.org.id,
- env: ctx.env,
- customerId,
- });
- try {
- await redis.set(key, "1", "PX", ttlMs);
- return true;
- } catch (error) {
- logger.error(`Failed to set test cache delete guard: ${error}`);
- return false;
- }
-};
-
-/**
- * Removes the test cache delete guard.
- */
-export const removeTestCacheDeleteGuard = async ({
- ctx,
- customerId,
-}: {
- ctx: AutumnContext;
- customerId: string;
-}): Promise => {
- const key = buildTestCacheDeleteGuardKey({
- orgId: ctx.org.id,
- env: ctx.env,
- customerId,
- });
- try {
- await redis.del(key);
- return true;
- } catch (error) {
- logger.error(`Failed to remove test cache delete guard: ${error}`);
- return false;
- }
-};
diff --git a/server/src/internal/customers/cusUtils/apiCusUtils/getApiBalance/cusEntsToEntityBreakdown.ts b/server/src/internal/customers/cusUtils/apiCusUtils/getApiBalance/cusEntsToEntityBreakdown.ts
deleted file mode 100644
index 48fb75bec..000000000
--- a/server/src/internal/customers/cusUtils/apiCusUtils/getApiBalance/cusEntsToEntityBreakdown.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import {
- type FullCusEntWithFullCusProduct,
- type FullCustomer,
- filterEntityProductCusEnts,
- filterOutEntityCusEnts,
- filterPerEntityCusEnts,
- getCusEntBalance,
- sumValues,
-} from "@autumn/shared";
-import { Decimal } from "decimal.js";
-
-import type { RequestContext } from "../../../../../honoUtils/HonoEnv.js";
-
-export const cusEntsToEntityBreakdown = ({
- ctx,
- fullCus,
- cusEnts,
-}: {
- ctx: RequestContext;
- cusEnts: FullCusEntWithFullCusProduct[];
- fullCus: FullCustomer;
-}) => {
- if (fullCus.entity) return undefined; // We don't need to show entity breakdown for a single entity.
- // Entity breakdown.
-
- const masterBalance = sumValues(
- filterOutEntityCusEnts({ cusEnts }).map((ce) => {
- const { balance } = getCusEntBalance({
- cusEnt: ce,
- });
- return balance;
- }),
- );
-
- const entityBalances: Record = {};
- const perEntityCusEnts = filterPerEntityCusEnts({ cusEnts });
-
- for (const cusEnt of perEntityCusEnts) {
- for (const entityId in cusEnt.entities) {
- if (!entityBalances[entityId]) {
- entityBalances[entityId] = 0;
- }
- entityBalances[entityId] = new Decimal(entityBalances[entityId])
- .add(cusEnt.entities[entityId].balance)
- .toNumber();
- }
- }
-
- const entityProductCusEnts = filterEntityProductCusEnts({ cusEnts });
- for (const cusEnt of entityProductCusEnts) {
- const entityId =
- fullCus.entities.find(
- (e) => e.internal_id === cusEnt.customer_product?.internal_entity_id,
- )?.id || cusEnt.customer_product?.entity_id;
-
- if (!entityId) continue;
-
- if (!entityBalances[entityId]) {
- entityBalances[entityId] = 0;
- }
- entityBalances[entityId] = new Decimal(entityBalances[entityId])
- .add(cusEnt.balance ?? 0)
- .toNumber();
- }
-
- if (Object.keys(entityBalances).length === 0) return undefined;
-
- return {
- master: masterBalance,
- entities: sumValues(Object.values(entityBalances)),
- };
-};
diff --git a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusUpcomingInvoice.ts b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusUpcomingInvoice.ts
deleted file mode 100644
index 035a826ba..000000000
--- a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusUpcomingInvoice.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import type { ApiCusUpcomingInvoice } from "@autumn/shared";
-import {
- type AppEnv,
- CusExpand,
- type FullCustomer,
- type Organization,
- stripeToAtmnAmount,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { createStripeCli } from "@/external/connect/createStripeCli.js";
-import { getEarliestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
-import { lineItemInCusProduct } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
-import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
-import { stripeDiscountToResponse } from "./stripeDiscountToResponse.js";
-
-export const getCusUpcomingInvoice = async ({
- db,
- org,
- env,
- fullCus,
- expand,
-}: {
- db: DrizzleCli;
- org: Organization;
- env: AppEnv;
- fullCus: FullCustomer;
- expand: CusExpand[];
-}) => {
- if (!expand.includes(CusExpand.UpcomingInvoice)) return undefined;
-
- const subIds = fullCus.customer_products.flatMap(
- (cp) => cp.subscription_ids || [],
- );
-
- if (subIds.length === 0) return null;
-
- const stripeCli = createStripeCli({ org, env });
-
- const subs = await getStripeSubs({
- stripeCli,
- subIds,
- });
-
- const sub = subs.reduce((acc, sub) => {
- const curSubPeriodEnd = getEarliestPeriodEnd({ sub });
- const nextSubPeriodEnd = getEarliestPeriodEnd({ sub });
- return nextSubPeriodEnd < curSubPeriodEnd ? sub : acc;
- }, subs[0]);
-
- const upcomingInvoice = await stripeCli.invoices.createPreview({
- customer: fullCus.processor?.id,
- subscription: sub.id,
- expand: ["discounts.source.coupon"],
- });
-
- const lines = [];
- for (const line of upcomingInvoice.lines.data) {
- const cusProd = fullCus.customer_products.find((cp) =>
- lineItemInCusProduct({ cusProduct: cp, lineItem: line }),
- );
-
- const atmnLineAmount = stripeToAtmnAmount({
- amount: line.amount,
- currency: line.currency,
- });
-
- lines.push({
- product_id: cusProd?.product.id || null,
- description: line.description || "",
- amount: atmnLineAmount,
- });
- }
-
- const stripeDiscounts = upcomingInvoice.discounts.filter(
- (d): d is Stripe.Discount =>
- typeof d === "object" && d !== null && "coupon" in d,
- ) as Stripe.Discount[];
-
- // Get reward in IDs
-
- const discounts = stripeDiscounts
- .map((d) =>
- stripeDiscountToResponse({
- discount: d,
- totalDiscountAmounts:
- upcomingInvoice.total_discount_amounts || undefined,
- }),
- )
- .filter((d) => d !== null);
-
- const atmnSubtotal = stripeToAtmnAmount({
- amount: upcomingInvoice.subtotal,
- currency: upcomingInvoice.currency,
- });
-
- const atmnTotal = stripeToAtmnAmount({
- amount: upcomingInvoice.total,
- currency: upcomingInvoice.currency,
- });
-
- const res: ApiCusUpcomingInvoice = {
- lines,
- discounts,
- subtotal: atmnSubtotal,
- total: atmnTotal,
- currency: upcomingInvoice.currency,
- };
-
- return res;
-};
diff --git a/server/src/internal/customers/cusUtils/cusResponseUtils/stripeDiscountToResponse.ts b/server/src/internal/customers/cusUtils/cusResponseUtils/stripeDiscountToResponse.ts
deleted file mode 100644
index 21031338d..000000000
--- a/server/src/internal/customers/cusUtils/cusResponseUtils/stripeDiscountToResponse.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import {
- CouponDurationType,
- RewardType,
- stripeToAtmnAmount,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-
-const parseStripeCouponDuration = (coupon: Stripe.Coupon) => {
- let duration_type: CouponDurationType = CouponDurationType.OneOff;
- let duration_value: number = 0;
-
- if (coupon.duration === "forever") {
- duration_type = CouponDurationType.Forever;
- } else if (coupon.duration === "once") {
- duration_type = CouponDurationType.OneOff;
- } else if (coupon.duration === "repeating") {
- duration_type = CouponDurationType.Months;
- duration_value = coupon.duration_in_months || 0;
- } else {
- duration_type = CouponDurationType.OneOff;
- }
-
- return {
- duration_type,
- duration_value,
- };
-};
-
-export const stripeDiscountToResponse = ({
- discount,
- totalDiscountAmounts,
-}: {
- discount: Stripe.Discount;
- totalDiscountAmounts?: Stripe.Invoice.TotalDiscountAmount[];
-}) => {
- const d = discount;
-
- const coupon = d.source.coupon;
- if (!coupon || typeof coupon === "string") {
- return null;
- }
-
- const { duration_type, duration_value } = parseStripeCouponDuration(coupon);
-
- const totalDiscountAmount = totalDiscountAmounts?.find(
- (t) => t.discount === d.id,
- );
-
- const totalAtmnDiscountAmount = stripeToAtmnAmount({
- amount: totalDiscountAmount?.amount || 0,
- currency: coupon.currency ?? undefined,
- });
-
- return {
- id: coupon.id,
- name: coupon.name ?? "",
- type: coupon.amount_off
- ? RewardType.FixedDiscount
- : RewardType.PercentageDiscount,
- discount_value: coupon.amount_off || coupon.percent_off || 0,
- currency: coupon.currency ?? null,
- start: d.start ?? null,
- end: d.end ?? null,
- // subscription_id: d.subscription ?? null,
- duration_type,
- duration_value,
-
- total_discount_amount: totalDiscountAmount?.amount
- ? totalAtmnDiscountAmount
- : null,
- };
-};
diff --git a/server/src/internal/customers/previews/checkProductUtils.ts b/server/src/internal/customers/previews/checkProductUtils.ts
deleted file mode 100644
index 82b6fc452..000000000
--- a/server/src/internal/customers/previews/checkProductUtils.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import type {
- Feature,
- Organization,
- ProductItem,
- ProductV2,
-} from "@autumn/shared";
-import { getFeatureNameWithCapital } from "@/internal/features/utils/displayUtils.js";
-import {
- featurePricetoPricecnItem,
- getPriceText,
-} from "@/internal/products/pricecn/pricecnUtils.js";
-import {
- isFeatureItem,
- isFeaturePriceItem,
- isPriceItem,
-} from "@/internal/products/product-items/productItemUtils/getItemType.js";
-import { notNullish } from "@/utils/genUtils.js";
-import { formatCurrency, formatTiers } from "./previewUtils.js";
-
-export const getProductChargeText = ({
- product,
- org,
- features,
-}: {
- product: ProductV2;
- org: Organization;
- features: Feature[];
-}) => {
- const basePrices = product.items.filter((i) => isPriceItem(i));
- const total = basePrices.reduce((acc, curr) => acc + curr.price!, 0);
-
- const itemStrs = [];
- if (total > 0) {
- itemStrs.push(
- formatCurrency({
- amount: total,
- defaultCurrency: org.default_currency!,
- }),
- );
- }
-
- const prepaidPrices = product.items.filter(
- (i) => isFeaturePriceItem(i) && i.usage_model == "prepaid",
- );
-
- const prepaidStrings = prepaidPrices.map((i) => {
- const feature = features.find((f) => f.id === i.feature_id);
- const priceStr = formatTiers({
- tiers: i.tiers!,
- org,
- });
-
- const featureStr =
- i.billing_units && i.billing_units > 1
- ? `${i.billing_units} ${feature?.name}`
- : feature?.name;
-
- return `${priceStr} / ${featureStr}`;
- });
- return [...itemStrs, ...prepaidStrings];
-};
-
-export const getItemDescription = ({
- item,
- features,
- product,
- org,
-}: {
- item: ProductItem;
- features: Feature[];
- product: ProductV2;
- org: Organization;
-}) => {
- const prices = product.items.filter((i) => !isFeatureItem(i));
-
- const priceStr = getPriceText({
- item,
- org,
- });
-
- if (isPriceItem(item)) {
- const baseName =
- prices.length == 1
- ? product.name
- : notNullish(item.interval)
- ? "Subscription"
- : "One-time";
-
- return baseName;
- } else {
- const feature = features.find((f) => f.id === item.feature_id);
- // let pricecnItem = featurePricetoPricecnItem({
- // feature,
- // item,
- // org,
- // });
-
- // // let combinedStr = pricecnItem.primaryText + " " + pricecnItem.secondaryText;
- // // combinedStr = `${feature?.name} - ${combinedStr}`;
- // // if (item.usage_model == "pay_per_use") {
- // // combinedStr = `${combinedStr}`;
- // // }
- return `${getFeatureNameWithCapital({ feature: feature! })}`;
- }
-};
diff --git a/server/src/internal/customers/previews/previewUtils.ts b/server/src/internal/customers/previews/previewUtils.ts
deleted file mode 100644
index 84a14014a..000000000
--- a/server/src/internal/customers/previews/previewUtils.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import type { Organization, PriceTier } from "@autumn/shared";
-
-export const formatCurrency = ({
- amount,
- defaultCurrency,
-}: {
- amount: number;
- defaultCurrency?: string;
-}) => {
- const formatter = new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: defaultCurrency || "usd",
- });
- return formatter.format(amount);
-};
-
-export const formatTiers = ({
- tiers,
-
- org,
-}: {
- tiers: PriceTier[];
-
- org: Organization;
-}) => {
- if (tiers.length == 1) {
- return formatCurrency({
- amount: tiers[0].amount,
- defaultCurrency: org.default_currency!,
- });
- }
-
- const tiersStart = formatCurrency({
- amount: tiers[0].amount,
- defaultCurrency: org.default_currency!,
- });
- const tiersEnd = formatCurrency({
- amount: tiers[tiers.length - 1].amount,
- defaultCurrency: org.default_currency!,
- });
-
- return `${tiersStart} - ${tiersEnd}`;
-};
-
-export const getItemsHtml = ({
- items,
- org,
-}: {
- items: any[];
- org: Organization;
-}) => {
- let html = "";
- const pricedItems = items.filter((item) => item.amount != 0);
- const totalAmount = pricedItems.reduce((acc: number, item: any) => {
- return acc + item.amount;
- }, 0);
-
- if (pricedItems.length == 1) {
- html += `${formatCurrency({
- amount: totalAmount,
- defaultCurrency: org.default_currency!,
- })}
`;
- } else {
- html += `${itemsToHtml({ items: pricedItems })} `;
- html += `Total: ${formatCurrency({
- amount: totalAmount,
- defaultCurrency: org.default_currency!,
- })}
`;
- }
-
- return html;
-};
-
-export const itemsToHtml = ({ items }: { items: any[] }) => {
- let html = "";
-
- for (const item of items) {
- if (item.amount == 0) {
- continue;
- }
-
- html += `- ${item.name || item.description}: ${formatCurrency({
- amount: item.amount,
- defaultCurrency: item.currency,
- })} `;
- }
-
- return html;
-};
diff --git a/server/src/internal/emails/sendMigrationEmail.ts b/server/src/internal/emails/sendMigrationEmail.ts
deleted file mode 100644
index e8a68a695..000000000
--- a/server/src/internal/emails/sendMigrationEmail.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { MigrationJobStep, type Organization } from "@autumn/shared";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { sendTextEmail } from "@/external/resend/resendUtils.js";
-import { safeResend } from "@/external/resend/safeResend.js";
-import { MigrationService } from "../migrations/MigrationService.js";
-import { FROM_AUTUMN } from "./constants.js";
-
-export const sendMigrationEmail = safeResend({
- fn: async ({
- db,
- migrationJobId,
- org,
- }: {
- db: DrizzleCli;
- migrationJobId: string;
- org: Organization;
- }) => {
- const migrationJob = await MigrationService.getJob({
- db,
- id: migrationJobId,
- });
-
- // Send email
- const getCustomersStep =
- migrationJob.step_details[MigrationJobStep.GetCustomers];
- const migrateStep =
- migrationJob.step_details[MigrationJobStep.MigrateCustomers];
-
- console.log("Sending migration email");
- await sendTextEmail({
- from: FROM_AUTUMN,
- to: "johnyeocx@gmail.com",
- subject: `Migration Job Finished -- ${migrationJob.id}`,
- body: `
-
- ORG: ${org.id}, ${org.slug}
- Step: Get migration customers
-
- 1. Total customers: ${getCustomersStep?.total_customers}
- 2. Canceled customers: ${getCustomersStep?.canceled_customers}
-
- Step: Migrate customers
-
- 1. Number of errors: ${migrateStep?.num_errors}
- 2. Failed customers:
- ${migrateStep?.failed_customers}
- `,
- });
- },
- action: "send migration email",
-});
diff --git a/server/src/internal/emails/sendOTPEmail.ts b/server/src/internal/emails/sendOTPEmail.ts
index 7183eee77..37e91c5f8 100644
--- a/server/src/internal/emails/sendOTPEmail.ts
+++ b/server/src/internal/emails/sendOTPEmail.ts
@@ -1,4 +1,4 @@
-import OTPEmail from "@emails/OTPEmail.js";
+import OTPEmail from "@emails/otpemail.js";
import { logger } from "@/external/logtail/logtailUtils.js";
import { createResendCli } from "@/external/resend/resendUtils.js";
import { FROM_AUTUMN } from "./constants.js";
diff --git a/server/src/internal/entities/entityUtils/apiEntityCacheUtils/getCachedApiEntity.ts b/server/src/internal/entities/entityUtils/apiEntityCacheUtils/getCachedApiEntity.ts
deleted file mode 100644
index 019fa0cfc..000000000
--- a/server/src/internal/entities/entityUtils/apiEntityCacheUtils/getCachedApiEntity.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import {
- type ApiEntityV1,
- ApiEntityV1Schema,
- type EntityLegacyData,
- EntityNotFoundError,
- type FullCustomer,
- filterEntityLevelCustomerEntitlementsFromFullCustomer,
- filterPlanAndFeatureExpand,
-} from "@autumn/shared";
-
-import { CACHE_CUSTOMER_VERSION } from "@lua/cacheConfig.js";
-import type { Redis } from "ioredis";
-import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
-import { getOrSetCachedFullCustomer } from "../../../customers/cusUtils/fullCustomerCacheUtils/getOrSetCachedFullCustomer.js";
-import { getApiEntityBase } from "../apiEntityUtils/getApiEntityBase.js";
-
-export const buildCachedApiEntityKey = ({
- entityId,
- customerId,
- orgId,
- env,
-}: {
- entityId: string;
- customerId: string;
- orgId: string;
- env: string;
-}) => {
- return `{${orgId}}:${env}:customer:${CACHE_CUSTOMER_VERSION}:${customerId}:entity:${entityId}`;
-};
-
-/**
- * Get ApiEntity from Redis cache
- * If not found, fetch from DB, cache it, and return
- * If skipCache is true, always fetch from DB
- */
-export const getCachedApiEntity = async ({
- ctx,
- customerId,
- entityId,
- skipCustomerMerge = false,
- fullCus,
- redisInstance: _redisInstance,
- cacheVersion: _cacheVersion,
-}: {
- ctx: AutumnContext;
- customerId: string;
- entityId: string;
- skipCustomerMerge?: boolean; // If true, returns only entity's own features (no customer merging)
- fullCus?: FullCustomer;
- redisInstance?: Redis; // Kept for backwards compatibility
- cacheVersion?: string; // Kept for backwards compatibility
-}): Promise<{ apiEntity: ApiEntityV1; legacyData: EntityLegacyData }> => {
- const getExpandedApiEntity = async () => {
- // Get FullCustomer from cache (reads from same cache that track Lua script updates)
- // Falls back to DB if cache miss
- if (!fullCus) {
- fullCus = await getOrSetCachedFullCustomer({
- ctx,
- customerId,
- entityId,
- source: "getCachedApiEntity",
- });
-
- fullCus.entity = fullCus.entities.find((e) => e.id === entityId);
- }
-
- const entity = fullCus.entity;
-
- if (!entity) {
- throw new EntityNotFoundError({ entityId });
- }
-
- // Build ApiEntity with full products for return
- const { apiEntity, legacyData } = await getApiEntityBase({
- ctx,
- entity,
- fullCus: fullCus,
- withAutumnId: true,
- });
-
- const { apiEntity: pureApiEntity } = await getApiEntityBase({
- ctx,
- entity,
- fullCus: filterEntityLevelCustomerEntitlementsFromFullCustomer({
- fullCustomer: fullCus,
- }),
- withAutumnId: true,
- });
-
- return {
- apiEntity: ApiEntityV1Schema.parse(
- skipCustomerMerge ? pureApiEntity : apiEntity,
- ),
- legacyData,
- };
- };
-
- const { apiEntity, legacyData } = await getExpandedApiEntity();
- const filteredApiEntity = filterPlanAndFeatureExpand({
- expand: ctx.expand,
- target: apiEntity,
- });
-
- return {
- apiEntity: filteredApiEntity,
- legacyData,
- };
-};
diff --git a/server/src/internal/entities/entityUtils/entityInvoiceUtils.ts b/server/src/internal/entities/entityUtils/entityInvoiceUtils.ts
deleted file mode 100644
index cc976cb5b..000000000
--- a/server/src/internal/entities/entityUtils/entityInvoiceUtils.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { type Feature, getFeatureName } from "@autumn/shared";
-import { AppEnv, Entity } from "autumn-js";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { EntityService } from "@/internal/api/entities/EntityService.js";
-
-export const getEntityInvoiceDescription = async ({
- db,
- internalEntityId,
- features,
- logger,
-}: {
- db: DrizzleCli;
- internalEntityId: string;
- features: Feature[];
- logger: any;
-}) => {
- try {
- const entity = await EntityService.getByInternalId({
- db,
- internalId: internalEntityId,
- });
-
- const feature = features.find(
- (f) => f.internal_id == entity?.internal_feature_id,
- );
-
- let entDetails = "";
- if (entity.name) {
- entDetails = `${entity.name}${entity.id ? ` (ID: ${entity.id})` : ""}`;
- } else if (entity.id) {
- entDetails = `${entity.id}`;
- }
-
- if (feature && entDetails) {
- const featureName = getFeatureName({
- feature,
- plural: false,
- capitalize: true,
- });
- return `${featureName}: ${entDetails}`;
- }
-
- return "";
- } catch (error) {
- logger.error(`Failed to get entity invoice description`, { error });
- return "";
- }
-};
diff --git a/server/src/internal/entities/handlers/handleCreateEntity/entityToAction.ts b/server/src/internal/entities/handlers/handleCreateEntity/entityToAction.ts
deleted file mode 100644
index 1449db1f1..000000000
--- a/server/src/internal/entities/handlers/handleCreateEntity/entityToAction.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import type { Entity } from "@autumn/shared";
-import { notNullish, nullish } from "@/utils/genUtils.js";
-
-export const logEntityToAction = ({
- entityToAction,
- logger,
-}: {
- entityToAction: any;
- logger: any;
-}) => {
- for (const id in entityToAction) {
- logger.info(
- `${id} - ${entityToAction[id].action}${
- entityToAction[id].replace
- ? ` (replace ${
- entityToAction[id].replace.id ||
- entityToAction[id].replace.internal_id
- })`
- : ""
- }`,
- );
- }
-};
-
-// Probably going to change
-export const getEntityToAction = ({
- inputEntities,
- existingEntities,
- feature,
- logger,
-}: {
- inputEntities: any[];
- existingEntities: Entity[];
- feature: any;
- logger: any;
-}) => {
- const entityToAction: any = {};
- let createCount = 0;
- const replacedEntities: string[] = [];
- for (const inputEntity of inputEntities) {
- const curEntity = existingEntities.find(
- (e: any) => e.id === inputEntity.id,
- );
-
- if (curEntity && curEntity.deleted) {
- entityToAction[inputEntity.id] = {
- action: "replace",
- replace: curEntity,
- entity: inputEntity,
- };
- replacedEntities.push(curEntity.id);
- continue;
- }
-
- let replaced = false;
-
- for (const entity of existingEntities) {
- if (entity.deleted && !replacedEntities.includes(entity.id)) {
- replaced = true;
- replacedEntities.push(entity.id);
-
- entityToAction[inputEntity.id] = {
- action: "replace",
- replace: entity,
- entity: inputEntity,
- };
- break;
- }
-
- // If there's an entity with null ID and cur input entity has an ID, fill it up!
- if (
- nullish(entity.id) &&
- notNullish(inputEntity.id) &&
- !replacedEntities.includes(entity.internal_id) &&
- entity.feature_id === feature.id
- ) {
- entityToAction[inputEntity.id] = {
- action: "replace",
- replace: entity,
- entity: inputEntity,
- };
-
- replacedEntities.push(entity.internal_id);
- replaced = true;
- break;
- }
- }
-
- if (!replaced) {
- entityToAction[inputEntity.id] = {
- action: "create",
- entity: inputEntity,
- };
- createCount++;
- }
- }
-
- logEntityToAction({ entityToAction, logger });
-
- return entityToAction;
-};
diff --git a/server/src/internal/features/utils/mapFeatureUtils.ts b/server/src/internal/features/utils/mapFeatureUtils.ts
deleted file mode 100644
index b9cc27c42..000000000
--- a/server/src/internal/features/utils/mapFeatureUtils.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import {
- ApiFeatureType,
- type ApiFeatureV0,
- type AppEnv,
- FeatureType,
- type FeatureUsageType,
-} from "@autumn/shared";
-import RecaseError from "@/utils/errorUtils.js";
-import {
- constructBooleanFeature,
- constructCreditSystem,
- constructMeteredFeature,
-} from "./constructFeatureUtils.js";
-
-export const fromApiFeature = ({
- apiFeature,
- orgId,
- env,
-}: {
- apiFeature: ApiFeatureV0;
- orgId: string;
- env: AppEnv;
-}) => {
- const isMetered =
- apiFeature.type === ApiFeatureType.SingleUsage ||
- apiFeature.type === ApiFeatureType.ContinuousUse;
-
- const featureType: FeatureType = isMetered
- ? FeatureType.Metered
- : (apiFeature.type as unknown as FeatureType);
-
- if (isMetered) {
- return constructMeteredFeature({
- featureId: apiFeature.id,
- name: apiFeature.name || "",
- usageType: apiFeature.type as unknown as FeatureUsageType,
- orgId,
- env,
- });
- }
-
- if (featureType === FeatureType.CreditSystem) {
- if (!apiFeature.credit_schema || apiFeature.credit_schema.length === 0) {
- throw new RecaseError({
- message: "Credit system schema is required",
- code: "CREDIT_SYSTEM_SCHEMA_REQUIRED",
- statusCode: 400,
- });
- }
-
- return constructCreditSystem({
- featureId: apiFeature.id,
- name: apiFeature.name || "",
- orgId,
- env,
- schema: apiFeature.credit_schema!,
- });
- }
-
- return constructBooleanFeature({
- featureId: apiFeature.id,
- name: apiFeature.name || "",
- orgId,
- env,
- });
-};
diff --git a/server/src/internal/orgs/AuthService.ts b/server/src/internal/orgs/AuthService.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/internal/orgs/handlers/stripeHandlers/handleGetDashboardUrl.ts b/server/src/internal/orgs/handlers/stripeHandlers/handleGetDashboardUrl.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/internal/orgs/onboarding/createOnboardingProducts.ts b/server/src/internal/orgs/onboarding/createOnboardingProducts.ts
deleted file mode 100644
index 34de26265..000000000
--- a/server/src/internal/orgs/onboarding/createOnboardingProducts.ts
+++ /dev/null
@@ -1,159 +0,0 @@
-import {
- AggregateType,
- AllowanceType,
- BillingInterval,
- EntInterval,
- // DB Models
- entitlements,
- FeatureType,
- FeatureUsageType,
- features,
- PriceType,
- prices,
- products,
-} from "@autumn/shared";
-import { AppEnv } from "autumn-js";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { generateId, keyToTitle } from "@/utils/genUtils.js";
-
-const defaultFeatures = [
- {
- internal_id: "",
- id: "pro_analytics",
- type: FeatureType.Boolean,
- display: {
- singular: "pro analytics",
- plural: "pro analytics",
- },
- },
- {
- internal_id: "",
- id: "chat_messages",
- type: FeatureType.Metered,
- config: {
- filters: [
- {
- value: ["chat_messages"],
- property: "",
- operator: "",
- },
- ],
- aggregate: {
- type: AggregateType.Count,
- },
- usage_type: FeatureUsageType.Single,
- display: {
- singular: "chat message",
- plural: "chat messages",
- },
- },
- },
-];
-
-export const createOnboardingProducts = async ({
- db,
- orgId,
-}: {
- db: DrizzleCli;
- orgId: string;
-}) => {
- const env = AppEnv.Sandbox;
- const insertedFeatures = defaultFeatures.map((f) => ({
- ...f,
- org_id: orgId,
- env,
- internal_id: generateId("fe"),
- name: keyToTitle(f.id),
- created_at: Date.now(),
- }));
-
- await db.insert(features).values(insertedFeatures as any);
-
- const defaultProducts = [
- {
- id: "free_example",
- name: "Free (Example)",
- env: AppEnv.Sandbox,
- is_default: true,
- entitlements: [
- {
- internal_feature_id: insertedFeatures[1].internal_id,
- feature_id: insertedFeatures[1].id,
- allowance: 10,
- interval: EntInterval.Month,
- allowance_type: AllowanceType.Fixed,
- },
- ],
- prices: [],
- },
- {
- id: "pro_example",
- name: "Pro (Example)",
- env: AppEnv.Sandbox,
- is_default: false,
- entitlements: [
- {
- internal_feature_id: insertedFeatures[0].internal_id,
- feature_id: insertedFeatures[0].id,
- },
- {
- internal_feature_id: insertedFeatures[1].internal_id,
- feature_id: insertedFeatures[1].id,
- allowance_type: AllowanceType.Unlimited,
- },
- ],
- prices: [
- {
- name: "Monthly",
- config: {
- type: PriceType.Fixed,
- amount: 20.5,
- interval: BillingInterval.Month,
- },
- },
- ],
- },
- ];
-
- const batchInsert = [];
- for (const product of defaultProducts) {
- const insertProduct = async (product: any) => {
- const internalProductId = generateId("pr");
-
- await db.insert(products).values({
- ...product,
- internal_id: internalProductId,
- org_id: orgId,
- env,
- group: "",
- is_add_on: false,
- created_at: Date.now(),
- version: 1,
- });
-
- for (const entitlement of product.entitlements) {
- await db.insert(entitlements).values({
- ...entitlement,
- id: generateId("en"),
- org_id: orgId,
- env,
- created_at: Date.now(),
- });
- }
-
- for (const price of product.prices) {
- await db.insert(prices).values({
- ...price,
- id: generateId("pr"),
- internal_product_id: internalProductId,
- created_at: Date.now(),
- org_id: orgId,
- });
- }
- };
-
- batchInsert.push(insertProduct(product));
- }
-
- await Promise.all(batchInsert);
-};
diff --git a/server/src/internal/orgs/onboarding/parseChatFeatures.ts b/server/src/internal/orgs/onboarding/parseChatFeatures.ts
deleted file mode 100644
index 9acbf0ac4..000000000
--- a/server/src/internal/orgs/onboarding/parseChatFeatures.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import {
- AggregateType,
- AppEnv,
- ChatFeatureCreditSchema,
- type ChatResultFeature,
- type CreditSystemConfig,
- FeatureType,
- FeatureUsageType,
- type MeteredConfig,
-} from "@autumn/shared";
-import { validateMeteredConfig } from "@/internal/features/featureUtils.js";
-import { constructFeature } from "@/internal/features/utils/constructFeatureUtils.js";
-import RecaseError from "@/utils/errorUtils.js";
-import { keyToTitle } from "@/utils/genUtils.js";
-
-const validateFeatures = (features: ChatResultFeature[]) => {
- features.forEach((feature) => {
- if (feature.type == "credit_system") {
- if (!feature.credit_schema) {
- throw new RecaseError({
- message: "Credit schema is required for credit system",
- code: "invalid_chat_feature",
- statusCode: 400,
- });
- } else {
- feature.credit_schema.forEach((item) => {
- if (!ChatFeatureCreditSchema.safeParse(item).success) {
- throw new RecaseError({
- message: "Invalid credit schema",
- code: "invalid_chat_feature",
- statusCode: 400,
- });
- }
-
- const meteredFeature = features.some(
- (m) => m.id == item.metered_feature_id && m.id != feature.id,
- );
- if (!meteredFeature) {
- throw new RecaseError({
- message: `Metered feature ${item.metered_feature_id} not found`,
- code: "invalid_chat_feature",
- statusCode: 400,
- });
- }
- });
- }
- }
- });
-};
-
-export const parseChatResultFeatures = ({
- features,
- orgId,
-}: {
- features: ChatResultFeature[];
- orgId: string;
-}) => {
- validateFeatures(features);
-
- return features.map((feature) => {
- const type =
- feature.type == "boolean"
- ? FeatureType.Boolean
- : feature.type == "credit_system"
- ? FeatureType.CreditSystem
- : FeatureType.Metered;
-
- let config: CreditSystemConfig | MeteredConfig | undefined;
- if (type == FeatureType.CreditSystem) {
- config = {
- schema: feature.credit_schema!.map((item) => ({
- feature_amount: 1,
- metered_feature_id: item.metered_feature_id,
- credit_amount: item.credit_cost,
- })),
- usage_type: FeatureUsageType.Single,
- };
- } else if (type == FeatureType.Metered) {
- config = validateMeteredConfig({
- usage_type: feature.type as FeatureUsageType,
- filters: [
- {
- property: "",
- operator: "",
- value: [],
- },
- ],
- aggregate: { type: AggregateType.Sum, property: "value" },
- });
- }
-
- const backendFeat = constructFeature({
- id: feature.id,
- name: keyToTitle(feature.id),
- type,
- env: AppEnv.Sandbox,
- config,
- orgId: orgId,
- display: feature.display,
- });
-
- return backendFeat;
- });
-};
diff --git a/server/src/internal/orgs/onboarding/parseChatProducts.ts b/server/src/internal/orgs/onboarding/parseChatProducts.ts
deleted file mode 100644
index 55162fa4f..000000000
--- a/server/src/internal/orgs/onboarding/parseChatProducts.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import {
- AppEnv,
- CreateProductV2ParamsSchema,
- type Entitlement,
- type Feature,
- type Price,
- type Product,
- type ProductV2,
-} from "@autumn/shared";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { handleNewProductItems } from "@/internal/products/product-items/productItemUtils/handleNewProductItems.js";
-import { constructProduct } from "@/internal/products/productUtils.js";
-
-export const parseChatProducts = async ({
- db,
- logger,
- features,
- orgId,
- chatProducts,
-}: {
- db: DrizzleCli;
- logger: any;
- features: Feature[];
- orgId: string;
- chatProducts: ProductV2[];
-}) => {
- const products: Product[] = [];
-
- const allPrices: Price[] = [];
- const allEnts: Entitlement[] = [];
-
- let currentFeatures = features;
- for (const product of chatProducts) {
- const backendProduct: Product = constructProduct({
- productData: CreateProductV2ParamsSchema.parse({
- ...product,
- }),
- orgId,
- env: AppEnv.Sandbox,
- });
-
- const {
- prices,
- entitlements,
- features: updatedFeatures,
- } = await handleNewProductItems({
- db,
- curPrices: [],
- curEnts: [],
- newItems: product.items,
- product: backendProduct,
- features: currentFeatures,
- saveToDb: false,
- isCustom: false,
- logger,
- });
-
- currentFeatures = updatedFeatures;
- products.push(backendProduct);
- allPrices.push(...prices);
-
- allEnts.push(
- ...entitlements.map((ent) => {
- return ent;
- }),
- );
- }
-
- return { products, prices: allPrices, ents: allEnts };
-};
diff --git a/server/src/internal/platform/platformLegacy/platformRouter.ts b/server/src/internal/platform/platformLegacy/platformRouter.ts
deleted file mode 100644
index 7b3b0d26c..000000000
--- a/server/src/internal/platform/platformLegacy/platformRouter.ts
+++ /dev/null
@@ -1,322 +0,0 @@
-import {
- AppEnv,
- member,
- type Organization,
- organizations,
- type StripeConfig,
- user as userTable,
-} from "@autumn/shared";
-import { Autumn } from "autumn-js";
-import { generateId } from "better-auth";
-import { and, eq } from "drizzle-orm";
-import { type NextFunction, Router } from "express";
-import { z } from "zod";
-import { createKey } from "@/internal/dev/api-keys/apiKeyUtils.js";
-import { handleStripeSecretKey } from "@/internal/orgs/orgUtils/handleStripeSecretKey";
-import { shouldReconnectStripe } from "@/internal/orgs/orgUtils.js";
-import { afterOrgCreated } from "@/utils/authUtils/afterOrgCreated.js";
-import type { ExtendedRequest } from "@/utils/models/Request.js";
-import { routeHandler } from "@/utils/routerUtils.js";
-
-const platformRouter = Router();
-
-const platformAuthMiddleware = async (
- req: any,
- res: any,
- next: NextFunction,
-) => {
- if (!process.env.AUTUMN_SECRET_KEY) next();
-
- try {
- const autumn = new Autumn();
- const { data, error } = await autumn.check({
- customer_id: req.org.id,
- feature_id: "platform",
- });
-
- if (error) {
- throw error;
- }
-
- if (!data?.allowed) {
- res.status(403).json({
- message:
- "You're not allowed to access the platform API. Please contact hey@useautumn.com to request access!",
- code: "not_allowed",
- });
- return;
- }
- next();
- } catch (error) {
- req.logger.error(`Failed to check if org is allowed to access platform`, {
- error,
- });
- res.status(500).json({
- message: "Failed to check if org is allowed to access platform",
- code: "internal_error",
- });
- }
-};
-
-platformRouter.use(platformAuthMiddleware);
-
-const ExchangeSchema = z.object({
- // organization_name: z.string().nullish(),
- // organization_slug: z.string().nullish(),
- organization: z
- .object({
- name: z.string().nonempty(),
- slug: z.string().nonempty(),
- })
- .nullish(),
- email: z.string().regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
- stripe_test_key: z.string().nonempty().optional(),
- stripe_live_key: z.string().nonempty().optional(),
-});
-
-platformRouter.post("/exchange", (req: any, res: any) =>
- routeHandler({
- req,
- res,
- action: "exchange",
- handler: async (req: ExtendedRequest, res: any) => {
- const { organization, email, stripe_test_key, stripe_live_key } =
- req.body;
-
- const { db, logger } = req;
-
- ExchangeSchema.parse({
- organization,
- email,
- stripe_test_key,
- stripe_live_key,
- });
-
- if (!stripe_test_key && !stripe_live_key) {
- res.status(400).json({
- message: "Either stripe_test_key or stripe_live_key is required",
- code: "invalid_request",
- });
- return;
- }
-
- // 1. Check if user with this email already exists
- let user = await db.query.user.findFirst({
- where: eq(userTable.email, email),
- });
-
- if (!user) {
- [user] = await db
- .insert(userTable)
- .values({
- id: generateId(),
- name: "",
- email,
- emailVerified: true,
- createdAt: new Date(),
- updatedAt: new Date(),
- role: "user",
- banned: false,
- banReason: null,
- banExpires: null,
- createdBy: req.org.id,
- })
- .returning();
- }
-
- logger.info(`User found / created: ${user.id} (${email})`);
-
- let org: Organization;
-
- // let membership = await db.query.member.findFirst({
- // with: {
- // organization: true,
- // },
- // where: and(
- // eq(member.userId, user.id!),
- // eq(member.role, "owner"),
- // organization_slug
- // ? eq(organizations.slug, organization_slug)
- // : undefined,
- // eq(organizations.created_by, req.org.id)
- // ),
- // });
- const orgSlug = organization?.slug
- ? `${organization.slug}_${req.org.id}`
- : undefined;
- const data = await db
- .select()
- .from(member)
- .innerJoin(organizations, eq(member.organizationId, organizations.id))
- .where(
- and(
- eq(member.userId, user.id!),
- eq(member.role, "owner"),
- orgSlug ? eq(organizations.slug, orgSlug) : undefined,
- eq(organizations.created_by, req.org.id),
- ),
- );
-
- const membership = data.length > 0 ? data[0] : null;
-
- if (!membership) {
- logger.info(`Connected to Stripe`);
-
- // 2. Create org
- const orgId = generateId();
-
- [org] = (await db
- .insert(organizations)
- .values({
- id: orgId,
- slug: orgSlug
- ? orgSlug
- : `platform_org_${Math.floor(10000000 + Math.random() * 90000000)}`,
-
- name: organization?.name || `Platform Org (${req.org.id})`,
- logo: "",
- createdAt: new Date(),
- metadata: "",
- created_by: req.org.id,
- })
- .returning()) as [Organization];
-
- await db.insert(member).values({
- id: generateId(),
- organizationId: orgId,
- userId: user.id!,
- role: "owner",
- createdAt: new Date(),
- });
-
- await afterOrgCreated({ org, user, createStripeAccount: false });
- } else {
- // org = (await db.query.organizations.findFirst({
- // where: eq(organizations.id, membership.organizationId),
- // })) as Organization;
- org = membership.organizations as Organization;
- }
-
- let sandboxKey: string | undefined;
- let prodKey: string | undefined;
-
- let finalStripeConfig: StripeConfig = {};
- let defaultCurrency = org.default_currency || "usd";
-
- // Connect stripe if not exists...
- if (stripe_test_key) {
- const reconnectStripe = await shouldReconnectStripe({
- org,
- env: AppEnv.Sandbox,
- stripeKey: stripe_test_key,
- logger: req.logger,
- });
-
- if (reconnectStripe) {
- const {
- test_api_key,
- test_webhook_secret,
- defaultCurrency: newDefaultCurrency,
- } = await handleStripeSecretKey({
- orgId: org.id,
- secretKey: stripe_test_key,
- env: AppEnv.Sandbox,
- });
-
- finalStripeConfig = {
- ...finalStripeConfig,
- test_api_key,
- test_webhook_secret,
- };
-
- if (!defaultCurrency) {
- defaultCurrency = newDefaultCurrency || "usd";
- }
- }
-
- sandboxKey = await createKey({
- db,
- orgId: org.id,
- env: AppEnv.Sandbox,
- name: "Platform API Key",
- prefix: "am_sk_test",
- meta: {},
- });
- }
-
- if (stripe_live_key) {
- const reconnectStripe = await shouldReconnectStripe({
- org,
- env: AppEnv.Live,
- stripeKey: stripe_live_key,
- logger: req.logger,
- });
-
- if (reconnectStripe) {
- console.log("Reconnecting stripe live");
- const {
- live_api_key,
- live_webhook_secret,
- defaultCurrency: newDefaultCurrency,
- } = await handleStripeSecretKey({
- orgId: org.id,
- secretKey: stripe_live_key,
- env: AppEnv.Live,
- });
-
- finalStripeConfig = {
- ...finalStripeConfig,
- live_api_key,
- live_webhook_secret,
- };
-
- if (!defaultCurrency) {
- defaultCurrency = newDefaultCurrency || "usd";
- }
- }
-
- prodKey = await createKey({
- db,
- orgId: org.id,
- env: AppEnv.Live,
- name: "Platform API Key",
- prefix: "am_sk_live",
- meta: {},
- });
- }
-
- // if (!org.stripe_config?.success_url) {
- // finalStripeConfig.success_url = `https://useautumn.com`;
- // }
-
- await db
- .update(organizations)
- .set({
- default_currency: defaultCurrency,
- stripe_connected: true,
- stripe_config: {
- ...org.stripe_config,
- ...finalStripeConfig,
- } as StripeConfig,
- })
- .where(eq(organizations.id, org.id));
- res.status(200).json({
- // org: {
- // id: org.id,
- // slug: org.slug,
- // name: org.name,
- // },
- // user: {
- // id: user.id!,
- // email,
- // },
- api_keys: {
- sandbox: sandboxKey,
- production: prodKey,
- },
- });
- },
- }),
-);
-
-export { platformRouter };
diff --git a/server/src/internal/products/entitlements/copyEntUtils.ts b/server/src/internal/products/entitlements/copyEntUtils.ts
deleted file mode 100644
index 95040e2b4..000000000
--- a/server/src/internal/products/entitlements/copyEntUtils.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { Entitlement } from "@autumn/shared";
-import { entitlements } from "@models/productModels/entModels/entTable";
-import { eq } from "drizzle-orm";
-import type { DrizzleCli } from "@/db/initDrizzle";
-import { generateId } from "../../../utils/genUtils";
-
-export const copyEnt = async ({
- db,
- entId,
- isCustom = true,
- internalProductId,
-}: {
- db: DrizzleCli;
- entId: string;
- isCustom?: boolean;
- internalProductId?: string;
-}) => {
- const ent = await db.query.entitlements.findFirst({
- where: eq(entitlements.id, entId),
- });
-
- let newEnt = structuredClone(ent!) as Entitlement;
-
- newEnt = {
- ...newEnt,
- id: generateId("ent"),
- created_at: Date.now(),
- is_custom: isCustom ?? newEnt.is_custom,
- internal_product_id: internalProductId || newEnt.internal_product_id,
- };
-
- return newEnt;
-};
diff --git a/server/src/internal/products/handlers/handleListProductsBeta.ts b/server/src/internal/products/handlers/handleListProductsBeta.ts
deleted file mode 100644
index a3a732a10..000000000
--- a/server/src/internal/products/handlers/handleListProductsBeta.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-// import { ProductService } from "@/internal/products/ProductService.js";
-// import type {
-// ExtendedRequest,
-// ExtendedResponse,
-// } from "@/utils/models/Request.js";
-// import { routeHandler } from "@/utils/routerUtils.js";
-// import { CusService } from "../../customers/CusService.js";
-// import { getProductResponse } from "../productUtils/productResponseUtils/getProductResponse.js";
-// import { sortFullProducts } from "../productUtils/sortProductUtils.js";
-
-// // biome-ignore lint/suspicious/noExplicitAny: alright buddy WRAP it up 👉🚪
-// export const handleListProductsBeta = async (req: any, res: any) =>
-// routeHandler({
-// req,
-// res,
-// action: "list products v2 (beta)",
-// handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
-// const { org, features, env, db } = req;
-// const customerId = req.query.customer_id;
-// const entityId = req.query.entity_id as string | undefined;
-// const includeAll = req.query.include_archived as unknown as boolean;
-
-// const [products, customer] = await Promise.all([
-// ProductService.listFull({
-// db,
-// orgId: org.id,
-// env,
-// archived: includeAll ? undefined : false,
-// }),
-// (async () => {
-// if (!customerId) {
-// return undefined;
-// }
-
-// return await CusService.getFull({
-// db,
-// idOrInternalId: customerId as string,
-// orgId: org.id,
-// env,
-// entityId: entityId as string,
-// withEntities: true,
-// withSubs: true,
-// });
-// })(),
-// ]);
-
-// if (req.query.v1_schema === "true") {
-// res.status(200).json({
-// list: products,
-// });
-// return;
-// }
-
-// sortFullProducts({ products });
-
-// const batchResponse = [];
-// for (const p of products) {
-// batchResponse.push(
-// getProductResponse({
-// product: p,
-// features,
-// currency: org.default_currency || undefined,
-// db,
-// fullCus: customer ? customer : undefined,
-// }),
-// );
-// }
-
-// const productResponse = await Promise.all(batchResponse);
-
-// res.status(200).json({
-// list: productResponse,
-// });
-// },
-// });
diff --git a/server/src/internal/products/handlers/handlePlanHasCustomers.ts b/server/src/internal/products/handlers/handlePlanHasCustomers.ts
deleted file mode 100644
index ad04a42ca..000000000
--- a/server/src/internal/products/handlers/handlePlanHasCustomers.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { ProductNotFoundError, productsAreSame } from "@autumn/shared";
-import { routeHandler } from "../../../utils/routerUtils.js";
-import { CusProductService } from "../../customers/cusProducts/CusProductService.js";
-import { ProductService } from "../ProductService.js";
-
-export const handlePlanHasCustomers = (req: any, res: any) =>
- routeHandler({
- req,
- res,
- action: "Get product has customers",
- handler: async (req: any, res: any) => {
- const { product_id } = req.params;
- const { db, features } = req;
-
- const product = await ProductService.getFull({
- db,
- idOrInternalId: product_id,
- orgId: req.orgId,
- env: req.env,
- });
-
- if (!product) {
- throw new ProductNotFoundError({ productId: product_id });
- }
-
- const cusProductsCurVersion =
- await CusProductService.getByInternalProductId({
- db,
- internalProductId: product.internal_id,
- });
-
- const { itemsSame, freeTrialsSame } = productsAreSame({
- newProductV2: req.body,
- curProductV1: product,
- features,
- });
-
- const productSame = itemsSame && freeTrialsSame;
-
- res.status(200).json({
- current_version: product.version,
- will_version: !productSame && cusProductsCurVersion.length > 0,
- archived: product.archived,
- });
- },
- });
diff --git a/server/src/internal/products/internalHandlers/handleHasEntityFeatureId.ts b/server/src/internal/products/internalHandlers/handleHasEntityFeatureId.ts
deleted file mode 100644
index 3ecd964ca..000000000
--- a/server/src/internal/products/internalHandlers/handleHasEntityFeatureId.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { createRoute } from "@/honoMiddlewares/routeHandler";
-import { EntitlementService } from "@/internal/products/entitlements/EntitlementService";
-
-/**
- * GET /products/has_entity_feature_id
- * Used by: vite/src/views/products/plan/hooks/useHasEntityFeatureId.ts
- */
-export const handleHasEntityFeatureId = createRoute({
- handler: async (c) => {
- const { db, org, env } = c.get("ctx");
-
- const hasEntityFeatureId = await EntitlementService.hasEntityFeatureId({
- db,
- orgId: org.id,
- env,
- });
-
- return c.json({ hasEntityFeatureId });
- },
-});
diff --git a/server/src/internal/products/prices/priceUtils/arrearProratedUtils/getUsageDiffLineItem.ts b/server/src/internal/products/prices/priceUtils/arrearProratedUtils/getUsageDiffLineItem.ts
deleted file mode 100644
index fa72eceec..000000000
--- a/server/src/internal/products/prices/priceUtils/arrearProratedUtils/getUsageDiffLineItem.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import {
- type FullEntitlement,
- getFeatureInvoiceDescription,
- type Organization,
- type Price,
- type Product,
- shouldProrate,
- type UsagePriceConfig,
- usageToFeatureName,
-} from "@autumn/shared";
-import { Decimal } from "decimal.js";
-import { constructPreviewItem } from "@/internal/invoices/previewItemUtils/constructPreviewItem.js";
-
-export const getUsageDiffLineItem = ({
- prevBalance,
- newBalance,
- org,
- price,
- newUsageAmount,
- ent,
- product,
-}: {
- prevBalance: number;
- newBalance: number;
- org: Organization;
- price: Price;
- newUsageAmount: number;
- ent: FullEntitlement;
- product: Product;
-}) => {
- const usageDiff = new Decimal(prevBalance).sub(newBalance).abs().toNumber();
- const isIncrease = newBalance <= prevBalance;
- const willProrate = isIncrease
- ? shouldProrate(price.proration_config?.on_increase)
- : shouldProrate(price.proration_config?.on_decrease);
-
- let description = getFeatureInvoiceDescription({
- feature: ent.feature,
- usage: usageDiff,
- billingUnits: (price.config as UsagePriceConfig).billing_units,
- });
-
- if (isIncrease) {
- description = `${product.name} - Additional ${description}`;
- } else {
- description = `Unused ${product.name} - ${description}`;
- }
-
- let previewLineItem = constructPreviewItem({
- price,
- org,
- amount: newUsageAmount,
- description,
- });
-
- if (!isIncrease && !willProrate) {
- const featureName = usageToFeatureName({
- usage: usageDiff,
- feature: ent.feature,
- });
-
- previewLineItem = constructPreviewItem({
- priceStr: `${usageDiff} free ${featureName}`,
- price,
- org,
- // description: `${product.name} - ${usageDiff} free ${featureName}`,
- description: `${product.name} - ${featureName}`,
- });
- }
-
- return previewLineItem;
-};
diff --git a/server/src/internal/products/prices/priceUtils/constructPriceUtils.ts b/server/src/internal/products/prices/priceUtils/constructPriceUtils.ts
deleted file mode 100644
index 660cb9a54..000000000
--- a/server/src/internal/products/prices/priceUtils/constructPriceUtils.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import {
- type FullProduct,
- PriceType,
- stripeToAtmnAmount,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import { subItemToAutumnInterval } from "@/external/stripe/utils.js";
-import { constructPrice } from "../priceUtils.js";
-
-export const subItemToFixedPrice = ({
- subItem,
- product,
- basePrice,
-}: {
- subItem: Stripe.SubscriptionItem;
- product: FullProduct;
- basePrice?: number;
-}) => {
- const { price } = subItem;
-
- const { interval, intervalCount } = subItemToAutumnInterval(subItem);
-
- const atmnAmount = stripeToAtmnAmount({
- amount: price.unit_amount || 0,
- currency: price.currency,
- });
-
- return constructPrice({
- internalProductId: product.internal_id,
- isCustom: true,
- orgId: product.org_id,
- fixedConfig: {
- type: PriceType.Fixed,
- amount: basePrice || atmnAmount,
- interval,
- interval_count: intervalCount,
- stripe_price_id: price.id,
-
- stripe_product_id: undefined,
- feature_id: undefined,
- internal_feature_id: undefined,
- },
- });
-};
diff --git a/server/src/internal/products/prices/priceUtils/copyPriceUtils.ts b/server/src/internal/products/prices/priceUtils/copyPriceUtils.ts
deleted file mode 100644
index 6eff9ad97..000000000
--- a/server/src/internal/products/prices/priceUtils/copyPriceUtils.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import {
- type FixedPriceConfig,
- type Price,
- PriceType,
- prices,
- type UsagePriceConfig,
-} from "@autumn/shared";
-import { eq } from "drizzle-orm";
-import type { DrizzleCli } from "@/db/initDrizzle.js";
-import { generateId } from "@/utils/genUtils.js";
-import { PriceService } from "../PriceService.js";
-
-export const copyPrice = async ({
- db,
- priceId,
- usagePriceConfig,
- fixedPriceConfig,
- isCustom,
- withPrevConfig = true,
-}: {
- db: DrizzleCli;
- priceId: string;
- usagePriceConfig?: Partial;
- fixedPriceConfig?: Partial;
- isCustom?: boolean;
- withPrevConfig?: boolean;
-}) => {
- const price = (await db.query.prices.findFirst({
- where: eq(prices.id, priceId),
- })) as Price;
-
- let newPrice = structuredClone(price);
-
- newPrice = {
- ...newPrice,
- id: generateId("pr"),
- created_at: Date.now(),
- is_custom: isCustom || newPrice.is_custom,
- };
-
- if (fixedPriceConfig) {
- newPrice = {
- ...newPrice,
- entitlement_id: null,
- config: {
- ...(withPrevConfig ? (newPrice.config as FixedPriceConfig) : {}),
- type: PriceType.Fixed,
- ...fixedPriceConfig,
- } as FixedPriceConfig,
- };
- }
-
- if (usagePriceConfig) {
- newPrice = {
- ...newPrice,
- config: {
- ...(newPrice.config as UsagePriceConfig),
- ...usagePriceConfig,
- },
- };
- }
-
- return newPrice;
-};
diff --git a/server/src/internal/products/prices/priceUtils/findPriceUtils.ts b/server/src/internal/products/prices/priceUtils/findPriceUtils.ts
index 9017f3d4e..c36eb61d3 100644
--- a/server/src/internal/products/prices/priceUtils/findPriceUtils.ts
+++ b/server/src/internal/products/prices/priceUtils/findPriceUtils.ts
@@ -115,13 +115,3 @@ export const priceToProduct = ({
(p: Product) => p.internal_id == price.internal_product_id,
);
};
-
-const filterByBillingType = ({
- prices,
- billingType,
-}: {
- prices: Price[];
- billingType: BillingType;
-}) => {
- return prices.filter((p) => getBillingType(p.config) == billingType);
-};
diff --git a/server/src/internal/products/product-items/productItemUtils/addIdsToProductItems.ts b/server/src/internal/products/product-items/productItemUtils/addIdsToProductItems.ts
deleted file mode 100644
index ff916b4ad..000000000
--- a/server/src/internal/products/product-items/productItemUtils/addIdsToProductItems.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import {
- BillingType,
- type EntInterval,
- type EntitlementWithFeature,
- entIntervalToValue,
- entToPrice,
- getBillingType,
- isFixedPrice,
- itemToEntInterval,
- type Price,
- type ProductItem,
- UsageModel,
-} from "@autumn/shared";
-
-import { isFeatureItem, isFeaturePriceItem } from "./getItemType.js";
-
-export const addIdsToProductItems = ({
- items,
- curPrices,
- curEnts,
-}: {
- items: ProductItem[];
- curPrices: Price[];
- curEnts: EntitlementWithFeature[];
-}) => {
- // 1. Handle base price
- const entIds = new Set();
- const priceIds = new Set();
-
- const basePriceItem = items.find((item) => item.price_id === null);
- const baseCurPrice = curPrices.find((price) => isFixedPrice(price));
-
- if (basePriceItem && baseCurPrice) {
- basePriceItem.price_id = baseCurPrice.id;
- basePriceItem.price_config = baseCurPrice.config;
- priceIds.add(baseCurPrice.id);
- }
-
- // Feature items
- const featureIdToItems: Record = {};
- const featureIdToCurEnts: Record = {};
- for (const item of items) {
- if (!item.feature_id) continue;
-
- if (!featureIdToItems[item.feature_id]) {
- featureIdToItems[item.feature_id] = [item];
- } else {
- featureIdToItems[item.feature_id].push(item);
- }
-
- featureIdToItems[item.feature_id].sort((a, b) => {
- if (isFeatureItem(a) && isFeatureItem(b)) return 0;
-
- // If there's a price, go first,
- if (isFeaturePriceItem(a) && !isFeaturePriceItem(b)) return -1;
- if (!isFeaturePriceItem(a) && isFeaturePriceItem(b)) return 1;
-
- // Sort by interval
- const aIntervalValue = entIntervalToValue(
- itemToEntInterval({ item: a }) as EntInterval,
- );
- const bIntervalValue = entIntervalToValue(
- itemToEntInterval({ item: b }) as EntInterval,
- );
- if (!aIntervalValue.eq(bIntervalValue)) {
- return aIntervalValue.sub(bIntervalValue).toNumber();
- }
-
- // If it's pay per use usage model, go first,
- if (
- a.usage_model === UsageModel.PayPerUse &&
- b.usage_model !== UsageModel.PayPerUse
- )
- return -1;
- if (
- a.usage_model !== UsageModel.PayPerUse &&
- b.usage_model === UsageModel.PayPerUse
- )
- return 1;
-
- return 0;
- });
- }
-
- for (const curEnt of curEnts) {
- if (!curEnt.feature_id) continue;
- if (!featureIdToCurEnts[curEnt.feature_id]) {
- featureIdToCurEnts[curEnt.feature_id] = [curEnt];
- } else {
- featureIdToCurEnts[curEnt.feature_id].push(curEnt);
- }
-
- featureIdToCurEnts[curEnt.feature_id].sort((a, b) => {
- // 1. a hasprice
- const aPrice = entToPrice({ ent: a, prices: curPrices });
- const bPrice = entToPrice({ ent: b, prices: curPrices });
-
- if (!aPrice && !bPrice) return 0;
- if (aPrice && !bPrice) return -1;
- if (!aPrice && bPrice) return 1;
-
- const aIntervalValue = entIntervalToValue(a.interval, a.interval_count);
- const bIntervalValue = entIntervalToValue(b.interval, b.interval_count);
- if (!aIntervalValue.eq(bIntervalValue)) {
- return aIntervalValue.sub(bIntervalValue).toNumber();
- }
-
- const isPrepaid =
- getBillingType(aPrice!.config) === BillingType.UsageInAdvance;
- const isPrepaidB =
- getBillingType(bPrice!.config) === BillingType.UsageInAdvance;
- if (isPrepaid && !isPrepaidB) return -1;
- if (!isPrepaid && isPrepaidB) return 1;
-
- return 0;
- });
- }
-
- for (const featureId in featureIdToItems) {
- for (let i = 0; i < featureIdToItems[featureId].length; i++) {
- // featureIdToItems[featureId][i].id = generateId("item");
- const entLength = featureIdToCurEnts[featureId]?.length ?? 0;
- if (entLength > i) {
- const ent = featureIdToCurEnts[featureId]?.[i];
- if (ent && !entIds.has(ent.id)) {
- entIds.add(ent.id);
- featureIdToItems[featureId][i].entitlement_id = ent.id;
- }
- }
- }
- }
-};
diff --git a/server/src/internal/products/product-items/productItemUtils/matchItemUtils.ts b/server/src/internal/products/product-items/productItemUtils/matchItemUtils.ts
deleted file mode 100644
index 4255e5fad..000000000
--- a/server/src/internal/products/product-items/productItemUtils/matchItemUtils.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Entitlement, Price, ProductItem } from "@autumn/shared";
-
-export const matchItemToPriceAndEnt = ({
- item,
- curPrices,
- curEnts,
-}: {
- item: ProductItem;
- curPrices: Price[];
- curEnts: Entitlement[];
-}) => {
- // return items.find(
- // (i) => i.feature_id === item.feature_id && i.interval === item.interval,
- // );
-};
diff --git a/server/src/internal/users/userRouter.ts b/server/src/internal/users/userRouter.ts
deleted file mode 100644
index a509585a8..000000000
--- a/server/src/internal/users/userRouter.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Router } from "express";
-
-export const userRouter: Router = Router();
-
-userRouter.get("", async (req: any, res) => {
- res.status(200).json({ userId: req.userId });
-});
diff --git a/server/src/test.ts b/server/src/test.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/utils/authUtils.ts b/server/src/utils/authUtils.ts
deleted file mode 100644
index 7827bed89..000000000
--- a/server/src/utils/authUtils.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export const parseAuthHeader = (req: any) => {
- const authHeader =
- req.headers["Authorization"] || req.headers["authorization"];
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
- return null;
- }
- const bearerToken = authHeader.split(" ")[1];
- return bearerToken;
-};
diff --git a/server/src/utils/cacheUtils/normalizeFromSchema.ts b/server/src/utils/cacheUtils/normalizeFromSchema.ts
deleted file mode 100644
index d41d6409a..000000000
--- a/server/src/utils/cacheUtils/normalizeFromSchema.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-import type { z } from "zod/v4";
-
-/**
- * Normalize data from Redis cache to fix cjson quirks.
- *
- * Different Redis providers handle cjson.encode differently:
- * - Empty objects {} may become empty arrays []
- * - null values may become undefined
- * - Empty arrays [] may become empty objects {}
- *
- * This function dynamically normalizes data based on a Zod schema structure.
- */
-
-/**
- * Get the type string from a Zod schema's _def
- */
-const getSchemaType = (schema: z.ZodTypeAny): string | undefined => {
- return (schema as any)._def?.type;
-};
-
-/**
- * Unwrap optional/nullable/effects to get the inner schema
- */
-const unwrapSchema = (schema: z.ZodTypeAny): z.ZodTypeAny => {
- const type = getSchemaType(schema);
- const def = (schema as any)._def;
-
- // Unwrap optional → innerType
- if (type === "optional") {
- return unwrapSchema(def.innerType);
- }
-
- // Unwrap nullable → innerType
- if (type === "nullable") {
- return unwrapSchema(def.innerType);
- }
-
- // Unwrap effects (transform, refine, etc.) → schema
- if (type === "effects") {
- return unwrapSchema(def.schema);
- }
-
- // Unwrap default → innerType
- if (type === "default") {
- return unwrapSchema(def.innerType);
- }
-
- return schema;
-};
-
-/**
- * Check if schema allows null values (has .nullable() wrapper)
- */
-const isNullable = (schema: z.ZodTypeAny): boolean => {
- const type = getSchemaType(schema);
- const def = (schema as any)._def;
-
- if (type === "nullable") {
- return true;
- }
-
- // Check wrapped types
- if (type === "optional" || type === "effects" || type === "default") {
- const innerSchema = def.innerType || def.schema;
- return isNullable(innerSchema);
- }
-
- return false;
-};
-
-/**
- * Check if a value is an empty object (not an array)
- */
-const isEmptyObject = (value: unknown): boolean => {
- return (
- value !== null &&
- value !== undefined &&
- typeof value === "object" &&
- !Array.isArray(value) &&
- Object.keys(value).length === 0
- );
-};
-
-/**
- * Normalize Lua empty table to array
- * Lua cjson encodes empty tables as {} (object) instead of [] (array)
- */
-export const normalizeToArray = (value: unknown): string[] => {
- if (Array.isArray(value)) return value;
- if (value && typeof value === "object" && Object.keys(value).length === 0)
- return [];
- return [];
-};
-
-/**
- * Dynamically normalize data based on Zod schema structure.
- * Handles Redis cjson quirks by walking the schema and fixing data accordingly.
- */
-export const normalizeFromSchema = ({
- schema,
- data,
-}: {
- schema: z.ZodTypeAny;
- data: unknown;
-}): T => {
- // Step 1: Check if data is undefined and field is nullable → convert to null
- if (data === undefined && isNullable(schema)) {
- return null as T;
- }
-
- // Step 2: Unwrap schema to get the base type
- const unwrapped = unwrapSchema(schema);
- const type = getSchemaType(unwrapped);
-
- // Step 3: Handle each Zod type
-
- // RECORD (object with dynamic keys)
- if (type === "record") {
- // If data is an empty array, convert to empty object
- if (Array.isArray(data) && data.length === 0) {
- return {} as T;
- }
-
- // If data is a valid object, recursively normalize values
- if (data && typeof data === "object" && !Array.isArray(data)) {
- const valueSchema = (unwrapped as any)._def.valueType;
- const normalized: Record = {};
-
- for (const key in data as Record) {
- normalized[key] = normalizeFromSchema({
- schema: valueSchema,
- data: (data as Record)[key],
- });
- }
-
- return normalized as T;
- }
- }
-
- // ARRAY
- if (type === "array") {
- // If data is an empty object, convert to empty array
- if (isEmptyObject(data)) {
- return [] as T;
- }
-
- // If data is a valid array, recursively normalize items
- if (Array.isArray(data)) {
- const itemSchema = (unwrapped as any)._def.element;
- return data.map((item) =>
- normalizeFromSchema({ schema: itemSchema, data: item }),
- ) as T;
- }
- }
-
- // OBJECT (with defined shape)
- if (type === "object") {
- // If data is not an object, return as-is
- if (!data || typeof data !== "object" || Array.isArray(data)) {
- return data as T;
- }
-
- // Recursively normalize all fields in the object
- const shape = (unwrapped as any)._def.shape;
- const normalized: Record = {
- ...(data as Record),
- };
-
- for (const key in shape) {
- // Hardcoded fix: scheduled_subscriptions should be an array, never null/undefined
- if (
- key === "scheduled_subscriptions" &&
- (normalized[key] === undefined || normalized[key] === null)
- ) {
- normalized[key] = [];
- } else {
- normalized[key] = normalizeFromSchema({
- schema: shape[key],
- data: normalized[key],
- });
- }
- }
-
- return normalized as T;
- }
-
- // For all other types (string, number, boolean, etc.), return as-is
- return data as T;
-};
diff --git a/server/src/utils/checkUtils/allCusProductsOnSubFree.ts b/server/src/utils/checkUtils/allCusProductsOnSubFree.ts
deleted file mode 100644
index f84a8f1ea..000000000
--- a/server/src/utils/checkUtils/allCusProductsOnSubFree.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
- cusProductToPrices,
- type FullCustomer,
- isFreeProduct,
-} from "@autumn/shared";
-
-export const allCusProductsOnSubFree = ({
- fullCus,
- subId,
-}: {
- fullCus: FullCustomer;
- subId: string;
-}) => {
- const isFree = fullCus.customer_products.every((cp) => {
- const hasSubId = cp.subscription_ids?.includes(subId);
- if (!hasSubId) return true;
- const prices = cusProductToPrices({ cusProduct: cp });
- if (isFreeProduct({ prices })) {
- return true;
- }
- return false;
- });
-
- if (isFree) {
- return true;
- }
- return false;
-};
diff --git a/server/src/utils/checkUtils/checkCusProducts.ts b/server/src/utils/checkUtils/checkCusProducts.ts
deleted file mode 100644
index d450c2fa4..000000000
--- a/server/src/utils/checkUtils/checkCusProducts.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import {
- CusProductStatus,
- cusProductToPrices,
- type FullCusProduct,
- isFreeProduct,
-} from "@autumn/shared";
-import type { FullCustomer } from "../../../../shared/models/cusModels/fullCusModel";
-import { getRelatedCusPrice } from "../../internal/customers/cusProducts/cusEnts/cusEntUtils";
-import { isOneOff } from "../../internal/products/productUtils";
-import type { StateCheckResult } from "./stateCheckTypes";
-
-export const checkCusProducts = async ({
- fullCus,
- result,
-}: {
- fullCus: FullCustomer;
- result: StateCheckResult;
-}): Promise => {
- const cusProducts = fullCus.customer_products;
- for (const cusProduct of cusProducts) {
- const productName = cusProduct.product?.name || cusProduct.product_id;
-
- // Check: Scheduled products should have a main product
- if (cusProduct.status === CusProductStatus.Scheduled) {
- const mainCusProd = cusProducts.find(
- (cp: FullCusProduct) =>
- cp.product.group === cusProduct.product.group &&
- cp.id !== cusProduct.id &&
- cp.status !== CusProductStatus.Scheduled &&
- (cusProduct.internal_entity_id
- ? cusProduct.internal_entity_id === cp.internal_entity_id
- : true),
- );
-
- if (!mainCusProd) {
- result.passed = false;
- result.errors.push(
- `Scheduled product "${productName}" has no main product`,
- );
- result.checks.push({
- name: `Scheduled Product: ${productName}`,
- type: "customer_product_correctness",
- passed: false,
- message: "No main product found for scheduled product",
- });
- } else {
- result.checks.push({
- name: `Scheduled Product: ${productName}`,
- type: "customer_product_correctness",
- passed: true,
- });
- }
- }
-
- // Check: No duplicate non-add-on, non-one-off products in same group
- // One-off products (like t-shirts) can coexist with any other products
- const currentPrices = cusProductToPrices({ cusProduct });
- const isCurrentOneOff = isOneOff(currentPrices);
-
- if (
- !cusProduct.product.is_add_on &&
- !isCurrentOneOff &&
- cusProduct.status !== CusProductStatus.Scheduled
- ) {
- const group = cusProduct.product.group;
- const otherCusProd = cusProducts.find((cp: FullCusProduct) => {
- if (cp.product.group !== group) return false;
- if (cp.id === cusProduct.id) return false;
- if (cp.product.is_add_on) return false;
- if (cp.status === CusProductStatus.Scheduled) return false;
- if (cp.internal_entity_id !== cusProduct.internal_entity_id)
- return false;
-
- // Also skip one-off products
- const otherPrices = cusProductToPrices({ cusProduct: cp });
- if (isOneOff(otherPrices)) return false;
-
- return true;
- });
-
- if (otherCusProd) {
- result.passed = false;
- result.errors.push(
- `Duplicate products in group "${group}": "${productName}" and "${otherCusProd.product?.name}"`,
- );
- result.checks.push({
- name: `Group Uniqueness: ${productName}`,
- type: "group_uniqueness",
- passed: false,
- message: `Found duplicate: ${otherCusProd.product?.name}`,
- });
- } else {
- result.checks.push({
- name: `Group Uniqueness: ${productName}`,
- type: "group_uniqueness",
- passed: true,
- });
- }
- }
-
- // Check: Customer entitlements with usage_allowed should have related cus_price
- const prices = cusProductToPrices({ cusProduct });
- if (
- !isOneOff(prices) &&
- !isFreeProduct({ prices }) &&
- cusProduct.status !== CusProductStatus.Scheduled
- ) {
- for (const cusEnt of cusProduct.customer_entitlements || []) {
- const cusPrice = getRelatedCusPrice(cusEnt, cusProduct.customer_prices);
-
- if (cusEnt.usage_allowed && !cusPrice) {
- result.passed = false;
- result.errors.push(
- `Product "${productName}": Feature "${cusEnt.feature_id}" has usage_allowed but no related cus_price`,
- );
- result.checks.push({
- name: `Entitlement Price: ${cusEnt.feature_id}`,
- type: "entitlement_price_correctness",
- passed: false,
- message: "usage_allowed but no cus_price",
- });
- }
- }
- }
- }
-};
diff --git a/server/src/utils/checkUtils/checkCustomerCorrect.ts b/server/src/utils/checkUtils/checkCustomerCorrect.ts
deleted file mode 100644
index 1909cda70..000000000
--- a/server/src/utils/checkUtils/checkCustomerCorrect.ts
+++ /dev/null
@@ -1,601 +0,0 @@
-/** biome-ignore-all lint/suspicious/noExplicitAny: <> */
-import assert from "node:assert";
-import {
- ApiVersion,
- type AppEnv,
- CusProductStatus,
- cusProductToEnts,
- cusProductToPrices,
- cusProductToProduct,
- type FullCusProduct,
- type FullCustomer,
- isFixedPrice,
- isOneOffPrice,
- isPrepaidPrice,
- type Organization,
-} from "@autumn/shared";
-import type { DrizzleCli } from "@server/db/initDrizzle";
-import { priceToStripeItem } from "@server/external/stripe/priceToStripeItem/priceToStripeItem";
-import { isStripeSubscriptionCanceled } from "@server/external/stripe/subscriptions/utils/classifyStripeSubscriptionUtils.js";
-import {
- cusProductInPhase,
- logPhaseItems,
- similarUnix,
-} from "@server/internal/customers/attach/mergeUtils/phaseUtils/phaseUtils";
-import { ACTIVE_STATUSES } from "@server/internal/customers/cusProducts/CusProductService";
-import { getExistingUsageFromCusProducts } from "@server/internal/customers/cusProducts/cusEnts/cusEntUtils";
-import { getExistingCusProducts } from "@server/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts";
-import { getUniqueUpcomingSchedulePairs } from "@server/internal/customers/cusProducts/cusProductUtils/getUpcomingSchedules";
-import { PriceService } from "@server/internal/products/prices/PriceService";
-import {
- formatPrice,
- getPriceEntitlement,
- getPriceOptions,
-} from "@server/internal/products/prices/priceUtils";
-import { isFreeProduct } from "@server/internal/products/productUtils";
-import type Stripe from "stripe";
-import { formatUnixToDateTime, nullish } from "../genUtils";
-import { allCusProductsOnSubFree } from "./allCusProductsOnSubFree";
-import type { SubItemDetail } from "./stateCheckTypes";
-
-/** Error thrown when subscription items don't match expected items. Carries item details for debugging. */
-export class SubItemMismatchError extends Error {
- subId: string;
- actualItems: SubItemDetail[];
- expectedItems: SubItemDetail[];
-
- constructor({
- message,
- subId,
- actualItems,
- expectedItems,
- }: {
- message: string;
- subId: string;
- actualItems: SubItemDetail[];
- expectedItems: SubItemDetail[];
- }) {
- super(message);
- this.name = "SubItemMismatchError";
- this.subId = subId;
- this.actualItems = actualItems;
- this.expectedItems = expectedItems;
- }
-}
-
-const defaultApiVersion = ApiVersion.V1_2;
-
-const cusProductToSubIds = ({
- cusProducts,
-}: {
- cusProducts: FullCusProduct[];
-}) => {
- return [...new Set(cusProducts.flatMap((cp) => cp.subscription_ids || []))];
-};
-
-/** Converts raw items to SubItemDetail format with product/price names */
-const itemsToSubItemDetails = async ({
- items,
- db,
-}: {
- items: { price: string; quantity: number; stripeProdId?: string }[];
- db: DrizzleCli;
-}): Promise => {
- const priceIds = items.map((item) => item.price).filter(Boolean);
- const autumnPrices = await PriceService.getByStripeIds({
- db,
- stripePriceIds: priceIds,
- });
-
- return items.map((item) => {
- const autumnPrice = autumnPrices[item.price];
- return {
- priceId: item.price,
- quantity: item.quantity || 0,
- productName: autumnPrice?.product?.name,
- priceName: autumnPrice ? formatPrice({ price: autumnPrice }) : undefined,
- };
- });
-};
-
-const compareActualItems = async ({
- actualItems,
- expectedItems,
- type,
- fullCus,
- db,
- phaseStartsAt,
- subId,
-}: {
- actualItems: any[];
- expectedItems: any[];
- type: "sub" | "schedule";
- fullCus: FullCustomer;
- phaseStartsAt?: number;
- db: DrizzleCli;
- subId: string;
-}) => {
- /** Helper to throw SubItemMismatchError with item details */
- const throwMismatchError = async (message: string) => {
- const actualDetails = await itemsToSubItemDetails({
- items: actualItems,
- db,
- });
- const expectedDetails = await itemsToSubItemDetails({
- items: expectedItems,
- db,
- });
- throw new SubItemMismatchError({
- message,
- subId,
- actualItems: actualDetails,
- expectedItems: expectedDetails,
- });
- };
-
- let skippedCount = 0;
-
- for (const expectedItem of expectedItems) {
- let actualItem = actualItems.find((item: any) => {
- if (item.price === (expectedItem as any).price) return true;
-
- // If only one item, allow matching by stripe prod id
-
- // If prices match, allow item.stripeProdId to match...
-
- // if (item.stripeProdId == (expectedItem as any).stripeProdId) return true;
-
- return false;
- });
-
- if (isFixedPrice(expectedItem.autumnPrice) && !actualItem) {
- actualItem = actualItems.find((item: any) => {
- return item.stripeProdId === expectedItem.stripeProdId;
- });
- }
-
- if (!actualItem) {
- // Allow skipping if canSkip is true
- if (expectedItem.canSkip) {
- skippedCount++;
- continue;
- }
-
- const { autumnPrice: _, ...rest } = expectedItem;
- console.log(`(${type}) Missing item:`, rest);
-
- // Actual items
- console.log(`(${type}) Actual items (${actualItems.length}):`);
- await logPhaseItems({
- db,
- items: actualItems,
- withId: true,
- });
-
- console.log(`(${type}) Expected items (${expectedItems.length}):`);
- await logPhaseItems({
- db,
- items: expectedItems,
- });
-
- await throwMismatchError(
- `actual item should exist for price ${expectedItem.price}`,
- );
- }
-
- // team manager...
- if (actualItem.price !== "price_1RGod7JvAPTxxzlIEbN6ZnW1") {
- if (actualItem?.quantity !== (expectedItem as any).quantity) {
- if (phaseStartsAt) {
- console.log(
- `Phase starts at: ${formatUnixToDateTime(phaseStartsAt)}`,
- );
- }
-
- console.log("Actual items:");
- await logPhaseItems({
- db,
- items: actualItems,
- withId: true,
- });
-
- console.log("Expected items:");
- await logPhaseItems({
- db,
- items: expectedItems,
- });
-
- console.log(
- `Item quantity mismatch: ${actualItem?.quantity} !== ${expectedItem.quantity}`,
- );
-
- const price = await PriceService.getByStripeId({
- db,
- stripePriceId: expectedItem.price,
- });
- if (price) {
- console.log(
- `Autumn price:`,
- `${price?.product.name} - ${formatPrice({ price })}`,
- );
- }
-
- console.log("--------------------------------");
-
- await throwMismatchError(
- `actual items quantity (${actualItem?.quantity}) should be equals to ${expectedItem.quantity}`,
- );
- }
- }
- }
-
- if (actualItems.length !== expectedItems.length) {
- // Fallback: allow if length matches after accounting for skipped items
- const expectedItemsCount = expectedItems.length - skippedCount;
- if (actualItems.length !== expectedItemsCount) {
- console.log("Actual items:");
- await logPhaseItems({
- db,
- items: actualItems,
- });
-
- console.log("Expected items:");
- await logPhaseItems({
- db,
- items: expectedItems,
- });
-
- await throwMismatchError(
- `actual items length (${actualItems.length}) should be equals to expected items length (${expectedItems.length}) for sub ${subId}`,
- );
- }
- }
-};
-
-export const checkCusSubCorrect = async ({
- db,
- fullCus,
- subs,
- schedules,
- org,
- env,
-}: {
- db: DrizzleCli;
- fullCus: FullCustomer;
- subs: Stripe.Subscription[];
- schedules: Stripe.SubscriptionSchedule[];
- org: Organization;
- env: AppEnv;
-}) => {
- // 1. Only 1 sub ID available
- const cusProducts = fullCus.customer_products;
- const subIds = cusProductToSubIds({ cusProducts });
-
- for (const subId of subIds) {
- // Filter to cusProducts with this specific subId (use const to avoid cumulative filtering)
- const subCusProducts = cusProducts.filter((cp) =>
- cp.subscription_ids?.includes(subId!),
- );
-
- // Get the items that should be in the sub
- const supposedSubItems = [];
-
- const scheduleUnixes = getUniqueUpcomingSchedulePairs({
- cusProducts: subCusProducts,
- now: Date.now(),
- });
-
- const supposedPhases: any[] = scheduleUnixes.map((unix) => {
- return {
- start_date: unix, // milliseconds
- items: [],
- };
- });
-
- // console.log(`\n\nChecking sub correct`);
- const printCusProduct = false;
- if (printCusProduct) {
- console.log(`\n\nChecking sub correct`);
- }
-
- for (const cusProduct of subCusProducts) {
- const prices = cusProductToPrices({ cusProduct });
- const ents = cusProductToEnts({ cusProduct });
- const product = cusProductToProduct({ cusProduct });
-
- // Add to schedules
- const scheduleIndexes: number[] = [];
- const apiVersion = cusProduct.api_semver || defaultApiVersion;
-
- // if (isFreeProduct(product.prices)) {
- // assert(
- // cusProduct.subscription_ids?.length === 0,
- // `free product ${cusProduct.product.name} should have no subs`,
- // );
- // continue;
- // }
-
- if (printCusProduct) {
- console.log(
- `Cus product: ${cusProduct.product.name}, Status: ${cusProduct.status}, Entity ID: ${cusProduct.entity_id}`,
- );
- console.log(`Starts at: ${formatUnixToDateTime(cusProduct.starts_at)}`);
- }
-
- scheduleUnixes.forEach((unix, index) => {
- if (
- cusProduct.status === CusProductStatus.Scheduled &&
- cusProductInPhase({ phaseStartMillis: unix, cusProduct })
- ) {
- scheduleIndexes.push(index);
- return;
- }
-
- if (cusProduct.status === CusProductStatus.Scheduled) return;
-
- if (cusProduct.product.is_add_on) {
- // 1. If it's canceled
- if (cusProduct.canceled && (cusProduct.ended_at || 0) > unix) {
- scheduleIndexes.push(index);
- return;
- } else if (!cusProduct.canceled) {
- scheduleIndexes.push(index);
- return;
- }
-
- return;
- }
-
- // 2. If main product, check that schedule is AFTER this phase
- const curScheduledProduct = subCusProducts.find(
- (cp) =>
- cp.product.group === product.group &&
- cp.status === CusProductStatus.Scheduled &&
- (cp.internal_entity_id
- ? cp.internal_entity_id === cusProduct.internal_entity_id
- : nullish(cp.internal_entity_id)),
- );
-
- if (!curScheduledProduct) {
- scheduleIndexes.push(index);
- return;
- }
-
- // If scheduled product NOT in phase, add main product to schedule
- if (
- !cusProductInPhase({
- phaseStartMillis: unix,
- cusProduct: curScheduledProduct,
- })
- ) {
- scheduleIndexes.push(index);
- }
- });
-
- if (printCusProduct) {
- console.log(`Schedule indexes:`, scheduleIndexes);
- console.log("--------------------------------");
- }
-
- // const hasScheduledProduct =
- cusProduct.status !== CusProductStatus.Scheduled &&
- !cusProduct.product.is_add_on &&
- subCusProducts.some(
- (cp) =>
- cp.product.group === product.group &&
- ACTIVE_STATUSES.includes(cp.status),
- );
-
- const addToSub = cusProduct.status !== CusProductStatus.Scheduled;
-
- for (const price of prices) {
- if (isOneOffPrice(price)) continue;
-
- const relatedEnt = getPriceEntitlement(price, ents);
- const options = getPriceOptions(price, cusProduct.options);
- const existingUsage = getExistingUsageFromCusProducts({
- entitlement: relatedEnt,
- cusProducts: subCusProducts,
- entities: fullCus.entities,
- carryExistingUsages: true,
- internalEntityId: cusProduct.internal_entity_id || undefined,
- });
-
- const res = priceToStripeItem({
- price,
- relatedEnt,
- product,
- org,
- options,
- existingUsage,
- withEntity: !!cusProduct.internal_entity_id,
- isCheckout: false,
- apiVersion,
- // productOptions: cusProduct.quantity
- // ? {
- // product_id: product.id,
- // quantity: Number(cusProduct.quantity || 1),
- // }
- // : undefined,
- });
-
- if (res?.lineItem && nullish(res.lineItem.quantity)) {
- res.lineItem.quantity = 0;
- }
-
- // console.log("API VERSION:", apiVersion);
- // console.log("LINE ITEM:", res?.lineItem);
- if (options?.upcoming_quantity && res?.lineItem) {
- res.lineItem.quantity = options.upcoming_quantity;
- }
-
- const lineItem: any = res?.lineItem;
- if (lineItem && res?.lineItem) {
- lineItem.quantity = Math.max(lineItem.quantity, 0);
- if (addToSub) {
- const existingIndex = supposedSubItems.findIndex(
- (si: any) => si.price === lineItem.price,
- );
-
- if (existingIndex !== -1) {
- supposedSubItems[existingIndex].quantity += lineItem.quantity;
- } else {
- supposedSubItems.push({
- ...res.lineItem,
- priceStr: `${product.id}-${formatPrice({ price })}`,
- stripeProdId: product.processor?.id,
- autumnPrice: price,
- canSkip: isPrepaidPrice(price) && res?.lineItem?.quantity === 0,
- });
- }
- }
-
- for (const scheduleIndex of scheduleIndexes) {
- const phase = supposedPhases[scheduleIndex];
- const existingIndex = phase.items.findIndex(
- (item: any) => item.price === lineItem.price,
- );
-
- if (existingIndex !== -1) {
- phase.items[existingIndex].quantity += lineItem.quantity!;
- } else {
- phase.items.push({
- price: lineItem.price,
- quantity: lineItem.quantity!,
- });
- }
- }
- }
- }
- }
-
- // Check if all free products are
- const sub = subs.find((sub) => sub.id === subId);
- const allCusProductsFree = allCusProductsOnSubFree({
- fullCus,
- subId: subId!,
- });
-
- if (allCusProductsFree) {
- // assert(!sub, `Sub ${subId} should not exist`);
- continue;
- }
-
- assert(!!sub, `Sub ${subId} should exist`);
-
- if (sub) {
- const actualItems = sub!.items.data.map((item: any) => ({
- id: item.id,
- price: item.price.id,
- quantity: item.quantity || 0,
- stripeProdId: item.price.product,
- }));
-
- await compareActualItems({
- actualItems,
- expectedItems: supposedSubItems,
- type: "sub",
- fullCus,
- db,
- subId,
- });
- }
-
- // Should be canceled
-
- const cusSubShouldBeCanceled = subCusProducts.every((cp) => {
- if (cp.subscription_ids?.includes(subId!)) {
- // 1. Get scheduled product
-
- const { curScheduledProduct } = getExistingCusProducts({
- cusProducts: fullCus.customer_products,
- product: cp.product,
- internalEntityId: cp.internal_entity_id,
- });
-
- if (curScheduledProduct) {
- const scheduledProduct = cusProductToProduct({
- cusProduct: curScheduledProduct,
- });
-
- if (!isFreeProduct(scheduledProduct.prices)) {
- return false;
- }
- }
-
- return cp.canceled;
- }
-
- return true;
- });
-
- const finalShouldBeCanceled = cusSubShouldBeCanceled;
-
- if (finalShouldBeCanceled) {
- assert(!sub!.schedule, `sub ${subId} should NOT have a schedule`);
- assert(
- isStripeSubscriptionCanceled(sub!),
- `sub ${subId} should be canceled`,
- );
- continue;
- }
-
- const schedule =
- supposedPhases.length > 0
- ? schedules.find((s) => s.id === sub!.schedule)
- : null;
-
- for (let i = 0; i < supposedPhases.length; i++) {
- const supposedPhase = supposedPhases[i];
-
- if (supposedPhase.items.length === 0) continue;
-
- const actualPhase = schedule?.phases?.[i + 1];
- assert(
- (schedule?.phases.length ?? 0) > i + 1,
- `Schedule should have more than ${i + 1} phases`,
- );
-
- assert(
- similarUnix({
- unix1: supposedPhase.start_date,
- unix2: actualPhase!.start_date * 1000,
- }),
- `Phase ${i} start date mismatch`,
- );
-
- const actualItems =
- actualPhase?.items.map((item) => ({
- price: (item.price as Stripe.Price).id,
- quantity: item.quantity,
- })) || [];
-
- await compareActualItems({
- actualItems,
- expectedItems: supposedPhase.items,
- type: "schedule",
- fullCus,
- db,
- phaseStartsAt: supposedPhase.start_date,
- subId,
- });
- }
-
- assert(
- !sub!.cancel_at,
- `sub ${subId} should not be canceled, was cancelled at ${
- sub!.cancel_at
- ? new Date(
- sub!.cancel_at > 1e12 ? sub!.cancel_at : sub!.cancel_at * 1000,
- ).toLocaleString("en-GB", {
- day: "numeric",
- month: "short",
- year: "numeric",
- hour: "2-digit",
- minute: "2-digit",
- })
- : sub!.cancel_at
- }`,
- );
- }
-};
diff --git a/server/src/utils/checkUtils/checkCustomerState.ts b/server/src/utils/checkUtils/checkCustomerState.ts
deleted file mode 100644
index 5f1cc2bc4..000000000
--- a/server/src/utils/checkUtils/checkCustomerState.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import type { AppEnv, FullCustomer, Organization } from "@autumn/shared";
-import type { DrizzleCli } from "@server/db/initDrizzle.js";
-import type Stripe from "stripe";
-import type { AutumnContext } from "../../honoUtils/HonoEnv.js";
-import { checkCusProducts } from "./checkCusProducts.js";
-import {
- checkCusSubCorrect,
- SubItemMismatchError,
-} from "./checkCustomerCorrect.js";
-import { checkSubCountMatch } from "./checkSubCountMatch.js";
-import { saveCheckState } from "./saveCheckState.js";
-import type { StateCheckResult } from "./stateCheckTypes.js";
-
-// Re-export for backwards compatibility
-export type { RedisChecksState } from "./stateCheckTypes.js";
-
-/**
- * Runs state checks on a customer and returns structured results.
- * Does not throw - captures all errors in the result object.
- */
-export const runCustomerStateChecks = async ({
- // db,
- ctx,
- fullCus,
- subs,
- schedules,
- // org,
- // env,
-}: {
- // db: DrizzleCli;
- ctx: AutumnContext;
- fullCus: FullCustomer;
- subs: Stripe.Subscription[];
- schedules: Stripe.SubscriptionSchedule[];
-}): Promise => {
- const { db, org, env } = ctx;
- const result: StateCheckResult = {
- passed: true,
- errors: [],
- warnings: [],
- checks: [],
- };
-
- // If fullCus has no processor ID, unlinked from Stripe
- if (!fullCus.processor?.id) {
- result.passed = true;
- result.warnings.push("Customer is unlinked from Stripe");
- result.checks.push({
- type: "overall_status",
- name: "Overall Status",
- passed: false,
- });
- return result;
- }
-
- // Check: Subscription correctness (from checkCusSubCorrect)
- await testSubscriptionCorrectness({
- db,
- fullCus,
- subs,
- schedules,
- org,
- env,
- result,
- });
-
- // Check each customer product
- await checkCusProducts({
- fullCus,
- result,
- });
-
- // Check: Subscription IDs match Stripe
- await checkSubCountMatch({
- ctx,
- fullCus,
- subs,
- result,
- });
-
- // Add summary info
- if (result.errors.length === 0 && result.warnings.length === 0) {
- result.checks.unshift({
- type: "overall_status",
- name: "Overall Status",
- passed: true,
- message: "All checks passed",
- });
- }
-
- // // Save to Redis only if there are failures
- // await saveCheckState({ org, env, fullCus, result });
-
- return result;
-};
-
-export const testSubscriptionCorrectness = async ({
- db,
- fullCus,
- subs,
- schedules,
- org,
- env,
- result,
-}: {
- db: DrizzleCli;
- fullCus: FullCustomer;
- subs: Stripe.Subscription[];
- schedules: Stripe.SubscriptionSchedule[];
- org: Organization;
- env: AppEnv;
- result: StateCheckResult;
-}): Promise => {
- try {
- await checkCusSubCorrect({
- db,
- fullCus,
- subs,
- schedules,
- org,
- env,
- });
- result.checks.push({
- name: "Subscription Correctness",
- type: "subscription_correctness",
- passed: true,
- });
- } catch (error) {
- result.passed = false;
- const errorMsg = error instanceof Error ? error.message : String(error);
- result.errors.push(`Subscription check failed: ${errorMsg}`);
- result.checks.push({
- name: "Subscription Correctness",
- type: "subscription_correctness",
- passed: false,
- message: errorMsg,
- });
-
- // Capture subscription item details if this is a SubItemMismatchError
- if (error instanceof SubItemMismatchError) {
- result.subscriptionDetails = {
- subId: error.subId,
- actualItems: error.actualItems,
- expectedItems: error.expectedItems,
- };
- }
- }
-};
diff --git a/server/src/utils/checkUtils/checkSubCountMatch.ts b/server/src/utils/checkUtils/checkSubCountMatch.ts
deleted file mode 100644
index ed63ad505..000000000
--- a/server/src/utils/checkUtils/checkSubCountMatch.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { FullCustomer } from "@autumn/shared";
-import { cusProductToPrices, isFreeProduct, metadata } from "@autumn/shared";
-import { count, inArray } from "drizzle-orm";
-import type Stripe from "stripe";
-import type { AutumnContext } from "@/honoUtils/HonoEnv";
-import { allCusProductsOnSubFree } from "./allCusProductsOnSubFree";
-import type { StateCheckResult } from "./stateCheckTypes";
-
-export const checkSubCountMatch = async ({
- ctx,
- fullCus,
- subs,
- result,
-}: {
- ctx: AutumnContext;
- fullCus: FullCustomer;
- subs: Stripe.Subscription[];
- result: StateCheckResult;
-}) => {
- const cusProducts = fullCus.customer_products;
- const subIds = [...new Set(cusProducts.flatMap((cp) => cp.subscription_ids))];
-
- const stripeSubs = subs.filter((sub) => {
- if (sub.status === "incomplete") return false;
- const subCustomerId =
- typeof sub.customer === "string" ? sub.customer : sub.customer?.id;
- return subCustomerId === fullCus.processor?.id;
- });
-
- const filteredSubIds = subIds.filter(
- (subId) => !allCusProductsOnSubFree({ fullCus, subId: subId! }),
- );
-
- if (stripeSubs.length !== filteredSubIds.length) {
- // 1. Check if there are any invoice metadata's waiting to be processed...
- // const invoiceMetadata = await MetadataService.getByStripeInvoiceId({
- // db: ctx.db,
- // stripeInvoiceId: stripeSubs.map((sub) => sub.latest_invoice),
- // type: MetadataType.InvoiceCheckout,
- // });
- const invoiceIds = stripeSubs.map((sub) => sub.latest_invoice);
- // Count of rows in metadata table with stripe_invoice_id in invoiceIds
- const metadataCount = await ctx.db
- .select({ count: count() })
- .from(metadata)
- .where(inArray(metadata.stripe_invoice_id, invoiceIds as string[]));
-
- if (
- stripeSubs.length - (metadataCount?.[0]?.count || 0) !==
- subIds.length
- ) {
- result.passed = false;
- const errorMsg = `Expected ${subIds.length} subs in total, found ${stripeSubs.length} in Stripe`;
- result.errors.push(errorMsg);
- result.checks.push({
- name: "Subscription Count Match",
- type: "sub_count_match",
- passed: false,
- message: errorMsg,
- });
- }
- // result.passed = false;
- // result.errors.push(
- // `Expected ${subIds.length} subs in total, found ${stripeSubs.length} in Stripe`,
- // );
- } else {
- result.checks.push({
- name: `Subscription Match`,
- type: "sub_id_matching",
- passed: true,
- });
- }
-};
diff --git a/server/src/utils/checkUtils/saveCheckState.ts b/server/src/utils/checkUtils/saveCheckState.ts
deleted file mode 100644
index 682ff9ac0..000000000
--- a/server/src/utils/checkUtils/saveCheckState.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import type { AppEnv, FullCustomer, Organization } from "@autumn/shared";
-import { upstash } from "../../external/redis/initUpstash.js";
-import type { RedisChecksState, StateCheckResult } from "./stateCheckTypes.js";
-
-type CheckType = RedisChecksState["checks"][number]["type"];
-
-/** Saves check state to Redis with merging logic for existing state. */
-export const saveCheckState = async ({
- org,
- env,
- fullCus,
- result,
-}: {
- org: Organization;
- env: AppEnv;
- fullCus: FullCustomer;
- result: StateCheckResult;
-}): Promise => {
- const newFailedChecks = result.checks
- .filter(
- (c): c is typeof c & { type: CheckType } => c.type !== "overall_status",
- )
- .filter((c) => !c.passed);
-
- // Skip Redis entirely if all checks pass
- if (newFailedChecks.length === 0) return;
-
- const stateKey = `state:${org.id}:${env}:${fullCus.internal_id}`;
-
- const existingState = (await upstash.get(
- stateKey,
- )) as RedisChecksState | null;
-
- if (existingState) {
- // Merge with existing state
- const existingFailedTypes = new Set(
- existingState.checks.map((c) => c.type),
- );
- const newFailedTypes = new Set(
- newFailedChecks.map((c) => c.type),
- );
-
- // Checks that were fixed (existed before but not now)
- const checksToRemove = new Set(
- [...existingFailedTypes].filter((type) => !newFailedTypes.has(type)),
- );
- // Checks that are new failures
- const checksToAdd = new Set(
- [...newFailedTypes].filter((type) => !existingFailedTypes.has(type)),
- );
-
- // Keep checks that still fail, remove ones that are fixed
- const updatedChecks = existingState.checks.filter(
- (c) => !checksToRemove.has(c.type),
- );
-
- // Add new failing checks
- for (const check of newFailedChecks) {
- if (checksToAdd.has(check.type)) {
- updatedChecks.push({
- type: check.type,
- passed: check.passed,
- message: check.message ?? "",
- data: check.data,
- });
- }
- }
-
- // Keep existing status - all status changes should be done through the dashboard
-
- const updatedState: RedisChecksState = {
- ...existingState,
- checks: updatedChecks,
- };
- await upstash.set(stateKey, JSON.stringify(updatedState));
- } else {
- // Create new state
- const newState: RedisChecksState = {
- status: "new",
- customer: {
- id: fullCus.id ?? "",
- email: fullCus.email || "",
- name: fullCus.name || "",
- env,
- processor: fullCus.processor,
- },
- org_id: org.id,
- env,
- checks: newFailedChecks.map((c) => ({
- type: c.type,
- passed: c.passed,
- message: c.message ?? "",
- data: c.data,
- })),
- };
- await upstash.set(stateKey, JSON.stringify(newState));
- }
-};
diff --git a/server/src/utils/checkUtils/stateCheckTypes.ts b/server/src/utils/checkUtils/stateCheckTypes.ts
deleted file mode 100644
index 9da007cd7..000000000
--- a/server/src/utils/checkUtils/stateCheckTypes.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { AppEnv } from "@autumn/shared";
-
-export type SubItemDetail = {
- priceId: string;
- quantity: number;
- productName?: string;
- priceName?: string;
-};
-
-export type StateCheckResult = {
- passed: boolean;
- errors: string[];
- warnings: string[];
- checks: {
- name: string;
- type:
- | "subscription_correctness"
- | "customer_product_correctness"
- | "sub_id_matching"
- | "sub_count_match"
- | "group_uniqueness"
- | "entitlement_price_correctness"
- | "overall_status";
- passed: boolean;
- message?: string;
- data?: unknown;
- }[];
- // Detailed sub info when there's a mismatch
- subscriptionDetails?: {
- subId: string;
- actualItems: SubItemDetail[];
- expectedItems: SubItemDetail[];
- };
-};
-
-export type RedisChecksState = {
- status: "new" | "ongoing" | "archived";
- customer: {
- id: string;
- email: string;
- name: string;
- env: AppEnv;
- processor?: string;
- };
- org_id: string;
- env: AppEnv;
- checks: {
- type: Exclude;
- passed: boolean;
- message: string;
- data?: unknown;
- }[];
-};
diff --git a/server/src/utils/colorUtils.ts b/server/src/utils/colorUtils.ts
deleted file mode 100644
index 2151d144c..000000000
--- a/server/src/utils/colorUtils.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import chalk from "chalk";
-
-export const colorize = (text: string, chalkColor: any) => {
- return `${chalkColor}${text}${chalk.reset}`;
-};
diff --git a/server/src/utils/importUtils/addProductFromSubs.ts b/server/src/utils/importUtils/addProductFromSubs.ts
index 91b9f77f0..e51c72d24 100644
--- a/server/src/utils/importUtils/addProductFromSubs.ts
+++ b/server/src/utils/importUtils/addProductFromSubs.ts
@@ -1,5 +1,4 @@
import {
- type CusProductStatus,
type EntitlementWithFeature,
type FullCustomer,
type FullProduct,
@@ -9,7 +8,7 @@ import {
} from "@autumn/shared";
import type Stripe from "stripe";
import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
-import { stripeToAutumnSubStatus } from "@/external/stripe/stripeSubUtils.js";
+import { stripeSubscriptionToAutumnStatus } from "@/external/stripe/subscriptions/index.js";
import { subToAutumnInterval } from "@/external/stripe/utils.js";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
@@ -113,7 +112,7 @@ export const addProductFromSubs = async ({
anchorToUnix: anchorToUnix || end * 1000,
subscriptionStatus: sub?.status
- ? (stripeToAutumnSubStatus(sub?.status) as CusProductStatus)
+ ? stripeSubscriptionToAutumnStatus({ stripeStatus: sub?.status })
: undefined,
canceledAt: sub?.canceled_at ? sub.canceled_at * 1000 : null,
diff --git a/server/src/utils/importUtils/addUsagePricesToSub.ts b/server/src/utils/importUtils/addUsagePricesToSub.ts
deleted file mode 100644
index 164ebc746..000000000
--- a/server/src/utils/importUtils/addUsagePricesToSub.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import {
- BillingInterval,
- BillingType,
- type FullProduct,
- type UsagePriceConfig,
-} from "@autumn/shared";
-import type Stripe from "stripe";
-import { findStripeItemForPrice } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
-import { filterByBillingType } from "@/internal/products/prices/priceUtils/findPriceUtils.js";
-
-export const addContUsePricesToSub = async ({
- stripe,
- sub,
- autumnProduct,
- quantity,
- logger,
-}: {
- stripe: Stripe;
- sub: Stripe.Subscription;
- autumnProduct: FullProduct;
- quantity: number;
- logger: any;
-}) => {
- let usagePrices = filterByBillingType({
- prices: autumnProduct.prices,
- billingType: BillingType.InArrearProrated,
- });
-
- usagePrices = usagePrices.filter(
- (p) => p.config.interval !== BillingInterval.OneOff,
- );
-
- logger.info(`Adding ${usagePrices.length} cont use prices to sub`);
-
- for (const usagePrice of usagePrices) {
- const config = usagePrice.config as UsagePriceConfig;
- const latestSub = await stripe.subscriptions.retrieve(sub.id);
- const subItem = findStripeItemForPrice({
- price: usagePrice,
- stripeItems: latestSub.items.data,
- });
-
- if (subItem) {
- logger.info(`Sub already has price for ${config.feature_id}`);
- continue;
- }
-
- const newSubItem = await stripe.subscriptionItems.create({
- subscription: sub.id,
- price: usagePrice.config.stripe_price_id!,
- proration_behavior: "none",
- quantity,
- });
-
- // logger.info(`New sub item:`, {
- // newSubItem,
- // });
-
- logger.info(`Successfully added ${config.feature_id} to sub`);
- }
-};
diff --git a/server/src/utils/importUtils/importUtils.ts b/server/src/utils/importUtils/importUtils.ts
deleted file mode 100644
index 4f940ba9b..000000000
--- a/server/src/utils/importUtils/importUtils.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { type FullProduct, isFixedPrice } from "@autumn/shared";
-import type Stripe from "stripe";
-import { subItemToFixedPrice } from "@/internal/products/prices/priceUtils/constructPriceUtils.js";
-
-// Scenario 1: Replace base price with new base price
-export const replaceBasePrice = async ({
- subItems,
- autumnProduct,
- basePrice,
-}: {
- subItems: Stripe.SubscriptionItem[];
- autumnProduct: FullProduct;
- basePrice?: number;
-}) => {
- const prices = autumnProduct.prices.filter((p) => !isFixedPrice(p));
-
- // Get first sub item
- const subItem = subItems[0];
- const customPrice = subItemToFixedPrice({
- subItem,
- product: autumnProduct,
- basePrice,
- });
-
- return [customPrice, ...prices];
-};
diff --git a/server/src/utils/routerUtils.ts b/server/src/utils/routerUtils.ts
deleted file mode 100644
index d9bcdb509..000000000
--- a/server/src/utils/routerUtils.ts
+++ /dev/null
@@ -1,293 +0,0 @@
-import { ErrCode } from "@autumn/shared";
-import { StatusCodes } from "http-status-codes";
-import qs from "qs";
-import { ZodAny, ZodError, ZodObject } from "zod";
-import { formatZodError } from "@/errors/formatZodError.js";
-import { withSpan as withSpanTracer } from "@/internal/analytics/tracer/spanUtils.js";
-import RecaseError, { handleRequestError } from "./errorUtils.js";
-import type { ExtendedRequest } from "./models/Request.js";
-import { handleExpressErrorSkip } from "./routerUtils/expressErrorSkip.js";
-
-/**
- * Parses query parameters with proper type coercion for validation
- */
-const parseQueryForValidation = (query: any): any => {
- // Re-parse the query string with qs to handle arrays and nested objects properly
- const queryString = new URLSearchParams(query).toString();
- const parsed = qs.parse(queryString, {
- comma: true, // Parse comma-separated values as arrays
- arrayLimit: 100,
- parseArrays: true,
- });
-
- // Type coercion for common cases
- const coerceValue = (value: any): any => {
- if (Array.isArray(value)) {
- return value.map(coerceValue);
- }
-
- if (typeof value === "string") {
- // Try to parse as number
- if (!isNaN(Number(value)) && value.trim() !== "") {
- return Number(value);
- }
- // Parse booleans
- if (value === "true") return true;
- if (value === "false") return false;
- }
-
- return value;
- };
-
- const result: any = {};
- for (const [key, value] of Object.entries(parsed)) {
- result[key] = coerceValue(value);
- }
-
- return result;
-};
-
-/**
- * Formats Zod validation errors into user-friendly messages
- */
-const formatZodValidationError = (error: ZodError): string => {
- const fieldErrors = error.issues.map((issue) => {
- const path = issue.path.length > 0 ? issue.path.join(".") : "root";
-
- // Handle different types of validation errors with more descriptive messages
- switch (issue.code) {
- case "invalid_type":
- if (issue.received === "undefined") {
- return `${path} is required`;
- }
- return `${path} must be a ${issue.expected}, received ${issue.received}`;
-
- case "too_small":
- if (issue.type === "number") {
- return `${path} must be at least ${issue.minimum}`;
- }
- if (issue.type === "string") {
- return `${path} must be at least ${issue.minimum} characters`;
- }
- if (issue.type === "array") {
- return `${path} must contain at least ${issue.minimum} items`;
- }
- return `${path} is too small`;
-
- case "too_big":
- if (issue.type === "number") {
- return `${path} must be at most ${issue.maximum}`;
- }
- if (issue.type === "string") {
- return `${path} must be at most ${issue.maximum} characters`;
- }
- if (issue.type === "array") {
- return `${path} must contain at most ${issue.maximum} items`;
- }
- return `${path} is too large`;
-
- case "invalid_enum_value":
- return `${path} must be one of: ${issue.options?.join(", ") || "valid options"}`;
-
- case "custom":
- return issue.message || `${path} is invalid`;
-
- default:
- return issue.message || `${path} is invalid`;
- }
- });
-
- // Remove duplicates and join with semicolons for multiple errors
- const uniqueErrors = [...new Set(fieldErrors)];
- return uniqueErrors.join("; ");
-};
-
-/**
- * Type-safe route handler with optional loader function.
- *
- * @example
- * // Without loader (load parameter is undefined)
- * routeHandler({
- * req, res, action: "get user",
- * handler: (req, res, load) => {
- * // load is undefined
- * }
- * });
- *
- * @example
- * // With loader (load parameter is inferred from loader return type)
- * routeHandler({
- * req, res, action: "get user",
- * loader: async (org, env, db) => ({ user: await db.query(...) }),
- * handler: (req, res, load) => {
- * // load is { user: User } - fully type-safe!
- * }
- * });
- */
-
-export const routeHandler = async ({
- req,
- res,
- action,
- handler,
- validator,
- queryValidator,
- loader,
- withSpan = false,
-}: {
- req: any;
- res: any;
- action: string;
- handler: (
- req: any,
- res: any,
- load: TLoad,
- query?: any,
- ) => Promise | void;
- validator?:
- | ((req: any, res: any) => Promise)
- | ZodAny
- | ZodObject;
- queryValidator?:
- | ((req: any, res: any) => Promise)
- | ZodAny
- | ZodObject;
- withSpan?: boolean;
-} & (TLoad extends undefined
- ? { loader?: never }
- : {
- loader: ({
- body,
- query,
- req,
- }: {
- body: any;
- query: any;
- req: ExtendedRequest;
- }) => Promise;
- })) => {
- try {
- let load: TLoad | undefined;
-
- if (!withSpan) {
- if (typeof validator === "function") {
- await validator(req, res);
- } else if (
- validator instanceof ZodAny ||
- validator instanceof ZodObject
- ) {
- const parseResult = validator.safeParse(req.body);
- if (!parseResult.success) {
- const errorMsg = formatZodValidationError(parseResult.error);
- throw new RecaseError({
- message: errorMsg,
- code: ErrCode.InvalidRequest,
- });
- }
- }
-
- if (typeof queryValidator === "function") {
- await queryValidator(req, res);
- } else if (
- queryValidator instanceof ZodAny ||
- queryValidator instanceof ZodObject
- ) {
- const parsedQuery = parseQueryForValidation(req.query);
- const parseResult = queryValidator.safeParse(parsedQuery);
- if (!parseResult.success) {
- const errorMsg = formatZodValidationError(parseResult.error);
- throw new RecaseError({
- message: errorMsg,
- code: ErrCode.InvalidRequest,
- });
- }
- // Update req.query with parsed values for use in loader
- req.query = parseResult.data;
- }
- if (loader) {
- load = await loader({
- body: req.body,
- query: req.query,
- req: req as ExtendedRequest,
- });
- }
- await handler(req, res, load as TLoad, req.query);
- } else {
- await withSpanTracer({
- name: action ?? "unknown",
- attributes: {
- org: req.org?.id,
- env: req.env,
- },
- fn: async () => {
- if (typeof validator === "function") {
- await validator(req, res);
- } else if (
- validator instanceof ZodAny ||
- validator instanceof ZodObject
- ) {
- const parseResult = validator.safeParse(req.body);
- if (!parseResult.success) {
- const errorMsg = formatZodValidationError(parseResult.error);
- throw new RecaseError({
- message: errorMsg,
- code: ErrCode.InvalidRequest,
- });
- }
- }
-
- if (typeof queryValidator === "function") {
- await queryValidator(req, res);
- } else if (
- queryValidator instanceof ZodAny ||
- queryValidator instanceof ZodObject
- ) {
- const parsedQuery = parseQueryForValidation(req.query);
- const parseResult = queryValidator.safeParse(parsedQuery);
- if (!parseResult.success) {
- const errorMsg = formatZodValidationError(parseResult.error);
- throw new RecaseError({
- message: errorMsg,
- code: ErrCode.InvalidRequest,
- });
- }
- // Update req.query with parsed values for use in loader
- req.query = parseResult.data;
- }
- if (loader) {
- load = await loader({
- body: req.body,
- query: req.query,
- req: req as ExtendedRequest,
- });
- }
- await handler(req, res, load as TLoad, req.query);
- },
- });
- }
- } catch (error) {
- // Check if error should be skipped (logged as warning)
- const skipResponse = handleExpressErrorSkip({ error, req, res });
- if (skipResponse) {
- return skipResponse;
- }
-
- // Handle Zod errors on /attach endpoint
- let handledError = error;
- if (error instanceof ZodError && req.originalUrl.includes("/attach")) {
- handledError = new RecaseError({
- message: formatZodError(error as any),
- code: ErrCode.InvalidInputs,
- statusCode: StatusCodes.BAD_REQUEST,
- });
- }
-
- // Handle all other errors
- handleRequestError({
- error: handledError,
- req,
- res,
- action,
- });
- }
-};
diff --git a/server/src/utils/routerUtils/expressErrorSkip.ts b/server/src/utils/routerUtils/expressErrorSkip.ts
deleted file mode 100644
index 0a9caf4a4..000000000
--- a/server/src/utils/routerUtils/expressErrorSkip.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-import {
- ErrCode,
- ProductNotFoundError,
- RecaseError as SharedRecaseError,
-} from "@autumn/shared";
-import Stripe from "stripe";
-import RecaseError from "../errorUtils.js";
-
-type ExpressRequest = {
- originalUrl: string;
- logger: {
- warn: (message: string) => void;
- };
- org?: {
- slug?: string;
- };
- orgId?: string;
- body?: any;
-};
-
-type ExpressResponse = {
- status: (code: number) => {
- json: (data: any) => any;
- };
-};
-
-/**
- * Checks if an error should be handled as a warning instead of an error.
- * Returns response object if handled, null otherwise.
- */
-export const handleExpressErrorSkip = ({
- error,
- req,
- res,
-}: {
- error: any;
- req: ExpressRequest;
- res: ExpressResponse;
-}) => {
- const originalUrl = req.originalUrl;
-
- if (error instanceof SharedRecaseError) {
- req.logger.warn(`${error.message}, path: ${originalUrl}`);
- return res.status(error.statusCode).json({
- message: error.message,
- code: error.code,
- });
- }
-
- // Handle RecaseError with EntityNotFound code
- if (error instanceof RecaseError) {
- if (error.code === ErrCode.EntityNotFound) {
- req.logger.warn(`${error.message}, org: ${req.org?.slug || req.orgId}`);
- return res.status(404).json({
- message: error.message,
- code: error.code,
- });
- }
- }
-
- if (error instanceof ProductNotFoundError) {
- return res.status(404).json({
- message: error.message,
- code: error.code,
- });
- }
-
- // Handle Stripe errors
- if (error instanceof Stripe.errors.StripeError) {
- // Exchange router invalid API key
- if (
- originalUrl.includes("/exchange") &&
- error.message.includes("Invalid API Key provided")
- ) {
- req.logger.warn("Exchange router, invalid API Key provided");
- return res.status(400).json({
- message: error.message,
- code: ErrCode.InvalidRequest,
- });
- }
-
- // Invalid email address
- if (
- error.message.includes("not a valid email address") ||
- error.message.includes("email: Invalid input")
- ) {
- req.logger.warn("Invalid email address");
- return res.status(400).json({
- message: error.message,
- code: ErrCode.InvalidRequest,
- });
- }
-
- // Billing portal config error
- if (
- originalUrl.includes("/billing_portal") &&
- error.message.includes("Provide a configuration or create your default")
- ) {
- req.logger.warn(`Billing portal config error, org: ${req.org?.slug}`);
- return res.status(404).json({
- message: error.message,
- code: ErrCode.InvalidRequest,
- });
- }
-
- // Billing portal return_url error
- if (
- originalUrl.includes("/billing_portal") &&
- error.message.includes("Invalid URL: An explicit scheme (such as https)")
- ) {
- req.logger.warn(
- `Billing portal return_url error, org: ${req.org?.slug}, return_url: ${req.body?.return_url}`,
- );
- return res.status(400).json({
- message: error.message,
- code: ErrCode.InvalidRequest,
- });
- }
-
- // Card declined error
- if (error.message.includes("Your card was declined.")) {
- req.logger.warn(`Card declined error, org: ${req.org?.slug}`);
- return res.status(400).json({
- message: error.message,
- code: ErrCode.InvalidRequest,
- });
- }
-
- // Cannot delete org with production customers
- if (
- error.message.includes("Cannot delete org with production mode customers")
- ) {
- req.logger.warn(
- `Cannot delete org with production customers, org: ${req.org?.slug}`,
- );
- return res.status(400).json({
- message: error.message,
- code: ErrCode.InvalidRequest,
- });
- }
-
- // Webhook endpoint limit reached
- if (
- error.message.includes(
- "You have reached the maximum of 16 test webhook endpoints",
- )
- ) {
- req.logger.warn(`Webhook endpoint limit reached, org: ${req.org?.slug}`);
- return res.status(400).json({
- message: error.message,
- code: ErrCode.InvalidRequest,
- });
- }
-
- // Generic invalid URL scheme error
- if (
- error.message.includes(
- "Invalid URL: An explicit scheme (such as https) must be provided",
- )
- ) {
- req.logger.warn(`Invalid URL scheme error, org: ${req.org?.slug}`);
- return res.status(400).json({
- message: error.message,
- code: ErrCode.InvalidRequest,
- });
- }
-
- // Not a valid URL error
- if (error.message.includes("Not a valid URL")) {
- req.logger.warn(`Not a valid URL error, org: ${req.org?.slug}`);
- return res.status(400).json({
- message: error.message,
- code: ErrCode.InvalidRequest,
- });
- }
- }
-
- // No skip case matched
- return null;
-};
diff --git a/server/src/utils/scriptUtils/logUtils/logSubItems.ts b/server/src/utils/scriptUtils/logUtils/logSubItems.ts
index eca8f8124..07dc09a92 100644
--- a/server/src/utils/scriptUtils/logUtils/logSubItems.ts
+++ b/server/src/utils/scriptUtils/logUtils/logSubItems.ts
@@ -1,6 +1,6 @@
import { stripeToAtmnAmount } from "@autumn/shared";
+import { subItemToAutumnInterval } from "@tests/utils/stripeUtils";
import type Stripe from "stripe";
-import { subItemToAutumnInterval } from "@/external/stripe/utils.js";
import type { Logger } from "../../../external/logtail/logtailUtils";
export const logSubItems = ({
diff --git a/server/src/utils/scriptUtils/resetOrgStripe.ts b/server/src/utils/scriptUtils/resetOrgStripe.ts
deleted file mode 100644
index 2da3e5beb..000000000
--- a/server/src/utils/scriptUtils/resetOrgStripe.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { AppEnv, type Organization } from "@autumn/shared";
-import { deleteAllStripeCustomers } from "@/external/stripe/stripeCusUtils.js";
-import {
- deactivateStripeMeters,
- deleteAllStripeProducts,
-} from "@/external/stripe/stripeProductUtils.js";
-
-export const resetOrgStripe = async ({ org }: { org: Organization }) => {
- const env = AppEnv.Sandbox;
-
- await deleteAllStripeCustomers({
- org,
- env,
- });
-
- await deleteAllStripeProducts({
- org,
- env,
- });
-
- await deactivateStripeMeters({
- org,
- env,
- });
-};
diff --git a/server/src/utils/scriptUtils/testUtils/initCustomerV3.ts b/server/src/utils/scriptUtils/testUtils/initCustomerV3.ts
index 85224394e..3d2e3f654 100644
--- a/server/src/utils/scriptUtils/testUtils/initCustomerV3.ts
+++ b/server/src/utils/scriptUtils/testUtils/initCustomerV3.ts
@@ -18,7 +18,7 @@ export const initCustomerV3 = async ({
}: {
ctx: TestContext;
customerId: string;
- attachPm?: "success" | "fail" | "authenticate";
+ attachPm?: "success" | "fail" | "authenticate" | "alipay";
customerData?: CustomerData;
withTestClock?: boolean;
withDefault?: boolean;
diff --git a/server/src/utils/versionUtils/legacyVersionUtils.ts b/server/src/utils/versionUtils/legacyVersionUtils.ts
deleted file mode 100644
index 034ee232d..000000000
--- a/server/src/utils/versionUtils/legacyVersionUtils.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { LegacyVersion, type Organization } from "@autumn/shared";
-
-export const floatToVersion = (version: number) => {
- if (Object.values(LegacyVersion).includes(version)) {
- return version;
- }
-
- return null;
-};
-
-export const getApiVersion = ({
- createdAt,
-}: {
- createdAt: number;
-}): LegacyVersion => {
- // v1.1 -- 17 April
-
- const v1_2 = new Date("2025-05-05");
- const v1_1 = new Date("2025-04-17");
-
- if (createdAt >= v1_2.getTime()) {
- return LegacyVersion.v1_2;
- }
-
- if (createdAt >= v1_1.getTime()) {
- return LegacyVersion.v1_1;
- }
-
- return LegacyVersion.v1;
-};
-
-export const orgToVersion = ({
- org,
- reqApiVersion,
-}: {
- org: Organization;
- reqApiVersion?: number;
-}) => {
- return reqApiVersion || LegacyVersion.v1;
-};
diff --git a/server/tests/setup-integration-tests.ts b/server/tests/setup-integration-tests.ts
index af4c953fa..6736366e1 100644
--- a/server/tests/setup-integration-tests.ts
+++ b/server/tests/setup-integration-tests.ts
@@ -1,4 +1,5 @@
import { execSync } from "node:child_process";
+import { loadLocalEnv } from "@/utils/envUtils";
const isUnitTest = () => {
return process.argv.some((arg) => arg.includes("unit"));
@@ -31,6 +32,6 @@ if (isUnitTest()) {
} else {
console.log("--- Setup integration tests ---");
await loadInfisicalSecrets();
- // loadLocalEnv();
+ loadLocalEnv();
console.log("--- Setup integration tests complete ---");
}
diff --git a/shared/api/billing/checkout/checkoutResponse.ts b/shared/api/billing/checkout/checkoutResponse.ts
deleted file mode 100644
index 145221c86..000000000
--- a/shared/api/billing/checkout/checkoutResponse.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-// import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.js";
-// import { z } from "zod/v4";
-// import { ApiPlanSchema } from "../../products/apiPlan.js";
-
-// // RESULT
-// export const CheckoutLineSchema = z.object({
-// description: z.string(),
-// amount: z.number(),
-// // item: ApiProductItemSchema.nullish(),
-// });
-
-// export const CheckoutResponseSchema = z.object({
-// url: z.string().nullish(),
-// customer_id: z.string(),
-// lines: z.array(CheckoutLineSchema),
-
-// plan: ApiPlanSchema.nullish(),
-// current_plan: ApiPlanSchema.nullish(),
-
-// options: z.array(FeatureOptionsSchema).nullish(),
-// total: z.number().nullish(),
-// currency: z.string().nullish(),
-// has_prorations: z.boolean().nullish(),
-// next_cycle: z
-// .object({
-// starts_at: z.number(),
-// total: z.number(),
-// })
-// .nullish(),
-// });
-
-// export type CheckoutLine = z.infer;
diff --git a/shared/api/core/checkProductModels.ts b/shared/api/core/checkProductModels.ts
deleted file mode 100644
index 5f652a014..000000000
--- a/shared/api/core/checkProductModels.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-import { z } from "zod/v4";
-import { CustomerDataSchema } from "../common/customerData.js";
-import { EntityDataSchema } from "../common/entityData.js";
-
-export const ProductScenarioSchema = z.enum([
- "scheduled",
- "active",
- "new",
- "renew",
- "upgrade",
- "downgrade",
- "cancel",
-]);
-
-// Check Product Schemas
-export const CheckProductParamsSchema = z.object({
- customer_id: z.string().meta({
- description: "The ID of the customer",
- example: "cus_123",
- }),
- product_id: z.string().meta({
- description: "The ID of the product to check",
- example: "pro_plan",
- }),
- entity_id: z.string().optional().meta({
- description: "The ID of the entity (optional)",
- example: "entity_123",
- }),
- customer_data: CustomerDataSchema.optional().meta({
- description:
- "Customer data to create or update the customer if they don't exist",
- }),
- entity_data: EntityDataSchema.optional().meta({
- description: "Entity data to create the entity if it doesn't exist",
- }),
- with_preview: z.boolean().optional().meta({
- description: "Whether to include preview information in the response",
- example: true,
- }),
-});
-
-export const CheckProductPreviewItemSchema = z.object({
- price: z.string().meta({
- description: "Formatted price string",
- example: "$10.00",
- }),
- description: z.string().meta({
- description: "Description of the item",
- example: "Base subscription",
- }),
- usage_model: z.enum(["prepaid", "pay_per_use"]).optional().meta({
- description: "The usage model for this item",
- example: "prepaid",
- }),
-});
-
-export const CheckProductPreviewOptionSchema = z.object({
- feature_id: z.string().meta({
- description: "The ID of the feature",
- example: "api_calls",
- }),
- feature_name: z.string().meta({
- description: "The name of the feature",
- example: "API Calls",
- }),
- billing_units: z.number().meta({
- description: "Number of billing units",
- example: 1000,
- }),
- price: z.number().optional().meta({
- description: "Price per billing unit",
- example: 0.01,
- }),
- tiers: z
- .array(
- z.object({
- to: z.union([z.number(), z.string()]).meta({
- description: "Upper limit of this tier (can be 'inf' for infinite)",
- example: 1000,
- }),
- amount: z.number().meta({
- description: "Price amount for this tier",
- example: 10,
- }),
- }),
- )
- .optional()
- .meta({
- description: "Tiered pricing structure",
- }),
-});
-
-export const CheckProductPreviewSchema = z.object({
- scenario: ProductScenarioSchema,
- product_id: z.string().meta({
- description: "The ID of the product",
- example: "pro_plan",
- }),
- product_name: z.string().meta({
- description: "The name of the product",
- example: "Pro Plan",
- }),
- recurring: z.boolean().meta({
- description: "Whether the product is recurring",
- example: true,
- }),
- error_on_attach: z.boolean().optional().meta({
- description: "Whether there would be an error attaching this product",
- example: false,
- }),
- next_cycle_at: z.number().optional().meta({
- description: "Timestamp of the next billing cycle",
- example: 1717000000000,
- }),
- current_product_name: z.string().optional().meta({
- description: "Name of the customer's current product",
- example: "Basic Plan",
- }),
- items: z.array(CheckProductPreviewItemSchema).optional().meta({
- description: "Individual items in the product",
- }),
- options: z.array(CheckProductPreviewOptionSchema).optional().meta({
- description: "Feature options available in the product",
- }),
- due_today: z
- .object({
- price: z.number().meta({
- description: "Amount due today",
- example: 10,
- }),
- currency: z.string().meta({
- description: "Currency code",
- example: "usd",
- }),
- })
- .optional()
- .meta({
- description: "Payment due today",
- }),
- due_next_cycle: z
- .object({
- price: z.number().meta({
- description: "Amount due next cycle",
- example: 50,
- }),
- currency: z.string().meta({
- description: "Currency code",
- example: "usd",
- }),
- })
- .optional()
- .meta({
- description: "Payment due in the next cycle",
- }),
-});
-
-export const CheckProductResultSchema = z.object({
- allowed: z.boolean().meta({
- description: "Whether the customer can attach the product",
- example: true,
- }),
- customer_id: z.string().meta({
- description: "The ID of the customer",
- example: "cus_123",
- }),
- product_id: z.string().meta({
- description: "The ID of the product",
- example: "pro_plan",
- }),
- entity_id: z.string().optional().meta({
- description: "The ID of the entity (if provided)",
- example: "entity_123",
- }),
- status: z.string().optional().meta({
- description: "Status code for the check result",
- example: "upgrade_available",
- }),
- preview: CheckProductPreviewSchema.optional().meta({
- description: "Preview information if with_preview was true",
- }),
-});
-
-export type CheckProductParams = z.infer;
-export type CheckProductResult = z.infer;
-export type CheckProductPreview = z.infer;
-export type CheckProductPreviewItem = z.infer<
- typeof CheckProductPreviewItemSchema
->;
-export type CheckProductPreviewOption = z.infer<
- typeof CheckProductPreviewOptionSchema
->;
diff --git a/shared/api/customer.ts b/shared/api/customer.ts
deleted file mode 100644
index a9ea33d3c..000000000
--- a/shared/api/customer.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-// import { writeFileSync } from "node:fs";
-// import yaml from "yaml";
-// import { z } from "zod/v4";
-// import { createDocument } from "zod-openapi";
-// import { AttachBodySchema } from "../models/attachModels/attachBody.js";
-// import { CusResponseSchema } from "../models/cusModels/cusResponseModels.js";
-
-// const customerId = z.string().meta({
-// description: "Your internal ID for the customer",
-// example: "cus_123",
-// id: "customer_id",
-// });
-
-// const featureId = z.string().meta({
-// description: "Feature ID as defined in the dashboard (eg. 'messages')",
-// example: "messages",
-// id: "feature_id",
-// });
-
-// const AttachResult = z
-// .object({
-// message: z.string().meta({
-// description: "A short description on the result of the operation",
-// example: "Successfully downgraded from Product A to Product B",
-// id: "message",
-// }),
-// product_ids: z.array(z.string()).meta({
-// description: "The IDs of the products that were attached",
-// example: ["pro", "one_off"],
-// id: "product_ids",
-// }),
-// customer_id: customerId,
-// })
-// .meta({ id: "AttachResult" });
-
-// const attachDefinition = {
-// summary: "Attach Product",
-// tags: ["core"],
-// "x-speakeasy-name-override": "attach",
-// requestBody: {
-// content: {
-// "application/json": { schema: AttachBodySchema },
-// },
-// },
-// responses: {
-// "200": {
-// description: "200 OK",
-// content: {
-// "application/json": { schema: AttachResult },
-// },
-// },
-// },
-// };
-
-// // Define Customer schema as a reusable component
-// const CustomerV1 = CusResponseSchema.meta({ id: "Customer" });
-
-// const document = createDocument({
-// openapi: "3.1.0",
-// info: {
-// title: "My API",
-// version: "1.0.0",
-// },
-// servers: [
-// {
-// url: "https://api.useautumn.com",
-// description: "Production server",
-// },
-// ],
-// security: [
-// {
-// secretKey: [],
-// },
-// ],
-// components: {
-// securitySchemes: {
-// secretKey: {
-// type: "http",
-// scheme: "bearer",
-// bearerFormat: "JWT",
-// },
-// },
-// },
-
-// paths: {
-// "/core/attach": {
-// post: attachDefinition,
-// },
-// "/customers/{customer_id}": {
-// get: {
-// summary: "Get customer",
-// "x-speakeasy-name-override": "get",
-// tags: ["customers"],
-// requestParams: { path: z.object({ customer_id: customerId }) },
-// responses: {
-// "200": {
-// description: "200 OK",
-// content: {
-// "application/json": { schema: CustomerV1 },
-// },
-// },
-// },
-// },
-// },
-// "/customers/{customer_id}/features/{feature_id}": {
-// patch: {
-// summary: "Update customer feature",
-// tags: ["customer.features"],
-// "x-speakeasy-name-override": "update",
-// requestParams: {
-// path: z.object({ customer_id: customerId, feature_id: featureId }),
-// },
-// requestBody: {
-// content: {
-// "application/json": {
-// schema: z.object({
-// usage: z.number(),
-// }),
-// },
-// },
-// },
-// responses: {
-// "200": {
-// description: "200 OK",
-// content: {
-// "application/json": {
-// schema: z.object({
-// usage: z.number(),
-// }),
-// },
-// },
-// },
-// },
-// },
-// },
-// },
-// });
-
-// // Export to YAML file during build
-// if (process.env.NODE_ENV !== "production") {
-// try {
-// const yamlContent = yaml.stringify(document);
-// writeFileSync("./openapi.yaml", yamlContent, "utf8");
-// console.log("OpenAPI document exported to openapi-customer.yaml");
-// } catch (error) {
-// console.error("Failed to export OpenAPI document:", error);
-// }
-// }
diff --git a/shared/api/customers/cusProducts/apiCusProduct.ts b/shared/api/customers/cusProducts/apiCusProduct.ts
deleted file mode 100644
index eb7662730..000000000
--- a/shared/api/customers/cusProducts/apiCusProduct.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { z } from "zod/v4";
-import { ApiProductItemSchema } from "../../products/planFeature/previousVersions/apiProductItem.js";
-
-export const ApiCusProductSchema = z.object({
- id: z.string().meta({
- description: "The unique identifier for the product",
- example: "pro_plan",
- }),
- name: z.string().nullable().meta({
- description: "The name of the product",
- example: "Pro Plan",
- }),
- group: z.string().nullable().meta({
- description: "The group the product belongs to",
- example: "product_set_1",
- }),
- status: z
- .enum(["active", "expired", "scheduled", "trialing", "past_due"])
- .meta({
- description: "Current status of the product for this customer",
- example: "active",
- }),
- canceled_at: z.number().nullish().meta({
- description: "Timestamp when the product was canceled for the customer",
- example: 1717000000,
- }),
- started_at: z.number().meta({
- description: "Timestamp when the customer started this product",
- example: 1700000000000,
- }),
- is_default: z.boolean().meta({
- description: "Whether this product is the default for the customer",
- example: true,
- }),
- is_add_on: z.boolean().meta({
- description: "Whether the product is an add-on",
- example: false,
- }),
- version: z.number().nullish().meta({
- description: "Version of the product",
- example: 1,
- }),
- stripe_subscription_ids: z
- .array(z.string())
- .nullish()
- .meta({
- description:
- "List of Stripe subscription IDs associated with this product, if any",
- example: ["sub_1Nc0JzBAbcxyz", "sub_1Nc0xyBAnopq"],
- }),
- current_period_start: z.number().nullish().meta({
- description: "Start of the current billing period",
- example: 1717000000,
- }),
- current_period_end: z.number().nullish().meta({
- description: "End of the current billing period",
- example: 1719600000,
- }),
- entity_id: z.string().nullish().meta({
- description:
- "ID of the entity this customer product is attached to, if applicable",
- example: "entity_1234abcd",
- }),
- items: z
- .array(ApiProductItemSchema)
- .nullish()
- .meta({
- description: "Array of product items defining the features and pricing",
- example: [
- {
- feature_id: "",
- feature_type: "single_use",
- included_usage: 123,
- interval: "month",
- usage_model: "prepaid",
- price: 123,
- billing_units: 1000,
- entity_feature_id: "",
- reset_usage_when_enabled: true,
- tiers: [
- {
- to: 100,
- amount: 10,
- },
- ],
- },
- ],
- }),
- quantity: z.number().optional().meta({
- description:
- "The number of units of this product held by the customer, if applicable",
- example: 1,
- }),
-});
-
-export type ApiCusProduct = z.infer;
diff --git a/shared/api/errors/archivedCodes/auth.ts b/shared/api/errors/archivedCodes/auth.ts
deleted file mode 100644
index 8f1a98bf5..000000000
--- a/shared/api/errors/archivedCodes/auth.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Authentication and authorization error codes
- */
-export const AuthErrorCode = {
- // API Version
- InvalidApiVersion: "invalid_api_version",
-
- // Secret Key Auth
- NoSecretKey: "no_secret_key",
- InvalidSecretKey: "invalid_secret_key",
- FailedToVerifySecretKey: "failed_to_verify_secret_key",
- FailedToFetchKeyFromAutumn: "failed_to_fetch_key_from_autumn",
-
- // Publishable Key Auth
- NoPublishableKey: "no_publishable_key",
- InvalidPublishableKey: "invalid_publishable_key",
- GetOrgFromPublishableKeyFailed: "get_org_from_publishable_key_failed",
- EndpointNotPublic: "endpoint_not_public",
- FailedToVerifyPublishableKey: "failed_to_verify_publishable_key",
-
- // General Auth
- NoAuthHeader: "no_auth_header",
- InvalidAuthHeader: "invalid_auth_header",
-} as const;
-
-export type AuthErrorCode = (typeof AuthErrorCode)[keyof typeof AuthErrorCode];
diff --git a/shared/api/errors/archivedCodes/customer.ts b/shared/api/errors/archivedCodes/customer.ts
deleted file mode 100644
index b0645c34f..000000000
--- a/shared/api/errors/archivedCodes/customer.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Customer-related error codes
- */
-export const CustomerErrorCode = {
- InvalidCustomer: "invalid_customer",
- CreateCustomerFailed: "create_customer_failed",
- CustomerNotFound: "customer_not_found",
- CustomersNotFound: "customers_not_found",
- CustomerAlreadyHasProduct: "customer_already_has_product",
- CustomerHasNoPaymentMethod: "customer_has_no_payment_method",
- CustomerHasNoBaseProduct: "customer_has_no_base_product",
- AttachProductToCustomerFailed: "attach_product_to_customer_failed",
- CustomerEntitlementNotFound: "customer_entitlement_not_found",
- MultipleCustomersFound: "multiple_customers_found",
- InvalidUpdateCustomerParams: "invalid_update_customer_params",
- DuplicateCustomerId: "duplicate_customer_id",
- DuplicateCustomerEmail: "duplicate_customer_email",
-} as const;
-
-export type CustomerErrorCode =
- (typeof CustomerErrorCode)[keyof typeof CustomerErrorCode];
diff --git a/shared/api/errors/archivedCodes/general.ts b/shared/api/errors/archivedCodes/general.ts
deleted file mode 100644
index 8cfbafdec..000000000
--- a/shared/api/errors/archivedCodes/general.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * General application error codes
- */
-export const GeneralErrorCode = {
- // Request validation
- InvalidInputs: "invalid_inputs",
- InvalidRequest: "invalid_request",
- InvalidExpand: "invalid_expand",
- InvalidOptions: "invalid_options",
-
- // Internal errors
- InternalError: "internal_error",
-
- // Organization
- OrgNotFound: "org_not_found",
- OrgHasCustomers: "org_has_customers",
-
- // Features
- FeatureNotFound: "feature_not_found",
- InvalidFeature: "invalid_feature",
- DuplicateFeatureId: "duplicate_feature_id",
- InvalidEventName: "invalid_event_name",
- FeatureLimitReached: "feature_limit_reached",
-
- // Entitlements
- InvalidEntitlement: "invalid_entitlement",
- CreateEntitlementFailed: "create_entitlement_failed",
- DeleteEntitlementFailed: "delete_entitlement_failed",
- InsufficientBalance: "insufficient_balance",
-
- // Invoices
- CreateInvoiceFailed: "create_invoice_failed",
- PayInvoiceFailed: "invoice_payment_failed",
-
- // Events
- InvalidEvent: "invalid_event",
- CreateEventFailed: "create_event_failed",
- DuplicateEvent: "duplicate_event",
-
- // Customer Products
- CusProductNotFound: "cus_product_not_found",
- NoActiveCusProducts: "no_active_cus_products",
- GetCusProductsFailed: "get_cus_products_failed",
-
- // Entities
- EntityNotFound: "entity_not_found",
- EntityAlreadyDeleted: "entity_already_deleted",
- EntityIdRequired: "entity_id_required",
- EntityBalanceNotFound: "entity_balance_not_found",
-
- // Subscriptions
- InsertSubscriptionFailed: "insert_subscription_failed",
- UpdateSubscriptionFailed: "update_subscription_failed",
-
- // Rewards
- InvalidReward: "invalid_reward",
- PromoCodeAlreadyExistsInStripe: "promo_code_already_exists_in_stripe",
- RewardNotFound: "reward_not_found",
- RewardProgramNotFound: "reward_program_not_found",
- InsertRewardProgramFailed: "insert_reward_program_failed",
- InsertReferralCodeFailed: "insert_referral_code_failed",
- ReferralCodeNotFound: "referral_code_not_found",
- UpdateRewardRedemptionFailed: "update_reward_redemption_failed",
- RewardRedemptionNotFound: "reward_redemption_not_found",
- InsertRewardRedemptionFailed: "insert_reward_redemption_failed",
-
- // Referral codes
- ReferralCodeMaxRedemptionsReached: "referral_code_max_redemptions_reached",
- ReferralNotFound: "referral_not_found",
- CustomerAlreadyRedeemedReferralCode:
- "customer_already_redeemed_referral_code",
- CustomerCannotRedeemOwnCode: "customer_cannot_redeem_own_code",
-
- // Migration
- InsertMigrationJobFailed: "insert_migration_job_failed",
- InsertMigrationErrorFailed: "insert_migration_error_failed",
- MigrationJobNotFound: "migration_job_not_found",
-
- // External services
- SupabaseNotFound: "supabase_not_found",
- ClickHouseDisabled: "clickhouse_disabled",
-} as const;
-
-export type GeneralErrorCode =
- (typeof GeneralErrorCode)[keyof typeof GeneralErrorCode];
diff --git a/shared/api/errors/archivedCodes/price.ts b/shared/api/errors/archivedCodes/price.ts
deleted file mode 100644
index f06d67d40..000000000
--- a/shared/api/errors/archivedCodes/price.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Price-related error codes
- */
-export const PriceErrorCode = {
- PriceNotFound: "price_not_found",
- CreatePriceFailed: "create_price_failed",
- InvalidPrice: "invalid_price",
- InvalidPriceId: "invalid_price_id",
- InvalidPriceOptions: "invalid_price_options",
- InvalidPriceConfig: "invalid_price_config",
- CusPriceNotFound: "cus_price_not_found",
- GetCusPriceFailed: "get_cus_price_failed",
-} as const;
-
-export type PriceErrorCode =
- (typeof PriceErrorCode)[keyof typeof PriceErrorCode];
diff --git a/shared/api/errors/archivedCodes/stripe.ts b/shared/api/errors/archivedCodes/stripe.ts
deleted file mode 100644
index 640c2ec5b..000000000
--- a/shared/api/errors/archivedCodes/stripe.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Stripe integration error codes
- */
-export const StripeErrorCode = {
- StripeError: "stripe_error",
- StripeKeyInvalid: "stripe_key_invalid",
- StripeKeyNotFound: "stripe_key_not_found",
- StripeConfigNotFound: "stripe_config_not_found",
-
- // Stripe Operations
- StripeDeleteCustomerFailed: "stripe_delete_customer_failed",
- StripeCreateCustomerFailed: "stripe_create_customer_failed",
- StripeCreateProductFailed: "stripe_create_product_failed",
- StripeCancelSubscriptionFailed: "stripe_cancel_subscription_failed",
- StripeGetPaymentMethodFailed: "stripe_get_payment_method_failed",
- StripeCardDeclined: "stripe_card_declined",
- StripeUpdateSubscriptionFailed: "stripe_update_subscription_failed",
- StripeCancelSubscriptionScheduleFailed:
- "stripe_cancel_subscription_schedule_failed",
- StripeCreateSubscriptionFailed: "stripe_create_subscription_failed",
- CreateStripeSubscriptionFailed: "create_stripe_subscription_failed",
-} as const;
-
-export type StripeErrorCode =
- (typeof StripeErrorCode)[keyof typeof StripeErrorCode];
diff --git a/shared/api/errors/archives/auth.ts b/shared/api/errors/archives/auth.ts
deleted file mode 100644
index 47eae70ae..000000000
--- a/shared/api/errors/archives/auth.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-// import { AuthErrorCode } from "../archivedCodes/auth.js";
-// import { AutumnError } from "./base.js";
-
-// /**
-// * No secret key provided error
-// */
-// export class NoSecretKeyError extends AutumnError {
-// constructor(message = "No secret key provided") {
-// super({
-// message,
-// code: AuthErrorCode.NoSecretKey,
-// statusCode: 401,
-// });
-// this.name = "NoSecretKeyError";
-// }
-// }
-
-// /**
-// * Invalid secret key error
-// */
-// export class InvalidSecretKeyError extends AutumnError {
-// constructor(message = "Invalid secret key") {
-// super({
-// message,
-// code: AuthErrorCode.InvalidSecretKey,
-// statusCode: 401,
-// });
-// this.name = "InvalidSecretKeyError";
-// }
-// }
-
-// /**
-// * No auth header error
-// */
-// export class NoAuthHeaderError extends AutumnError {
-// constructor(message = "No authorization header provided") {
-// super({
-// message,
-// code: AuthErrorCode.NoAuthHeader,
-// statusCode: 401,
-// });
-// this.name = "NoAuthHeaderError";
-// }
-// }
-
-// /**
-// * Invalid auth header error
-// */
-// export class InvalidAuthHeaderError extends AutumnError {
-// constructor(message = "Invalid authorization header") {
-// super({
-// message,
-// code: AuthErrorCode.InvalidAuthHeader,
-// statusCode: 401,
-// });
-// this.name = "InvalidAuthHeaderError";
-// }
-// }
-
-// /**
-// * Invalid API version error
-// */
-// export class InvalidApiVersionError extends AutumnError {
-// constructor(version: string) {
-// super({
-// message: `Invalid API version: ${version}`,
-// code: AuthErrorCode.InvalidApiVersion,
-// statusCode: 400,
-// });
-// this.name = "InvalidApiVersionError";
-// }
-// }
-
-// /**
-// * No publishable key error
-// */
-// export class NoPublishableKeyError extends AutumnError {
-// constructor(message = "No publishable key provided") {
-// super({
-// message,
-// code: AuthErrorCode.NoPublishableKey,
-// statusCode: 401,
-// });
-// this.name = "NoPublishableKeyError";
-// }
-// }
-
-// /**
-// * Invalid publishable key error
-// */
-// export class InvalidPublishableKeyError extends AutumnError {
-// constructor(message = "Invalid publishable key") {
-// super({
-// message,
-// code: AuthErrorCode.InvalidPublishableKey,
-// statusCode: 401,
-// });
-// this.name = "InvalidPublishableKeyError";
-// }
-// }
-
-// /**
-// * Endpoint not public error
-// */
-// export class EndpointNotPublicError extends AutumnError {
-// constructor(message = "This endpoint is not public") {
-// super({
-// message,
-// code: AuthErrorCode.EndpointNotPublic,
-// statusCode: 403,
-// });
-// this.name = "EndpointNotPublicError";
-// }
-// }
diff --git a/shared/api/errors/archives/customer.ts b/shared/api/errors/archives/customer.ts
deleted file mode 100644
index fd7f87336..000000000
--- a/shared/api/errors/archives/customer.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-// import { CustomerErrorCode } from "../archivedCodes/customer.js";
-// import { AutumnError } from "./base.js";
-
-// /**
-// * Customer not found error
-// */
-// export class CustomerNotFoundError extends AutumnError {
-// constructor(customerId: string) {
-// super({
-// message: `Customer ${customerId} not found`,
-// code: CustomerErrorCode.CustomerNotFound,
-// statusCode: 404,
-// });
-// this.name = "CustomerNotFoundError";
-// }
-// }
-
-// /**
-// * Invalid customer error
-// */
-// export class InvalidCustomerError extends AutumnError {
-// constructor(message = "Invalid customer") {
-// super({
-// message,
-// code: CustomerErrorCode.InvalidCustomer,
-// statusCode: 400,
-// });
-// this.name = "InvalidCustomerError";
-// }
-// }
-
-// /**
-// * Customer already has product error
-// */
-// export class CustomerAlreadyHasProductError extends AutumnError {
-// constructor(customerId: string, productId: string) {
-// super({
-// message: `Customer ${customerId} already has product ${productId}`,
-// code: CustomerErrorCode.CustomerAlreadyHasProduct,
-// statusCode: 400,
-// });
-// this.name = "CustomerAlreadyHasProductError";
-// }
-// }
-
-// /**
-// * Customer has no payment method error
-// */
-// export class CustomerHasNoPaymentMethodError extends AutumnError {
-// constructor(message = "Customer has no payment method") {
-// super({
-// message,
-// code: CustomerErrorCode.CustomerHasNoPaymentMethod,
-// statusCode: 400,
-// });
-// this.name = "CustomerHasNoPaymentMethodError";
-// }
-// }
-
-// /**
-// * Multiple customers found error
-// */
-// export class MultipleCustomersFoundError extends AutumnError {
-// constructor(message = "Multiple customers found") {
-// super({
-// message,
-// code: CustomerErrorCode.MultipleCustomersFound,
-// statusCode: 400,
-// });
-// this.name = "MultipleCustomersFoundError";
-// }
-// }
-
-// /**
-// * Duplicate customer ID error
-// */
-// export class DuplicateCustomerIdError extends AutumnError {
-// constructor(customerId: string) {
-// super({
-// message: `Customer ${customerId} already exists`,
-// code: CustomerErrorCode.DuplicateCustomerId,
-// statusCode: 400,
-// });
-// this.name = "DuplicateCustomerIdError";
-// }
-// }
-
-// /**
-// * Duplicate customer email error
-// */
-// export class DuplicateCustomerEmailError extends AutumnError {
-// constructor(email: string) {
-// super({
-// message: `Customer with email ${email} already exists`,
-// code: CustomerErrorCode.DuplicateCustomerEmail,
-// statusCode: 400,
-// });
-// this.name = "DuplicateCustomerEmailError";
-// }
-// }
diff --git a/shared/api/errors/archives/general.ts b/shared/api/errors/archives/general.ts
deleted file mode 100644
index 88609c2e3..000000000
--- a/shared/api/errors/archives/general.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-// import { GeneralErrorCode } from "../archivedCodes/general.js";
-// import { AutumnError } from "./base.js";
-
-// /**
-// * Invalid request error
-// */
-// export class InvalidRequestError extends AutumnError {
-// constructor(message = "Invalid request") {
-// super({
-// message,
-// code: GeneralErrorCode.InvalidRequest,
-// statusCode: 400,
-// });
-// this.name = "InvalidRequestError";
-// }
-// }
-
-// /**
-// * Invalid inputs error (typically for validation failures)
-// */
-// export class InvalidInputsError extends AutumnError {
-// constructor(message = "Invalid inputs") {
-// super({
-// message,
-// code: GeneralErrorCode.InvalidInputs,
-// statusCode: 400,
-// });
-// this.name = "InvalidInputsError";
-// }
-// }
-
-// /**
-// * Internal server error
-// */
-// export class InternalError extends AutumnError {
-// constructor(message = "Internal server error") {
-// super({
-// message,
-// code: GeneralErrorCode.InternalError,
-// statusCode: 500,
-// });
-// this.name = "InternalError";
-// }
-// }
-
-// /**
-// * Organization not found error
-// */
-// export class OrgNotFoundError extends AutumnError {
-// constructor(message = "Organization not found") {
-// super({
-// message,
-// code: GeneralErrorCode.OrgNotFound,
-// statusCode: 404,
-// });
-// this.name = "OrgNotFoundError";
-// }
-// }
-
-// /**
-// * Feature not found error
-// */
-// export class FeatureNotFoundError extends AutumnError {
-// constructor(featureId: string) {
-// super({
-// message: `Feature ${featureId} not found`,
-// code: GeneralErrorCode.FeatureNotFound,
-// statusCode: 404,
-// });
-// this.name = "FeatureNotFoundError";
-// }
-// }
-
-// /**
-// * Invalid feature error
-// */
-// export class InvalidFeatureError extends AutumnError {
-// constructor(message = "Invalid feature") {
-// super({
-// message,
-// code: GeneralErrorCode.InvalidFeature,
-// statusCode: 400,
-// });
-// this.name = "InvalidFeatureError";
-// }
-// }
-
-// /**
-// * Entity not found error
-// */
-// export class EntityNotFoundError extends AutumnError {
-// constructor(entityId: string) {
-// super({
-// message: `Entity ${entityId} not found`,
-// code: GeneralErrorCode.EntityNotFound,
-// statusCode: 404,
-// });
-// this.name = "EntityNotFoundError";
-// }
-// }
-
-// /**
-// * Insufficient balance error
-// */
-// export class InsufficientBalanceError extends AutumnError {
-// constructor(message = "Insufficient balance") {
-// super({
-// message,
-// code: GeneralErrorCode.InsufficientBalance,
-// statusCode: 400,
-// });
-// this.name = "InsufficientBalanceError";
-// }
-// }
diff --git a/shared/api/errors/archives/price.ts b/shared/api/errors/archives/price.ts
deleted file mode 100644
index bb429ecca..000000000
--- a/shared/api/errors/archives/price.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-// import { PriceErrorCode } from "../archivedCodes/price.js";
-// import { AutumnError } from "./base.js";
-
-// /**
-// * Price not found error
-// */
-// export class PriceNotFoundError extends AutumnError {
-// constructor(priceId: string) {
-// super({
-// message: `Price ${priceId} not found`,
-// code: PriceErrorCode.PriceNotFound,
-// statusCode: 404,
-// });
-// this.name = "PriceNotFoundError";
-// }
-// }
-
-// /**
-// * Invalid price error
-// */
-// export class InvalidPriceError extends AutumnError {
-// constructor(message = "Invalid price") {
-// super({
-// message,
-// code: PriceErrorCode.InvalidPrice,
-// statusCode: 400,
-// });
-// this.name = "InvalidPriceError";
-// }
-// }
-
-// /**
-// * Invalid price config error
-// */
-// export class InvalidPriceConfigError extends AutumnError {
-// constructor(message = "Invalid price configuration") {
-// super({
-// message,
-// code: PriceErrorCode.InvalidPriceConfig,
-// statusCode: 400,
-// });
-// this.name = "InvalidPriceConfigError";
-// }
-// }
-
-// /**
-// * Invalid price options error
-// */
-// export class InvalidPriceOptionsError extends AutumnError {
-// constructor(message = "Invalid price options") {
-// super({
-// message,
-// code: PriceErrorCode.InvalidPriceOptions,
-// statusCode: 400,
-// });
-// this.name = "InvalidPriceOptionsError";
-// }
-// }
diff --git a/shared/api/features/featuresOpenApi.ts b/shared/api/features/featuresOpenApi.ts
deleted file mode 100644
index a966e6293..000000000
--- a/shared/api/features/featuresOpenApi.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import { z } from "zod/v4";
-import {
- getListResponseSchema,
- SuccessResponseSchema,
-} from "../common/commonResponses.js";
-import {
- ApiFeatureV0Schema,
- FEATURE_EXAMPLE,
-} from "./prevVersions/apiFeatureV0.js";
-import {
- CreateFeatureV0ParamsSchema,
- UpdateFeatureV0ParamsSchema,
-} from "./prevVersions/featureV0OpModels.js";
-
-// Register the schema with .meta() for OpenAPI spec generation
-export const ApiFeatureWithMeta = ApiFeatureV0Schema.extend({
- type: z.enum(["boolean", "single_use", "continuous_use", "credit_system"]),
-}).meta({
- id: "Feature",
- description: "",
- example: FEATURE_EXAMPLE,
-});
-
-export const featureOps = {
- "/features": {
- get: {
- summary: "List Features",
- tags: ["features"],
- responses: {
- "200": {
- description: "",
- content: {
- "application/json": {
- schema: getListResponseSchema({ schema: ApiFeatureWithMeta }),
- },
- },
- },
- },
- },
- post: {
- summary: "Create Feature",
- tags: ["features"],
- requestBody: {
- content: {
- "application/json": { schema: CreateFeatureV0ParamsSchema },
- },
- },
- responses: {
- "200": {
- description: "",
- content: { "application/json": { schema: ApiFeatureWithMeta } },
- },
- },
- },
- },
- "/features/{feature_id}": {
- get: {
- summary: "Get Feature",
- tags: ["features"],
- requestParams: {
- path: z.object({
- feature_id: z.string(),
- }),
- },
- responses: {
- "200": {
- description: "",
- content: { "application/json": { schema: ApiFeatureWithMeta } },
- },
- },
- },
- post: {
- summary: "Update Feature",
- tags: ["features"],
- requestParams: {
- path: z.object({
- feature_id: z.string(),
- }),
- },
- requestBody: {
- content: {
- "application/json": { schema: UpdateFeatureV0ParamsSchema },
- },
- },
- responses: {
- "200": {
- description: "",
- content: { "application/json": { schema: ApiFeatureWithMeta } },
- },
- },
- },
- delete: {
- summary: "Delete Feature",
- tags: ["features"],
- requestParams: {
- path: z.object({
- feature_id: z.string(),
- }),
- },
- responses: {
- "200": {
- description: "",
- content: {
- "application/json": {
- schema: SuccessResponseSchema,
- },
- },
- },
- },
- },
- },
-};
diff --git a/shared/api/versionUtils/orgVersionUtils.ts b/shared/api/versionUtils/orgVersionUtils.ts
deleted file mode 100644
index 6d755ca12..000000000
--- a/shared/api/versionUtils/orgVersionUtils.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-// import type { Organization } from "@models/orgModels/orgTable.js";
-// import { ApiVersionClass } from "./ApiVersionClass.js";
-// import { legacyToSemVer, semVerToLegacy } from "./convertVersionUtils.js";
-
-// /**
-// * DEPRECATED: Use ctx.apiVersion directly
-// * Middleware handles version resolution automatically
-// */
-// export function getOrgApiVersion({
-// org,
-// reqApiVersion,
-// }: {
-// org: Organization;
-// reqApiVersion?: ApiVersionClass;
-// }): ApiVersionClass {
-// if (reqApiVersion) {
-// return reqApiVersion;
-// }
-
-// const legacyVersion = org.api_version || org.config?.api_version || 1.0;
-// const semver = legacyToSemVer({ legacyVersion });
-
-// if (!semver) {
-// throw new Error(`Invalid version for org: ${legacyVersion}`);
-// }
-
-// return new ApiVersionClass(semver);
-// }
-
-// /**
-// * Convert ApiVersionClass → legacy float
-// * @example toLegacyVersion({ apiVersion: ctx.apiVersion }) // 1.1
-// */
-// export function toLegacyVersion({
-// apiVersion,
-// }: {
-// apiVersion: ApiVersionClass;
-// }): number {
-// const legacy = semVerToLegacy({ semver: apiVersion.value });
-// return legacy || 1.0;
-// }
diff --git a/shared/archives/orgModels_old.ts b/shared/archives/orgModels_old.ts
deleted file mode 100644
index 8676a0839..000000000
--- a/shared/archives/orgModels_old.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-// Old File
-
-import { z } from "zod/v4";
-import { OrgConfigSchema } from "../models/orgModels/orgConfig.js";
-
-export const StripeConfigSchema = z.object({
- test_api_key: z.string(),
- live_api_key: z.string(),
- test_webhook_secret: z.string(),
- live_webhook_secret: z.string(),
- success_url: z.string(),
-});
-
-export const SvixConfigSchema = z.object({
- sandbox_app_id: z.string(),
- live_app_id: z.string(),
-});
-
-export const OrganizationSchema = z.object({
- id: z.string(),
- slug: z.string(),
- default_currency: z.string().default("usd"),
- stripe_connected: z.boolean().default(false),
- stripe_config: StripeConfigSchema.optional().nullable(),
-
- test_pkey: z.string(),
- live_pkey: z.string(),
- created_at: z.number(),
-
- svix_config: z.object({
- sandbox_app_id: z.string(),
- live_app_id: z.string(),
- }),
-
- config: OrgConfigSchema,
- api_version: z.number().nullish(),
-});
-
-export type Organization = z.infer;
-export type StripeConfig = z.infer;
-export type SvixConfig = z.infer;
diff --git a/shared/models/cusModels/invoiceModels/invoiceItemModels.ts b/shared/models/cusModels/invoiceModels/invoiceItemModels.ts
deleted file mode 100644
index dee31a913..000000000
--- a/shared/models/cusModels/invoiceModels/invoiceItemModels.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { z } from "zod/v4";
-
-const InvoiceItemSchema = z.object({
- id: z.string(),
- created_at: z.number(),
- updated_at: z.number(),
-
- // customer_id: z.string(), // just to show
- customer_id: z.string(),
- customer_price_id: z.string(),
- period_start: z.number(),
- period_end: z.number(),
- proration_start: z.number(),
- proration_end: z.number(),
-
- quantity: z.number(), // of feature
- amount: z.number(),
- currency: z.string(),
- added_to_stripe: z.boolean(),
-});
-
-export type InvoiceItem = z.infer;
diff --git a/shared/models/cusProductModels/cusEntModels/rolloverModels/rolloverModels.ts b/shared/models/cusProductModels/cusEntModels/rolloverModels/rolloverModels.ts
deleted file mode 100644
index 4ab05c8f9..000000000
--- a/shared/models/cusProductModels/cusEntModels/rolloverModels/rolloverModels.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// import { z } from "zod/v4";
-// import { EntityBalance, EntityBalanceSchema } from "../../../index.js";
-// import { jsonb } from "drizzle-orm/pg-core";
-
-// export const RolloverModelSchema = z.object({
-// id: z.string(),
-// cus_ent_id: z.string(),
-// balance: z.number(),
-// entities: z.record(z.string(), EntityBalanceSchema),
-// expires_at: z.number().nullable(),
-// });
-
-// export type RolloverModel = z.infer;
diff --git a/shared/models/otherModels/metadataModels.ts b/shared/models/otherModels/metadataModels.ts
deleted file mode 100644
index bf322cc04..000000000
--- a/shared/models/otherModels/metadataModels.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-// import { z } from "zod/v4";
-
-// export const AutumnMetadataSchema = z.object({
-// id: z.string(),
-// created_at: z.number(),
-// expires_at: z.number(),
-// data: z.any(),
-// });
-
-// export type AutumnMetadata = z.infer;
diff --git a/shared/models/productModels/pricecnProductModel.ts b/shared/models/productModels/pricecnProductModel.ts
deleted file mode 100644
index 9cff4a9a6..000000000
--- a/shared/models/productModels/pricecnProductModel.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export interface PricecnProduct {
- name: string;
- description?: string;
-
- price: {
- primaryText: string;
- secondaryText?: string;
- };
-
- everythingFrom?: string;
- buttonText?: string;
- recommendText?: string;
-
- items: {
- primaryText: string;
- secondaryText?: string;
- }[];
-}
diff --git a/shared/models/productV2Models/productItemModels/checkProdItemModels.ts b/shared/models/productV2Models/productItemModels/checkProdItemModels.ts
deleted file mode 100644
index 7e967d4ff..000000000
--- a/shared/models/productV2Models/productItemModels/checkProdItemModels.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// import { z } from "zod/v4";
-// import { UsageModel } from "../productItemModels.js";
-
-// export const CheckProdItemSchema = z.object({
-// feature_id: z.string().nullish(),
-// feature_name: z.string().nullish(),
-// usage_model: z.nativeEnum(UsageModel).nullish(),
-
-// description: z.string(),
-// price: z.number().nullish(),
-
-// billing_units: z.number().nullish(),
-// });
-
-// export type CheckProdItem = z.infer;
diff --git a/shared/utils/billingUtils/invoicingUtils/lineItemUtils/lineItemToCredit.ts b/shared/utils/billingUtils/invoicingUtils/lineItemUtils/lineItemToCredit.ts
deleted file mode 100644
index 053d20ec5..000000000
--- a/shared/utils/billingUtils/invoicingUtils/lineItemUtils/lineItemToCredit.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { LineItem } from "../../../../models/billingModels/invoicingModels/lineItem";
-
-// Helper function - lives in lineItemUtils
-export const lineItemToCredit = (item: LineItem): LineItem => ({
- ...item,
- amount: -item.amount,
- description: `Unused ${item.description}`,
-});
diff --git a/shared/utils/billingUtils/invoicingUtils/prorationUtils/applyProrationToLineItem.ts b/shared/utils/billingUtils/invoicingUtils/prorationUtils/applyProrationToLineItem.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/shared/utils/cusEntUtils/balanceUtils/cusEntToPurchasedBalance.ts b/shared/utils/cusEntUtils/balanceUtils/cusEntToPurchasedBalance.ts
deleted file mode 100644
index 9f6d23acd..000000000
--- a/shared/utils/cusEntUtils/balanceUtils/cusEntToPurchasedBalance.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import type { FullCusEntWithFullCusProduct } from "@models/cusProductModels/cusEntModels/cusEntWithProduct";
-import { BillingType } from "@models/productModels/priceModels/priceEnums";
-import { getCusEntBalance } from "@utils/cusEntUtils/balanceUtils";
-import { cusEntToCusPrice } from "@utils/cusEntUtils/convertCusEntUtils/cusEntToCusPrice";
-import { entToOptions } from "@utils/productUtils/convertProductUtils";
-import { getBillingType } from "@utils/productUtils/priceUtils";
-import { nullish } from "@utils/utils";
-import { Decimal } from "decimal.js";
-
-export const cusEntToPurchasedBalance = ({
- cusEnt,
- entityId,
-}: {
- cusEnt: FullCusEntWithFullCusProduct;
- entityId?: string;
-}) => {
- const cusPrice = cusEntToCusPrice({ cusEnt });
-
- if (!cusEnt.customer_product) return 0;
- if (nullish(cusPrice)) {
- const { balance } = getCusEntBalance({
- cusEnt,
- entityId,
- });
-
- return Math.max(0, -balance);
- }
-
- const billingType = getBillingType(cusPrice.price.config);
- const billingUnits = cusPrice.price.config.billing_units || 1;
-
- if (billingType === BillingType.UsageInAdvance) {
- // Purchased balance is how much was prepaid
- const cusProduct = cusEnt.customer_product;
- const options = entToOptions({
- ent: cusEnt.entitlement,
- options: cusProduct?.options ?? [],
- });
-
- const quantity = options?.quantity || 0;
- const quantityWithBillingUnits = new Decimal(quantity)
- .mul(billingUnits)
- .toNumber();
-
- // Add negative cus ent balance too
- const { balance } = getCusEntBalance({
- cusEnt,
- entityId,
- });
-
- return quantityWithBillingUnits + Math.max(0, -balance);
- }
-
- const { balance } = getCusEntBalance({
- cusEnt,
- entityId,
- });
-
- return Math.max(0, -balance);
-};
diff --git a/shared/utils/cusEntUtils/balanceUtils/cusEntsToPlanId.ts b/shared/utils/cusEntUtils/balanceUtils/cusEntsToPlanId.ts
deleted file mode 100644
index 7c0057576..000000000
--- a/shared/utils/cusEntUtils/balanceUtils/cusEntsToPlanId.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { FullCusEntWithFullCusProduct } from "../../../models/cusProductModels/cusEntModels/cusEntWithProduct";
-
-export const cusEntsToPlanId = ({
- cusEnts,
-}: {
- cusEnts: FullCusEntWithFullCusProduct[];
-}) => {
- if (cusEnts.length !== 1) return null;
-
- return cusEnts[0].customer_product?.product.id ?? null;
-};
diff --git a/shared/utils/cusProductUtils/convertCusProduct/cusProductToCusEnts.ts b/shared/utils/cusProductUtils/convertCusProduct/cusProductToCusEnts.ts
deleted file mode 100644
index 888300009..000000000
--- a/shared/utils/cusProductUtils/convertCusProduct/cusProductToCusEnts.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { FullCusEntWithFullCusProduct } from "../../../models/cusProductModels/cusEntModels/cusEntWithProduct.js";
-import type { FullCusProduct } from "../../../models/cusProductModels/cusProductModels.js";
-
-export const cusProductToCusEnts = ({
- cusProduct,
-}: {
- cusProduct: FullCusProduct;
-}): FullCusEntWithFullCusProduct[] => {
- return cusProduct.customer_entitlements.map((ce) => ({
- ...ce,
- customer_product: cusProduct,
- }));
-};
diff --git a/shared/utils/productV2Utils/productItemUtils/matchItem.ts b/shared/utils/productV2Utils/productItemUtils/matchItem.ts
deleted file mode 100644
index 5c187a990..000000000
--- a/shared/utils/productV2Utils/productItemUtils/matchItem.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { Entitlement } from "../../../models/productModels/entModels/entModels.js";
-import type { Price } from "../../../models/productModels/priceModels/priceModels.js";
-import type { ProductItem } from "../../../models/productV2Models/productItemModels/productItemModels.js";
-
-export const matchItemToPriceAndEnt = ({
- item,
- curPrices,
- curEnts,
- taken,
-}: {
- item: ProductItem;
- curPrices: Price[];
- curEnts: Entitlement[];
- taken: Set;
-}) => {
- const leftEnts = curEnts.filter((ent) => !taken.has(ent.id));
- const leftPrices = curPrices.filter((price) => !taken.has(price.id));
-
- // return items.find(
- // (i) => i.feature_id === item.feature_id && i.interval === item.interval,
- // );
-};
diff --git a/shared/utils/productV3Utils/mapToProductV3.ts b/shared/utils/productV3Utils/mapToProductV3.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/vite/src/App.tsx b/vite/src/App.tsx
index f1b19efb7..1e6634213 100644
--- a/vite/src/App.tsx
+++ b/vite/src/App.tsx
@@ -12,8 +12,8 @@ import { AdminView } from "./views/admin/AdminView";
import { ImpersonateRedirect } from "./views/admin/ImpersonateRedirect";
import { OAuthClientsView } from "./views/admin/oauth/OAuthClientsView";
import { AcceptInvitation } from "./views/auth/AcceptInvitation";
-import { PasswordSignIn } from "./views/auth/components/PasswordSignIn";
import { Consent } from "./views/auth/Consent";
+import { PasswordSignIn } from "./views/auth/components/PasswordSignIn";
import { SignIn } from "./views/auth/SignIn";
import { Otp } from "./views/cli/Otp";
import CustomersPage from "./views/customers/CustomersPage";
diff --git a/vite/src/components/autumn/PlanCardPreview.tsx b/vite/src/components/autumn/PlanCardPreview.tsx
deleted file mode 100644
index e9f3591fc..000000000
--- a/vite/src/components/autumn/PlanCardPreview.tsx
+++ /dev/null
@@ -1,214 +0,0 @@
-import {
- isFeaturePriceItem,
- type ProductItem,
- type ProductV2,
- productV2ToBasePrice,
- productV2ToFeatureItems,
-} from "@autumn/shared";
-import { Button } from "@/components/v2/buttons/Button";
-import { Card, CardContent, CardHeader } from "@/components/v2/cards/Card";
-import { Separator } from "@/components/v2/separator";
-import { cn } from "@/lib/utils";
-import { keyToTitle } from "@/utils/formatUtils/formatTextUtils";
-import { PlanFeatureIcon } from "@/views/products/plan/components/plan-card/PlanFeatureIcon";
-
-interface PlanCardPreviewProps {
- product: ProductV2;
- buttonText?: string;
- onButtonClick?: () => void;
- recommended?: boolean;
- disabled?: boolean;
- loading?: boolean;
-}
-
-// Custom dot component
-const CustomDotIcon = () => {
- return
;
-};
-
-// Simplified feature row for preview (non-interactive)
-const PlanFeatureRowPreview = ({ item }: { item: ProductItem }) => {
- // Use pre-computed display if available, otherwise compute it
- const display = item.display || { primary_text: "", secondary_text: "" };
-
- return (
-
- {/* Left side - Icons and text */}
-
-
-
-
-
- {display.primary_text}
- {display.secondary_text && (
-
- {" "}
- {display.secondary_text}
-
- )}
-
-
-
-
- );
-};
-
-export const PlanCardPreview = ({
- product,
- buttonText = "Subscribe",
- onButtonClick,
- recommended = false,
- disabled = false,
- loading = false,
-}: PlanCardPreviewProps) => {
- const basePrice = productV2ToBasePrice({ product: product as ProductV2 });
- const featureItems = productV2ToFeatureItems({
- items: product.items as ProductItem[],
- });
-
- return (
-
-
-
- {/* Title */}
-
{product.name}
- {/* Price */}
- {product.items.filter((x) => isFeaturePriceItem(x as ProductItem))
- .length > 0 &&
- (basePrice?.price === 0 || basePrice?.price == null) ? (
-
Varies
- ) : typeof basePrice?.price === "number" && basePrice?.price > 0 ? (
-
- ${basePrice?.price}/
- {keyToTitle(basePrice?.interval ?? "one-off", {
- exclusionMap: { one_off: "one-off" },
- }).toLowerCase()}
-
- ) : (
-
Free
- )}
- {/* Description */}
- {product.description && (
-
- {product.description}
-
- )}
-
-
-
-
-
-
- {/* Feature list */}
- {featureItems.length > 0 && (
-
- {featureItems.map((item, index) => (
-
- ))}
-
- )}
- {/* Action button */}
- {/* */}
- {/* */}
-
-
-
{buttonText}
- {!disabled && (
-
- Arrow Right
-
-
-
-
- )}
-
-
-
{buttonText}
- {!disabled && (
-
- Arrow Right
-
-
-
-
- )}
-
-
- {/*
-
- When checking out in test mode -
- use 4242 4242 4242 4242 as the card number,
- 04/42 as the expiry date, and{" "}
- any CVC .
- */}
- {/* */}
-
-
- );
-};
diff --git a/vite/src/components/autumn/PricingTableContainer.tsx b/vite/src/components/autumn/PricingTableContainer.tsx
deleted file mode 100644
index c7cc29009..000000000
--- a/vite/src/components/autumn/PricingTableContainer.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { usePricingTable } from "autumn-js/react";
-import PricingTablePreview from "./pricing-table-preview";
-
-interface PricingTableContainerProps {
- setConnectStripeOpen: (open: boolean) => void;
-}
-
-export const PricingTableContainer = ({
- setConnectStripeOpen,
-}: PricingTableContainerProps) => {
- const { products, isLoading, refetch } = usePricingTable();
-
- if (isLoading) {
- return (
-
-
Loading pricing table...
-
- );
- }
-
- return (
-
- );
-};
diff --git a/vite/src/components/autumn/attach-dialog.tsx b/vite/src/components/autumn/attach-dialog.tsx
deleted file mode 100644
index 1d043ea90..000000000
--- a/vite/src/components/autumn/attach-dialog.tsx
+++ /dev/null
@@ -1,285 +0,0 @@
-"use client";
-
-import type { CheckProductPreview } from "autumn-js";
-import { useCustomer } from "autumn-js/react";
-import { ArrowRight, Loader2 } from "lucide-react";
-import type React from "react";
-import { useEffect, useState } from "react";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { getAttachContent } from "@/lib/autumn/attach-content";
-import { cn } from "@/lib/utils";
-
-export interface AttachDialogProps {
- open: boolean;
- setOpen: (open: boolean) => void;
- preview: CheckProductPreview;
- onClick: (options?: any) => Promise;
-}
-
-export default function AttachDialog(params?: AttachDialogProps) {
- const { attach } = useCustomer();
- const [loading, setLoading] = useState(false);
- const [optionsInput, setOptionsInput] = useState(
- params?.preview?.options || [],
- );
-
- const getTotalPrice = () => {
- let sum = due_today?.price || 0;
- optionsInput.forEach((option) => {
- if (option.price && option.quantity) {
- sum += option.price * (option.quantity / option.billing_units);
- }
- });
- return sum;
- };
-
- useEffect(() => {
- setOptionsInput(params?.preview?.options || []);
- }, [params?.preview?.options]);
-
- if (!params || !params.preview) {
- return <>>;
- }
-
- const { open, setOpen, preview } = params;
- const { items, due_today } = preview;
- const { title, message } = getAttachContent(preview);
-
- return (
-
-
- {title}
-
- {message}
-
- {(items || optionsInput.length > 0) && (
-
- {items?.map((item) => (
-
- {item.description}
- {item.price}
-
- ))}
-
- {optionsInput?.map((option, index) => {
- return (
-
- );
- })}
-
- )}
-
-
- {due_today && (
-
- Due Today
-
- {new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: due_today.currency,
- }).format(getTotalPrice())}
-
-
- )}
- {
- setLoading(true);
- await attach({
- productId: preview.product_id,
- options: optionsInput.map((option) => ({
- featureId: option.feature_id,
- quantity: option.quantity || 0,
- })),
- });
- setOpen(false);
- setLoading(false);
- }}
- disabled={loading}
- className="min-w-16 flex items-center gap-2"
- >
- {loading ? (
-
- ) : (
- <>
- Confirm
- >
- )}
-
-
-
-
- );
-}
-
-export const PriceItem = ({
- children,
- className,
- ...props
-}: {
- children: React.ReactNode;
- className?: string;
-} & React.HTMLAttributes) => {
- return (
-
- {children}
-
- );
-};
-
-interface FeatureOption {
- feature_id: string;
- feature_name: string;
- billing_units: number;
- price?: number;
- quantity?: number;
-}
-
-interface FeatureOptionWithRequiredPrice
- extends Omit {
- price: number;
- quantity: number;
-}
-
-export const OptionsInput = ({
- className,
- option,
- optionsInput,
- setOptionsInput,
- index,
- ...props
-}: {
- className?: string;
- option: FeatureOptionWithRequiredPrice;
- optionsInput: FeatureOption[];
- setOptionsInput: (options: FeatureOption[]) => void;
- index: number;
-} & React.HTMLAttributes) => {
- const { feature_name, billing_units, quantity, price } = option;
- return (
-
- {feature_name}
- ) => {
- const newOptions = [...optionsInput];
- newOptions[index].quantity = parseInt(e.target.value) * billing_units;
- setOptionsInput(newOptions);
- }}
- >
-
- × ${price} per {billing_units === 1 ? " " : billing_units}{" "}
- {feature_name}
-
-
-
- );
-};
-
-export const QuantityInput = ({
- children,
- onChange,
- value,
- className,
- ...props
-}: {
- children: React.ReactNode;
- value: string | number;
- onChange: (e: React.ChangeEvent) => void;
- className?: string;
-} & React.HTMLAttributes) => {
- const currentValue = Number(value) || 0;
-
- const handleValueChange = (newValue: number) => {
- const syntheticEvent = {
- target: { value: String(newValue) },
- } as React.ChangeEvent;
- onChange(syntheticEvent);
- };
-
- return (
-
-
-
- currentValue > 0 && handleValueChange(currentValue - 1)
- }
- disabled={currentValue <= 0}
- className="h-6 w-6 pb-0.5"
- >
- -
-
- {currentValue}
- handleValueChange(currentValue + 1)}
- className="h-6 w-6 pb-0.5"
- >
- +
-
-
- {children}
-
- );
-};
-
-export const TotalPrice = ({ children }: { children: React.ReactNode }) => {
- return (
-
- {children}
-
- );
-};
-
-export const PricingDialogButton = ({
- children,
- size,
- onClick,
- disabled,
- className,
-}: {
- children: React.ReactNode;
- size?: "sm" | "lg" | "default" | "icon";
- onClick: () => void;
- disabled?: boolean;
- className?: string;
-}) => {
- return (
-
- {children}
-
-
- );
-};
diff --git a/vite/src/components/autumn/checkout-dialog.tsx b/vite/src/components/autumn/checkout-dialog.tsx
deleted file mode 100644
index 35b2df946..000000000
--- a/vite/src/components/autumn/checkout-dialog.tsx
+++ /dev/null
@@ -1,462 +0,0 @@
-"use client";
-import * as AccordionPrimitive from "@radix-ui/react-accordion";
-import type { CheckoutResult, ProductItem } from "autumn-js";
-import { useCustomer } from "autumn-js/react";
-import { ArrowRight, ChevronDown, Loader2 } from "lucide-react";
-import type React from "react";
-import { useEffect, useState } from "react";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
-} from "@/components/ui/accordion";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { getCheckoutContent } from "@/lib/autumn/checkout-content";
-import { cn } from "@/lib/utils";
-import { useModelPricingContext } from "@/views/onboarding2/model-pricing/ModelPricingContext";
-
-export interface CheckoutDialogProps {
- open: boolean;
- setOpen: (open: boolean) => void;
- checkoutResult: CheckoutResult;
-}
-
-const formatCurrency = ({
- amount,
- currency,
-}: {
- amount: number;
- currency: string;
-}) => {
- return new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: currency,
- }).format(amount);
-};
-
-export default function CheckoutDialog(params: CheckoutDialogProps) {
- const { attach } = useCustomer();
- const [checkoutResult, setCheckoutResult] = useState<
- CheckoutResult | undefined
- >(params?.checkoutResult);
- const { refetch } = useModelPricingContext();
-
- useEffect(() => {
- if (params.checkoutResult) {
- setCheckoutResult(params.checkoutResult);
- }
- }, [params.checkoutResult]);
-
- const [loading, setLoading] = useState(false);
-
- if (!checkoutResult) {
- return <>>;
- }
-
- const { open, setOpen } = params;
- const { title, message } = getCheckoutContent(checkoutResult);
-
- const isFree = checkoutResult?.product.properties?.is_free;
- const isPaid = isFree === false;
-
- return (
-
-
- {title}
- {message}
-
- {isPaid && checkoutResult && (
-
- )}
-
-
- {
- setLoading(true);
-
- const options = checkoutResult.options.map((option) => {
- return {
- featureId: option.feature_id,
- quantity: option.quantity,
- };
- });
-
- await attach({
- productId: checkoutResult.product.id,
- options,
- openInNewTab: true,
- });
- await refetch();
- setOpen(false);
- setLoading(false);
- }}
- disabled={loading}
- className="min-w-16 flex items-center gap-2 !bg-zinc-900 hover:!bg-zinc-600"
- >
- {loading ? (
-
- ) : (
- <>
- Confirm
- >
- )}
-
-
-
-
- );
-}
-
-function PriceInformation({
- checkoutResult,
- setCheckoutResult,
-}: {
- checkoutResult: CheckoutResult;
- setCheckoutResult: (checkoutResult: CheckoutResult) => void;
-}) {
- return (
-
-
-
-
- {checkoutResult?.has_prorations && checkoutResult.lines.length > 0 && (
-
- )}
-
-
-
- );
-}
-
-function DueAmounts({ checkoutResult }: { checkoutResult: CheckoutResult }) {
- const { next_cycle, product } = checkoutResult;
- const nextCycleAtStr = next_cycle
- ? new Date(next_cycle.starts_at).toLocaleDateString()
- : undefined;
-
- const hasUsagePrice = product.items.some(
- (item) => item.usage_model === "pay_per_use",
- );
-
- const showNextCycle = next_cycle && next_cycle.total !== checkoutResult.total;
-
- return (
-
-
-
-
-
- {formatCurrency({
- amount: checkoutResult?.total,
- currency: checkoutResult?.currency,
- })}
-
-
- {showNextCycle && (
-
-
-
Due next cycle ({nextCycleAtStr})
-
-
- {formatCurrency({
- amount: next_cycle.total,
- currency: checkoutResult?.currency,
- })}
- {hasUsagePrice && + usage prices }
-
-
- )}
-
- );
-}
-
-function ProductItems({
- checkoutResult,
- setCheckoutResult,
-}: {
- checkoutResult: CheckoutResult;
- setCheckoutResult: (checkoutResult: CheckoutResult) => void;
-}) {
- const isUpdateQuantity =
- checkoutResult?.product.scenario === "active" &&
- checkoutResult.product.properties.updateable;
- return (
-
-
Price
- {checkoutResult?.product.items
- .filter((item) => item.type !== "feature")
- .map((item, index) => {
- if (item.usage_model == "prepaid") {
- return (
-
- );
- }
-
- if (isUpdateQuantity) {
- return null;
- }
-
- return (
-
-
- {item.feature ? item.feature.name : "Subscription"}
-
-
- {item.display?.primary_text} {item.display?.secondary_text}
-
-
- );
- })}
-
- );
-}
-
-function CheckoutLines({ checkoutResult }: { checkoutResult: CheckoutResult }) {
- return (
-
-
-
-
-
-
- {checkoutResult?.lines
- .filter((line) => line.amount != 0)
- .map((line, index) => {
- return (
-
-
{line.description}
-
- {new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: checkoutResult?.currency,
- }).format(line.amount)}
-
-
- );
- })}
-
-
-
- );
-}
-
-function CustomAccordionTrigger({
- className,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
-
- {children}
-
-
- );
-}
-
-const PrepaidItem = ({
- item,
- checkoutResult,
- setCheckoutResult,
-}: {
- item: ProductItem;
- checkoutResult: CheckoutResult;
- setCheckoutResult: (checkoutResult: CheckoutResult) => void;
-}) => {
- const { quantity = 0, billing_units: billingUnits = 1 } = item;
- const [quantityInput, setQuantityInput] = useState(
- (quantity / billingUnits).toString(),
- );
- const { checkout } = useCustomer();
- const [loading, setLoading] = useState(false);
- const [open, setOpen] = useState(false);
- const scenario = checkoutResult.product.scenario;
-
- const handleSave = async () => {
- setLoading(true);
- try {
- const newOptions = checkoutResult.options
- .filter((option) => option.feature_id !== item.feature_id)
- .map((option) => {
- return {
- featureId: option.feature_id,
- quantity: option.quantity,
- };
- });
-
- newOptions.push({
- featureId: item.feature_id!,
- quantity: Number(quantityInput) * billingUnits,
- });
-
- const { data, error } = await checkout({
- productId: checkoutResult.product.id,
- options: newOptions,
- dialog: CheckoutDialog,
- openInNewTab: true,
- });
-
- if (error) {
- console.error(error);
- return;
- }
- setCheckoutResult(data!);
- } catch (error) {
- console.error(error);
- } finally {
- setLoading(false);
- setOpen(false);
- }
- };
-
- const disableSelection = scenario === "renew";
-
- return (
-
-
-
- {item.feature?.name}
-
-
-
- Qty: {quantity}
-
-
-
-
-
{item.feature?.name}
-
- {item.display?.primary_text} {item.display?.secondary_text}
-
-
-
-
-
-
setQuantityInput(e.target.value)}
- />
-
- {billingUnits > 1 && `x ${billingUnits} `}
- {item.feature?.name}
-
-
-
-
- {loading ? (
-
- ) : (
- "Save"
- )}
-
-
-
-
-
-
- {item.display?.primary_text} {item.display?.secondary_text}
-
-
- );
-};
-
-export const PriceItem = ({
- children,
- className,
- ...props
-}: {
- children: React.ReactNode;
- className?: string;
-} & React.HTMLAttributes) => {
- return (
-
- {children}
-
- );
-};
-
-export const PricingDialogButton = ({
- children,
- size,
- onClick,
- disabled,
- className,
-}: {
- children: React.ReactNode;
- size?: "sm" | "lg" | "default" | "icon";
- onClick: () => void;
- disabled?: boolean;
- className?: string;
-}) => {
- return (
-
- {children}
-
-
- );
-};
diff --git a/vite/src/components/autumn/pricing-table-preview.tsx b/vite/src/components/autumn/pricing-table-preview.tsx
deleted file mode 100644
index 1a62a7b7d..000000000
--- a/vite/src/components/autumn/pricing-table-preview.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { InfoIcon } from "@phosphor-icons/react";
-import type { Product } from "autumn-js";
-import { useCustomer } from "autumn-js/react";
-import { useState } from "react";
-import { useOrg } from "@/hooks/common/useOrg";
-import OnboardingCheckoutDialog from "@/views/onboarding3/OnboardingCheckoutDialog";
-import { useOnboardingStore } from "@/views/onboarding3/store/useOnboardingStore";
-import { PlanCardPreview } from "./PlanCardPreview";
-
-interface PricingTableProps {
- products?: ProductV2[];
- setConnectStripeOpen: (open: boolean) => void;
- onCheckoutComplete?: () => void;
-}
-
-export default function PricingTablePreview({
- products,
- setConnectStripeOpen,
-}: PricingTableProps) {
- const { org } = useOrg();
- const { checkout } = useCustomer({
- swrConfig: {
- refreshInterval: 0,
- },
- });
- const setLastUsedProductId = useOnboardingStore(
- (state) => state.setLastUsedProductId,
- );
- const [loadingProductId, setLoadingProductId] = useState(null);
-
- if (!products || products.length === 0) {
- return null;
- }
-
- const handleSubscribe = async (product: ProductV2) => {
- // Track the product ID that was clicked
- if (product.id) {
- setLastUsedProductId(product.id);
- }
-
- if (product.id) {
- setLoadingProductId(product.id);
- try {
- await checkout({
- productId: product.id,
- dialog: OnboardingCheckoutDialog,
- openInNewTab: true,
- successUrl: `${window.location.origin}/sandbox/onboarding?step=playground&m=p`,
- });
- } catch (error) {
- console.error("Checkout error:", error);
- } finally {
- setLoadingProductId(null);
- }
- } else if (product.display?.button_url) {
- window.open(product.display?.button_url, "_blank");
- }
- };
-
- const getButtonText = (product: Product) => {
- if (product.scenario === "active") {
- return "Current product";
- }
- if (product.scenario === "downgrade") {
- return "Downgrade";
- }
- if (product.scenario === "upgrade") {
- return "Upgrade";
- }
- if (product.scenario === "scheduled") {
- return "Scheduled";
- }
- return product.display?.button_text || "Subscribe";
- };
-
- const isRecommended = (product: Product) => {
- return !!product.display?.recommend_text;
- };
-
- // Dynamic grid classes based on product count
- const getGridClasses = () => {
- const productCount = products.length;
- // Always one column on small screens (vertical flow), more columns only on larger screens
- if (productCount === 1) {
- return "flex flex-col gap-6 max-w-md mx-auto px-4"; // Always vertical
- } else if (productCount === 2) {
- return "flex flex-col gap-6 max-w-2xl mx-auto px-4 sm:grid sm:grid-cols-2 sm:flex-none"; // Vertical on mobile, 2 columns on sm+
- } else {
- return "flex flex-col gap-6 max-w-7xl mx-auto px-4 sm:grid lg:grid-cols-2 2xl:grid-cols-3 sm:flex-none"; // Vertical on mobile, 2 columns on sm+, 3 on lg+
- }
- };
-
- return (
-
-
-
-
-
-
- Test mode checkout:
-
-
-
-
- Card number
-
-
-
- 4242 4242 4242 4242
-
-
-
-
-
-
-
-
- {products.map((product, index) => (
-
handleSubscribe(product)}
- recommended={isRecommended(product as Product)}
- disabled={
- (product.scenario === "active" &&
- !product.properties?.updateable) ||
- product.scenario === "scheduled"
- }
- loading={loadingProductId === product.id}
- />
- ))}
-
-
- );
-}
diff --git a/vite/src/components/autumn/pricing-table.tsx b/vite/src/components/autumn/pricing-table.tsx
deleted file mode 100644
index 47691ecec..000000000
--- a/vite/src/components/autumn/pricing-table.tsx
+++ /dev/null
@@ -1,388 +0,0 @@
-import type { Product, ProductItem } from "autumn-js";
-import { useCustomer } from "autumn-js/react";
-import { Loader2 } from "lucide-react";
-import React, { createContext, useContext, useState } from "react";
-import CheckoutDialog from "@/components/autumn/checkout-dialog";
-import { Button } from "@/components/ui/button";
-import { Switch } from "@/components/ui/switch";
-import { useOrg } from "@/hooks/common/useOrg";
-import { getPricingTableContent } from "@/lib/autumn/pricing-table-content";
-import { cn } from "@/lib/utils";
-
-export default function PricingTable({
- products,
- setConnectStripeOpen,
-}: {
- products?: Product[];
- setConnectStripeOpen: (open: boolean) => void;
-}) {
- const { org } = useOrg();
- const { checkout } = useCustomer();
- const [isAnnual, setIsAnnual] = useState(false);
-
- const intervals = Array.from(
- new Set(
- products?.map((p) => p.properties?.interval_group).filter((i) => !!i),
- ),
- );
-
- const multiInterval = intervals.length > 1;
-
- const intervalFilter = (product: Product) => {
- if (!product.properties?.interval_group) {
- return true;
- }
-
- if (multiInterval) {
- if (isAnnual) {
- return product.properties?.interval_group === "year";
- } else {
- return product.properties?.interval_group === "month";
- }
- }
-
- return true;
- };
-
- return (
-
- {products && (
-
- {products.filter(intervalFilter).map((product, index) => (
- {
- if (product.id) {
- const result = await checkout({
- productId: product.id,
- dialog: CheckoutDialog,
- openInNewTab: true,
- successUrl: `${window.location.origin}`,
- });
- } else if (product.display?.button_url) {
- window.open(product.display?.button_url, "_blank");
- }
- },
- }}
- />
- ))}
-
- )}
-
- );
-}
-
-const PricingTableContext = createContext<{
- isAnnualToggle: boolean;
- setIsAnnualToggle: (isAnnual: boolean) => void;
- products: Product[];
- showFeatures: boolean;
-}>({
- isAnnualToggle: false,
- setIsAnnualToggle: () => {},
- products: [],
- showFeatures: true,
-});
-
-export const usePricingTableContext = (componentName: string) => {
- const context = useContext(PricingTableContext);
-
- if (context === undefined) {
- throw new Error(`${componentName} must be used within `);
- }
-
- return context;
-};
-
-export const PricingTableContainer = ({
- children,
- products,
- showFeatures = true,
- className,
- isAnnualToggle,
- setIsAnnualToggle,
- multiInterval,
-}: {
- children?: React.ReactNode;
- products?: Product[];
- showFeatures?: boolean;
- className?: string;
- isAnnualToggle: boolean;
- setIsAnnualToggle: (isAnnual: boolean) => void;
- multiInterval: boolean;
-}) => {
- if (!products) {
- throw new Error("products is required in ");
- }
-
- if (products.length === 0) {
- return <>>;
- }
-
- const hasRecommended = products?.some((p) => p.display?.recommend_text);
- return (
-
-
- {multiInterval && (
-
p.display?.recommend_text) && "mb-8",
- )}
- >
-
-
- )}
-
- {children}
-
-
-
- );
-};
-
-interface PricingCardProps {
- productId: string;
- showFeatures?: boolean;
- className?: string;
- onButtonClick?: (event: React.MouseEvent) => void;
- buttonProps?: React.ComponentProps<"button">;
-}
-
-export const PricingCard = ({
- productId,
- className,
- buttonProps,
-}: PricingCardProps) => {
- const { products, showFeatures } = usePricingTableContext("PricingCard");
-
- const product = products.find((p) => p.id === productId);
-
- if (!product) {
- throw new Error(`Product with id ${productId} not found`);
- }
-
- const { name, display: productDisplay } = product;
-
- const { buttonText } = getPricingTableContent(product);
-
- const isRecommended = productDisplay?.recommend_text ? true : false;
- const mainPriceDisplay = product.properties?.is_free
- ? {
- primary_text: "Free",
- }
- : product.items[0].display;
-
- const featureItems = product.properties?.is_free
- ? product.items
- : product.items.slice(1);
-
- return (
-
- {productDisplay?.recommend_text && (
-
- )}
-
-
-
-
-
- {productDisplay?.name || name || (
-
- Name this product
-
- )}
-
- {productDisplay?.description && (
-
-
{productDisplay?.description}
-
- )}
-
-
-
-
- {mainPriceDisplay?.primary_text}{" "}
- {mainPriceDisplay?.secondary_text && (
-
- {mainPriceDisplay?.secondary_text}
-
- )}
-
-
-
-
- {showFeatures && featureItems.length > 0 && (
-
- )}
-
-
-
- {productDisplay?.button_text || buttonText}
-
-
-
-
- );
-};
-
-// Pricing Feature List
-export const PricingFeatureList = ({
- items,
- everythingFrom,
- className,
-}: {
- items: ProductItem[];
- everythingFrom?: string;
- className?: string;
-}) => {
- return (
-
- {everythingFrom && (
-
Everything from {everythingFrom}, plus:
- )}
-
- {items.map((item, index) => (
-
-
- {item.display?.primary_text}
- {item.display?.secondary_text && (
-
- {item.display?.secondary_text}
-
- )}
-
-
- ))}
-
-
- );
-};
-
-// Pricing Card Button
-export interface PricingCardButtonProps extends React.ComponentProps<"button"> {
- recommended?: boolean;
- buttonUrl?: string;
-}
-
-export const PricingCardButton = React.forwardRef<
- HTMLButtonElement,
- PricingCardButtonProps
->(({ recommended, children, className, onClick, ...props }, ref) => {
- const [loading, setLoading] = useState(false);
-
- const handleClick = async (e: React.MouseEvent) => {
- setLoading(true);
- try {
- await onClick?.(e);
- } catch (error) {
- console.error(error);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
- {loading ? (
-
- ) : (
- <>
-
- {children}
- →
-
-
- {children}
- →
-
- >
- )}
-
- );
-});
-PricingCardButton.displayName = "PricingCardButton";
-
-// Annual Switch
-export const AnnualSwitch = ({
- isAnnualToggle,
- setIsAnnualToggle,
-}: {
- isAnnualToggle: boolean;
- setIsAnnualToggle: (isAnnual: boolean) => void;
-}) => {
- return (
-
- Monthly
-
- Annual
-
- );
-};
-
-export const RecommendedBadge = ({ recommended }: { recommended: string }) => {
- return (
-
- {recommended}
-
- );
-};
diff --git a/vite/src/components/forms/attach-product/attach-feature-preview.tsx b/vite/src/components/forms/attach-product/attach-feature-preview.tsx
deleted file mode 100644
index be2d90d7d..000000000
--- a/vite/src/components/forms/attach-product/attach-feature-preview.tsx
+++ /dev/null
@@ -1,224 +0,0 @@
-import type {
- CheckoutResponseV0,
- FullCusEntWithFullCusProduct,
- FullCusProduct,
- ProductItem,
-} from "@autumn/shared";
-import { getCusEntBalance, ProductItemFeatureType } from "@autumn/shared";
-import { useMemo } from "react";
-import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
-import {
- deduplicateEntitlements,
- flattenCustomerEntitlements,
-} from "@/views/customers2/components/table/customer-feature-usage/customerFeatureUsageUtils";
-
-interface FeatureBalanceChange {
- featureId: string;
- featureName: string;
- currentBalance: number | null;
- newBalance: number | null;
- status: "changed" | "added" | "removed";
- display: {
- singular?: string;
- plural?: string;
- };
-}
-
-export function AttachFeaturePreview({
- previewData,
-}: {
- previewData?: CheckoutResponseV0 | null;
-}) {
- const { customer } = useCusQuery();
-
- const featureChanges = useMemo((): FeatureBalanceChange[] => {
- if (!previewData?.product || !customer) return [];
-
- const currentProduct = previewData.current_product;
- const newProduct = previewData.product;
-
- // Get customer entitlements and deduplicate to get current balances
- const filteredCustomerProducts =
- customer?.customer_products?.filter((cp: FullCusProduct) => {
- // Filter to only include products matching the current product if it exists
- if (currentProduct) {
- return cp.product_id === currentProduct.id;
- }
- return true;
- }) || [];
-
- const cusEnts = flattenCustomerEntitlements({
- customerProducts: filteredCustomerProducts,
- });
-
- const { entitlements: deduplicatedCusEnts } = deduplicateEntitlements({
- entitlements: cusEnts,
- entityId: null,
- });
-
- // Create a map of current balances
- const currentBalanceMap = new Map<
- string,
- { balance: number; feature: FullCusEntWithFullCusProduct }
- >();
- for (const cusEnt of deduplicatedCusEnts) {
- const featureId = cusEnt.entitlement.feature.id;
- const { balance } = getCusEntBalance({
- cusEnt,
- entityId: null,
- });
- currentBalanceMap.set(featureId, {
- balance,
- feature: cusEnt,
- });
- }
-
- // Get feature items from current and new products
- const currentFeatureItems =
- currentProduct?.items?.filter(
- (item: ProductItem) =>
- item.type === "feature" &&
- item.feature_type === ProductItemFeatureType.SingleUse,
- ) || [];
-
- const newFeatureItems =
- newProduct?.items?.filter(
- (item: ProductItem) =>
- item.type === "feature" &&
- item.feature_type === ProductItemFeatureType.SingleUse,
- ) || [];
-
- // Create maps for easy lookup
- const currentFeaturesMap = new Map(
- currentFeatureItems
- .filter((item: ProductItem) => item.feature_id)
- .map((item: ProductItem) => [
- item.feature_id as string,
- item.included_usage || 0,
- ]),
- );
-
- const newFeaturesMap = new Map<
- string,
- {
- includedUsage: number;
- feature: ProductItem["feature"];
- display: Record;
- }
- >(
- newFeatureItems
- .filter((item: ProductItem) => item.feature_id)
- .map((item: ProductItem) => [
- item.feature_id as string,
- {
- includedUsage: item.included_usage || 0,
- feature: item.feature,
- display: (item.feature?.display as Record) || {},
- },
- ]),
- );
-
- // Get all unique feature IDs
- const allFeatureIds = new Set([
- ...currentFeaturesMap.keys(),
- ...newFeaturesMap.keys(),
- ]);
-
- const changes: FeatureBalanceChange[] = [];
-
- for (const featureId of allFeatureIds) {
- const featureIdStr = String(featureId);
- const currentIncludedUsage = currentFeaturesMap.get(featureIdStr);
- const newFeatureData = newFeaturesMap.get(featureIdStr);
- const currentBalanceData = currentBalanceMap.get(featureIdStr);
-
- // Use actual current balance if available, otherwise use included usage from product
- const currentBalance: number | null =
- currentBalanceData?.balance ??
- (typeof currentIncludedUsage === "number"
- ? currentIncludedUsage
- : null);
- const newBalance: number | null = newFeatureData?.includedUsage ?? null;
-
- // Determine status
- let status: "changed" | "added" | "removed";
- if (currentIncludedUsage !== undefined && newFeatureData) {
- status = "changed";
- } else if (newFeatureData) {
- status = "added";
- } else {
- status = "removed";
- }
-
- // Get feature name and display info
- const featureName =
- newFeatureData?.feature?.name ??
- currentBalanceData?.feature.entitlement.feature.name ??
- "";
- const display =
- newFeatureData?.display ??
- (currentBalanceData?.feature.entitlement.feature.display as Record<
- string,
- unknown
- >) ??
- {};
-
- changes.push({
- featureId: featureIdStr,
- featureName: featureName ?? "",
- currentBalance,
- newBalance,
- status,
- display: display as { singular?: string; plural?: string },
- });
- }
-
- return changes;
- }, [previewData, customer]);
-
- if (!featureChanges.length) return null;
-
- return (
-
-
- Balance Changes
-
-
- {featureChanges.map((change) => {
- const displayUnit =
- change.display?.plural ||
- change.display?.singular ||
- change.featureName.toLowerCase();
-
- return (
-
-
{displayUnit}
-
- {change.status === "removed" ? (
-
- {change.currentBalance}
-
- ) : change.status === "added" ? (
-
- {change.newBalance}
-
- ) : (
- <>
- {change.currentBalance}
- →
-
- {change.newBalance}
-
- >
- )}
-
-
- );
- })}
-
-
- );
-}
diff --git a/vite/src/components/forms/attach-product/attach-product-product-picker.tsx b/vite/src/components/forms/attach-product/attach-product-product-picker.tsx
deleted file mode 100644
index 9e34e6d7c..000000000
--- a/vite/src/components/forms/attach-product/attach-product-product-picker.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Button } from "@/components/v2/buttons/Button";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import type { UseAttachProductForm } from "./use-attach-product-form";
-
-interface AttachProductProductPickerProps {
- form: UseAttachProductForm;
-}
-
-export function AttachProductProductPicker({
- form,
-}: AttachProductProductPickerProps) {
- const { products: allProducts, isLoading } = useProductsQuery();
-
- if (isLoading) {
- return Loading products...
;
- }
-
- const activeProducts = allProducts.filter((p) => !p.archived);
-
- // Group products by group field
- const groupedProducts = activeProducts.reduce(
- (acc, product) => {
- const group = product.group || "Other";
- if (!acc[group]) acc[group] = [];
- acc[group].push(product);
- return acc;
- },
- {} as Record,
- );
-
- const handleProductSelect = (productId: string) => {
- form.setFieldValue("productId", productId);
- };
-
- return (
-
- {Object.entries(groupedProducts).map(([group, products]) => (
-
-
{group}
-
- {products.map((product) => (
- handleProductSelect(product.id)}
- variant="secondary"
- >
- {product.name}
-
- ))}
-
-
- ))}
-
- );
-}
diff --git a/vite/src/components/forms/cancel-subscription/components/RefundBehaviorSection.tsx b/vite/src/components/forms/cancel-subscription/components/RefundBehaviorSection.tsx
deleted file mode 100644
index 775f26ffa..000000000
--- a/vite/src/components/forms/cancel-subscription/components/RefundBehaviorSection.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { CreditCardIcon, WalletIcon } from "@phosphor-icons/react";
-import { AnimatePresence, motion } from "motion/react";
-import { useUpdateSubscriptionFormContext } from "@/components/forms/update-subscription-v2";
-import { COLLAPSE_TRANSITION } from "@/components/forms/update-subscription-v2/constants/animationConstants";
-import { PanelButton } from "@/components/v2/buttons/PanelButton";
-import { SheetSection } from "@/components/v2/sheets/SharedSheetComponents";
-
-export function RefundBehaviorSection() {
- const { form, formValues, previewQuery } = useUpdateSubscriptionFormContext();
-
- const cancelAction = formValues.cancelAction;
- const refundBehavior = formValues.refundBehavior ?? "grant_invoice_credits";
- const previewTotal = previewQuery.data?.total ?? -1;
-
- const showRefundToggle =
- cancelAction === "cancel_immediately" && previewTotal < 0;
-
- return (
-
- {showRefundToggle && (
-
-
-
-
-
- form.setFieldValue(
- "refundBehavior",
- "grant_invoice_credits",
- )
- }
- icon={ }
- />
-
-
- Credit to balance
-
-
- Add credit to customer's account for future invoices.
-
-
-
-
-
-
- form.setFieldValue(
- "refundBehavior",
- "refund_payment_method",
- )
- }
- icon={ }
- />
-
-
Refund to card
-
- Refund the unused amount back to their payment method.
-
-
-
-
-
-
- )}
-
- );
-}
diff --git a/vite/src/components/forms/cancel-subscription/index.ts b/vite/src/components/forms/cancel-subscription/index.ts
deleted file mode 100644
index ca8a9b5b1..000000000
--- a/vite/src/components/forms/cancel-subscription/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export { BillingBehaviorSection } from "./components/BillingBehaviorSection";
-export { CancelFooter } from "./components/CancelFooter";
-export { CancelModeSection } from "./components/CancelModeSection";
-export { CancelPreviewSection } from "./components/CancelPreviewSection";
-export { RefundBehaviorSection } from "./components/RefundBehaviorSection";
diff --git a/vite/src/components/forms/uncancel-subscription/index.ts b/vite/src/components/forms/uncancel-subscription/index.ts
deleted file mode 100644
index d2cbbf08d..000000000
--- a/vite/src/components/forms/uncancel-subscription/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { UncancelFooter } from "./components/UncancelFooter";
-export { UncancelPreviewSection } from "./components/UncancelPreviewSection";
diff --git a/vite/src/components/forms/update-subscription-v2/types/cancelActionSchema.ts b/vite/src/components/forms/update-subscription-v2/types/cancelActionSchema.ts
deleted file mode 100644
index 825b0651b..000000000
--- a/vite/src/components/forms/update-subscription-v2/types/cancelActionSchema.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { z } from "zod/v4";
-
-export const CancelActionSchema = z.enum([
- "cancel_immediately",
- "cancel_end_of_cycle",
-]);
-export type CancelActionValue = z.infer;
diff --git a/vite/src/components/general/ChatWidget.tsx b/vite/src/components/general/ChatWidget.tsx
deleted file mode 100644
index 8a3937ef3..000000000
--- a/vite/src/components/general/ChatWidget.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-"use client";
-
-import { useEffect } from "react";
-import { useSession } from "@/lib/auth-client";
-
-declare global {
- interface Window {
- patch?: {
- config: {
- organizationId: string;
- email?: string | null;
- name?: string | null;
- avatar_url?: string | null;
- };
- };
- }
-}
-
-let widgetInitialized = false;
-
-export function ChatWidget() {
- const { data: session, isPending } = useSession();
-
- useEffect(() => {
- if (isPending || widgetInitialized) return;
-
- widgetInitialized = true;
-
- // Set config
- window.patch = {
- config: {
- organizationId: import.meta.env.VITE_PATCH_ORG_ID,
- email: session?.user?.email,
- name: session?.user?.name,
- avatar_url: session?.user?.image,
- },
- };
-
- // Load script
- const script = document.createElement("script");
- script.src = "https://chat.patch.bot/widget/loader.js";
- script.async = true;
- document.head.appendChild(script);
-
- return;
- }, [session, isPending]);
-
- return null;
-}
diff --git a/vite/src/components/general/CodeDisplay.tsx b/vite/src/components/general/CodeDisplay.tsx
deleted file mode 100644
index 7e8419d26..000000000
--- a/vite/src/components/general/CodeDisplay.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { common, createStarryNight } from "@wooorm/starry-night";
-import { toHtml } from "hast-util-to-html";
-import React from "react";
-
-// @ts-expect-error - This is a global stylesheet
-import "@wooorm/starry-night/style/dark";
-import CopyButton from "@/components/general/CopyButton";
-
-interface CodeDisplayProps {
- code: string;
- language: string;
-}
-
-const getStarryNight = async () => {
- return await createStarryNight(common);
-};
-
-export const CodeDisplay: React.FC = ({ code, language }) => {
- const [highlightedCode, setHighlightedCode] = React.useState("");
-
- // Format language name to look nicer
- const formatLanguage = (lang: string) => {
- return lang.charAt(0).toUpperCase() + lang.slice(1).toLowerCase();
- };
-
- React.useEffect(() => {
- const highlight = async () => {
- const starryNight = await getStarryNight();
- const scope = starryNight.flagToScope(language);
-
- if (scope) {
- const tree = starryNight.highlight(code, scope);
- const html = toHtml(tree as any);
- // Wrap the HTML in a div with inline styles
- const styledHtml = `${html}
`;
- setHighlightedCode(styledHtml);
- } else {
- setHighlightedCode(
- `${code}
`,
- );
- }
- };
-
- highlight();
- }, [code, language]);
-
- return (
-
-
-
- {formatLanguage(language)}
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/components/general/InfoTooltip.tsx b/vite/src/components/general/InfoTooltip.tsx
deleted file mode 100644
index 4115a4fb1..000000000
--- a/vite/src/components/general/InfoTooltip.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-// import { InfoIcon } from "lucide-react";
-// import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
-
-// export const InfoTooltip = ({ children }: { children: React.ReactNode }) => {
-// return (
-//
-//
-//
-//
-//
-// {children}
-//
-//
-// );
-// };
diff --git a/vite/src/components/general/OnboardingStep.tsx b/vite/src/components/general/OnboardingStep.tsx
deleted file mode 100644
index 343bf9b0f..000000000
--- a/vite/src/components/general/OnboardingStep.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Wallet } from "lucide-react";
-import type React from "react";
-import { cn } from "@/lib/utils";
-
-interface StepProps {
- title: string;
- children: React.ReactNode;
- className?: string;
- description?: React.ReactNode;
- number?: number;
-}
-
-function Step({ title, children, className, description, number }: StepProps) {
- return (
-
-
- {/*
*/}
-
- {number || "1"}
-
-
-
-
-
{title}
- {description && (
-
- {description}
-
- )}
-
-
{children}
-
-
- );
-}
-
-export default Step;
diff --git a/vite/src/components/general/PageSectionHeader.tsx b/vite/src/components/general/PageSectionHeader.tsx
deleted file mode 100644
index e56b8d0bf..000000000
--- a/vite/src/components/general/PageSectionHeader.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { cn } from "@/lib/utils";
-
-export const PageSectionHeader = ({
- title,
- titleComponent,
- endContent,
- isOnboarding = false,
- addButton,
- className,
- classNames,
- menuComponent,
- isSecondary = false,
-}: {
- title?: string;
- titleComponent?: React.ReactNode;
- endContent?: React.ReactNode;
- isOnboarding?: boolean;
- addButton?: React.ReactNode;
- className?: string;
- classNames?: {
- title?: string;
- };
- menuComponent?: React.ReactNode;
- isSecondary?: boolean;
-}) => {
- return (
-
-
- {title && (
-
- {title}
-
- )}
- {titleComponent}
-
-
- {endContent}
- {addButton && (
-
{addButton}
- )}
- {menuComponent && (
-
{menuComponent}
- )}
-
-
- );
-};
diff --git a/vite/src/components/general/SelectType.tsx b/vite/src/components/general/SelectType.tsx
deleted file mode 100644
index 72355cef1..000000000
--- a/vite/src/components/general/SelectType.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { cn } from "@/lib/utils";
-
-export const SelectType = ({
- title,
- description,
- icon,
- isSelected,
- onClick,
- disabled = false,
-}: {
- title: string;
- description: string;
- icon: React.ReactNode;
- isSelected: boolean;
- onClick: () => void;
- disabled?: boolean;
-}) => {
- return (
-
-
- {/*
- {icon}
-
*/}
-
{icon}
-
- {title}
-
-
- {description}
-
- );
-};
diff --git a/vite/src/components/general/SideAccordion.tsx b/vite/src/components/general/SideAccordion.tsx
deleted file mode 100644
index 0df02a028..000000000
--- a/vite/src/components/general/SideAccordion.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { Plus, PlusIcon } from "lucide-react";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { cn } from "@/lib/utils";
-import { Button } from "../ui/button";
-import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
-
-interface SideAccordionProps {
- title: string;
- value: string;
- children: React.ReactNode;
- className?: string;
- onClick?: () => void;
- buttonIcon?: React.ReactNode;
- isOpen?: boolean;
- onToggle?: (value: string) => void;
- disabled?: boolean;
- disabledReason?: string;
-}
-
-export function SideAccordion({
- title,
- value,
- children,
- className,
- onClick,
- buttonIcon,
- isOpen = false,
- onToggle,
- disabled = false,
- disabledReason,
-}: SideAccordionProps) {
- const handleButtonClick = () => {
- if (!isOpen && onToggle) {
- onToggle(value);
- }
-
- if (onClick) {
- onClick();
- }
- };
-
- const SideButton = (
-
- {buttonIcon}
-
- );
-
- return (
-
-
-
-
- {onClick &&
- (disabledReason ? (
-
- {SideButton}
- {disabledReason}
-
- ) : (
- SideButton
- ))}
-
-
-
- {children}
-
-
-
-
- );
-}
diff --git a/vite/src/components/general/ToggleDisplayButton.tsx b/vite/src/components/general/ToggleDisplayButton.tsx
deleted file mode 100644
index 5c3c03d0b..000000000
--- a/vite/src/components/general/ToggleDisplayButton.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { cn } from "@/lib/utils";
-
-import { Button } from "../ui/button";
-
-export const ToggleDisplayButton = ({
- show,
- disabled,
- onClick,
- children,
- className,
-}: any) => {
- return (
-
- {children}
-
- );
-};
diff --git a/vite/src/components/general/buttons/ClickableIcon.tsx b/vite/src/components/general/buttons/ClickableIcon.tsx
deleted file mode 100644
index 9ff0658cf..000000000
--- a/vite/src/components/general/buttons/ClickableIcon.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Slot } from "@radix-ui/react-slot";
-import type { ButtonHTMLAttributes } from "react";
-import { cn } from "@/lib/utils";
-
-interface ClickableIconProps extends ButtonHTMLAttributes {
- icon: React.ReactElement;
- asChild?: boolean;
-}
-
-export const ClickableIcon = ({
- icon,
- onClick,
- asChild,
- ...props
-}: ClickableIconProps) => {
- const Comp = asChild ? Slot : "button";
-
- return (
-
- {icon}
-
- );
-};
diff --git a/vite/src/components/general/modal-components/DialogContentWrapper.tsx b/vite/src/components/general/modal-components/DialogContentWrapper.tsx
deleted file mode 100644
index 6a3db9ed1..000000000
--- a/vite/src/components/general/modal-components/DialogContentWrapper.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { DialogContent } from "@/components/ui/dialog";
-import { cn } from "@/lib/utils";
-
-export const CustomDialogContent = ({
- children,
- fromTop = true,
- setStep,
- className,
-}: {
- children: React.ReactNode;
- fromTop?: boolean;
- setStep?: (step: any) => void;
- className?: string;
-}) => {
- return (
-
- {children}
-
- );
-};
-
-export const CustomDialogBody = ({
- children,
- className,
-}: {
- children: React.ReactNode;
- className?: string;
-}) => {
- return (
-
- {children}
-
- );
-};
-
-export const CustomDialogFooter = ({
- children,
- className,
-}: {
- children: React.ReactNode;
- className?: string;
-}) => {
- return (
-
- {children}
-
- );
-};
diff --git a/vite/src/components/general/sidebar/sidebar-label.tsx b/vite/src/components/general/sidebar/sidebar-label.tsx
deleted file mode 100644
index b19b06684..000000000
--- a/vite/src/components/general/sidebar/sidebar-label.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { cn } from "@/lib/utils";
-
-export const SidebarLabel = ({
- className,
- children,
-}: {
- className?: string;
- children: React.ReactNode;
-}) => {
- return (
-
- {children}
-
- );
-};
diff --git a/vite/src/components/general/table-components/ContextMenuWrapper.tsx b/vite/src/components/general/table-components/ContextMenuWrapper.tsx
deleted file mode 100644
index 18759d44b..000000000
--- a/vite/src/components/general/table-components/ContextMenuWrapper.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useState } from "react";
-
-export const useContextMenu = () => {
- const [openKeyId, setOpenKeyId] = useState(null);
-
- return {
- openKeyId,
- setOpenKeyId,
- getContextMenuProps: (keyId: string) => ({
- onOpenChange: (open: boolean) => setOpenKeyId(open ? keyId : null),
- }),
- };
-};
diff --git a/vite/src/components/general/table-components/HamburgerMenu.tsx b/vite/src/components/general/table-components/HamburgerMenu.tsx
deleted file mode 100644
index d5f73bc86..000000000
--- a/vite/src/components/general/table-components/HamburgerMenu.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { EllipsisVertical } from "lucide-react";
-import { Button } from "@/components/ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { InfoTooltip } from "../modal-components/InfoTooltip";
-
-export interface MenuAction {
- type: "item" | "sub";
- label: string;
- onClick?: () => void;
- tooltip?: string;
- children?: MenuAction[];
-}
-
-export const HamburgerMenu = ({
- dropdownOpen,
- setDropdownOpen,
- actions,
- triggerClassName,
- contentClassName,
- contentAlign = "end",
-}: {
- dropdownOpen: boolean;
- setDropdownOpen: (open: boolean) => void;
- actions: MenuAction[];
- triggerClassName?: string;
- contentClassName?: string;
- contentAlign?: "start" | "center" | "end";
-}) => {
- const renderMenuAction = (action: MenuAction) => {
- if (action.type === "sub") {
- return (
-
-
- {action.label}
- {action.tooltip && (
-
- {action.tooltip}
-
- )}
-
-
- {action.children?.map(renderMenuAction)}
-
-
- );
- }
-
- return (
- {
- action.onClick?.();
- setDropdownOpen(false);
- }}
- className="cursor-pointer"
- >
- {action.label}
-
- );
- };
-
- return (
-
-
- }
- className={`w-10 h-10 p-0 text-purple-600 hover:text-purple-700 hover:bg-purple-50 ${triggerClassName || ""}`}
- />
-
-
- {actions.map(renderMenuAction)}
-
-
- );
-};
diff --git a/vite/src/components/general/table-components/TableButton.tsx b/vite/src/components/general/table-components/TableButton.tsx
deleted file mode 100644
index 946258d65..000000000
--- a/vite/src/components/general/table-components/TableButton.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Button, ButtonProps } from "@/components/ui/button";
-import { cn } from "@/lib/utils";
-
-export const TableButton = ({
- className,
- children,
- icon,
- onClick,
-}: {
- className?: string;
- children?: any;
- icon?: React.ReactNode;
- onClick?: any;
-}) => {
- return (
- {
- e.preventDefault();
- e.stopPropagation();
- onClick?.();
- }}
- >
- {children && {children} }
- {icon && {icon}
}
-
- );
-};
diff --git a/vite/src/components/pricing/attach-pricing-dialog.tsx b/vite/src/components/pricing/attach-pricing-dialog.tsx
deleted file mode 100644
index 09a774804..000000000
--- a/vite/src/components/pricing/attach-pricing-dialog.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { cn } from "@/lib/utils";
-import { Button } from "../ui/button";
-export const PriceItem = ({
- children,
- className,
- ...props
-}: {
- children: React.ReactNode;
- className?: string;
-} & React.HTMLAttributes) => {
- return (
-
- {children}
-
- );
-};
-
-export const QuantityInput = ({
- children,
- onChange,
- value,
- className,
- ...props
-}: {
- children: React.ReactNode;
- value: string | number;
- onChange: (e: React.ChangeEvent) => void;
- className?: string;
-} & React.HTMLAttributes) => {
- const currentValue = Number(value) || 0;
-
- const handleValueChange = (newValue: number) => {
- const syntheticEvent = {
- target: { value: String(newValue) },
- } as React.ChangeEvent;
- onChange(syntheticEvent);
- };
-
- return (
-
-
-
- currentValue > 0 && handleValueChange(currentValue - 1)
- }
- disabled={currentValue <= 0}
- className="h-6 w-6 pb-0.5"
- >
- -
-
- {currentValue}
- handleValueChange(currentValue + 1)}
- className="h-6 w-6 pb-0.5"
- >
- +
-
-
- {children}
-
- );
-};
-
-export const TotalPrice = ({ children }: { children: React.ReactNode }) => {
- return (
-
- {children}
-
- );
-};
diff --git a/vite/src/components/pricing/pricing-dialog.tsx b/vite/src/components/pricing/pricing-dialog.tsx
deleted file mode 100644
index 1170154a1..000000000
--- a/vite/src/components/pricing/pricing-dialog.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-import { ArrowRight } from "lucide-react";
-import React from "react";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { cn } from "@/lib/utils";
-
-export const PricingDialog = ({
- open,
- setOpen,
- children,
- className,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
- children: React.ReactNode;
- className?: string;
-}) => {
- return (
-
-
- {children}
-
-
- );
-};
-
-export const PricingDialogTitle = ({
- children,
- className,
-}: {
- children: React.ReactNode;
- className?: string;
-}) => {
- return (
-
- {children}
-
- );
-};
-
-export const Information = ({
- children,
- className,
-}: {
- children: React.ReactNode;
- className?: string;
-}) => {
- return (
-
- {children}
-
- );
-};
-
-export const PriceItem = ({
- children,
- className,
- ...props
-}: {
- children: React.ReactNode;
- className?: string;
-} & React.HTMLAttributes) => {
- return (
-
- {children}
-
- );
-};
-
-export const QuantityInput = ({
- children,
- onChange,
- value,
- className,
- ...props
-}: {
- children: React.ReactNode;
- value: string | number;
- onChange: (e: React.ChangeEvent) => void;
- className?: string;
-} & React.HTMLAttributes) => {
- const currentValue = Number(value) || 0;
-
- const handleValueChange = (newValue: number) => {
- const syntheticEvent = {
- target: { value: String(newValue) },
- } as React.ChangeEvent;
- onChange(syntheticEvent);
- };
-
- return (
-
-
-
- currentValue > 0 && handleValueChange(currentValue - 1)
- }
- disabled={currentValue <= 0}
- className="h-6 w-6 pb-0.5"
- >
- -
-
- {currentValue}
- handleValueChange(currentValue + 1)}
- className="h-6 w-6 pb-0.5"
- >
- +
-
-
- {children}
-
- );
-};
-
-export const PricingDialogFooter = ({
- children,
- className,
-}: {
- children: React.ReactNode;
- className?: string;
-}) => {
- return (
-
- {children}
-
- );
-};
-
-export const TotalPrice = ({ children }: { children: React.ReactNode }) => {
- return (
-
- {children}
-
- );
-};
-
-export const PricingDialogButton = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps
->(({ children, onClick, disabled, size, className, ...props }, ref) => {
- return (
-
- {children}
-
-
- );
-});
-PricingDialogButton.displayName = "PricingDialogButton";
diff --git a/vite/src/components/ui/alert.tsx b/vite/src/components/ui/alert.tsx
deleted file mode 100644
index ed46e5b5f..000000000
--- a/vite/src/components/ui/alert.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { cva, type VariantProps } from "class-variance-authority";
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-const alertVariants = cva(
- "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
- {
- variants: {
- variant: {
- default: "bg-background text-foreground",
- destructive:
- "text-destructive-foreground [&>svg]:text-current *:data-[slot=alert-description]:text-destructive-foreground/80",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-function Alert({
- className,
- variant,
- ...props
-}: React.ComponentProps<"div"> & VariantProps) {
- return (
-
- );
-}
-
-function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function AlertDescription({
- className,
- ...props
-}: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-export { Alert, AlertTitle, AlertDescription };
diff --git a/vite/src/components/ui/border-beam.tsx b/vite/src/components/ui/border-beam.tsx
deleted file mode 100644
index 3ddc2edac..000000000
--- a/vite/src/components/ui/border-beam.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import { type MotionStyle, motion, type Transition } from "motion/react";
-
-import { cn } from "@/lib/utils";
-
-interface BorderBeamProps {
- /**
- * The size of the border beam.
- */
- size?: number;
- /**
- * The duration of the border beam.
- */
- duration?: number;
- /**
- * The delay of the border beam.
- */
- delay?: number;
- /**
- * The color of the border beam from.
- */
- colorFrom?: string;
- /**
- * The color of the border beam to.
- */
- colorTo?: string;
- /**
- * The motion transition of the border beam.
- */
- transition?: Transition;
- /**
- * The class name of the border beam.
- */
- className?: string;
- /**
- * The style of the border beam.
- */
- style?: React.CSSProperties;
- /**
- * Whether to reverse the animation direction.
- */
- reverse?: boolean;
- /**
- * The initial offset position (0-100).
- */
- initialOffset?: number;
- /**
- * The border width of the beam.
- */
- borderWidth?: number;
-}
-
-export const BorderBeam = ({
- className,
- size = 50,
- delay = 0,
- duration = 6,
- colorFrom = "#ffaa40",
- colorTo = "#9c40ff",
- transition,
- style,
- reverse = false,
- initialOffset = 0,
- borderWidth = 1,
-}: BorderBeamProps) => {
- return (
-
-
-
- );
-};
diff --git a/vite/src/components/ui/carousel.tsx b/vite/src/components/ui/carousel.tsx
deleted file mode 100644
index 3da8ff8ce..000000000
--- a/vite/src/components/ui/carousel.tsx
+++ /dev/null
@@ -1,240 +0,0 @@
-"use client";
-
-import useEmblaCarousel, {
- type UseEmblaCarouselType,
-} from "embla-carousel-react";
-import { ArrowLeft, ArrowRight } from "lucide-react";
-import * as React from "react";
-import { Button } from "@/components/ui/button";
-import { cn } from "@/lib/utils";
-
-type CarouselApi = UseEmblaCarouselType[1];
-type UseCarouselParameters = Parameters;
-type CarouselOptions = UseCarouselParameters[0];
-type CarouselPlugin = UseCarouselParameters[1];
-
-type CarouselProps = {
- opts?: CarouselOptions;
- plugins?: CarouselPlugin;
- orientation?: "horizontal" | "vertical";
- setApi?: (api: CarouselApi) => void;
-};
-
-type CarouselContextProps = {
- carouselRef: ReturnType[0];
- api: ReturnType[1];
- scrollPrev: () => void;
- scrollNext: () => void;
- canScrollPrev: boolean;
- canScrollNext: boolean;
-} & CarouselProps;
-
-const CarouselContext = React.createContext(null);
-
-function useCarousel() {
- const context = React.useContext(CarouselContext);
-
- if (!context) {
- throw new Error("useCarousel must be used within a ");
- }
-
- return context;
-}
-
-function Carousel({
- orientation = "horizontal",
- opts,
- setApi,
- plugins,
- className,
- children,
- ...props
-}: React.ComponentProps<"div"> & CarouselProps) {
- const [carouselRef, api] = useEmblaCarousel(
- {
- ...opts,
- axis: orientation === "horizontal" ? "x" : "y",
- },
- plugins,
- );
- const [canScrollPrev, setCanScrollPrev] = React.useState(false);
- const [canScrollNext, setCanScrollNext] = React.useState(false);
-
- const onSelect = React.useCallback((api: CarouselApi) => {
- if (!api) return;
- setCanScrollPrev(api.canScrollPrev());
- setCanScrollNext(api.canScrollNext());
- }, []);
-
- const scrollPrev = React.useCallback(() => {
- api?.scrollPrev();
- }, [api]);
-
- const scrollNext = React.useCallback(() => {
- api?.scrollNext();
- }, [api]);
-
- const handleKeyDown = React.useCallback(
- (event: React.KeyboardEvent) => {
- if (event.key === "ArrowLeft") {
- event.preventDefault();
- scrollPrev();
- } else if (event.key === "ArrowRight") {
- event.preventDefault();
- scrollNext();
- }
- },
- [scrollPrev, scrollNext],
- );
-
- React.useEffect(() => {
- if (!api || !setApi) return;
- setApi(api);
- }, [api, setApi]);
-
- React.useEffect(() => {
- if (!api) return;
- onSelect(api);
- api.on("reInit", onSelect);
- api.on("select", onSelect);
-
- return () => {
- api?.off("select", onSelect);
- };
- }, [api, onSelect]);
-
- return (
-
-
- {children}
-
-
- );
-}
-
-function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
- const { carouselRef, orientation } = useCarousel();
-
- return (
-
- );
-}
-
-function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
- const { orientation } = useCarousel();
-
- return (
-
- );
-}
-
-function CarouselPrevious({
- className,
- variant = "outline",
- size = "icon",
- ...props
-}: React.ComponentProps) {
- const { orientation, scrollPrev, canScrollPrev } = useCarousel();
-
- return (
-
-
- Previous slide
-
- );
-}
-
-function CarouselNext({
- className,
- variant = "outline",
- size = "icon",
- ...props
-}: React.ComponentProps) {
- const { orientation, scrollNext, canScrollNext } = useCarousel();
-
- return (
-
-
- Next slide
-
- );
-}
-
-export {
- type CarouselApi,
- Carousel,
- CarouselContent,
- CarouselItem,
- CarouselPrevious,
- CarouselNext,
-};
diff --git a/vite/src/components/ui/collapsible.tsx b/vite/src/components/ui/collapsible.tsx
deleted file mode 100644
index 584cc60ec..000000000
--- a/vite/src/components/ui/collapsible.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
-
-function Collapsible({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function CollapsibleTrigger({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function CollapsibleContent({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/vite/src/components/ui/context-menu.tsx b/vite/src/components/ui/context-menu.tsx
deleted file mode 100644
index d777154fc..000000000
--- a/vite/src/components/ui/context-menu.tsx
+++ /dev/null
@@ -1,250 +0,0 @@
-import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
-import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function ContextMenu({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function ContextMenuTrigger({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function ContextMenuGroup({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function ContextMenuPortal({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function ContextMenuSub({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function ContextMenuRadioGroup({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function ContextMenuSubTrigger({
- className,
- inset,
- children,
- ...props
-}: React.ComponentProps & {
- inset?: boolean;
-}) {
- return (
-
- {children}
-
-
- );
-}
-
-function ContextMenuSubContent({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function ContextMenuContent({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- );
-}
-
-function ContextMenuItem({
- className,
- inset,
- variant = "default",
- ...props
-}: React.ComponentProps & {
- inset?: boolean;
- variant?: "default" | "destructive";
-}) {
- return (
-
- );
-}
-
-function ContextMenuCheckboxItem({
- className,
- children,
- checked,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
-
-
- {children}
-
- );
-}
-
-function ContextMenuRadioItem({
- className,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
-
-
- {children}
-
- );
-}
-
-function ContextMenuLabel({
- className,
- inset,
- ...props
-}: React.ComponentProps & {
- inset?: boolean;
-}) {
- return (
-
- );
-}
-
-function ContextMenuSeparator({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function ContextMenuShortcut({
- className,
- ...props
-}: React.ComponentProps<"span">) {
- return (
-
- );
-}
-
-export {
- ContextMenu,
- ContextMenuTrigger,
- ContextMenuContent,
- ContextMenuItem,
- ContextMenuCheckboxItem,
- ContextMenuRadioItem,
- ContextMenuLabel,
- ContextMenuSeparator,
- ContextMenuShortcut,
- ContextMenuGroup,
- ContextMenuPortal,
- ContextMenuSub,
- ContextMenuSubContent,
- ContextMenuSubTrigger,
- ContextMenuRadioGroup,
-};
diff --git a/vite/src/components/ui/drawer.tsx b/vite/src/components/ui/drawer.tsx
deleted file mode 100644
index d015a68ac..000000000
--- a/vite/src/components/ui/drawer.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import type * as React from "react";
-import { Drawer as DrawerPrimitive } from "vaul";
-
-import { cn } from "@/lib/utils";
-
-function Drawer({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DrawerTrigger({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DrawerPortal({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DrawerClose({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DrawerOverlay({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function DrawerContent({
- className,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
- {children}
-
-
- );
-}
-
-function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function DrawerTitle({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function DrawerDescription({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-export {
- Drawer,
- DrawerPortal,
- DrawerOverlay,
- DrawerTrigger,
- DrawerClose,
- DrawerContent,
- DrawerHeader,
- DrawerFooter,
- DrawerTitle,
- DrawerDescription,
-};
diff --git a/vite/src/components/ui/progress.tsx b/vite/src/components/ui/progress.tsx
deleted file mode 100644
index 919d35ee4..000000000
--- a/vite/src/components/ui/progress.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import * as ProgressPrimitive from "@radix-ui/react-progress";
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Progress({
- className,
- value,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- );
-}
-
-export { Progress };
diff --git a/vite/src/components/ui/shine-border.tsx b/vite/src/components/ui/shine-border.tsx
deleted file mode 100644
index 86016ce08..000000000
--- a/vite/src/components/ui/shine-border.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-interface ShineBorderProps extends React.HTMLAttributes {
- /**
- * Width of the border in pixels
- * @default 1
- */
- borderWidth?: number;
- /**
- * Duration of the animation in seconds
- * @default 14
- */
- duration?: number;
- /**
- * Color of the border, can be a single color or an array of colors
- * @default "#000000"
- */
- shineColor?: string | string[];
-}
-
-/**
- * Shine Border
- *
- * An animated background border effect component with configurable properties.
- */
-export function ShineBorder({
- borderWidth = 1,
- duration = 14,
- shineColor = "#000000",
- className,
- style,
- ...props
-}: ShineBorderProps) {
- return (
-
- );
-}
diff --git a/vite/src/components/ui/sonner.tsx b/vite/src/components/ui/sonner.tsx
deleted file mode 100644
index c504f805e..000000000
--- a/vite/src/components/ui/sonner.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { useTheme } from "next-themes";
-import { Toaster as Sonner, type ToasterProps } from "sonner";
-
-const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme();
-
- return (
-
- );
-};
-
-export { Toaster };
diff --git a/vite/src/components/v2/PlanSelector.tsx b/vite/src/components/v2/PlanSelector.tsx
deleted file mode 100644
index a9a0d4a4b..000000000
--- a/vite/src/components/v2/PlanSelector.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { CaretDownIcon, CubeIcon } from "@phosphor-icons/react";
-import { useState } from "react";
-import { cn } from "@/lib/utils";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "./dropdowns/DropdownMenu";
-
-interface PlanSelectorProps {
- plans: ProductV2[];
- selectedPlanId: string | null;
- onPlanChange: (planId: string) => void;
- className?: string;
-}
-
-export function PlanSelector({
- plans,
- selectedPlanId,
- onPlanChange,
- className,
-}: PlanSelectorProps) {
- const [open, setOpen] = useState(false);
- const selectedPlan = plans.find((p) => p.id === selectedPlanId);
-
- const handleSelect = (planId: string) => {
- onPlanChange(planId);
- setOpen(false);
- };
-
- return (
-
-
-
- {selectedPlan ? (
-
-
-
-
-
- {selectedPlan.name}
-
-
- ) : (
- Select plan
- )}
-
-
-
-
-
- {plans.length === 0 ? (
-
- No plans found.
-
- ) : (
- plans.map((plan) => (
-
handleSelect(plan.id)}
- className="py-2 px-2.5"
- >
-
-
- ))
- )}
-
-
-
- );
-}
diff --git a/vite/src/components/v2/buttons/AreaSwitch.tsx b/vite/src/components/v2/buttons/AreaSwitch.tsx
deleted file mode 100644
index 033c88879..000000000
--- a/vite/src/components/v2/buttons/AreaSwitch.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-"use client";
-
-import { useId, useState } from "react";
-import { cn } from "@/lib/utils";
-import { Switch } from "./Switch";
-
-interface AreaSwitchProps {
- className?: string;
- checked?: boolean;
- defaultChecked?: boolean;
- onCheckedChange?: (checked: boolean) => void;
- title: string;
- disabled?: boolean;
- hide?: boolean;
- description?: string;
- children?: React.ReactNode;
-}
-
-function AreaSwitch({
- className,
- checked: controlledChecked,
- defaultChecked = false,
- onCheckedChange,
- title,
- disabled = false,
- hide = false,
- description,
- children,
-}: AreaSwitchProps) {
- const id = useId();
- const isControlled = controlledChecked !== undefined;
- const [internalChecked, setInternalChecked] = useState(defaultChecked);
- const checked = isControlled ? controlledChecked : internalChecked;
-
- if (hide) return null;
-
- const handleCheckedChange = (newChecked: boolean) => {
- if (disabled) return;
- if (!isControlled) {
- setInternalChecked(newChecked);
- }
- onCheckedChange?.(newChecked);
- };
-
- return (
-
-
-
-
-
- {title}
-
- {/* Expanded content */}
- {(children || description) && (
-
- {description &&
{description}
}
- {children}
-
- )}
-
-
- );
-}
-
-export { AreaSwitch };
diff --git a/vite/src/components/v2/buttons/HoverClickableIcon.tsx b/vite/src/components/v2/buttons/HoverClickableIcon.tsx
deleted file mode 100644
index 012f2af52..000000000
--- a/vite/src/components/v2/buttons/HoverClickableIcon.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import type { MouseEvent, ReactElement } from "react";
-import { Button } from "@/components/ui/button";
-
-interface HoverClickableIconProps {
- icon: ReactElement;
- onClick?: (e: MouseEvent) => void;
- disabled?: boolean;
- className?: string;
- size?: number;
- "aria-label"?: string;
-}
-
-export const HoverClickableIcon = ({
- icon,
- onClick,
- disabled = false,
- className = "",
- size = 24,
- "aria-label": ariaLabel,
-}: HoverClickableIconProps) => {
- return (
- {
- e.stopPropagation();
- onClick?.(e);
- }}
- disabled={disabled}
- aria-label={ariaLabel}
- >
-
- {icon}
-
-
- );
-};
diff --git a/vite/src/components/v2/buttons/Switch.tsx b/vite/src/components/v2/buttons/Switch.tsx
deleted file mode 100644
index 117bb9a22..000000000
--- a/vite/src/components/v2/buttons/Switch.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-"use client";
-
-import * as SwitchPrimitive from "@radix-ui/react-switch";
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Switch({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- );
-}
-
-export { Switch };
diff --git a/vite/src/components/v2/checkboxes/LongCheckbox.tsx b/vite/src/components/v2/checkboxes/LongCheckbox.tsx
deleted file mode 100644
index 12e6e9261..000000000
--- a/vite/src/components/v2/checkboxes/LongCheckbox.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-"use client";
-
-import React from "react";
-import { cn } from "@/lib/utils";
-import { Checkbox } from "./Checkbox";
-
-interface LongCheckboxProps {
- className?: string;
- checked?: boolean;
- onCheckedChange?: (checked: boolean) => void;
- title: string;
- subtitle?: string;
- disabled?: boolean;
-}
-
-function LongCheckbox({
- className,
- checked = false,
- onCheckedChange,
- title,
- subtitle,
- disabled = false,
-}: LongCheckboxProps) {
- const id = React.useId();
-
- return (
-
-
-
-
{title}
- {subtitle && (
-
{subtitle}
- )}
-
-
- );
-}
-
-export { LongCheckbox };
diff --git a/vite/src/components/v2/selects/LabelSelect.tsx b/vite/src/components/v2/selects/LabelSelect.tsx
deleted file mode 100644
index f62a14b27..000000000
--- a/vite/src/components/v2/selects/LabelSelect.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import type React from "react";
-import { useId } from "react";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "./Select";
-
-type Option = {
- value: string;
- label: React.ReactNode;
- disabled?: boolean;
-};
-
-export const LabelSelect = ({
- label,
- placeholder,
- options,
- children,
- ...props
-}: {
- label: string | React.ReactElement;
- placeholder: string;
- options?: Option[];
- children?: React.ReactNode;
-} & React.ComponentProps) => {
- const selectId = useId();
-
- return (
-
-
{label}
-
-
-
-
-
- {options
- ? options.map((option) => (
-
- {option.label}
-
- ))
- : children}
-
-
-
- );
-};
diff --git a/vite/src/hooks/common/useClickWithoutDrag.ts b/vite/src/hooks/common/useClickWithoutDrag.ts
deleted file mode 100644
index 4189675e2..000000000
--- a/vite/src/hooks/common/useClickWithoutDrag.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { useCallback, useRef } from "react";
-
-interface UseClickWithoutDragOptions {
- threshold?: number;
-}
-
-/**
- * Hook to detect genuine clicks vs drag operations
- * Only triggers callback if mouse up is close to mouse down position
- */
-export function useClickWithoutDrag(
- callback: (e: React.MouseEvent) => void,
- { threshold = 5 }: UseClickWithoutDragOptions = {},
-) {
- const mouseDownPos = useRef<{ x: number; y: number } | null>(null);
-
- const handleMouseDown = useCallback((e: React.MouseEvent) => {
- mouseDownPos.current = { x: e.clientX, y: e.clientY };
- }, []);
-
- const handleClick = useCallback(
- (e: React.MouseEvent) => {
- if (!mouseDownPos.current) {
- callback(e);
- return;
- }
-
- const dx = Math.abs(e.clientX - mouseDownPos.current.x);
- const dy = Math.abs(e.clientY - mouseDownPos.current.y);
- const distance = Math.sqrt(dx * dx + dy * dy);
-
- // Only trigger callback if mouse didn't move much (not a drag)
- if (distance <= threshold) {
- callback(e);
- }
-
- mouseDownPos.current = null;
- },
- [callback, threshold],
- );
-
- return { handleMouseDown, handleClick };
-}
diff --git a/vite/src/hooks/common/useSecondaryTab.tsx b/vite/src/hooks/common/useSecondaryTab.tsx
deleted file mode 100644
index a2d5be4cd..000000000
--- a/vite/src/hooks/common/useSecondaryTab.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { parseAsString, useQueryStates } from "nuqs";
-import { useEffect, useState } from "react";
-import { useNavigate, useSearchParams } from "react-router";
-
-type SecondaryTabType =
- | "api_keys"
- | "stripe"
- | "products"
- | "rewards"
- | "features"
- | "webhooks";
-
-export const useSecondaryTab = ({
- defaultTab,
-}: {
- defaultTab?: SecondaryTabType;
-}) => {
- const navigate = useNavigate();
- // const [searchParams] = useSearchParams();
- const [queryStates, setQueryStates] = useQueryStates({
- tab: parseAsString.withDefault(defaultTab || ""),
- });
-
- const [stableStates, setStableStates] = useState(queryStates);
-
- useEffect(() => {
- // if (defaultTab && !stableStates.tab) {
- // navigate(`?tab=${defaultTab}`);
- // }
- }, [defaultTab]);
-
- return (stableStates.tab as SecondaryTabType) || "";
-};
diff --git a/vite/src/hooks/common/useShowDeployButton.tsx b/vite/src/hooks/common/useShowDeployButton.tsx
deleted file mode 100644
index 503fc09a3..000000000
--- a/vite/src/hooks/common/useShowDeployButton.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import type { CustomerWithProducts } from "@autumn/shared";
-import { useCallback, useEffect, useState } from "react";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useGeneralQuery } from "../queries/useGeneralQuery";
-
-const DEPLOY_BUTTON_STORAGE_KEY = "autumn_show_deploy_button";
-
-export const useShowDeployButton = () => {
- const { products } = useProductsQuery();
- const { data: customersData, isLoading: isLoadingCustomers } =
- useGeneralQuery({
- url: "/customers/all/search",
- method: "POST",
- queryKey: ["customers", "all", "search"],
- });
-
- const getStoredValue = useCallback((): boolean | null => {
- try {
- const stored = localStorage.getItem(DEPLOY_BUTTON_STORAGE_KEY);
- return stored ? JSON.parse(stored) : null;
- } catch {
- return null;
- }
- }, []);
-
- const [showDeployButton, setShowDeployButton] = useState(() => {
- const stored = getStoredValue();
- return stored !== null ? stored : false;
- });
-
- // Only show checking state if we don't have a stored value AND data is still loading
- const isChecking = getStoredValue() === null && isLoadingCustomers;
-
- useEffect(() => {
- // Wait for data to be available before checking
- if (!customersData) return;
-
- const checkConditions = () => {
- try {
- // Check if at least 1 product exists
- const hasProduct = products.length > 0;
-
- if (!hasProduct) {
- const newValue = false;
- // Only update if different
- setShowDeployButton((prev) => {
- if (prev !== newValue) {
- localStorage.setItem(
- DEPLOY_BUTTON_STORAGE_KEY,
- JSON.stringify(newValue),
- );
- return newValue;
- }
- return prev;
- });
- return;
- }
-
- // Check if at least 1 non-demo customer exists
- const hasNonDemoCustomer = customersData.customers.some(
- (customer: CustomerWithProducts) =>
- customer.id !== "onboarding_demo_user",
- );
-
- const shouldShow = hasProduct && hasNonDemoCustomer;
-
- // Only update if the value changed
- setShowDeployButton((prev) => {
- if (prev !== shouldShow) {
- localStorage.setItem(
- DEPLOY_BUTTON_STORAGE_KEY,
- JSON.stringify(shouldShow),
- );
- return shouldShow;
- }
- return prev;
- });
- } catch (error) {
- console.error("Error checking deploy button conditions:", error);
- setShowDeployButton(false);
- }
- };
-
- checkConditions();
- }, [products.length, customersData]);
-
- return { showDeployButton, isChecking };
-};
diff --git a/vite/src/lib/auth-client.ts b/vite/src/lib/auth-client.ts
index 36edf2942..c438fd844 100644
--- a/vite/src/lib/auth-client.ts
+++ b/vite/src/lib/auth-client.ts
@@ -1,11 +1,11 @@
import { dashClient } from "@better-auth/dash/client";
import { oauthProviderClient } from "@better-auth/oauth-provider/client";
-import { createAuthClient } from "better-auth/client";
import {
adminClient,
emailOTPClient,
organizationClient,
} from "better-auth/client/plugins";
+import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: import.meta.env.VITE_BACKEND_URL,
@@ -18,11 +18,4 @@ export const authClient = createAuthClient({
],
});
-export const {
- useSession,
- signIn,
-
-
-
- useListOrganizations,
-} = authClient;
+export const { useSession, signIn, useListOrganizations } = authClient;
diff --git a/vite/src/lib/autumn/attach-content.tsx b/vite/src/lib/autumn/attach-content.tsx
deleted file mode 100644
index 0adc924a2..000000000
--- a/vite/src/lib/autumn/attach-content.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import type { CheckProductPreview } from "autumn-js";
-
-export const getAttachContent = (preview: CheckProductPreview) => {
- const {
- scenario,
- product_name,
- recurring,
- current_product_name,
- next_cycle_at,
- } = preview;
-
- const nextCycleAtStr = next_cycle_at
- ? new Date(next_cycle_at).toLocaleDateString()
- : undefined;
-
- switch (scenario) {
- case "scheduled":
- return {
- title: {product_name} product already scheduled
,
- message: (
-
- You are currently on product {current_product_name} and are
- scheduled to start {product_name} on {nextCycleAtStr}.
-
- ),
- };
-
- case "active":
- return {
- title: Product already active
,
- message: You are already subscribed to this product.
,
- };
-
- case "new":
- if (recurring) {
- return {
- title: Subscribe to {product_name}
,
- message: (
-
- By clicking confirm, you will be subscribed to {product_name} and
- your card will be charged immediately.
-
- ),
- };
- } else {
- return {
- title: Purchase {product_name}
,
- message: (
-
- By clicking confirm, you will purchase {product_name} and your
- card will be charged immediately.
-
- ),
- };
- }
-
- case "renew":
- return {
- title: Renew
,
- message: (
-
- By clicking confirm, you will renew your subscription to{" "}
- {product_name}.
-
- ),
- };
-
- case "upgrade":
- return {
- title: Upgrade to {product_name}
,
- message: (
-
- By clicking confirm, you will upgrade to {product_name} and your
- payment method will be charged immediately.
-
- ),
- };
-
- case "downgrade":
- return {
- title: Downgrade to {product_name}
,
- message: (
-
- By clicking confirm, your current subscription to{" "}
- {current_product_name} will be cancelled and a new subscription to{" "}
- {product_name} will begin on {nextCycleAtStr}.
-
- ),
- };
-
- case "cancel":
- return {
- title: Cancel
,
- message: (
-
- By clicking confirm, your subscription to {current_product_name}{" "}
- will end on {nextCycleAtStr}.
-
- ),
- };
-
- default:
- return {
- title: Change Subscription
,
- message: You are about to change your subscription.
,
- };
- }
-};
diff --git a/vite/src/lib/autumn/checkout-content.tsx b/vite/src/lib/autumn/checkout-content.tsx
deleted file mode 100644
index dff03499a..000000000
--- a/vite/src/lib/autumn/checkout-content.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import type { CheckoutResult } from "autumn-js";
-
-export const getCheckoutContent = (checkoutResult: CheckoutResult) => {
- const { product, current_product, next_cycle } = checkoutResult;
- const { is_one_off, is_free, has_trial, updateable } = product.properties;
- const scenario = product.scenario;
-
- const nextCycleAtStr = next_cycle
- ? new Date(next_cycle.starts_at).toLocaleDateString()
- : undefined;
-
- const productName = product.name;
-
- if (is_one_off) {
- return {
- title: Purchase {productName}
,
- message: (
-
- By clicking confirm, you will purchase {productName} and your card
- will be charged immediately.
-
- ),
- };
- }
-
- if (scenario == "active" && updateable) {
- if (updateable) {
- return {
- title: Update Product
,
- message: (
-
- Update your prepaid quantity. You'll be charged or credited the
- prorated difference based on your current billing cycle.
-
- ),
- };
- }
- }
-
- if (has_trial) {
- return {
- title: Start trial for {productName}
,
- message: (
-
- By clicking confirm, you will start a free trial of {productName}{" "}
- which ends on {nextCycleAtStr}.
-
- ),
- };
- }
-
- switch (scenario) {
- case "scheduled":
- return {
- title: {productName} product already scheduled
,
- message: (
-
- You are currently on product {current_product.name} and are
- scheduled to start {productName} on {nextCycleAtStr}.
-
- ),
- };
-
- case "active":
- return {
- title: Product already active
,
- message: You are already subscribed to this product.
,
- };
-
- case "new":
- if (is_free) {
- return {
- title: Enable {productName}
,
- message: (
-
- By clicking confirm, {productName} will be enabled immediately.
-
- ),
- };
- }
-
- return {
- title: Subscribe to {productName}
,
- message: (
-
- By clicking confirm, you will be subscribed to {productName} and
- your card will be charged immediately.
-
- ),
- };
- case "renew":
- return {
- title: Renew
,
- message: (
-
- By clicking confirm, you will renew your subscription to{" "}
- {productName}.
-
- ),
- };
-
- case "upgrade":
- return {
- title: Upgrade to {productName}
,
- message: (
-
- By clicking confirm, you will upgrade to {productName} and your
- payment method will be charged immediately.
-
- ),
- };
-
- case "downgrade":
- return {
- title: Downgrade to {productName}
,
- message: (
-
- By clicking confirm, your current subscription to{" "}
- {current_product.name} will be cancelled and a new subscription to{" "}
- {productName} will begin on {nextCycleAtStr}.
-
- ),
- };
-
- case "cancel":
- return {
- title: Cancel
,
- message: (
-
- By clicking confirm, your subscription to {current_product.name}{" "}
- will end on {nextCycleAtStr}.
-
- ),
- };
-
- default:
- return {
- title: Change Subscription
,
- message: You are about to change your subscription.
,
- };
- }
-};
diff --git a/vite/src/lib/autumn/get-paywall-texts.tsx b/vite/src/lib/autumn/get-paywall-texts.tsx
deleted file mode 100644
index 82017b723..000000000
--- a/vite/src/lib/autumn/get-paywall-texts.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { CheckFeaturePreview } from "autumn-js";
-
-export const getPaywallDialogTexts = (preview: CheckFeaturePreview) => {
- const { scenario, products, feature_name } = preview;
-
- if (products.length === 0) {
- switch (scenario) {
- case "usage_limit":
- return {
- title: `Feature Unavailable`,
- message: `You have reached the usage limit for ${feature_name}. Please contact us to increase your limit.`,
- };
- default:
- return {
- title: "Feature Unavailable",
- message:
- "This feature is not available for your account. Please contact us to enable it.",
- };
- }
- }
-
- const nextProduct = products[0];
-
- const isAddOn = nextProduct && nextProduct.is_add_on;
-
- const title = nextProduct.free_trial
- ? `Start trial for ${nextProduct.name}`
- : nextProduct.is_add_on
- ? `Purchase ${nextProduct.name}`
- : `Upgrade to ${nextProduct.name}`;
-
- let message = "";
- if (isAddOn) {
- message = `Please purchase the ${nextProduct.name} add-on to continue using ${feature_name}.`;
- } else {
- message = `Please upgrade to the ${nextProduct.name} product to continue using ${feature_name}.`;
- }
-
- switch (scenario) {
- case "usage_limit":
- return {
- title: title,
- message: `You have reached the usage limit for ${feature_name}. ${message}`,
- };
- case "feature_flag":
- return {
- title: title,
- message: `This feature is not available for your account. ${message}`,
- };
- default:
- return {
- title: "Feature Unavailable",
- message: "This feature is not available for your account.",
- };
- }
-};
diff --git a/vite/src/lib/autumn/get-product-change-texts.tsx b/vite/src/lib/autumn/get-product-change-texts.tsx
deleted file mode 100644
index 0d8e75059..000000000
--- a/vite/src/lib/autumn/get-product-change-texts.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import type { CheckProductPreview } from "autumn-js";
-
-export const getProductChangeTexts = (preview: CheckProductPreview) => {
- const {
- scenario,
- product_name,
- recurring,
- current_product_name,
- next_cycle_at,
- } = preview;
-
- const nextCycleAtStr = next_cycle_at
- ? new Date(next_cycle_at).toLocaleDateString()
- : undefined;
-
- switch (scenario) {
- case "scheduled":
- return {
- title: {product_name} product already scheduled
,
- message: (
-
- You are currently on product {current_product_name} and are
- scheduled to start {product_name} on {nextCycleAtStr}.
-
- ),
- };
-
- case "active":
- return {
- title: Product already active
,
- message: You are already subscribed to this product.
,
- };
-
- case "new":
- if (recurring) {
- return {
- title: Subscribe to {product_name}
,
- message: (
-
- By clicking confirm, you will be subscribed to {product_name} and
- your card will be charged immediately.
-
- ),
- };
- } else {
- return {
- title: Purchase {product_name}
,
- message: (
-
- By clicking confirm, you will purchase {product_name} and your
- card will be charged immedaitely.
-
- ),
- };
- }
-
- case "renew":
- return {
- title: Renew
,
- message: (
-
- By clicking confirm, you will renew your subscription to{" "}
- {product_name}.
-
- ),
- };
-
- case "upgrade":
- return {
- title: Upgrade to {product_name}
,
- message: (
-
- By clicking confirm, you will upgrade your subscription to{" "}
- {product_name} and your card will be charged immediately.
-
- ),
- };
-
- case "downgrade":
- return {
- title: Downgrade to {product_name}
,
- message: (
-
- By clicking confirm, your current subscription to{" "}
- {current_product_name} will be cancelled and a new subscription to{" "}
- {product_name} will begin on {nextCycleAtStr}.
-
- ),
- };
-
- case "cancel":
- return {
- title: Cancel
,
- message: (
-
- By clicking confirm, your subscription to {current_product_name}{" "}
- will end on {nextCycleAtStr}.
-
- ),
- };
-
- default:
- return {
- title: Change Subscription
,
- message: You are about to change your subscription.
,
- };
- }
-};
diff --git a/vite/src/lib/autumn/pricing-table-content.tsx b/vite/src/lib/autumn/pricing-table-content.tsx
deleted file mode 100644
index 8137633bf..000000000
--- a/vite/src/lib/autumn/pricing-table-content.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import type { Product } from "autumn-js";
-
-export const getPricingTableContent = (product: Product) => {
- const { scenario, free_trial, properties } = product;
- const { is_one_off, updateable, has_trial } = properties;
-
- if (has_trial) {
- return {
- buttonText: Start Free Trial
,
- };
- }
-
- switch (scenario) {
- case "scheduled":
- return {
- buttonText: Product Scheduled
,
- };
-
- case "active":
- if (updateable) {
- return {
- buttonText: Update Product
,
- };
- }
-
- return {
- buttonText: Current Product
,
- };
-
- case "new":
- if (is_one_off) {
- return {
- buttonText: Purchase
,
- };
- }
-
- return {
- buttonText: Get started
,
- };
-
- case "renew":
- return {
- buttonText: Renew
,
- };
-
- case "upgrade":
- return {
- buttonText: Upgrade
,
- };
-
- case "downgrade":
- return {
- buttonText: Downgrade
,
- };
-
- case "cancel":
- return {
- buttonText: Cancel Product
,
- };
-
- default:
- return {
- buttonText: Get Started
,
- };
- }
-};
diff --git a/vite/src/services/CreditService.tsx b/vite/src/services/CreditService.tsx
deleted file mode 100644
index 19b52c578..000000000
--- a/vite/src/services/CreditService.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { AxiosInstance } from "axios";
-
-export class CreditService {
- static async createSystem(axiosInstance: AxiosInstance, data: any) {
- return await axiosInstance.post("/credits/systems", data);
- }
-}
diff --git a/vite/src/services/customers/CheckoutService.tsx b/vite/src/services/customers/CheckoutService.tsx
deleted file mode 100644
index 132bdf48f..000000000
--- a/vite/src/services/customers/CheckoutService.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-// import { AxiosInstance } from "axios";
-
-// export const CheckoutService = {
-// createCheckout: async (axiosInstance: AxiosInstance, data: any) => {
-// const response = await axiosInstance.post("/v1/checkout", data);
-// return response.data;
-// },
-// };
diff --git a/vite/src/services/products/PriceService.tsx b/vite/src/services/products/PriceService.tsx
deleted file mode 100644
index 4591eb57a..000000000
--- a/vite/src/services/products/PriceService.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { AxiosInstance } from "axios";
-
-export class PriceService {
- static async createPrice(axiosInstance: AxiosInstance, data: any) {
- await axiosInstance.post(`/v1/prices`, data);
- }
-
- static async deletePrice(axiosInstance: AxiosInstance, priceId: string) {
- await axiosInstance.delete(`/v1/prices/${priceId}`);
- }
-}
diff --git a/vite/src/utils/dateUtils.ts b/vite/src/utils/dateUtils.ts
deleted file mode 100644
index a3e077b5b..000000000
--- a/vite/src/utils/dateUtils.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const unixHasPassed = (unix: number) => {
- return unix < Date.now();
-};
diff --git a/vite/src/utils/formatUtils/formatUtils.ts b/vite/src/utils/formatUtils/formatUtils.ts
deleted file mode 100644
index 065e9c0a6..000000000
--- a/vite/src/utils/formatUtils/formatUtils.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-function stringToSnakeCase(str: string): string {
- return str
- .replace(/([a-z])([A-Z])/g, "$1_$2")
- .replace(/[-\s]+/g, "_")
- .toLowerCase();
-}
-
-export const toSnakeCase = (obj: any, excludeKeys?: string[]): any => {
- if (Array.isArray(obj)) {
- return obj.map((item) => toSnakeCase(item, excludeKeys));
- } else if (obj !== null && typeof obj === "object") {
- return Object.fromEntries(
- Object.entries(obj).map(([key, value]) => {
- const snakeKey = stringToSnakeCase(key);
-
- // If this key should be excluded, convert the key but keep the value as-is
- if (excludeKeys && excludeKeys.includes(key)) {
- return [snakeKey, value];
- }
-
- // Otherwise, convert both key and recursively process the value
- return [snakeKey, toSnakeCase(value, excludeKeys)];
- }),
- );
- }
- return obj;
-};
diff --git a/vite/src/utils/models.ts b/vite/src/utils/models.ts
deleted file mode 100644
index 3988fc291..000000000
--- a/vite/src/utils/models.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export enum ProductActionState {
- NoChanges = "no_changes",
- UpdateOptionsOnly = "update_options_only",
- CreateCustomVersion = "create_custom_version",
- EnableProduct = "enable_product",
-}
diff --git a/vite/src/utils/navUtils.ts b/vite/src/utils/navUtils.ts
deleted file mode 100644
index 48e2e1091..000000000
--- a/vite/src/utils/navUtils.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export const updateSearchParams = ({
- navigate,
- params,
-}: {
- navigate: any;
- params: Record;
-}) => {
- const url = new URL(window.location.href);
- Object.entries(params).forEach(([key, value]) => {
- url.searchParams.set(key, value);
- });
-
- // console.log("New url:", url.toString());
- navigate({
- search: url.search,
- });
-};
diff --git a/vite/src/utils/product/itemIntervalUtils.ts b/vite/src/utils/product/itemIntervalUtils.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/vite/src/utils/product/product-item/validateProductItem.ts b/vite/src/utils/product/product-item/validateProductItem.ts
deleted file mode 100644
index d313a6e8e..000000000
--- a/vite/src/utils/product/product-item/validateProductItem.ts
+++ /dev/null
@@ -1,211 +0,0 @@
-import {
- type Feature,
- FeatureUsageType,
- type FrontendProductItem,
- type RolloverConfig,
- RolloverExpiryDurationType,
-} from "@autumn/shared";
-import { toast } from "sonner";
-import { invalidNumber, notNullish, nullish } from "@/utils/genUtils";
-import { isFeatureItem, isFeaturePriceItem } from "../getItemType";
-
-export const validateProductItem = ({
- item,
- features,
-}: {
- item: FrontendProductItem;
- features: Feature[];
-}) => {
- const feature = features.find((f) => f.id === item.feature_id);
-
- // Sanitize product item
- if (
- feature &&
- feature.config?.usage_type === FeatureUsageType.Continuous &&
- isFeatureItem(item)
- ) {
- item.interval = null;
- }
-
- if (item.isPrice && item.isVariable && !item.feature_id) {
- toast.error("Please select a feature");
- return null;
- }
-
- if (notNullish(item.price)) {
- if (invalidNumber(item.price)) {
- toast.error("Please enter a valid price amount");
- return null;
- }
-
- if (item.price === 0) {
- toast.error("Price should be greater than 0");
- return null;
- }
-
- item.price = parseFloat(item.price.toString());
- }
-
- if (!invalidNumber(item.included_usage)) {
- item.included_usage = Number(item.included_usage);
-
- // Check if included usage is negative
- if (item.included_usage < 0) {
- toast.error("Included usage must be 0 or greater");
- return null;
- }
- }
-
- if (isFeaturePriceItem(item) && nullish(item.usage_model)) {
- toast.error("Please select a usage model");
- return null;
- }
-
- if (item.tiers && item.price) item.price = null;
-
- if (item.tiers) {
- let previousTo = 0;
-
- const allFree = item.tiers.every((tier) => tier.amount === 0);
-
- if (allFree) {
- if (item.tiers.length === 1) {
- toast.error("Price should be greater than 0");
- } else {
- toast.error("Should have at least one tier with price greater than 0");
- }
- return null;
- }
-
- const freeTier =
- item.tiers.length > 0 && item.tiers[0].amount === 0
- ? item.tiers[0]
- : null;
-
- // const includedUsage = parseFloat(item.included_usage?.toString() || "0");
-
- let finalTiers = item.tiers;
-
- if (freeTier) {
- finalTiers = finalTiers.slice(1);
- item.included_usage = parseFloat(freeTier.to.toString() || "0");
-
- finalTiers = finalTiers.map((tier) => {
- tier.amount -= freeTier.amount;
- return tier;
- });
- }
-
- for (let i = 0; i < finalTiers.length; i++) {
- const tier = finalTiers[i];
-
- // Check if amount is actually a number
- if (typeof tier.amount !== "number") {
- tier.amount = parseFloat(tier.amount);
- }
-
- // Check if amount is valid
- if (invalidNumber(tier.amount)) {
- toast.error("Please enter valid prices for all tiers");
- return null;
- }
-
- // Check if amount is negative
- if (tier.amount < 0) {
- toast.error("Please set a positive usage price");
- return null;
- }
-
- // Skip other validations if 'to' is "inf"
- if (tier.to === "inf") {
- continue;
- }
-
- tier.to = Number(tier.to);
-
- // Check if 'to' is a number and valid
- if (typeof tier.to !== "number" || invalidNumber(tier.to)) {
- toast.error("Please enter valid usage limits for all tiers");
- return null;
- }
-
- // Ensure tiers are in ascending order
- if (tier.to < previousTo) {
- toast.error("Tiers must be in ascending order");
- return null;
- }
-
- if (tier.to === previousTo) {
- toast.error(`tier ${i + 1} should have a greater 'to'`);
- return null;
- }
-
- previousTo = tier.to;
- }
-
- item.tiers = finalTiers;
- }
-
- // Validate billing units
- if (item.billing_units && invalidNumber(item.billing_units)) {
- toast.error("Please enter valid billing units");
- return null;
- } else {
- if (isFeaturePriceItem(item)) {
- item.billing_units = Number(item.billing_units);
- } else {
- item.billing_units = undefined;
- }
- }
-
- if (item.config?.rollover) {
- const rollover = item.config?.rollover as RolloverConfig;
-
- if (rollover.max && rollover.max !== null) {
- rollover.max = parseFloat(rollover.max.toString());
- }
-
- if (rollover.duration !== RolloverExpiryDurationType.Forever) {
- rollover.length = parseFloat(rollover.length.toString());
- } else {
- rollover.length = 0;
- }
-
- if (
- item.interval === null ||
- nullish(item.included_usage) ||
- item.included_usage === 0
- ) {
- item.config!.rollover = null;
- return item;
- }
-
- if (rollover.max !== null && invalidNumber(rollover.max)) {
- toast.error("Please enter a valid maximum rollover amount");
- return null;
- }
-
- if (invalidNumber(rollover.length)) {
- toast.error("Please enter a valid rollover duration");
- item.config.rollover = undefined;
- return null;
- }
-
- if (typeof rollover.max === "number" && rollover.max < 0) {
- toast.error("Please enter a positive rollover max amount");
- item.config.rollover = undefined;
- return null;
- }
-
- if (
- rollover.duration === RolloverExpiryDurationType.Month &&
- rollover.length < 0
- ) {
- toast.error("Please enter a positive rollover length");
- item.config.rollover = undefined;
- return null;
- }
- }
-
- return item;
-};
diff --git a/vite/src/utils/setSearchParams.tsx b/vite/src/utils/setSearchParams.tsx
deleted file mode 100644
index 212445035..000000000
--- a/vite/src/utils/setSearchParams.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { useLocation, useNavigate, useSearchParams } from "react-router";
-
-export const useSetSearchParams = () => {
- const navigate = useNavigate();
- const location = useLocation();
-
- return (params: any) => {
- const searchParams = new URLSearchParams(location.search);
- for (const [key, value] of Object.entries(params)) {
- searchParams.set(key, value as string);
- }
- navigate(`${location.pathname}?${searchParams.toString()}`);
- };
-};
diff --git a/vite/src/utils/supabase/client.ts b/vite/src/utils/supabase/client.ts
deleted file mode 100644
index 4f693958f..000000000
--- a/vite/src/utils/supabase/client.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { createBrowserClient } from "@supabase/ssr";
-
-export function createClient() {
- return createBrowserClient(
- import.meta.env.VITE_SUPABASE_URL || "",
- import.meta.env.VITE_SUPABASE_ANON_KEY || "",
- );
-}
diff --git a/vite/src/views/admin/AdminTable.tsx b/vite/src/views/admin/AdminTable.tsx
deleted file mode 100644
index 40d3d0fe8..000000000
--- a/vite/src/views/admin/AdminTable.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import type { ColumnDef } from "@tanstack/react-table";
-import { useCallback, useState } from "react";
-import { useAxiosSWR } from "@/services/useAxiosSwr";
-import { DataTable } from "./DataTable";
-
-export const AdminTable = ({
- path,
- columns,
- title,
-}: {
- path: string;
- columns: ColumnDef[];
- title: string;
-}) => {
- const [search, setSearch] = useState("");
- const [after, setAfter] = useState(undefined);
- const [before, setBefore] = useState(undefined);
- const [sortKey, setSortKey] = useState("createdAt");
- const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
- const [page, setPage] = useState(1);
-
- // Build query string for params
- const params = new URLSearchParams();
- if (search) params.append("search", search);
- if (after) params.append("after", after);
- if (before) params.append("before", before);
- if (sortKey) params.append("sortKey", sortKey);
- if (sortOrder) params.append("sortOrder", sortOrder);
- const url = `${path}${params.toString() ? `?${params.toString()}` : ""}`;
-
- const { data, isLoading } = useAxiosSWR({
- url,
- });
-
- const rows = data?.rows || [];
-
- const pageInfo = {
- hasNextPage: data?.hasNextPage || false,
- hasPrevPage: rows.length !== 0,
- lastItem: `${rows[rows.length - 1]?.id},${rows[rows.length - 1]?.createdAt}`,
- firstItem: `${rows[0]?.id},${rows[0]?.createdAt}`,
- page,
- };
-
- const handleSearch = useCallback((value: string) => {
- setSearch(value);
- setAfter(undefined);
- setBefore(undefined);
- setPage(1);
- }, []);
-
- const handlePaginate = useCallback(
- (direction: "next" | "prev") => {
- if (direction === "next") {
- setAfter(pageInfo.lastItem);
- setBefore(undefined);
- } else {
- setBefore(pageInfo.firstItem);
- setAfter(undefined);
- }
- setPage((p) => (direction === "next" ? p + 1 : Math.max(1, p - 1)));
- },
- [pageInfo],
- );
-
- const handleSort = useCallback((key: string, order: "asc" | "desc") => {
- setSortKey(key);
- setSortOrder(order);
- setAfter(undefined);
- setBefore(undefined);
- setPage(1);
- }, []);
-
- return (
-
-
{title}
-
- columns={columns}
- data={rows}
- isLoading={isLoading}
- pageInfo={pageInfo}
- onSearch={handleSearch}
- onPaginate={handlePaginate}
- onSort={handleSort}
- sortKey={sortKey}
- sortOrder={sortOrder}
- />
-
- );
-};
diff --git a/vite/src/views/admin/DataTable.tsx b/vite/src/views/admin/DataTable.tsx
deleted file mode 100644
index ff9af88d2..000000000
--- a/vite/src/views/admin/DataTable.tsx
+++ /dev/null
@@ -1,190 +0,0 @@
-import {
- type Cell,
- type ColumnDef,
- flexRender,
- getCoreRowModel,
- getSortedRowModel,
- type Header,
- type HeaderGroup,
- type Row,
- type SortingState,
- useReactTable,
-} from "@tanstack/react-table";
-import { SearchIcon } from "lucide-react";
-import * as React from "react";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table";
-
-interface DataTableProps {
- columns: ColumnDef[];
- data: TData[];
- isLoading: boolean;
- pageInfo: {
- hasNextPage: boolean;
- hasPrevPage: boolean;
- nextCursor?: string;
- prevCursor?: string;
- page: number;
- };
- onSearch: (search: string) => void;
- onPaginate: (direction: "next" | "prev") => void;
- onSort: (sortKey: string, sortOrder: "asc" | "desc") => void;
- sortKey: string;
- sortOrder: "asc" | "desc";
-}
-
-export function DataTable({
- columns,
- data,
- isLoading,
- pageInfo,
- onSearch,
- onPaginate,
- onSort,
-}: DataTableProps) {
- const [search, setSearch] = React.useState("");
- const [sorting, setSorting] = React.useState([]);
-
- const debounceRef = React.useRef(null);
-
- React.useEffect(() => {
- if (debounceRef.current) clearTimeout(debounceRef.current);
- debounceRef.current = setTimeout(() => {
- onSearch(search);
- }, 300);
- return () => {
- if (debounceRef.current) clearTimeout(debounceRef.current);
- };
- }, [search, onSearch]);
-
- React.useEffect(() => {
- if (sorting.length > 0) {
- const sort = sorting[0];
- onSort(sort.id, sort.desc ? "desc" : "asc");
- }
- }, [sorting, onSort]);
-
- const table = useReactTable({
- data,
- columns,
- getCoreRowModel: getCoreRowModel(),
- getSortedRowModel: getSortedRowModel(),
- state: {
- sorting,
- },
- onSortingChange: setSorting,
- manualSorting: true,
- });
-
- return (
-
-
- setSearch(e.target.value)}
- className="max-w-sm"
- />
-
-
-
-
- {table.getHeaderGroups().map((headerGroup: HeaderGroup) => (
-
- {headerGroup.headers.map((header: Header) => {
- const width = (header.column.columnDef as any).width;
- return (
- {
- if (header.column.getCanSort()) {
- header.column.toggleSorting();
- }
- }}
- className={
- header.column.getCanSort()
- ? "cursor-pointer select-none"
- : ""
- }
- >
- {flexRender(
- header.column.columnDef.header,
- header.getContext(),
- )}
- {header.column.getIsSorted()
- ? header.column.getIsSorted() === "asc"
- ? " ▲"
- : " ▼"
- : null}
-
- );
- })}
-
- ))}
-
-
- {isLoading ? (
-
-
- Loading...
-
-
- ) : data.length ? (
- table.getRowModel().rows.map((row: Row) => (
-
- {row.getVisibleCells().map((cell: Cell) => {
- return (
-
- {flexRender(
- cell.column.columnDef.cell,
- cell.getContext(),
- )}
-
- );
- })}
-
- ))
- ) : (
-
-
- No results.
-
-
- )}
-
-
-
-
- onPaginate("prev")}
- disabled={pageInfo.page == 1}
- >
- Previous
-
- Page {pageInfo.page}
- onPaginate("next")}
- disabled={!pageInfo.hasNextPage}
- >
- Next
-
-
-
- );
-}
diff --git a/vite/src/views/admin/OrgTable.tsx b/vite/src/views/admin/OrgTable.tsx
deleted file mode 100644
index 3001b0fa9..000000000
--- a/vite/src/views/admin/OrgTable.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export const OrgTable = () => {
- return OrgTable
;
-};
diff --git a/vite/src/views/admin/orgColumns.tsx b/vite/src/views/admin/orgColumns.tsx
deleted file mode 100644
index d0a97df87..000000000
--- a/vite/src/views/admin/orgColumns.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import type { ColumnDef, Row } from "@tanstack/react-table";
-import type { User } from "better-auth";
-import { format } from "date-fns";
-import CopyButton from "@/components/general/CopyButton";
-import { ImpersonateButton } from "./components/ImpersonateBtn";
-
-export type Org = {
- id: string;
- name: string;
- slug: string;
- createdAt: string;
- users: User[];
-};
-
-// Helper to add width and canCopy to columns
-interface ExtraColumnOptions {
- width?: string | number;
- canCopy?: boolean;
-}
-
-type OrgColumnDef = ColumnDef & ExtraColumnOptions;
-
-export const columns: OrgColumnDef[] = [
- {
- accessorKey: "id",
- header: "ID",
- width: 60,
- canCopy: true,
- cell: ({ row }: { row: Row }) => {
- const value = row.getValue("id") as string;
- return (
-
-
-
- );
- },
- },
- {
- accessorKey: "name",
- header: "Name",
- width: 150,
- cell: ({ row }: { row: Row }) => {
- const value = row.getValue("name") as string;
- return {value}
;
- },
- },
-
- {
- accessorKey: "slug",
- header: "Slug",
- canCopy: true,
- width: 200,
- cell: ({ row }: { row: Row }) => {
- const value = row.getValue("slug") as string;
- return (
-
- {value}
-
- );
- },
- },
- {
- accessorKey: "createdAt",
- header: "Created At",
- width: 150,
- cell: ({ row }: { row: Row }) => {
- const value = row.getValue("createdAt");
- return (
-
- {format(new Date(value as string), "dd MMM HH:mm")}
-
- );
- },
- },
- {
- accessorKey: "impersonate",
- header: "Impersonate",
- width: 150,
- cell: ({ row }: { row: Row }) => {
- const users = row.getValue("users") as User[];
-
- if (!users || users.length === 0) {
- return null;
- }
-
- return ;
- },
- },
- {
- accessorKey: "users",
- header: "Users",
- width: "100%",
- cell: ({ row }: { row: Row }) => {
- const value = row.getValue("users");
- return (
-
- {(value as User[]).map((user) => user.email)}
-
- );
- },
- },
-];
diff --git a/vite/src/views/admin/userColumns.tsx b/vite/src/views/admin/userColumns.tsx
deleted file mode 100644
index cedbad58d..000000000
--- a/vite/src/views/admin/userColumns.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import type { ColumnDef, Row } from "@tanstack/react-table";
-import { format } from "date-fns";
-import { toast } from "sonner";
-import CopyButton from "@/components/general/CopyButton";
-import { Button } from "@/components/ui/button";
-import { authClient } from "@/lib/auth-client";
-import { impersonateUser } from "./adminUtils";
-import { ImpersonateButton } from "./components/ImpersonateBtn";
-
-export type User = {
- id: string;
- name: string;
- email: string;
- createdAt: string;
- lastSignedIn: string;
-};
-
-// Helper to add width and canCopy to columns
-interface ExtraColumnOptions {
- width?: string | number;
- canCopy?: boolean;
-}
-
-type UserColumnDef = ColumnDef & ExtraColumnOptions;
-
-export const columns: UserColumnDef[] = [
- {
- accessorKey: "id",
- header: "ID",
- width: 60,
- canCopy: true,
- cell: ({ row }: { row: Row }) => {
- const value = row.getValue("id") as string;
- return (
-
-
-
- );
- },
- },
- {
- accessorKey: "name",
- header: "Name",
- width: 150,
- cell: ({ row }: { row: Row }) => {
- const value = row.getValue("name") as string;
- return {value}
;
- },
- },
- {
- accessorKey: "email",
- header: "Email",
- canCopy: true,
- width: 200,
- cell: ({ row }: { row: Row }) => {
- const value = row.getValue("email") as string;
- return (
-
- {value}
-
- );
- },
- },
- {
- accessorKey: "createdAt",
- header: "Created At",
- width: 150,
- cell: ({ row }: { row: Row }) => {
- const value = row.getValue("createdAt");
-
- return (
-
- {format(new Date(value as string), "dd MMM HH:mm")}
-
- );
- },
- },
-
- {
- id: "impersonate",
- header: "Impersonate",
- width: "100%",
- cell: ({ row }: { row: Row }) => (
-
- ),
- enableSorting: false,
- enableHiding: false,
- },
-];
diff --git a/vite/src/views/customers/components/CreateCustomer.tsx b/vite/src/views/customers/components/CreateCustomer.tsx
deleted file mode 100644
index af4fcb52e..000000000
--- a/vite/src/views/customers/components/CreateCustomer.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import { useRef, useState } from "react";
-import { useHotkeys } from "react-hotkeys-hook";
-import { useNavigate } from "react-router";
-import { toast } from "sonner";
-import { Button as OldButton } from "@/components/ui/button";
-import { Button } from "@/components/v2/buttons/Button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/v2/dialogs/Dialog";
-import { FormLabel as FieldLabel } from "@/components/v2/form/FormLabel";
-import { Input } from "@/components/v2/inputs/Input";
-import { CusService } from "@/services/customers/CusService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr, navigateTo } from "@/utils/genUtils";
-
-function CreateCustomer() {
- const navigate = useNavigate();
- const axiosInstance = useAxiosInstance();
- const [open, setOpen] = useState(false);
- const [fields, setFields] = useState<{ [key: string]: string }>({
- name: "",
- id: "",
- email: "",
- fingerprint: "",
- });
-
- const [isLoading, setIsLoading] = useState(false);
-
- const nameInputRef = useRef(null);
-
- const handleCreate = async () => {
- setIsLoading(true);
-
- try {
- const { data } = await CusService.createCustomer(axiosInstance, {
- ...fields,
- id: fields.id ? fields.id : null,
- name: fields.name || null,
- email: fields.email ? fields.email.trim() : null,
- fingerprint: fields.fingerprint ? fields.fingerprint : undefined,
- });
-
- const customer = data.customer || data;
- if (customer) {
- navigateTo(
- `/customers/${
- customer.id || customer.autumn_id || customer.internal_id
- }`,
- navigate,
- );
- }
- toast.success("Customer created successfully");
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to create customer"));
- }
- setIsLoading(false);
- };
-
- useHotkeys(
- "n",
- (event) => {
- event.preventDefault();
- setOpen(true);
- // Focus name input after dialog open (after next tick)
- setTimeout(() => {
- nameInputRef.current?.focus();
- }, 0);
- },
- { enableOnFormTags: false },
- );
-
- useHotkeys(
- "meta+enter",
- (event) => {
- event.preventDefault();
- handleCreate();
- },
- { enableOnFormTags: true },
- );
-
- return (
- {
- setOpen(val);
- if (val) {
- // Focus name input when dialog is opened with the button as well
- setTimeout(() => {
- nameInputRef.current?.focus();
- }, 0);
- }
- }}
- >
-
-
- Customer
-
-
-
-
- Create Customer
-
-
-
- Email
- setFields({ ...fields, email: e.target.value })}
- />
-
-
-
- Create
-
-
-
-
- );
-}
-
-export default CreateCustomer;
diff --git a/vite/src/views/customers/components/CustomerRowToolbar.tsx b/vite/src/views/customers/components/CustomerRowToolbar.tsx
deleted file mode 100644
index 4d0fdaeb2..000000000
--- a/vite/src/views/customers/components/CustomerRowToolbar.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import type { Customer } from "@autumn/shared";
-import { Delete, Trash } from "lucide-react";
-import { useState } from "react";
-import SmallSpinner from "@/components/general/SmallSpinner";
-import { ToolbarButton } from "@/components/general/table-components/ToolbarButton";
-import { ContextMenuItem } from "@/components/ui/context-menu";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-
-export const CustomerRowToolbarItems = ({
- setDeleteOpen,
- isContextMenu = false,
-}: {
- setDeleteOpen: (open: boolean) => void;
- isContextMenu?: boolean;
-}) => {
- const MenuItem = isContextMenu ? ContextMenuItem : DropdownMenuItem;
-
- return (
- <>
- {
- setDeleteOpen(true);
- }}
- >
-
- Delete
-
-
-
- >
- );
-};
-
-export const CustomerRowToolbar = ({
- customer,
- setDeleteOpen,
-}: {
- customer: Customer;
- setDeleteOpen: (open: boolean) => void;
-}) => {
- const [dropdownOpen, setDropdownOpen] = useState(false);
-
- return (
- <>
-
-
-
-
- e.stopPropagation()}
- >
-
-
-
- >
- );
-};
diff --git a/vite/src/views/customers/components/CustomersPagination.tsx b/vite/src/views/customers/components/CustomersPagination.tsx
deleted file mode 100644
index b2752f92d..000000000
--- a/vite/src/views/customers/components/CustomersPagination.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import SmallSpinner from "@/components/general/SmallSpinner";
-import {
- Pagination,
- PaginationContent,
- PaginationItem,
- PaginationNext,
- PaginationPrevious,
-} from "@/components/ui/pagination";
-import { useCusSearchQuery } from "../hooks/useCusSearchQuery";
-import { useCustomersQueryStates } from "../hooks/useCustomersQueryStates";
-
-export const CustomersPagination = () => {
- const { isLoading, totalCount } = useCusSearchQuery();
- const { queryStates, setQueryStates } = useCustomersQueryStates();
-
- const totalPages = Math.ceil((totalCount || 0) / 50);
- const currentPage = Number(queryStates.page) || 1;
- const canGoPrev = currentPage > 1;
- const canGoNext = totalPages > 0 && currentPage < totalPages;
- return (
-
- {isLoading ? (
-
-
-
- ) : (
-
-
-
- {
- e.preventDefault();
- if (!canGoPrev) return;
- await setQueryStates({
- page: currentPage - 1,
- });
- }}
- isActive={canGoPrev}
- aria-disabled={!canGoPrev}
- className={`text-xs cursor-pointer p-1 h-6 ${!canGoPrev ? "pointer-events-none opacity-50" : ""}`}
- />
-
-
- {currentPage} / {Math.max(totalPages, 1)}
-
-
- {
- e.preventDefault();
- if (!canGoNext) return;
- await setQueryStates({
- page: currentPage + 1,
- });
- }}
- isActive={canGoNext}
- aria-disabled={!canGoNext}
- className={`text-xs cursor-pointer p-1 h-6 ${!canGoNext ? "pointer-events-none opacity-50" : ""}`}
- />
-
-
-
- )}
-
- );
-};
diff --git a/vite/src/views/customers/components/CustomersSearchBar.tsx b/vite/src/views/customers/components/CustomersSearchBar.tsx
deleted file mode 100644
index 3af165e93..000000000
--- a/vite/src/views/customers/components/CustomersSearchBar.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { debounce } from "lodash";
-import { Search } from "lucide-react";
-import { useEffect, useMemo, useRef, useState } from "react";
-import { useLocation, useNavigate, useSearchParams } from "react-router";
-import SmallSpinner from "@/components/general/SmallSpinner";
-import { useCustomersQueryStates } from "../hooks/useCustomersQueryStates";
-
-export function CustomersSearchBar() {
- const { queryStates, setQueryStates } = useCustomersQueryStates();
-
- const navigate = useNavigate();
- const location = useLocation();
- // const [loading, setLoading] = useState(false);
- // const prevQueryRef = useRef(queryStates.q);
-
- const debouncedSearch = useMemo(
- () =>
- debounce(async (query: string) => {
- setQueryStates({ q: query, page: 1 });
- }, 350),
- [location.search, location.pathname, navigate, setQueryStates],
- );
-
- // const handleQueryChange = async () => {
- // setLoading(true);
-
- // setLoading(false);
- // };
-
- // useEffect(() => {
- // const searchParamQuery = queryStates.q || "";
- // if (searchParamQuery !== prevQueryRef.current) {
- // prevQueryRef.current = searchParamQuery;
- // handleQueryChange();
- // }
- // }, [queryStates.q]);
-
- const handleChange = (e: React.ChangeEvent) => {
- const q = e.target.value;
- debouncedSearch(q);
- };
-
- return (
-
-
-
- {/*
{loading && }
*/}
-
- );
-}
diff --git a/vite/src/views/customers/components/CustomersTable.tsx b/vite/src/views/customers/components/CustomersTable.tsx
deleted file mode 100644
index 133f49baa..000000000
--- a/vite/src/views/customers/components/CustomersTable.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import {
- CusProductSchema,
- CusProductStatus,
- CustomerSchema,
- FullCusProduct,
- ProductSchema,
-} from "@autumn/shared";
-import type React from "react";
-
-import { Link, useNavigate } from "react-router";
-import { z } from "zod";
-import CopyButton from "@/components/general/CopyButton";
-import { Item, Row } from "@/components/general/TableGrid";
-import { Badge } from "@/components/ui/badge";
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { cn } from "@/lib/utils";
-import { unixHasPassed } from "@/utils/dateUtils";
-import { useEnv } from "@/utils/envUtils";
-import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
-import { getRedirectUrl } from "@/utils/genUtils";
-import { getVersionCounts } from "@/utils/productUtils";
-import { CustomerRowToolbar } from "./CustomerRowToolbar";
-import { CustomerRow } from "./customers-table/CustomerRow";
-
-const CustomerWithProductsSchema = CustomerSchema.extend({
- customer_products: z.array(
- CusProductSchema.extend({ product: ProductSchema }),
- ),
-});
-type CustomerWithProducts = z.infer;
-
-export const CustomersTable = ({
- customers,
-}: {
- customers: CustomerWithProducts[];
-}) => {
- return (
- <>
-
- - Name
- - ID
- - Email
- - Products
- - Created At
-
-
- {customers.map((customer, index) => (
-
- ))}
- >
- );
-};
-
-export const CustomTableCell = ({
- children,
- className,
- colSpan,
-}: {
- children: React.ReactNode;
- className?: string;
- colSpan?: number;
-}) => {
- return (
-
- {children}
-
- );
-};
diff --git a/vite/src/views/customers/components/StatusBadge.tsx b/vite/src/views/customers/components/StatusBadge.tsx
deleted file mode 100644
index e69e70c5e..000000000
--- a/vite/src/views/customers/components/StatusBadge.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { CusProductStatus } from "@autumn/shared";
-import { Badge } from "@/components/ui/badge";
-
-export const StatusBadge = ({ status }: { status: string }) => {
- const statusToVariant: any = {
- [CusProductStatus.Active]: "green",
- [CusProductStatus.Scheduled]: "blue",
- [CusProductStatus.PastDue]: "yellow",
- [CusProductStatus.Expired]: "red",
- };
-
- return {status} ;
-};
diff --git a/vite/src/views/customers/components/customers-table/CustomerRow.tsx b/vite/src/views/customers/components/customers-table/CustomerRow.tsx
deleted file mode 100644
index 5afed72a1..000000000
--- a/vite/src/views/customers/components/customers-table/CustomerRow.tsx
+++ /dev/null
@@ -1,289 +0,0 @@
-import {
- CusProductSchema,
- CusProductStatus,
- CustomerSchema,
- type FullCusProduct,
- ProductSchema,
-} from "@autumn/shared";
-import type React from "react";
-import { useEffect, useState } from "react";
-import { Link, useNavigate } from "react-router";
-import { z } from "zod";
-import CopyButton from "@/components/general/CopyButton";
-import { useContextMenu } from "@/components/general/table-components/ContextMenuWrapper";
-import { Badge } from "@/components/ui/badge";
-import {
- ContextMenu,
- ContextMenuContent,
- ContextMenuTrigger,
-} from "@/components/ui/context-menu";
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { cn } from "@/lib/utils";
-import { unixHasPassed } from "@/utils/dateUtils";
-import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
-import { pushPage } from "@/utils/genUtils";
-import { getVersionCounts } from "@/utils/productUtils";
-import { DeleteCustomerDialog } from "../../customer/components/DeleteCustomerDialog";
-import { useCusSearchQuery } from "../../hooks/useCusSearchQuery";
-import {
- CustomerRowToolbar,
- CustomerRowToolbarItems,
-} from "../CustomerRowToolbar";
-
-const CustomerWithProductsSchema = CustomerSchema.extend({
- customer_products: z.array(
- CusProductSchema.extend({ product: ProductSchema }),
- ),
-});
-type CustomerWithProducts = z.infer;
-
-const getCusProductsInfo = ({
- customer,
- versionCounts,
-}: {
- customer: CustomerWithProducts;
- versionCounts: Record;
-}) => {
- if (!customer.customer_products || customer.customer_products.length === 0) {
- return <>>;
- }
-
- // Filter out expired products first
- const activeProducts = customer.customer_products.filter(
- (cusProduct) => cusProduct.status !== CusProductStatus.Expired,
- );
-
- if (activeProducts.length === 0) {
- return <>>;
- }
-
- const getProductBadge = ({
- cusProduct,
- versionCounts,
- }: {
- cusProduct: FullCusProduct;
- versionCounts: Record;
- }) => {
- const name = cusProduct.product.name;
- const status = cusProduct.status;
-
- const versionCount = versionCounts[cusProduct.product.id];
- const version = cusProduct.product.version;
-
- const prodName = (
- <>
- {name}
- {versionCount > 1 && (
- <>
-
- v{version}
-
- >
- )}
- >
- );
-
- if (status === CusProductStatus.PastDue) {
- return (
- <>
- {prodName} {" "}
-
- Past Due
-
- >
- );
- } else {
- if (cusProduct.canceled_at) {
- return (
- <>
- {prodName} {" "}
-
- Canceled
-
- >
- );
- } else if (
- cusProduct.trial_ends_at &&
- !unixHasPassed(cusProduct.trial_ends_at)
- ) {
- return (
- <>
- {prodName} {" "}
-
- Trial
-
- >
- );
- } else {
- return (
- <>
- {prodName}
- >
- );
- }
- }
- };
-
- return (
- <>
-
- {activeProducts.slice(0, 1).map((cusProduct: any, index: number) => (
-
- {getProductBadge({ cusProduct, versionCounts })}
- {activeProducts.length > 1 && (
-
-
-
-
- +{activeProducts.length - 1}
-
-
-
- {activeProducts
- .slice(1)
- .map((p: any) => p.product.name)
- .join(", ")}
-
-
-
- )}
-
- ))}
-
- >
- );
-};
-
-export const CustomerRow = ({
- customer,
- index,
-}: {
- customer: CustomerWithProducts;
- index: number;
-}) => {
- const { products } = useProductsQuery();
- const navigate = useNavigate();
-
- const versionCounts = getVersionCounts(products);
-
- const [openKeyId, setOpenKeyId] = useState(null);
- const [deleteOpen, setDeleteOpen] = useState(false);
-
- const handleRowClick = () => {
- navigate(
- pushPage({
- path: `/customers/${customer.id || customer.internal_id}`,
- }),
- );
- };
-
- return (
-
- setOpenKeyId(open ? customer.id || customer.internal_id : null)
- }
- >
-
-
- {/* */}
-
-
{customer.name}
-
- {customer.id ? (
-
- {customer.id}
-
- ) : (
- NULL
- )}
-
-
{customer.email}
-
- {getCusProductsInfo({ customer, versionCounts })}
-
-
- {formatUnixToDateTime(customer.created_at).date}
-
- {" "}
- {formatUnixToDateTime(customer.created_at).time}{" "}
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export const CustomTableCell = ({
- children,
- className,
- colSpan,
-}: {
- children: React.ReactNode;
- className?: string;
- colSpan?: number;
-}) => {
- return (
-
- {children}
-
- );
-};
diff --git a/vite/src/views/customers/components/customers-top-bar/CustomersTopBar.tsx b/vite/src/views/customers/components/customers-top-bar/CustomersTopBar.tsx
deleted file mode 100644
index 111cef9b3..000000000
--- a/vite/src/views/customers/components/customers-top-bar/CustomersTopBar.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import {
- Pagination,
- PaginationContent,
- PaginationItem,
- PaginationNext,
- PaginationPrevious,
-} from "@/components/ui/pagination";
-import { useCusSearchQuery } from "../../hooks/useCusSearchQuery";
-import { useCustomersQueryStates } from "../../hooks/useCustomersQueryStates";
-import CreateCustomer from "../CreateCustomer";
-import { CustomersPagination } from "../CustomersPagination";
-import { CustomersSearchBar } from "../CustomersSearchBar";
-import CustomersFilterButton from "../filter-dropdown/CustomersFilterButton";
-
-export const CustomersTopBar = () => {
- const { queryStates, setQueryStates } = useCustomersQueryStates();
- const { totalCount } = useCusSearchQuery();
-
- return (
-
- );
-};
diff --git a/vite/src/views/customers/components/filter-dropdown/CustomersFilterButton.tsx b/vite/src/views/customers/components/filter-dropdown/CustomersFilterButton.tsx
deleted file mode 100644
index 0a16ae36e..000000000
--- a/vite/src/views/customers/components/filter-dropdown/CustomersFilterButton.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { ListFilter, X } from "lucide-react";
-import { useState } from "react";
-import { Button } from "@/components/ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/v2/dropdowns/DropdownMenu";
-import { useCustomersQueryStates } from "../../hooks/useCustomersQueryStates";
-import { useSavedViewsQuery } from "../../hooks/useSavedViewsQuery";
-import { FilterStatusSubMenu } from "./FilterStatusSubMenu";
-import { ProductsSubMenu } from "./ProductsSubMenu";
-import { SaveViewPopover } from "./SavedViewPopover";
-import { SavedViews } from "./SavedViews";
-
-function CustomersFilterButton() {
- const { setQueryStates } = useCustomersQueryStates();
- const [open, setOpen] = useState(false);
-
- const { data, refetch: refetchSavedViews } = useSavedViewsQuery();
-
- const views = data?.views || [];
-
- const clearFilters = () => {
- setQueryStates({
- status: [],
- version: [],
- none: false,
- });
- };
-
- const closeFilterModal = () => {
- setOpen(false);
- };
-
- return (
-
-
-
-
-
-
-
-
- {views.length > 0 && (
-
- )}
-
-
clearFilters()}
- className="cursor-pointer justify-center gap-0 flex-1"
- >
-
- Clear
-
-
-
-
-
-
-
-
- );
-}
-
-export default CustomersFilterButton;
-
-export const RenderFilterTrigger = ({ setOpen }: any) => {
- return (
-
-
-
- Filter
-
-
- );
-};
diff --git a/vite/src/views/customers/components/filter-dropdown/SavedViewsDropdown.tsx b/vite/src/views/customers/components/filter-dropdown/SavedViewsDropdown.tsx
deleted file mode 100644
index 9e3595826..000000000
--- a/vite/src/views/customers/components/filter-dropdown/SavedViewsDropdown.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { BookmarkIcon, Trash2 } from "lucide-react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { useCustomersQueryStates } from "../../hooks/useCustomersQueryStates";
-import { useSavedViewsQuery } from "../../hooks/useSavedViewsQuery";
-
-interface SavedView {
- id: string;
- name: string;
- filters: string; // base64 encoded
- created_at: string;
-}
-
-export const SavedViewsDropdown = () => {
- // const { env, setFilters, setQueryStates, mutate } = useCustomersContext();
- const { queryStates, setQueryStates } = useCustomersQueryStates();
- const axiosInstance = useAxiosInstance();
- const {
- data: savedViewsData,
- isLoading: loading,
- refetch: refetchSavedViews,
- } = useSavedViewsQuery();
-
- // const { data: savedViewsData, isLoading: loading, mutate: refetchSavedViews } = useAxiosSWR({
- // url: "/saved_views",
- // env,
- // });
-
- const views = savedViewsData?.views || [];
-
- const applyView = async (view: SavedView) => {
- try {
- // Decode base64 filters
- const decodedParams = atob(view.filters);
- const params = new URLSearchParams(decodedParams);
-
- // Apply all parameters using setQueryStates (this will reset pagination automatically)
- const statusParam = params.get("status") || "";
- const versionParam = params.get("version") || "";
- const noneParam = params.get("none");
-
- const queryParams = {
- page: 1,
- lastItemId: "",
- q: params.get("q") || "",
- status: statusParam ? statusParam.split(",").filter(Boolean) : [],
- version: versionParam ? versionParam.split(",").filter(Boolean) : [],
- none: noneParam === "true",
- };
-
- setQueryStates(queryParams);
-
- // Explicitly trigger a data refetch to ensure the view is applied immediately
- await refetchSavedViews();
-
- toast.success(`Applied view: ${view.name}`);
- } catch (error) {
- console.error(error);
- toast.error("Failed to apply view");
- }
- };
-
- const deleteView = async (
- viewId: string,
- viewName: string,
- e: React.MouseEvent,
- ) => {
- e.preventDefault();
- e.stopPropagation();
-
- try {
- await axiosInstance.delete(`/saved_views/${viewId}`);
- toast.success(`Deleted view: ${viewName}`);
- await refetchSavedViews(); // Refresh list
- } catch (error) {
- console.error(error);
- toast.error(getBackendErr(error, "Failed to delete view"));
- }
- };
-
- return (
-
-
-
-
- Views
-
-
-
-
- Saved Views
-
-
- {loading ? (
- Loading...
- ) : views.length === 0 ? (
- No saved views
- ) : (
-
- {views.map((view: SavedView) => (
- applyView(view)}
- className="flex items-center justify-between cursor-pointer"
- >
- {view.name}
- deleteView(view.id, view.name, e)}
- className="ml-2 p-1 hover:bg-red-100 rounded"
- >
-
-
-
- ))}
-
- )}
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/CustomerContext.tsx b/vite/src/views/customers/customer/CustomerContext.tsx
deleted file mode 100644
index 70f22b4db..000000000
--- a/vite/src/views/customers/customer/CustomerContext.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createContext, useContext } from "react";
-
-export const CustomerContext = createContext
(null);
-
-export const useCustomerContext = () => {
- const context = useContext(CustomerContext);
-
- if (context === undefined) {
- throw new Error(
- "useCustomerContext must be used within a CustomerContextProvider",
- );
- }
-
- return context;
-};
diff --git a/vite/src/views/customers/customer/CustomerEventsList.tsx b/vite/src/views/customers/customer/CustomerEventsList.tsx
deleted file mode 100644
index b4df05674..000000000
--- a/vite/src/views/customers/customer/CustomerEventsList.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { useState } from "react";
-import { useNavigate, useParams } from "react-router";
-import { Item, Row } from "@/components/general/TableGrid";
-import { Button } from "@/components/ui/button";
-import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { cn } from "@/lib/utils";
-import {
- formatUnixToDateTime,
- formatUnixToDateTimeWithMs,
-} from "@/utils/formatUtils/formatDateUtils";
-import { navigateTo } from "@/utils/genUtils";
-import { useCustomerContext } from "./CustomerContext";
-import { useCusEventsQuery } from "./hooks/useCusEventsQuery";
-
-export const CustomerEventsList = () => {
- const navigate = useNavigate();
- const [selectedEvent, setSelectedEvent] = useState(null);
- const { customer_id } = useParams();
- const { showEntityView } = useCustomerContext();
- const { events, isLoading, error } = useCusEventsQuery();
-
- console.log("events", events);
-
- return (
-
-
setSelectedEvent(null)}
- >
-
- Event Details
-
- {JSON.stringify(selectedEvent, null, 2)}
-
-
-
-
-
-
Events
-
-
-
- navigateTo(`/analytics?customer_id=${customer_id}`, navigate)
- }
- >
- Analyse Events
-
-
-
-
-
- {isLoading ? (
-
-
- Loading events for this customer...
-
-
- ) : events && events.length === 0 ? (
-
-
- No events received for this customer
-
-
- ) : (
-
- - Event Name
-
- - Value
- - Status
- {showEntityView && }
- - Timestamp
-
-
- )}
-
- {events &&
- events.map((event: any) => (
-
setSelectedEvent(event)}
- >
- - {event.event_name}
-
- -
-
- {event.value || event.properties.value || 1}
-
-
- -
-
POST
- 200
-
- {showEntityView && }
- -
-
-
- {formatUnixToDateTime(event.timestamp).date}{" "}
- {formatUnixToDateTime(event.timestamp).time}{" "}
-
-
- {formatUnixToDateTimeWithMs(event.timestamp)}
-
-
-
-
-
- ))}
-
-
- Showing last 10 events
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/CustomerToolbar.tsx b/vite/src/views/customers/customer/CustomerToolbar.tsx
deleted file mode 100644
index f13bb34f4..000000000
--- a/vite/src/views/customers/customer/CustomerToolbar.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import type { Customer } from "@autumn/shared";
-import { Delete, Settings } from "lucide-react";
-import { useState } from "react";
-import { useNavigate } from "react-router";
-import { toast } from "sonner";
-import SmallSpinner from "@/components/general/SmallSpinner";
-import { Button } from "@/components/ui/button";
-import { Dialog, DialogTrigger } from "@/components/ui/dialog";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { cn } from "@/lib/utils";
-import { CusService } from "@/services/customers/CusService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { navigateTo } from "@/utils/genUtils";
-import { useCustomerContext } from "./CustomerContext";
-import AddCouponDialogContent from "./components/add-coupon/AddCouponDialogContent";
-import { DeleteCustomerDialog } from "./components/DeleteCustomerDialog";
-import UpdateCustomerDialog from "./components/UpdateCustomerDialog";
-
-export const CustomerToolbar = ({
- className,
- customer,
-}: {
- className?: string;
- customer: Customer;
-}) => {
- const navigate = useNavigate();
- const { env } = useCustomerContext();
-
- const axiosInstance = useAxiosInstance({ env });
- const [deleteLoading, setDeleteLoading] = useState(false);
- const [settingsOpen, setSettingsOpen] = useState(false);
- const [deleteOpen, setDeleteOpen] = useState(false);
- const [modalOpen, setModalOpen] = useState(false);
- const [modalType, setModalType] = useState<"add-coupon" | "edit">(
- "add-coupon",
- );
-
- const handleDelete = async () => {
- setDeleteLoading(true);
- try {
- await CusService.deleteCustomer(
- axiosInstance,
- customer.id || customer.internal_id,
- );
- navigateTo("/customers", navigate, env);
- } catch (error) {
- toast.error("Failed to delete customer");
- }
- setDeleteLoading(false);
- setSettingsOpen(false);
- };
-
- return (
- <>
-
-
- {modalOpen && modalType === "add-coupon" ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
- {
- e.stopPropagation();
- e.preventDefault();
- setDeleteOpen(true);
- setSettingsOpen(false);
- }}
- >
-
- Delete
- {deleteLoading ? : }
-
-
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/customers/customer/CustomerView.tsx b/vite/src/views/customers/customer/CustomerView.tsx
deleted file mode 100644
index 6f48ff886..000000000
--- a/vite/src/views/customers/customer/CustomerView.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-"use client";
-
-import { CusProductStatus } from "@autumn/shared";
-import { useEffect, useState } from "react";
-import { Link, useSearchParams } from "react-router";
-import { notNullish, pushPage } from "@/utils/genUtils";
-import ErrorScreen from "@/views/general/ErrorScreen";
-import LoadingScreen from "@/views/general/LoadingScreen";
-import { CustomerContext } from "./CustomerContext";
-import { CustomerEventsList } from "./CustomerEventsList";
-import { CustomerPageHeader } from "./components/customer-header/CustomerPageHeader";
-import { CustomerSidebar } from "./components/customer-sidebar/CustomerSidebar";
-import { CustomerProductList } from "./customer-product-list/CustomerProductList";
-import { CustomerEntitlementsList } from "./entitlements/CustomerEntitlementsList";
-import { useCusQuery } from "./hooks/useCusQuery";
-import { useCusReferralQuery } from "./hooks/useCusReferralQuery";
-import { InvoicesTable } from "./InvoicesTable";
-
-export default function CustomerView() {
- // const { customer_id } = useParams();
- const [searchParams] = useSearchParams();
- const entityIdParam = searchParams.get("entity_id");
-
- const { customer, isLoading: cusLoading, error, refetch } = useCusQuery();
-
- useCusReferralQuery();
-
- // const {
- // data,
- // isLoading,
- // mutate: cusMutate,
- // } = useAxiosSWR({
- // url: `/customers/${customer_id}/data`,
- // env,
- // });
-
- // const { data: referrals } = useAxiosSWR({
- // url: `/customers/${customer_id}/referrals`,
- // env,
- // });
- // const { data: rewardsData } = useAxiosSWR({
- // url: `/products/rewards`,
- // env,
- // });
-
- const [setAddCouponOpen] = useState(false);
- const [entityId, setEntityId] = useState(entityIdParam);
-
- useEffect(() => {
- if (entityIdParam) {
- setEntityId(entityIdParam);
- } else {
- setEntityId(null);
- }
- }, [entityIdParam]);
-
- if (cusLoading) return ;
-
- if (!customer) {
- return (
-
- Customer not found
-
- Return
-
-
- );
- }
-
- // const { customer, products, invoices, coupons, discount, events, entities } =
- // data;
-
- const showEntityView = customer.customer_products.some(
- (cp: any) =>
- notNullish(cp.internal_entity_id) &&
- cp.status !== CusProductStatus.Expired,
- );
-
- return (
-
-
-
- );
-}
diff --git a/vite/src/views/customers/customer/InvoicesTable.tsx b/vite/src/views/customers/customer/InvoicesTable.tsx
deleted file mode 100644
index 18a9b4313..000000000
--- a/vite/src/views/customers/customer/InvoicesTable.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import type { Entity, Invoice, InvoiceDiscount } from "@autumn/shared";
-import { toast } from "sonner";
-import { AdminHover } from "@/components/general/AdminHover";
-import { Item, Row } from "@/components/general/TableGrid";
-import { useOrgStripeQuery } from "@/hooks/queries/useOrgStripeQuery";
-import { cn } from "@/lib/utils";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
-import { getStripeInvoiceLink } from "@/utils/linkUtils";
-import { getInvoiceHoverTexts } from "@/views/admin/adminUtils";
-import { useCustomerContext } from "./CustomerContext";
-import { CusProductEntityItem } from "./components/CusProductEntityItem";
-import { useCusQuery } from "./hooks/useCusQuery";
-
-export const InvoicesTable = () => {
- // const { env, invoices, products, entityId, entities, showEntityView } =
- // useCustomerContext();
- const env = useEnv();
- const { entityId, showEntityView } = useCustomerContext();
- const { customer, products, entities } = useCusQuery();
- const { stripeAccount } = useOrgStripeQuery();
- const axiosInstance = useAxiosInstance();
- const invoices = customer.invoices;
-
- const entity = entities.find(
- (e: Entity) => e.id === entityId || e.internal_id === entityId,
- );
-
- const getStripeInvoice = async (stripeInvoiceId: string) => {
- try {
- const { data } = await axiosInstance.get(
- `/v1/invoices/${stripeInvoiceId}/stripe`,
- );
- return data;
- } catch {
- toast.error("Failed to get invoice URL");
- return null;
- }
- };
-
- const getTotalDiscountAmount = (invoice: Invoice) => {
- return invoice.discounts.reduce(
- (acc: number, discount: InvoiceDiscount) => {
- return acc + discount.amount_used;
- },
- 0,
- );
- };
-
- const invoicesFiltered = invoices.filter((invoice: Invoice) => {
- return entity ? invoice.internal_entity_id === entity.internal_id : true;
- });
-
- return (
-
-
-
- Invoices
-
-
- {/* Add any header controls here if needed */}
-
-
-
- {invoicesFiltered.length === 0 ? (
-
-
No invoice history found
-
- ) : (
-
- - Products
- {showEntityView && - Entity
}
- - Total
- - Status
- - Created At
-
-
- )}
-
- {invoicesFiltered.map((invoice: Invoice) => (
-
{
- const stripeInvoice = await getStripeInvoice(invoice.stripe_id);
- if (!stripeInvoice.hosted_invoice_url) {
- window.open(
- getStripeInvoiceLink({
- stripeInvoice,
- env,
- accountId: stripeAccount?.id,
- }),
- "_blank",
- );
- return;
- }
-
- if (stripeInvoice?.hosted_invoice_url) {
- window.open(stripeInvoice.hosted_invoice_url, "_blank");
- }
- }}
- >
- -
-
-
- {invoice.product_ids
- .map((p: string) => {
- return products.find((product: any) => product.id === p)
- ?.name;
- })
- .join(", ")}
-
-
-
- {showEntityView && (
- -
-
-
- )}
- -
- {invoice.total.toFixed(2)} {invoice.currency.toUpperCase()}
- {getTotalDiscountAmount(invoice) > 0 && (
-
- {" "}
- (-{getTotalDiscountAmount(invoice).toFixed(2)})
-
- )}
-
- - {invoice.status}
- -
- {formatUnixToDateTime(invoice.created_at).date}{" "}
- {formatUnixToDateTime(invoice.created_at).time}{" "}
-
-
-
- ))}
-
- );
-};
diff --git a/vite/src/views/customers/customer/add-product/CreateCheckoutContext.tsx b/vite/src/views/customers/customer/add-product/CreateCheckoutContext.tsx
deleted file mode 100644
index c5aa5b039..000000000
--- a/vite/src/views/customers/customer/add-product/CreateCheckoutContext.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createContext, useContext } from "react";
-
-export const AddProductContext = createContext(null);
-
-export const useAddProductContext = () => {
- const context = useContext(AddProductContext);
-
- if (context === undefined) {
- throw new Error(
- "useAddProductContext must be used within a AddProductContextProvider",
- );
- }
-
- return context;
-};
diff --git a/vite/src/views/customers/customer/analytics/ViewUserEvents.tsx b/vite/src/views/customers/customer/analytics/ViewUserEvents.tsx
deleted file mode 100644
index 066903b8f..000000000
--- a/vite/src/views/customers/customer/analytics/ViewUserEvents.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useParams } from "react-router";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-
-export const ViewUserEvents = ({ customer }: { customer: any }) => {
- const params = useParams();
- console.log(params, customer);
-
- return (
-
-
-
- Analyse Events
-
-
-
- Are you absolutely sure?
-
- This action cannot be undone. This will permanently delete your
- account and remove your data from our servers.
- {JSON.stringify(customer, null, 4)}
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/components/CusProductEntityItem.tsx b/vite/src/views/customers/customer/components/CusProductEntityItem.tsx
deleted file mode 100644
index 529d868d9..000000000
--- a/vite/src/views/customers/customer/components/CusProductEntityItem.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { FullCusProduct } from "@autumn/shared";
-import { ExternalLinkIcon } from "lucide-react";
-import { useNavigate } from "react-router";
-import { TableButton } from "@/components/general/table-components/TableButton";
-import { updateSearchParams } from "@/utils/navUtils";
-import { useCustomerContext } from "../CustomerContext";
-import { useCusQuery } from "../hooks/useCusQuery";
-
-export const CusProductEntityItem = ({
- internalEntityId,
-}: {
- internalEntityId?: string | null;
-}) => {
- const { customer } = useCusQuery();
-
- const entity = customer.entities.find(
- (e: any) => e.internal_id === internalEntityId,
- );
-
- const navigate = useNavigate();
- return internalEntityId ? (
- {
- updateSearchParams({
- navigate,
- params: {
- entity_id: entity?.id || entity?.internal_id,
- },
- });
- }}
- >
-
- {entity?.name || entity?.id || "Unknown"}
-
-
- ) : (
-
- );
-};
diff --git a/vite/src/views/customers/customer/components/EntityHeader.tsx b/vite/src/views/customers/customer/components/EntityHeader.tsx
deleted file mode 100644
index 98a177d15..000000000
--- a/vite/src/views/customers/customer/components/EntityHeader.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { Entity } from "@autumn/shared";
-import { X } from "lucide-react";
-import { useLocation, useNavigate } from "react-router";
-import { Button } from "@/components/ui/button";
-
-export const EntityHeader = ({ entity }: { entity?: Entity }) => {
- const location = useLocation();
- const navigate = useNavigate();
-
- if (!entity) {
- return null;
- }
-
- return (
-
- Entity:
- {entity.name}
- {
- const params = new URLSearchParams(location.search);
- params.delete("entity_id");
- navigate(`${location.pathname}?${params.toString()}`);
- }}
- >
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/components/add-coupon/AddCouponDialogContent.tsx b/vite/src/views/customers/customer/components/add-coupon/AddCouponDialogContent.tsx
deleted file mode 100644
index 99c71ac83..000000000
--- a/vite/src/views/customers/customer/components/add-coupon/AddCouponDialogContent.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import { type Reward, RewardType } from "@autumn/shared";
-import { useState } from "react";
-import { toast } from "sonner";
-import { WarningBox } from "@/components/general/modal-components/WarningBox";
-import { Button } from "@/components/ui/button";
-import {
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
-import { CusService } from "@/services/customers/CusService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { getOriginalCouponId } from "@/utils/product/couponUtils";
-import { useCusQuery } from "../../hooks/useCusQuery";
-import { useCusReferralQuery } from "../../hooks/useCusReferralQuery";
-
-const AddCouponDialogContent = ({
- setOpen,
-}: {
- setOpen: (open: boolean) => void;
-}) => {
- const { stripeCus } = useCusReferralQuery();
- const { customer, refetch } = useCusQuery();
-
- const [couponSelected, setCouponSelected] = useState(null);
- const [loading, setLoading] = useState(false);
- const axiosInstance = useAxiosInstance();
-
- const { rewards } = useRewardsQuery();
-
- const handleAddClicked = async () => {
- try {
- setLoading(true);
- await CusService.addCouponToCustomer({
- axios: axiosInstance,
- customer_id: customer.id,
- coupon_id: couponSelected!.internal_id,
- });
- setOpen(false);
- await refetch();
- toast.success("Reward added to customer");
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to create coupon"));
- } finally {
- setLoading(false);
- }
- };
-
- const existingDiscount = stripeCus?.discount;
-
- const getExistingCoupon = () => {
- if (existingDiscount) {
- return rewards.find(
- (c: Reward) => c.id === getOriginalCouponId(existingDiscount.coupon.id),
- );
- } else {
- return null;
- }
- };
-
- if (!rewards) return null;
-
- return (
-
- Add Reward
- {getExistingCoupon() && (
-
- Reward {getExistingCoupon()?.name} already applied. Adding a new one
- will replace the existing one.
-
- )}
-
- {
- const coupon = rewards.find((c: Reward) => c.internal_id === value);
- if (coupon) {
- setCouponSelected(coupon);
- }
- }}
- >
-
-
-
-
- {/* If empty */}
-
- {rewards && rewards.length > 0 ? (
- rewards.map((coupon: Reward) => {
- if (coupon.type == RewardType.FreeProduct) return null;
- return (
-
- {coupon.name}
-
- );
- })
- ) : (
-
- No coupons found
-
- )}
-
-
-
-
- handleAddClicked()}
- disabled={!couponSelected}
- isLoading={loading}
- >
- Add Reward
-
-
-
- );
-};
-
-export default AddCouponDialogContent;
diff --git a/vite/src/views/customers/customer/components/customer-header/CustomerBreadcrumbs.tsx b/vite/src/views/customers/customer/components/customer-header/CustomerBreadcrumbs.tsx
deleted file mode 100644
index 7760f3ecc..000000000
--- a/vite/src/views/customers/customer/components/customer-header/CustomerBreadcrumbs.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { useLocation, useNavigate } from "react-router";
-import { AdminHover } from "@/components/general/AdminHover";
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-import { useEnv } from "@/utils/envUtils";
-import { navigateTo } from "@/utils/genUtils";
-import { useCustomerContext } from "../../CustomerContext";
-import { useCusQuery } from "../../hooks/useCusQuery";
-
-export const CustomerBreadcrumbs = () => {
- const env = useEnv();
- const navigate = useNavigate();
- const location = useLocation();
-
- const { customer } = useCusQuery();
- const { entityId, setEntityId } = useCustomerContext();
-
- const entity = customer.entities.find((e: any) => e.id === entityId);
-
- return (
-
-
-
- navigateTo("/customers", navigate, env)}
- >
- e.id)
- .join(", "),
- },
- ]}
- >
- Customers
-
-
-
-
-
- {entityId ? (
- {
- setEntityId(null);
- const params = new URLSearchParams(location.search);
- params.delete("entity_id");
- navigate(`${location.pathname}?${params.toString()}`);
- }}
- >
- {customer.name || customer.id || customer.email}
-
- ) : (
-
- {customer.name || customer.id || customer.email}
-
- )}
-
- {entityId && (
- <>
-
-
- {entity?.name || entityId}
-
- >
- )}
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/components/customer-header/CustomerPageHeader.tsx b/vite/src/views/customers/customer/components/customer-header/CustomerPageHeader.tsx
deleted file mode 100644
index cd32db678..000000000
--- a/vite/src/views/customers/customer/components/customer-header/CustomerPageHeader.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { useCustomerContext } from "../../CustomerContext";
-import { useCusQuery } from "../../hooks/useCusQuery";
-import { CustomerBreadcrumbs } from "./CustomerBreadcrumbs";
-import { SelectEntity } from "./SelectEntity";
-
-export const CustomerPageHeader = () => {
- const { customer } = useCusQuery();
- const { entityId, setEntityId } = useCustomerContext();
-
- return (
-
-
-
-
-
- {customer.name ? (
- {customer.name}
- ) : customer.id ? (
- {customer.id}
- ) : (
- {customer.email}
- )}
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/components/customer-header/SelectEntity.tsx b/vite/src/views/customers/customer/components/customer-header/SelectEntity.tsx
deleted file mode 100644
index 048151db1..000000000
--- a/vite/src/views/customers/customer/components/customer-header/SelectEntity.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { type Entity, type Feature, FeatureUsageType } from "@autumn/shared";
-import { PlusIcon } from "lucide-react";
-import { useState } from "react";
-import { useLocation, useNavigate } from "react-router";
-import { Button } from "@/components/ui/button";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useCustomerContext } from "../../CustomerContext";
-import { useCusQuery } from "../../hooks/useCusQuery";
-import { CreateEntity } from "../customer-sidebar/create-entity/CreateEntity";
-
-export const SelectEntity = ({
- entityId,
- entities,
-}: {
- entityId?: string;
- entities: Entity[];
-}) => {
- const cusContext = useCustomerContext();
-
- const [open, setOpen] = useState(false);
- const navigate = useNavigate();
- const location = useLocation();
- const { features } = useFeaturesQuery();
-
- if (!entities || entities.length === 0) {
- const hasContinuousUseFeatures = features?.some(
- (feature: Feature) =>
- feature.config?.usage_type === FeatureUsageType.Continuous,
- );
-
- if (!hasContinuousUseFeatures) return null;
-
- return (
- <>
-
- setOpen(true)}
- >
-
- Create entity
-
- >
- );
- }
-
- const entity = entities.find(
- (entity: Entity) =>
- entity.id === entityId || entity.internal_id == entityId,
- );
-
- return (
- <>
-
-
-
{
- if (value == "create-entity") {
- setOpen(true);
- } else {
- const params = new URLSearchParams(location.search);
- params.set("entity_id", value);
- navigate(`${location.pathname}?${params.toString()}`);
- }
- }}
- >
- {
- const params = new URLSearchParams(location.search);
- params.delete("entity_id");
- navigate(`${location.pathname}?${params.toString()}`);
- }
- : undefined
- }
- >
-
-
- {entityId ? (
-
- {entity?.name || entity?.id || entity?.internal_id}
-
- ) : (
- Select an entity
- )}
-
-
-
-
- {entities.map((entity: Entity) => (
-
- {entity.id || entity.internal_id}{" "}
- {entity.name ? (
- ({entity.name})
- ) : (
- ""
- )}
-
- ))}
- {cusContext && (
-
-
- Create entity
-
- )}
-
-
-
- >
- );
-};
diff --git a/vite/src/views/customers/customer/components/customer-sidebar/CustomerDetails.tsx b/vite/src/views/customers/customer/components/customer-sidebar/CustomerDetails.tsx
deleted file mode 100644
index 91bc9e4ce..000000000
--- a/vite/src/views/customers/customer/components/customer-sidebar/CustomerDetails.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import { faStripe } from "@fortawesome/free-brands-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { ArrowUpRightFromSquare } from "lucide-react";
-import CopyButton from "@/components/general/CopyButton";
-import { SideAccordion } from "@/components/general/SideAccordion";
-import { SidebarLabel } from "@/components/general/sidebar/sidebar-label";
-import { Button } from "@/components/ui/button";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useOrgStripeQuery } from "@/hooks/queries/useOrgStripeQuery";
-import { useEnv } from "@/utils/envUtils";
-import { getStripeCusLink } from "@/utils/linkUtils";
-import { useCusQuery } from "../../hooks/useCusQuery";
-
-export const CustomerDetails = ({
- setIsModalOpen,
- setModalType,
-}: {
- setIsModalOpen: (isModalOpen: boolean) => void;
- setModalType: (modalType: string) => void;
-}) => {
- const { customer } = useCusQuery();
- const env = useEnv();
- const { org } = useOrg();
- const { stripeAccount } = useOrgStripeQuery();
-
- return (
-
-
-
-
ID
-
-
- {customer.id ? (
-
- {customer.id}
-
- ) : (
- {
- setIsModalOpen(true);
- setModalType("customer");
- }}
- >
- N/A
-
- )}
-
-
-
-
Name
-
- {
- setIsModalOpen(true);
- setModalType("customer");
- }}
- >
-
- {customer.name || None }
-
-
-
-
-
Email
-
- {
- setIsModalOpen(true);
- setModalType("customer");
- }}
- >
-
- {customer.email || None }
-
-
-
-
-
- Fingerprint
-
-
- {
- setIsModalOpen(true);
- setModalType("customer");
- }}
- >
-
- {customer.fingerprint || None }
-
-
-
-
- {customer.processor?.id && (
- <>
-
- Stripe
-
-
-
-
-
{
- if (stripeAccount) {
- window.open(
- getStripeCusLink({
- customerId: customer.processor?.id,
- env,
- accountId: stripeAccount.id,
- }),
- "_blank",
- );
- } else {
- window.location.href = getStripeCusLink({
- customerId: customer.processor?.id,
- env,
- });
- }
- }}
- >
-
-
-
-
-
-
- >
- )}
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/components/customer-sidebar/CustomerEntities.tsx b/vite/src/views/customers/customer/components/customer-sidebar/CustomerEntities.tsx
deleted file mode 100644
index 39527948d..000000000
--- a/vite/src/views/customers/customer/components/customer-sidebar/CustomerEntities.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { type Entity, type Feature, getFeatureName } from "@autumn/shared";
-import CopyButton from "@/components/general/CopyButton";
-import { SideAccordion } from "@/components/general/SideAccordion";
-import { SidebarLabel } from "@/components/general/sidebar/sidebar-label";
-import { useCustomerContext } from "../../CustomerContext";
-import { useCusQuery } from "../../hooks/useCusQuery";
-
-export const CustomerEntities = () => {
- const { customer, features } = useCusQuery();
- const { entityId } = useCustomerContext();
-
- const entities = customer.entities;
-
- const entity = entities.find(
- (entity: Entity) =>
- entity.id === entityId || entity.internal_id === entityId,
- );
-
- const feature = features.find(
- (feature: Feature) => entity?.internal_feature_id === feature.internal_id,
- );
-
- const featureName = getFeatureName({
- feature,
- plural: false,
- capitalize: true,
- });
-
- if (!entity) {
- return null;
- }
-
- return (
-
-
-
- {/*
*/}
-
ID
-
-
- {entity.id ? (
-
- {entity?.id}
-
- ) : (
- N/A
- )}
-
-
- {entity && (
- <>
-
Name
-
- {entity?.name}
-
-
Feature
-
- {featureName.toLowerCase()}
-
- >
- )}
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/components/customer-sidebar/CustomerSidebar.tsx b/vite/src/views/customers/customer/components/customer-sidebar/CustomerSidebar.tsx
deleted file mode 100644
index e51b048ff..000000000
--- a/vite/src/views/customers/customer/components/customer-sidebar/CustomerSidebar.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { useState } from "react";
-import { Accordion } from "@/components/ui/accordion";
-import { Dialog } from "@/components/ui/dialog";
-import { CustomerToolbar } from "../../CustomerToolbar";
-import { useCusQuery } from "../../hooks/useCusQuery";
-import UpdateCustomerDialog from "../UpdateCustomerDialog";
-import { CustomerDetails } from "./CustomerDetails";
-import { CustomerEntities } from "./CustomerEntities";
-import { CustomerRewards } from "./customer-rewards";
-
-export const CustomerSidebar = () => {
- const { customer } = useCusQuery();
- const entities = customer.entities;
-
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [modalType, setModalType] = useState("coupon");
-
- return (
-
-
-
-
-
-
-
-
-
-
- {entities.length > 0 && }
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/components/customer-sidebar/create-entity/CreateEntity.tsx b/vite/src/views/customers/customer/components/customer-sidebar/create-entity/CreateEntity.tsx
deleted file mode 100644
index 9993cfac0..000000000
--- a/vite/src/views/customers/customer/components/customer-sidebar/create-entity/CreateEntity.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { useState } from "react";
-import { useNavigate } from "react-router";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { useCustomerContext } from "../../../CustomerContext";
-import { useCusQuery } from "../../../hooks/useCusQuery";
-import { EntityConfig } from "./EntityConfig";
-
-export const CreateEntity = ({
- open,
- setOpen,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
-}) => {
- const cusContext = useCustomerContext();
- const { customer, refetch } = useCusQuery();
- const navigate = useNavigate();
-
- const [isLoading, setIsLoading] = useState(false);
- const [entity, setEntity] = useState({
- id: "",
- name: "",
- });
-
- const axiosInstance = useAxiosInstance();
-
- if (!cusContext) return null;
-
- // const { customer, cusMutate } = cusContext;
-
- const handleCreateClicked = async () => {
- setIsLoading(true);
- try {
- const { data } = await axiosInstance.post(
- `/v1/customers/${
- customer.id || customer.internal_id
- }/entities?with_autumn_id=true`,
- {
- id: entity.id || null,
- name: entity.name || null,
- feature_id: entity.feature_id,
- customer_id: customer.id,
- },
- );
-
- await refetch();
- setOpen(false);
-
- const params = new URLSearchParams(location.search);
- params.set("entity_id", data.id || data.autumn_id);
- navigate(`${location.pathname}?${params.toString()}`);
-
- toast.success("Entity created successfully");
- } catch (error) {
- console.log(error);
- toast.error(getBackendErr(error, "Failed to create entity"));
- } finally {
- setIsLoading(false);
- }
- };
-
- return (
-
-
-
-
- Create Entity
-
-
-
-
-
- Create
-
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/components/customer-sidebar/create-entity/EntityConfig.tsx b/vite/src/views/customers/customer/components/customer-sidebar/create-entity/EntityConfig.tsx
deleted file mode 100644
index a496a8212..000000000
--- a/vite/src/views/customers/customer/components/customer-sidebar/create-entity/EntityConfig.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { type Feature, FeatureUsageType } from "@autumn/shared";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useCustomerContext } from "../../../CustomerContext";
-
-export const EntityConfig = ({
- entity,
- setEntity,
-}: {
- entity: any;
- setEntity: (entity: any) => void;
-}) => {
- const { features } = useFeaturesQuery();
-
- return (
- <>
-
-
- Feature ID
- setEntity({ ...entity, feature_id: value })}
- >
-
-
-
-
- {features
- .filter(
- (feature: Feature) =>
- feature.config?.usage_type == FeatureUsageType.Continuous,
- )
- .map((feature: Feature) => (
-
- {feature.name}
-
- ))}
-
-
-
- >
- );
-};
diff --git a/vite/src/views/customers/customer/components/customer-sidebar/customer-rewards.tsx b/vite/src/views/customers/customer/components/customer-sidebar/customer-rewards.tsx
deleted file mode 100644
index 7e07d0d3c..000000000
--- a/vite/src/views/customers/customer/components/customer-sidebar/customer-rewards.tsx
+++ /dev/null
@@ -1,216 +0,0 @@
-import { stripeToAtmnAmount } from "@autumn/shared";
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { useState } from "react";
-import { Link } from "react-router";
-import { SideAccordion } from "@/components/general/SideAccordion";
-import { Button } from "@/components/ui/button";
-import { Dialog } from "@/components/ui/dialog";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
-import { useEnv } from "@/utils/envUtils";
-import { getRedirectUrl } from "@/utils/genUtils";
-import AddCouponDialogContent from "../../components/add-coupon/AddCouponDialogContent";
-import { useCusReferralQuery } from "../../hooks/useCusReferralQuery";
-
-export const CustomerRewards = () => {
- // const { discount, env } = useCustomerContext();
-
- const env = useEnv();
- // const { customer, rewards } = useCusQuery();
- const { referred, redeemed, stripeCus } = useCusReferralQuery();
- useRewardsQuery();
-
- const [isModalOpen, setIsModalOpen] = useState(false);
-
- const getDiscountText = (discount: any) => {
- const coupon = discount.coupon;
- const atmnAmountOff = stripeToAtmnAmount({
- amount: coupon.amount_off,
- currency: coupon.currency,
- });
- if (coupon.amount_off) {
- return (
-
- {`${coupon.name} `}
-
- (${atmnAmountOff} {coupon.currency.toUpperCase()})
-
-
- );
- }
- if (coupon.percent_off) {
- return (
-
- {`${coupon.name} `}
- ({coupon.percent_off}% off)
-
- );
- }
- return coupon.name;
- };
-
- return (
-
-
-
-
-
-
- <>
-
- Coupon
-
-
- setIsModalOpen(true)}
- >
- {stripeCus?.discount ? (
- getDiscountText(stripeCus?.discount)
- ) : (
- Add Coupon
- )}
-
-
- >
- {referred?.length > 0 && (
- <>
-
- Referrals
-
-
-
-
-
-
- {referred.length} referred
-
-
-
-
-
-
- {referred.map((referral: any) => (
-
-
- {referral.customer.name}
-
-
-
- ))}
-
-
-
-
-
- Activated
-
-
-
-
-
- {referred.filter((r: any) => r.triggered).length}{" "}
- activated
-
-
-
-
-
-
- {referred
- .filter((r: any) => r.triggered)
- .map((referral: any) => (
-
-
- {referral.customer.name}
-
- {/*
- ({referral.customer.id})
-
*/}
-
-
- ))}
-
-
-
- >
- )}
- {redeemed?.length > 0 && (
- <>
-
- Referred by
-
-
-
-
-
-
- {redeemed[0].referral_code?.customer.name}
-
-
-
-
-
-
-
- {redeemed[0].referral_code?.customer.id}{" "}
- {redeemed[0].referral_code.code}
-
-
-
- >
- )}
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/customer-product-list/AttachProductDropdown.tsx b/vite/src/views/customers/customer/customer-product-list/AttachProductDropdown.tsx
deleted file mode 100644
index 8686f1667..000000000
--- a/vite/src/views/customers/customer/customer-product-list/AttachProductDropdown.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import type { Entity, ProductV2 } from "@autumn/shared";
-import { useCustomer } from "autumn-js/react";
-import { Blend, Search } from "lucide-react";
-import { useState } from "react";
-import { useNavigate } from "react-router";
-import SmallSpinner from "@/components/general/SmallSpinner";
-import { Button } from "@/components/ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { Input } from "@/components/ui/input";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { navigateTo } from "@/utils/genUtils";
-import { AddProductContext } from "../add-product/CreateCheckoutContext";
-import { useCustomerContext } from "../CustomerContext";
-import { useCusQuery } from "../hooks/useCusQuery";
-
-function AttachProductDropdown({
- setMultiAttachOpen,
-}: {
- setMultiAttachOpen: (open: boolean) => void;
-}) {
- const { env, entityId } = useCustomerContext();
- const { products, customer, entities } = useCusQuery();
-
- const axiosInstance = useAxiosInstance({ env });
- const { customer: autumnCustomer } = useCustomer();
-
- const [options, setOptions] = useState([]);
- const [searchQuery, setSearchQuery] = useState("");
- const [open, setOpen] = useState(false);
- const { org } = useOrg();
-
- const filteredProducts = products.filter((product: ProductV2) => {
- if (product.archived) return false;
- if (product.is_add_on && !searchQuery) return true;
-
- const entity = entities.find((e: Entity) => e.id === entityId);
-
- // const customerHasProduct = customer.customer_products?.some((cp: any) => {
- // const idMatch = cp.product_id === product.id;
- // const entityIdMatch = entity
- // ? cp.internal_entity_id === entity?.internal_id
- // : true;
- // const isAddOn = product.is_add_on;
- // const isActive = cp.status === CusProductStatus.Active;
-
- // return idMatch && entityIdMatch && isAddOn && isActive;
- // });
-
- return product.name.toLowerCase().includes(searchQuery.toLowerCase());
- });
-
- const navigate = useNavigate();
-
- const handleAddProduct = async (productId: string, setLoading: any) => {
- // let stripeConnected = org?.stripe_connected;
-
- // if (!stripeConnected) {
- // const { data: org } = await OrgService.get(axiosInstance);
- // stripeConnected = org?.stripe_connected;
- // }
-
- // if (!stripeConnected) {
- // toast.error("Connect to Stripe to add products to customers");
- // const redirectUrl = getRedirectUrl(`/customers/${customer.id}`, env);
- // navigateTo(`/dev?tab=stripe`, navigate, env);
- // return;
- // }
-
- navigateTo(
- `/customers/${customer.id || customer.internal_id}/${productId}${
- entityId ? `?entity_id=${entityId}` : ""
- }`,
- navigate,
- env,
- );
- };
-
- return (
-
-
-
-
- Attach Product
-
-
-
-
- setSearchQuery(e.target.value)}
- onClick={(e) => e.stopPropagation()}
- onKeyDown={(e) => e.stopPropagation()}
- autoFocus
- />
-
- {filteredProducts.length === 0 ? (
-
- No new products found
-
- ) : (
- filteredProducts.map((product: ProductV2) => (
-
- ))
- )}
- {autumnCustomer?.features.multi_attach && (
- setMultiAttachOpen(true)}
- >
-
-
- )}
-
-
-
-
- );
-}
-
-export default AttachProductDropdown;
-
-const DropdownProductItem = ({
- product,
- handleAddProduct,
-}: {
- product: ProductV2;
- handleAddProduct: any;
-}) => {
- const [isLoading, setIsLoading] = useState(false);
- return (
- {
- e.stopPropagation();
- e.preventDefault();
-
- await handleAddProduct(product.id, setIsLoading);
- }}
- >
- {isLoading && }
-
- {product.name}
- ({product.id})
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/customer-product-list/CusProductStatus.tsx b/vite/src/views/customers/customer/customer-product-list/CusProductStatus.tsx
deleted file mode 100644
index a2d84e866..000000000
--- a/vite/src/views/customers/customer/customer-product-list/CusProductStatus.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { CusProductStatus, type FullCusProduct } from "@autumn/shared";
-import { differenceInDays } from "date-fns";
-import { Badge } from "@/components/ui/badge";
-import { cn } from "@/lib/utils";
-import { keyToTitle } from "@/utils/formatUtils/formatTextUtils";
-import { CusProductStripeLink } from "./CusProductStripeLink";
-
-export const CusProductStatusItem = ({
- cusProduct,
-}: {
- cusProduct: FullCusProduct;
-}) => {
- const getStatus = () => {
- if (cusProduct.status == CusProductStatus.Expired) {
- return CusProductStatus.Expired;
- }
-
- const trialing =
- cusProduct.trial_ends_at && cusProduct.trial_ends_at > Date.now();
-
- const canceled = cusProduct.canceled;
-
- if (canceled) return "canceled";
-
- if (trialing) {
- return CusProductStatus.Trialing;
- }
-
- return cusProduct.status;
- };
-
- const getTitle = () => {
- const status = getStatus();
- if (status === CusProductStatus.Trialing) {
- const daysTillEnd = differenceInDays(
- new Date(cusProduct.trial_ends_at!),
- new Date(),
- );
- return `trial (${daysTillEnd}d)`;
- }
- return keyToTitle(getStatus()).toLowerCase();
- };
-
- const statusToColor: Record = {
- [CusProductStatus.Active]: "bg-lime-500",
- [CusProductStatus.Expired]: "bg-stone-800",
- [CusProductStatus.PastDue]: "bg-red-500",
- [CusProductStatus.Scheduled]: "bg-blue-500",
- [CusProductStatus.Trialing]: "bg-blue-400",
- canceled: "bg-gray-500",
- [CusProductStatus.Unknown]: "bg-gray-500",
- };
-
- return (
-
-
- {getTitle()}
-
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/customer-product-list/CusProductStripeLink.tsx b/vite/src/views/customers/customer/customer-product-list/CusProductStripeLink.tsx
deleted file mode 100644
index 8e1259578..000000000
--- a/vite/src/views/customers/customer/customer-product-list/CusProductStripeLink.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { CusProductStatus, type FullCusProduct } from "@autumn/shared";
-import { ArrowUpRightFromSquare } from "lucide-react";
-import React from "react";
-import { Link } from "react-router";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useOrgStripeQuery } from "@/hooks/queries/useOrgStripeQuery";
-import { useEnv } from "@/utils/envUtils";
-import { getStripeSubLink, getStripeSubScheduleLink } from "@/utils/linkUtils";
-
-export const CusProductStripeLink = ({
- cusProduct,
-}: {
- cusProduct: FullCusProduct;
-}) => {
- const env = useEnv();
- const { org } = useOrg();
- const { stripeAccount } = useOrgStripeQuery();
- return (
- <>
- {cusProduct.subscription_ids &&
- cusProduct.subscription_ids.length > 0 && (
-
- {cusProduct.subscription_ids.map((subId: string) => {
- return (
- {
- e.stopPropagation();
- if (stripeAccount) {
- window.open(
- getStripeSubLink({
- subscriptionId: subId,
- env,
- accountId: stripeAccount.id,
- }),
- "_blank",
- );
- } else {
- window.open(
- getStripeSubLink({ subscriptionId: subId, env }),
- );
- }
- }}
- >
-
-
- );
- })}
-
- )}
- {cusProduct.status == CusProductStatus.Scheduled &&
- cusProduct.scheduled_ids &&
- cusProduct.scheduled_ids.length > 0 && (
-
- {cusProduct.scheduled_ids.map((subId: string) => {
- return (
- {
- e.stopPropagation();
- if (stripeAccount) {
- window.open(
- getStripeSubScheduleLink({
- scheduledId: subId,
- env,
- accountId: stripeAccount.id,
- }),
- "_blank",
- );
- } else {
- window.open(
- getStripeSubScheduleLink({ scheduledId: subId, env }),
- );
- }
- }}
- >
-
-
- );
- })}
-
- )}
- >
- );
-};
diff --git a/vite/src/views/customers/customer/customer-product-list/CusProductToolbar.tsx b/vite/src/views/customers/customer/customer-product-list/CusProductToolbar.tsx
deleted file mode 100644
index eaf867d42..000000000
--- a/vite/src/views/customers/customer/customer-product-list/CusProductToolbar.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import type { FullCusProduct } from "@autumn/shared";
-import { ArrowLeftRight, Delete } from "lucide-react";
-import { useState } from "react";
-import { ToolbarButton } from "@/components/general/table-components/ToolbarButton";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { useSheetStore } from "@/hooks/stores/useSheetStore";
-import { useCustomerContext } from "../CustomerContext";
-import { useCusQuery } from "../hooks/useCusQuery";
-import { TransferProductDialog } from "./TransferProductDialog";
-
-export const CusProductToolbar = ({
- cusProduct,
-}: {
- cusProduct: FullCusProduct;
-}) => {
- const { customer } = useCusQuery();
- const { showEntityView } = useCustomerContext();
- const [dialogOpen, setDialogOpen] = useState(false);
- const [transferOpen, setTransferOpen] = useState(false);
- const setSheet = useSheetStore((s) => s.setSheet);
- return (
- <>
-
-
-
-
-
-
- {(showEntityView || customer.entities.length > 0) && (
- {
- e.preventDefault();
- e.stopPropagation();
- setTransferOpen(true);
- setDialogOpen(false);
- }}
- >
- Transfer
-
-
- )}
-
- {
- e.preventDefault();
- e.stopPropagation();
- setSheet({ type: "subscription-cancel", itemId: cusProduct.id });
- setDialogOpen(false);
- }}
- >
- Cancel
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/customers/customer/customer-product-list/CustomerProductList.tsx b/vite/src/views/customers/customer/customer-product-list/CustomerProductList.tsx
deleted file mode 100644
index 998bc024b..000000000
--- a/vite/src/views/customers/customer/customer-product-list/CustomerProductList.tsx
+++ /dev/null
@@ -1,227 +0,0 @@
-import { CusProductStatus, type FullCusProduct } from "@autumn/shared";
-import { useState } from "react";
-import { useNavigate } from "react-router";
-import { AdminHover } from "@/components/general/AdminHover";
-import { Item, Row } from "@/components/general/TableGrid";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { cn } from "@/lib/utils";
-import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
-import { compareStatus, notNullish, pushPage } from "@/utils/genUtils";
-import { getVersionCounts } from "@/utils/productUtils";
-import { useCustomerContext } from "../CustomerContext";
-import { CusProductEntityItem } from "../components/CusProductEntityItem";
-import { CusProductStatusItem } from "../customer-product-list/CusProductStatus";
-import { useCusQuery } from "../hooks/useCusQuery";
-import { MultiAttachDialog } from "../product/multi-attach/MultiAttachDialog";
-import AttachProductDropdown from "./AttachProductDropdown";
-import { CusProductToolbar } from "./CusProductToolbar";
-
-export const CustomerProductList = () => {
- const navigate = useNavigate();
- const { customer } = useCusQuery();
- const { entityId, showEntityView } = useCustomerContext();
-
- const { products } = useProductsQuery();
- const versionCounts = getVersionCounts(products);
-
- // const { env, versionCounts, entities, entityId, showEntityView } =
- // useCustomerContext();
-
- const [showExpired, setShowExpired] = useState(false);
- const [multiAttachOpen, setMultiAttachOpen] = useState(false);
-
- const entities = customer.entities;
-
- const sortedProducts = customer.customer_products
- .filter((cp: FullCusProduct) => {
- if (showExpired) {
- return true;
- }
-
- const entity = entities.find((e: any) => e.id === entityId);
-
- const entityMatches =
- entity && notNullish(cp.internal_entity_id)
- ? cp.internal_entity_id === entity.internal_id ||
- cp.customer_entitlements.some(
- (ce: any) =>
- ce.entities &&
- Object.keys(ce.entities).includes(entity.internal_id),
- )
- : true;
-
- return (
- cp.status !== CusProductStatus.Expired &&
- (entityId ? entityMatches : true)
- );
- })
- .sort((a: any, b: any) => {
- if (a.status !== b.status) {
- return compareStatus(a.status, b.status);
- }
-
- return b.created_at - a.created_at;
- });
-
- const getCusProductHoverTexts = (cusProduct: FullCusProduct) => {
- return [
- {
- key: "Cus Product ID",
- value: cusProduct.id,
- },
- ...(cusProduct.subscription_ids
- ? cusProduct.subscription_ids.map((id: string) => ({
- key: "Stripe Subscription ID",
- value: id,
- }))
- : []),
- ...(cusProduct.scheduled_ids
- ? [
- {
- key: "Stripe Scheduled IDs",
- value: cusProduct.scheduled_ids.join(", "),
- },
- ]
- : []),
- {
- key: "Entity ID",
- value: cusProduct.entity_id || "N/A",
- },
- ];
- };
-
- return (
-
-
-
- Products
-
-
-
-
setShowExpired(!showExpired)}
- >
- Show Expired
-
-
-
-
-
-
- {sortedProducts.length === 0 ? (
-
-
Attach a product to this customer
-
- ) : (
-
- - Name
- {showEntityView && - Entity
}
- - Product ID
- - Status
- - Created At
-
-
- )}
- {sortedProducts.map((cusProduct: FullCusProduct) => {
- return (
-
{
- const entity = entities.find(
- (e: any) => e.internal_id === cusProduct.internal_entity_id,
- );
-
- pushPage({
- path: `/customers/${customer.id || customer.internal_id}/${cusProduct.product_id}`,
- queryParams: {
- id: cusProduct.id,
- entity_id: entity
- ? entity.id || entity.internal_id
- : undefined,
- },
- navigate,
- });
-
- // navigateTo(
- // `/customers/${customer.id || customer.internal_id}/${
- // cusProduct.product_id
- // }?id=${cusProduct.id}${
- // entity ? `&entity_id=${entity.id || entity.internal_id}` : ""
- // }`,
- // navigate
- // );
- }}
- >
- -
-
-
-
{cusProduct.product.name}
- {versionCounts[cusProduct.product.id] > 1 && (
-
- v{cusProduct.product.version}
-
- )}
-
- {cusProduct.quantity > 1 && (
-
- x{cusProduct.quantity}
-
- )}
-
-
-
- {showEntityView && (
- -
-
-
- )}
- -
- {cusProduct.product_id}
-
- -
-
-
- -
- {formatUnixToDateTime(cusProduct.created_at).date}{" "}
- {formatUnixToDateTime(cusProduct.created_at).time}
-
- -
- {cusProduct.status !== CusProductStatus.Expired && (
-
- )}
-
-
- );
- })}
-
- );
-};
diff --git a/vite/src/views/customers/customer/customer-product-list/TransferProductDialog.tsx b/vite/src/views/customers/customer/customer-product-list/TransferProductDialog.tsx
deleted file mode 100644
index 35e86bf0a..000000000
--- a/vite/src/views/customers/customer/customer-product-list/TransferProductDialog.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import type { FullCusProduct } from "@autumn/shared";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { useCustomerContext } from "../CustomerContext";
-import { useCusQuery } from "../hooks/useCusQuery";
-
-export const TransferProductDialog = ({
- cusProduct,
- open,
- setOpen,
-}: {
- cusProduct: FullCusProduct;
- open: boolean;
- setOpen: (open: boolean) => void;
-}) => {
- const { customer, refetch } = useCusQuery();
- const axiosInstance = useAxiosInstance();
- const [loading, setLoading] = useState(false);
- const [selectedEntity, setSelectedEntity] = useState(null);
- const filteredEntities = customer.entities.filter(
- (entity: any) => entity.internal_id !== cusProduct.internal_entity_id,
- );
-
- useEffect(() => {
- if (open) {
- setSelectedEntity(null);
- }
- }, [open]);
-
- const handleClicked = async () => {
- if (!selectedEntity) {
- toast.error("Please select an entity to transfer the plan to");
- return;
- }
-
- setLoading(true);
-
- try {
- const fromEntity = customer.entities.find(
- (e: any) => e.internal_id === cusProduct.internal_entity_id,
- );
- await axiosInstance.post(
- `/v1/customers/${cusProduct.customer_id}/transfer`,
- {
- // internal_entity_id: selectedEntity.internal_id,
- from_entity_id: fromEntity?.id,
- to_entity_id: selectedEntity.id,
- product_id: cusProduct.product_id,
- // customer_product_id: cusProduct.id,
- },
- );
- await refetch();
- toast.success("Plan transferred successfully");
- setOpen(false);
- } catch (error) {
- console.log(error);
- toast.error(getBackendErr(error, "Failed to transfer plan"));
- }
- setLoading(false);
- };
-
- return (
-
- e.stopPropagation()}
- >
-
- Transfer Product
-
-
-
- Select another entity to transfer this product to.
-
-
- {
- setSelectedEntity(
- customer.entities.find((e: any) => e.id === value),
- );
- }}
- disabled={filteredEntities.length == 0}
- >
-
-
-
-
- {filteredEntities.length > 0 ? (
- filteredEntities.map((entity: any) => (
-
-
- {entity.name || entity.internal_id}{" "}
- {entity.id && (
- ({entity.id})
- )}
-
-
- ))
- ) : (
-
- No other entities. Please create an entity first.
-
- )}
-
-
-
-
-
- Transfer
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/customer-product-list/UpdateStatusDropdownBtn.tsx b/vite/src/views/customers/customer/customer-product-list/UpdateStatusDropdownBtn.tsx
deleted file mode 100644
index a1568a8b0..000000000
--- a/vite/src/views/customers/customer/customer-product-list/UpdateStatusDropdownBtn.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { CusProductStatus, type FullCusProduct } from "@autumn/shared";
-import { useState } from "react";
-import { toast } from "sonner";
-import SmallSpinner from "@/components/general/SmallSpinner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { CusService } from "@/services/customers/CusService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { keyToTitle } from "@/utils/formatUtils/formatTextUtils";
-import { getBackendErr } from "@/utils/genUtils";
-import { useCustomerContext } from "../CustomerContext";
-
-export const UpdateStatusDropdownBtn = ({
- cusProduct,
-}: {
- cusProduct: FullCusProduct;
-}) => {
- const [loading, setLoading] = useState(false);
- const [showDefaultWarning, setShowDefaultWarning] = useState(false);
- const { env, cusMutate } = useCustomerContext();
- const axiosInstance = useAxiosInstance({ env });
-
- const handleStatusUpdate = async () => {
- setLoading(true);
- try {
- await CusService.updateCusProductStatus(axiosInstance, cusProduct.id, {
- status: CusProductStatus.Expired,
- });
- await cusMutate();
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to update status"));
- }
- setLoading(false);
- };
-
- const handleExpireClick = () => {
- // Check if this is the expired status and if the product is default
- if (cusProduct.product?.is_default) {
- setShowDefaultWarning(true);
- } else {
- handleStatusUpdate();
- }
- };
-
- return (
- <>
-
-
-
- Expire Default Product
-
-
-
- This is the default product. Expiring it will reattach it to the
- customer and reset their features.
-
-
-
-
- setShowDefaultWarning(false)}
- >
- Cancel
-
- {
- setShowDefaultWarning(false);
- handleStatusUpdate();
- }}
- >
- Expire Default
-
-
-
-
-
-
-
- {loading ? (
-
- ) : (
- <>
- Expire
- >
- )}
-
- >
- );
-};
diff --git a/vite/src/views/customers/customer/entitlements/CusEntBalance.tsx b/vite/src/views/customers/customer/entitlements/CusEntBalance.tsx
deleted file mode 100644
index f803a1b8a..000000000
--- a/vite/src/views/customers/customer/entitlements/CusEntBalance.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import {
- AllowanceType,
- FeatureType,
- type FullCusEntWithFullCusProduct,
-} from "@autumn/shared";
-import { Decimal } from "decimal.js";
-import { useCustomerContext } from "../CustomerContext";
-
-const BalanceWrapper = ({ children }: { children: React.ReactNode }) => {
- return (
-
- {children}
-
- );
-};
-
-export const CusEntBalance = ({
- cusEnt,
-}: {
- cusEnt: FullCusEntWithFullCusProduct;
-}) => {
- const { entityId } = useCustomerContext();
- const ent = cusEnt.entitlement;
- const feature = ent.feature;
- const rollovers = cusEnt.rollovers;
-
- if (feature.type === FeatureType.Boolean) {
- return <>>;
- }
-
- if (ent.allowance_type === AllowanceType.Unlimited) {
- return Unlimited ;
- }
-
- if (entityId && cusEnt.entities?.[entityId]) {
- const entityBalance = cusEnt.entities?.[entityId]?.balance;
- const rolloverAmount = rollovers
- .filter((x) => x.entities && x.entities[entityId])
- .reduce(
- (sum, rollover) => sum + (rollover.entities?.[entityId]?.balance || 0),
- 0,
- );
-
- return (
-
-
- {entityBalance}
- {rolloverAmount > 0 && (
-
- {" + "}
- {rolloverAmount} (rolled over)
-
- )}
-
-
- );
- }
-
- if (cusEnt.entities) {
- const totalBalance = Object.values(cusEnt.entities).reduce(
- (sum, entity) =>
- sum + (entity.balance || 0) + (entity.additional_balance ?? 0),
- 0,
- );
-
- const rolloverAmount = cusEnt.rollovers.reduce((sum, rollover) => {
- // Add global rollover balance
- return (
- sum +
- Object.values(rollover.entities).reduce(
- (entitySum: number, entity: any) =>
- entitySum +
- (entity.balance || 0) +
- (entity.additional_balance ?? 0),
- 0,
- )
- );
- }, 0);
-
- return (
-
-
- {totalBalance}
- {rolloverAmount > 0 && (
-
- {" + "}
- {rolloverAmount} (rolled over)
-
- )}
-
-
- );
- }
-
- const rolloverAmount = cusEnt.rollovers.reduce((sum, rollover) => {
- return sum + (rollover.balance || 0);
- }, 0);
-
- return (
-
-
- {new Decimal(cusEnt.balance ?? 0)
- .add(cusEnt.additional_balance)
- .toNumber()}
- {rolloverAmount > 0 && (
- + {rolloverAmount} (rolled over)
- )}
- {cusEnt.replaceables.length > 0 && (
-
- {` (${cusEnt.replaceables.length} free)`}
-
- )}
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/entitlements/CustomerEntitlementsList.tsx b/vite/src/views/customers/customer/entitlements/CustomerEntitlementsList.tsx
deleted file mode 100644
index 3fa168288..000000000
--- a/vite/src/views/customers/customer/entitlements/CustomerEntitlementsList.tsx
+++ /dev/null
@@ -1,284 +0,0 @@
-import {
- FeatureType,
- type FullCusEntWithFullCusProduct,
- type FullCusProduct,
- type FullCustomerEntitlement,
-} from "@autumn/shared";
-import { useState } from "react";
-import { AdminHover } from "@/components/general/AdminHover";
-import { Item, Row } from "@/components/general/TableGrid";
-
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { cn } from "@/lib/utils";
-import {
- formatUnixToDate,
- formatUnixToDateTime,
-} from "@/utils/formatUtils/formatDateUtils";
-import { useCustomerContext } from "../CustomerContext";
-import { CusProductEntityItem } from "../components/CusProductEntityItem";
-import { useCusQuery } from "../hooks/useCusQuery";
-import { CusEntBalance } from "./CusEntBalance";
-import UpdateCusEntitlement from "./UpdateCusEntitlement";
-
-export const CustomerEntitlementsList = () => {
- const [featureType, setFeatureType] = useState(
- FeatureType.Metered,
- );
- const [showExpired, setShowExpired] = useState(false);
-
- const { entityId, showEntityView } = useCustomerContext();
- const { customer, products, features, entities } = useCusQuery();
-
- const [selectedCusEntitlement, setSelectedCusEntitlement] =
- useState(null);
-
- const cusEnts: FullCusEntWithFullCusProduct[] =
- customer.customer_products.flatMap((cp: any) => {
- return cp.customer_entitlements.map((e: any) => ({
- ...e,
- customer_product: cp,
- }));
- });
-
- const filteredEntitlements = cusEnts.filter(
- (cusEnt: FullCusEntWithFullCusProduct) => {
- const entFeatureType = cusEnt.entitlement.feature.type;
- const cusProduct = cusEnt.customer_product;
-
- const isExpired = cusProduct?.status === "expired";
- const isScheduled = cusProduct?.status === "scheduled";
-
- // Filter by feature type
- const featureTypeMatches =
- featureType === FeatureType.Boolean
- ? entFeatureType === FeatureType.Boolean
- : entFeatureType === FeatureType.Metered ||
- entFeatureType === FeatureType.CreditSystem;
-
- // Filter by expired status
- const expiredStatusMatches = showExpired ? true : !isExpired;
-
- // Filter by entity
- const entity = entities.find((e: any) => e.id === entityId);
- let entityMatch = true;
- if (entityId) {
- entityMatch = false;
-
- const cusProduct = customer.customer_products.find(
- (p: any) => p.id === cusEnt.customer_product_id,
- );
-
- const productAttachedToEntity =
- cusProduct?.internal_entity_id === entity?.internal_id;
-
- const cusEntContainsEntity =
- Object.keys(cusEnt.entities || {}).includes(entity?.id) ||
- cusEnt.entitlement.entity_feature_id === entity?.feature_id;
-
- entityMatch = productAttachedToEntity || cusEntContainsEntity;
- }
-
- return (
- featureTypeMatches &&
- expiredStatusMatches &&
- !isScheduled &&
- entityMatch
- );
- },
- );
-
- const handleSelectCusEntitlement = (cusEnt: FullCustomerEntitlement) => {
- setSelectedCusEntitlement(cusEnt);
- };
-
- const getAdminHoverTexts = (cusEnt: FullCustomerEntitlement) => {
- const entitlement = cusEnt.entitlement;
- const featureEntities = entities.filter(
- (e: any) => e.feature_id === entitlement.feature.id,
- );
-
- const hoverTexts = [
- {
- key: "Cus Ent ID",
- value: cusEnt.id,
- },
- ];
-
- if (featureEntities.length > 0) {
- hoverTexts.push({
- key: "Entities",
- value: featureEntities
- .map((e: any) => `${e.id} (${e.name})${e.deleted ? " Deleted" : ""}`)
- .join("\n"),
- });
- } else if (cusEnt.entities && Object.keys(cusEnt.entities).length > 0) {
- const mappedEntities = Object.keys(cusEnt.entities)
- .map((e: any) => {
- const entity = entities.find((ee: any) => ee.id === e);
- const balance = cusEnt.entities![e].balance;
- return `${entity?.id} (${entity?.name}): ${balance}`;
- })
- .join("\n");
- hoverTexts.push({
- key: "Entities",
- value: mappedEntities,
- });
- }
-
- if (cusEnt.rollovers.length > 0) {
- hoverTexts.push({
- key: "Rollovers",
- value: cusEnt.rollovers
- .map((r: any) => {
- if (Object.values(r.entities).length > 0) {
- return (
- Object.values(r.entities)
- .map((e: any) => `${e.balance} (${e.id})`)
- .join(", ") +
- ` (expires: ${r.expires_at ? formatUnixToDate(r.expires_at) : "N/A"})`
- );
- } else {
- return `${r.balance} (ex: ${r.expires_at ? formatUnixToDate(r.expires_at) : "N/A"})`;
- }
- })
- .join("\n"),
- });
- }
-
- return hoverTexts;
- };
-
- return (
-
-
-
- Available Features
-
-
-
-
-
-
- setShowExpired(!showExpired)}
- >
- Show Expired
-
-
- setFeatureType(FeatureType.Metered)}
- >
- Metered
-
- setFeatureType(FeatureType.Boolean)}
- >
- Boolean
-
-
-
-
-
-
-
-
-
- {filteredEntitlements.length === 0 ? (
-
-
- Attach a product to grant access to features
-
-
- ) : (
- <>
-
- - Feature
- {showEntityView && - Entity
}
- -
- {featureType === FeatureType.Metered && "Balance"}
-
- - Product
- -
- {featureType === FeatureType.Metered && "Next Reset"}
-
-
-
- >
- )}
-
- {filteredEntitlements.map((cusEnt: FullCusEntWithFullCusProduct) => {
- const entitlement = cusEnt.entitlement;
-
- return (
-
- featureType === FeatureType.Metered &&
- handleSelectCusEntitlement(cusEnt)
- }
- >
- -
-
- {entitlement.feature.name}
-
-
- {showEntityView && (
- -
-
-
- )}
- -
-
-
-
- -
-
- {/* {getProductName(cusEnt)} */}
- {cusEnt.customer_product.product.name}
- {customer.customer_products.find(
- (cp: FullCusProduct) => cp.id === cusEnt.customer_product_id,
- )?.status === "expired" && (
-
- expired
-
- )}
-
-
- -
- {formatUnixToDateTime(cusEnt.next_reset_at).date}{" "}
- {formatUnixToDateTime(cusEnt.next_reset_at).time}
-
-
-
- );
- })}
-
- );
-};
diff --git a/vite/src/views/customers/customer/entitlements/UpdateCusEntitlement.tsx b/vite/src/views/customers/customer/entitlements/UpdateCusEntitlement.tsx
deleted file mode 100644
index e234f15bd..000000000
--- a/vite/src/views/customers/customer/entitlements/UpdateCusEntitlement.tsx
+++ /dev/null
@@ -1,194 +0,0 @@
-import {
- type FullCusProduct,
- type FullCustomerEntitlement,
- getCusEntBalance,
-} from "@autumn/shared";
-import { Decimal } from "decimal.js";
-import { AlertCircle } from "lucide-react";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import CopyButton from "@/components/general/CopyButton";
-import { DateInputUnix } from "@/components/general/DateInputUnix";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr, notNullish } from "@/utils/genUtils";
-// import { useCustomerContext } from "../../customers2/customer/CustomerContext";
-import { useCustomerContext } from "../../../customers2/customer/CustomerContext";
-import { useCusQuery } from "../hooks/useCusQuery";
-
-function UpdateCusEntitlement({
- selectedCusEntitlement,
- setSelectedCusEntitlement,
-}: {
- selectedCusEntitlement: FullCustomerEntitlement | null;
- setSelectedCusEntitlement: (cusEnt: FullCustomerEntitlement | null) => void;
-}) {
- const { customer, refetch } = useCusQuery();
- const { entityId } = useCustomerContext();
-
- const cusEnt = selectedCusEntitlement;
-
- const [updateLoading, setUpdateLoading] = useState(false);
- const axiosInstance = useAxiosInstance();
-
- const [updateFields, setUpdateFields] = useState({
- balance: cusEnt
- ? getCusEntBalance({
- cusEnt,
- entityId,
- }).balance
- : null,
-
- next_reset_at: cusEnt?.next_reset_at,
- });
-
- const getCusProduct = (cusEnt: FullCustomerEntitlement) => {
- const cusProduct = customer.customer_products.find(
- (cp: FullCusProduct) => cp.id === cusEnt.customer_product_id,
- );
- return cusProduct;
- };
-
- useEffect(() => {
- if (!cusEnt) return;
- const { balance, additional_balance } = getCusEntBalance({
- cusEnt: cusEnt,
- entityId,
- });
- setUpdateFields({
- balance: new Decimal(balance).add(additional_balance).toNumber(),
- next_reset_at: cusEnt?.next_reset_at,
- });
- }, [selectedCusEntitlement]);
-
- if (!selectedCusEntitlement) return null;
-
- const entitlement = selectedCusEntitlement.entitlement;
- const feature = entitlement.feature;
- const cusProduct = getCusProduct(selectedCusEntitlement);
-
- const handleUpdateCusEntitlement = async (
- cusEnt: FullCustomerEntitlement,
- ) => {
- const balanceInt = parseFloat(updateFields.balance);
- if (isNaN(balanceInt)) {
- toast.error("Balance not valid");
- return;
- }
-
- if (cusPrice && updateFields.next_reset_at !== cusEnt.next_reset_at) {
- toast.error(`Not allowed to change reset at for paid features`);
- return;
- }
-
- setUpdateLoading(true);
- try {
- // await axiosInstance.post("/v1/balances/update", {
- // customer_id: customer.id || customer.internal_id,
- // feature_id: feature.id,
- // current_balance: balanceInt,
- // customer_entitlement_id: cusEnt.id,
- // entity_id: entityId ?? undefined,
- // // usage: 0,
- // });
- const customerId = customer.id || customer.internal_id;
- await axiosInstance.post(
- `/v1/customers/${customerId}/entitlements/${cusEnt.id}`,
- {
- balance: balanceInt,
- next_reset_at: updateFields.next_reset_at,
- entity_id: entityId ?? undefined,
- },
- );
-
- toast.success("Entitlement updated successfully");
- await refetch();
- setSelectedCusEntitlement(null);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to update entitlement"));
- }
- setUpdateLoading(false);
- };
-
- const cusPrice = cusProduct?.customer_prices.find(
- (cp: any) => cp.price.entitlement_id === cusEnt?.entitlement.id,
- );
-
- return (
- setSelectedCusEntitlement(null)}
- >
-
-
-
-
- {feature.name}
-
- {feature.id}
-
-
-
-
-
- Balance
- {
- setUpdateFields({
- ...updateFields,
- balance: e.target.value,
- });
- }}
- />
-
-
-
- Can't update reset at for paid
- features
-
- )
- }
- >
- Next Reset
-
-
{
- setUpdateFields({ ...updateFields, next_reset_at: unixDate });
- }}
- />
-
-
-
- handleUpdateCusEntitlement(selectedCusEntitlement)}
- >
- Update
-
-
-
-
- );
-}
-
-export default UpdateCusEntitlement;
diff --git a/vite/src/views/customers/customer/product/CustomerProductView.tsx b/vite/src/views/customers/customer/product/CustomerProductView.tsx
deleted file mode 100644
index b12bff033..000000000
--- a/vite/src/views/customers/customer/product/CustomerProductView.tsx
+++ /dev/null
@@ -1,211 +0,0 @@
-"use client";
-
-import type { ProductItem, ProductV2 } from "@autumn/shared";
-import { useEffect, useRef, useState } from "react";
-import { Link, useParams, useSearchParams } from "react-router";
-import { CustomToaster } from "@/components/general/CustomToaster";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { notNullish } from "@/utils/genUtils";
-import { sortProductItems } from "@/utils/productUtils";
-import ErrorScreen from "@/views/general/ErrorScreen";
-import LoadingScreen from "@/views/general/LoadingScreen";
-import { ManageProduct } from "@/views/products/product/ManageProduct";
-import { ProductContext } from "@/views/products/product/ProductContext";
-import ProductSidebar from "@/views/products/product/ProductSidebar";
-import { useCusQuery } from "../hooks/useCusQuery";
-import { CustomerProductBreadcrumbs } from "./components/CustomerProductBreadcrumbs";
-import { useAttachState } from "./hooks/useAttachState";
-import { useCusProductQuery } from "./hooks/useCusProductQuery";
-import { ProductOptions } from "./ProductOptions";
-
-interface OptionValue {
- feature_id: string;
- threshold?: number;
- quantity?: number;
-}
-
-function getProductUrlParams({
- version,
- customer_product_id,
- entity_id,
-}: {
- version?: string | null;
- customer_product_id?: string | null;
- entity_id?: string | null;
-}) {
- const params = new URLSearchParams();
- if (version) params.append("version", version);
- if (customer_product_id)
- params.append("customer_product_id", customer_product_id);
- if (entity_id) params.append("entity_id", entity_id);
- const str = params.toString();
- return str ? `?${str}` : "";
-}
-
-export default function CustomerProductView() {
- const { customer_id, product_id } = useParams();
- const [searchParams] = useSearchParams();
- const entityIdParam = searchParams.get("entity_id");
-
- const { isLoading: orgLoading } = useOrg();
- const { isLoading: featuresLoading } = useFeaturesQuery();
-
- const initialProductRef = useRef(null);
-
- const [options, setOptions] = useState([]);
- const [entityId, setEntityId] = useState(entityIdParam);
- const [entityFeatureIds, setEntityFeatureIds] = useState([]);
-
- const version = searchParams.get("version");
-
- const {
- product: originalProduct,
- cusProduct,
- isLoading,
- error,
- } = useCusProductQuery();
- const [product, setProduct] = useState(
- originalProduct ?? null,
- );
-
- const { isLoading: cusLoading } = useCusQuery();
-
- const attachState = useAttachState({
- product,
- setProduct,
- initialProductRef,
- cusProduct,
- });
-
- useEffect(() => {
- if (entityIdParam) {
- setEntityId(entityIdParam);
- } else {
- setEntityId(null);
- }
- }, [entityIdParam]);
-
- useEffect(() => {
- if (!originalProduct) return;
-
- const product = originalProduct;
-
- console.log("[CPV] effect", {
- prodId: originalProduct.id,
- v: originalProduct.version,
- cusId: cusProduct?.id,
- });
-
- // Update initialProductRef BEFORE setProduct to ensure useAttachState
- // effect has the correct baseline when it runs
- initialProductRef.current = structuredClone({
- ...product,
- items: sortProductItems(product.items),
- });
-
- setProduct(product);
-
- setEntityFeatureIds(
- Array.from(
- new Set(
- product.items
- .filter((item: ProductItem) => notNullish(item.entity_feature_id))
- .map((item: ProductItem) => item.entity_feature_id!),
- ),
- ),
- );
-
- if (cusProduct?.options) {
- setOptions(cusProduct.options);
- } else {
- setOptions([]);
- }
- }, [originalProduct, cusProduct]);
-
- if (error) {
- return (
-
-
- Customer {customer_id} or product {product_id} not found
-
-
- );
- }
-
- if (isLoading || cusLoading || orgLoading || featuresLoading || !product)
- return ;
-
- if (!customer_id || !product_id) {
- return Customer or product not found
;
- }
-
- return (
-
-
-
-
-
-
-
- {product &&
}
- {options.length > 0 && (
-
- )}
-
-
-
-
-
-
- );
-}
-
-export const CopyUrl = ({
- url,
- isInvoice = false,
-}: {
- url: string;
- isInvoice: boolean;
-}) => {
- return (
-
- {!isInvoice && (
-
- This link will expire in 24 hours
-
- )}
-
-
- {url}
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/ProductOptions.tsx b/vite/src/views/customers/customer/product/ProductOptions.tsx
deleted file mode 100644
index 178f2ac22..000000000
--- a/vite/src/views/customers/customer/product/ProductOptions.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { notNullish } from "@/utils/genUtils";
-
-export const ProductOptions = ({
- options,
- setOptions,
-}: {
- options: any[];
- setOptions: (options: any[]) => void;
-}) => {
- return (
-
-
-
- Billing Quantity
-
-
-
- {options.map((option) => (
-
-
{option.feature_id}
-
- {option.quantity !== undefined && option.quantity !== null && (
-
-
{option.quantity}
- {notNullish(option.upcoming_quantity) && (
-
- (Upcoming: {option.upcoming_quantity})
-
- )}
-
- )}
-
-
- ))}
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/AttachButton.tsx b/vite/src/views/customers/customer/product/components/AttachButton.tsx
deleted file mode 100644
index 6f385b631..000000000
--- a/vite/src/views/customers/customer/product/components/AttachButton.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { Upload } from "lucide-react";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { useProductQueryState } from "@/views/products/product/hooks/useProductQuery";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import { useCusQuery } from "../../hooks/useCusQuery";
-import { AttachModal } from "./AttachModal";
-import { getAttachBody } from "./attachProductUtils";
-
-export const AttachButton = () => {
- const axios = useAxiosInstance();
- const [open, setOpen] = useState(false);
- const [buttonLoading, setButtonLoading] = useState(false);
-
- const { customer } = useCusQuery();
-
- const { queryStates } = useProductQueryState();
- const { attachState, product, entityId } = useProductContext();
- const { buttonText, setPreview } = attachState;
-
- const handleAttachClicked = async () => {
- setButtonLoading(true);
-
- try {
- const res = await axios.post(
- "/v1/attach/preview",
- getAttachBody({
- customerId: customer.id || customer.internal_id,
- attachState,
- product,
- entityId,
- version: queryStates.version || product.version,
- }),
- );
-
- setPreview(res.data);
- setOpen(true);
- } catch (error) {
- console.log("error", error);
- toast.error(getBackendErr(error, "Failed to attach plan"));
- }
-
- setButtonLoading(false);
- };
-
- return (
- <>
-
- }
- disabled={attachState.buttonDisabled}
- isLoading={buttonLoading}
- >
- {buttonText}
-
- >
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/AttachModal.tsx b/vite/src/views/customers/customer/product/components/AttachModal.tsx
deleted file mode 100644
index 8f42be3d0..000000000
--- a/vite/src/views/customers/customer/product/components/AttachModal.tsx
+++ /dev/null
@@ -1,290 +0,0 @@
-import {
- AttachBranch,
- AttachFunction,
- type Entity,
- ErrCode,
-} from "@autumn/shared";
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { useState } from "react";
-import { useNavigate } from "react-router";
-import { toast } from "sonner";
-import { PriceItem } from "@/components/pricing/attach-pricing-dialog";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { useOrgStripeQuery } from "@/hooks/queries/useOrgStripeQuery";
-import { cn } from "@/lib/utils";
-import { CusService } from "@/services/customers/CusService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import {
- getBackendErr,
- getBackendErrObj,
- getRedirectUrl,
- navigateTo,
- nullish,
-} from "@/utils/genUtils";
-import { getStripeInvoiceLink } from "@/utils/linkUtils";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import { useCusQuery } from "../../hooks/useCusQuery";
-import { AttachPreviewDetails } from "./AttachPreviewDetails";
-import { AttachInfo } from "./attach-preview/AttachInfo";
-import { getAttachBody } from "./attachProductUtils";
-import { InvoiceCustomerButton } from "./InvoiceCustomerButton";
-
-export const AttachModal = ({
- open,
- setOpen,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
-}) => {
- const { customer, entities } = useCusQuery();
- const { product, entityId, attachState, version } = useProductContext();
- const { stripeAccount } = useOrgStripeQuery();
-
- const navigation = useNavigate();
- const env = useEnv();
- const axiosInstance = useAxiosInstance();
-
- const { preview, options, flags } = attachState;
- const [checkoutLoading, setCheckoutLoading] = useState(false);
- const [invoiceLoading] = useState(false);
-
- const getName = () => {
- const cusName =
- customer?.name || customer?.id || customer.email || customer.internal_id;
-
- if (entityId) {
- const entity = entities.find(
- (e: Entity) => e.id === entityId || e.internal_id === entityId,
- );
- const entityName = entity?.name || entity?.id || entity?.internal_id;
- return `${cusName} (${entityName})`;
- }
-
- return cusName;
- };
-
- const getCusId = () => {
- let cusId = customer.id || customer.internal_id;
- if (entityId) {
- cusId = `${cusId}?entity_id=${entityId}`;
- }
- return cusId;
- };
-
- const invoiceAllowed = () => {
- if (preview?.branch === AttachBranch.SameCustomEnts || flags.isFree) {
- return false;
- }
-
- if (preview?.branch === AttachBranch.Downgrade) {
- return false;
- }
-
- if (preview?.branch === AttachBranch.Renew) {
- return false;
- }
-
- // const dueToday = preview?.due_today;
- // if (dueToday && dueToday.total == 0) {
- // return false;
- // }
-
- return true;
- };
-
- const getButtonText = () => {
- if (preview?.branch === AttachBranch.Downgrade) {
- return "Confirm Downgrade";
- }
-
- if (preview?.branch === AttachBranch.SameCustomEnts || flags.isFree) {
- return "Confirm";
- }
-
- if (flags.isCanceled) {
- return "Renew Plan";
- }
-
- if (preview?.func === AttachFunction.CreateCheckout) {
- return "Checkout Page";
- }
-
- const dueToday = preview?.due_today;
-
- if (dueToday && dueToday.total === 0) {
- return "Confirm";
- }
-
- return "Charge Customer";
- };
-
- const handleAttachClicked = async ({
- useInvoice,
- enableProductImmediately,
- setLoading,
- }: {
- useInvoice: boolean;
- enableProductImmediately?: boolean;
- setLoading: (loading: boolean) => void;
- }) => {
- const cusId = getCusId();
-
- for (const option of options) {
- if (
- nullish(option.quantity) &&
- preview?.branch !== AttachBranch.SameCustomEnts &&
- preview?.branch !== AttachBranch.NewVersion
- ) {
- toast.error(`Quantity for ${option.feature_name} is required`);
- return;
- }
- }
-
- try {
- setLoading(true);
-
- const redirectUrl = getRedirectUrl(`/customers/${cusId}`, env);
-
- const attachBody = getAttachBody({
- customerId: customer.id || customer.internal_id,
- entityId,
- product,
- optionsInput:
- preview?.branch !== AttachBranch.NewVersion &&
- preview?.branch !== AttachBranch.SameCustomEnts
- ? options
- : undefined,
- attachState,
- useInvoice,
- enableProductImmediately,
- successUrl: `${import.meta.env.VITE_FRONTEND_URL}${redirectUrl}`,
- version: product.version,
- });
-
- const { data } = await CusService.attach(axiosInstance, attachBody);
-
- if (data.checkout_url) {
- window.open(data.checkout_url, "_blank");
- } else if (data.invoice) {
- window.open(
- getStripeInvoiceLink({
- stripeInvoice: data.invoice,
- env,
- accountId: stripeAccount?.id,
- }),
- "_blank",
- );
- }
- navigateTo(`/customers/${cusId}`, navigation, env);
-
- toast.success(data.message || "Successfully attached plan");
- setOpen(false);
- } catch (error) {
- console.log("Error creating product: ", error);
- const errObj = getBackendErrObj(error);
-
- if (errObj?.code === ErrCode.StripeConfigNotFound) {
- toast.error(errObj?.message);
- const redirectUrl = getRedirectUrl(`/customers/${customer.id}`, env);
- navigateTo(
- `/integrations/stripe?redirect=${redirectUrl}`,
- navigation,
- env,
- );
- } else {
- toast.error(getBackendErr(error, "Error creating plan"));
- }
- } finally {
- setLoading(false);
- }
- };
-
- const [configOpen] = useState(false);
-
- const mainWidth = "w-lg";
-
- return (
-
-
-
-
-
- Attach plan
-
-
-
-
-
Details
-
-
- Plan
- {product?.name}
-
-
-
- Customer
- {getName()}
-
-
-
- {preview && !flags.isFree &&
}
-
-
-
-
-
-
-
-
-
-
- {invoiceAllowed() && (
-
- )}
- }
- isLoading={checkoutLoading}
- disabled={invoiceLoading || checkoutLoading}
- onClick={() =>
- handleAttachClicked({
- useInvoice: false,
- setLoading: setCheckoutLoading,
- })
- }
- >
- {getButtonText()}
-
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/AttachPreviewDetails.tsx b/vite/src/views/customers/customer/product/components/AttachPreviewDetails.tsx
deleted file mode 100644
index 0e3e9839a..000000000
--- a/vite/src/views/customers/customer/product/components/AttachPreviewDetails.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { AttachBranch } from "@autumn/shared";
-import React from "react";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import { AttachNewItems } from "./attach-preview/AttachNewItems";
-import { DueNextCycle } from "./attach-preview/DueNextCycle";
-import { DueToday } from "./attach-preview/DueToday";
-import { OptionsInput } from "./attach-preview/OptionsInput";
-import { UpdateQuantity } from "./attach-preview/UpdateQuantity";
-
-export const AttachPreviewDetails = () => {
- const { attachState } = useProductContext();
- const { preview } = attachState;
-
- if (!preview) {
- return null;
- }
-
- const branch = preview.branch;
- const isUpdatePrepaidQuantity = branch == AttachBranch.UpdatePrepaidQuantity;
-
- return (
-
-
- {!isUpdatePrepaidQuantity && (
- <>
-
-
- >
- )}
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/CustomerProductBadge.tsx b/vite/src/views/customers/customer/product/components/CustomerProductBadge.tsx
deleted file mode 100644
index 5400dc6b7..000000000
--- a/vite/src/views/customers/customer/product/components/CustomerProductBadge.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Badge } from "@/components/ui/badge";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import { useCusQuery } from "../../hooks/useCusQuery";
-
-export const CustomerProductBadge = () => {
- const { customer } = useCusQuery();
- const { product } = useProductContext();
-
- if (!customer) return null;
-
- return (
-
-
- {product.isCustom ? (
- <>
- Custom {product.name} version for
- >
- ) : (
- <>
- {product.isCustom ? "Custom" : "Product"}{" "}
- {product.name} for
- >
- )}
-
-
-
- {customer.name || customer.id || customer.email}
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/CustomerProductBreadcrumbs.tsx b/vite/src/views/customers/customer/product/components/CustomerProductBreadcrumbs.tsx
deleted file mode 100644
index 4862d8f4a..000000000
--- a/vite/src/views/customers/customer/product/components/CustomerProductBreadcrumbs.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { useNavigate } from "react-router";
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-import { navigateTo, pushPage } from "@/utils/genUtils";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import { useCusQuery } from "../../hooks/useCusQuery";
-
-export const CustomerProductBreadcrumbs = () => {
- const { customer } = useCusQuery();
- const { product, entityId } = useProductContext();
- const navigation = useNavigate();
-
- return (
-
-
-
- navigateTo("/customers", navigation)}
- >
- Customers
-
-
-
-
- pushPage({
- path: `/customers/${customer.id || customer.internal_id}`,
- navigate: navigation,
- queryParams: {
- entity_id: entityId,
- },
- })
- }
- >
- {customer.name
- ? customer.name
- : customer.id
- ? customer.id
- : customer.email}
-
-
- {product.name}
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/InvoiceCustomerButton.tsx b/vite/src/views/customers/customer/product/components/InvoiceCustomerButton.tsx
deleted file mode 100644
index 1d988725c..000000000
--- a/vite/src/views/customers/customer/product/components/InvoiceCustomerButton.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import { AttachBranch, AttachFunction } from "@autumn/shared";
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { useState } from "react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Button } from "@/components/ui/button";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-
-export const InvoiceCustomerButton = ({
- handleAttachClicked,
- preview,
- disabled,
- checkoutAllowed,
-}: {
- handleAttachClicked: any;
- preview?: any;
- disabled?: boolean;
- checkoutAllowed?: boolean;
-}) => {
- const [immediateLoading, setImmediateLoading] = useState(false);
- const [afterPaymentLoading, setAfterPaymentLoading] = useState(false);
- const buttonsDisabled = immediateLoading || afterPaymentLoading;
-
- const allowedBranches = [
- AttachBranch.New,
- AttachBranch.MainIsTrial,
- AttachBranch.MainIsFree,
- AttachBranch.OneOff,
- AttachBranch.AddOn,
- ];
-
- // const immediateDisabled = !allowedBranches.includes(preview?.branch);
-
- return (
-
-
- }
- disableStartIcon={true}
- tabIndex={-1}
- tooltipContent="This will enable the plan for the customer immediately, and redirect you to Stripe to finalize the invoice"
- disabled={disabled}
- >
- Invoice Customer
-
-
-
-
-
-
Enable Plan Immediately
-
- This will enable the plan for the customer immediately, and
- redirect you to Stripe to finalize the invoice
-
-
- handleAttachClicked({
- useInvoice: true,
- enableProductImmediately: true,
- setLoading: setImmediateLoading,
- })
- }
- >
- Invoice and enable immediately
-
-
-
- {(checkoutAllowed ||
- preview?.func == AttachFunction.CreateCheckout ||
- preview?.func == AttachFunction.AddProduct ||
- preview?.func == AttachFunction.OneOff) && (
-
-
-
Enable Plan After Payment
-
- This will generate an invoice link for the customer, and enable
- the plan after they pay the invoice
-
-
- handleAttachClicked({
- useInvoice: true,
- enableProductImmediately: false,
- setLoading: setAfterPaymentLoading,
- })
- }
- >
- Invoice and enable after payment
-
-
-
- )}
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/ToggleConfigButton.tsx b/vite/src/views/customers/customer/product/components/ToggleConfigButton.tsx
deleted file mode 100644
index b133927fc..000000000
--- a/vite/src/views/customers/customer/product/components/ToggleConfigButton.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { ArrowLeft, ArrowRight } from "lucide-react";
-import { Button } from "@/components/ui/button";
-
-export const ToggleConfigButton = ({
- configOpen,
- setConfigOpen,
-}: {
- configOpen: boolean;
- setConfigOpen: (open: boolean) => void;
-}) => {
- return (
- setConfigOpen(!configOpen)}
- >
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/attach-preview/AdjustQuantity.tsx b/vite/src/views/customers/customer/product/components/attach-preview/AdjustQuantity.tsx
deleted file mode 100644
index d6bb6609b..000000000
--- a/vite/src/views/customers/customer/product/components/attach-preview/AdjustQuantity.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { formatAmount } from "@autumn/shared";
-import React from "react";
-import {
- PriceItem,
- QuantityInput,
-} from "@/components/pricing/attach-pricing-dialog";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useProductContext } from "@/views/products/product/ProductContext";
-
-export const AdjustableOptions = () => {
- const { org } = useOrg();
- const { attachState, product } = useProductContext();
- const { options, setOptions } = attachState;
- const currency = org.default_currency || "USD";
-
- if (options.length == 0) return null;
-
- return (
-
- {options.map((option: any, index: number) => {
- const { feature_name, billing_units, quantity, price } = option;
- return (
-
-
- {product.name} - {feature_name}
-
- ) => {
- const newOptions = [...options];
- newOptions[index].quantity =
- parseInt(e.target.value) * billing_units;
- setOptions(newOptions);
- }}
- >
-
- ×{" "}
- {formatAmount({
- currency,
- amount: price,
- maxFractionDigits: 2,
- })}{" "}
- per {billing_units === 1 ? " " : billing_units} {feature_name}
-
-
-
- );
- })}
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/attach-preview/AttachInfo.tsx b/vite/src/views/customers/customer/product/components/attach-preview/AttachInfo.tsx
deleted file mode 100644
index bf20f1063..000000000
--- a/vite/src/views/customers/customer/product/components/attach-preview/AttachInfo.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import {
- AttachBranch,
- FeatureType,
- FeatureUsageType,
- type ProductItem,
- ProductItemFeatureType,
-} from "@autumn/shared";
-import { format } from "date-fns";
-import { CheckCircle, InfoIcon } from "lucide-react";
-import { formatUnixToDate } from "@/utils/formatUtils/formatDateUtils";
-import { notNullish } from "@/utils/genUtils";
-import { useProductContext } from "@/views/products/product/ProductContext";
-
-export const AttachInfo = () => {
- const { attachState, product } = useProductContext();
- const { preview, flags } = attachState;
-
- const currentProduct = preview?.current_product;
- const scheduledProduct = preview?.scheduled_product;
-
- const getAttachDescription = () => {
- if (preview?.branch == AttachBranch.Downgrade) {
- let text = `The customer is currently on ${currentProduct.name} and will downgrade to ${product.name} on ${formatUnixToDate(preview.due_next_cycle?.due_at)}`;
- if (scheduledProduct) {
- text += `. The scheduled product ${scheduledProduct.product.name} will also be removed.`;
- }
- return text;
- }
-
- if (preview?.free_trial) {
- let text = `The free trial for ${product.name} will end on ${formatUnixToDate(preview.due_next_cycle?.due_at)}`;
- if (currentProduct && currentProduct.free_trial) {
- text += ` and the customer's current trial to ${currentProduct.name} will be canceled.`;
- } else {
- text += `.`;
- }
- return text;
- }
-
- if (preview?.branch == AttachBranch.NewVersion) {
- const usagePriceExists = product.items.some((item: ProductItem) => {
- const priceExists = notNullish(item.tiers) || notNullish(item.price);
- return (
- item.feature_type === ProductItemFeatureType.SingleUse && priceExists
- );
- });
-
- let text = `You are switching this customer to version ${product.version} of ${product.name}.`;
- if (preview.due_next_cycle?.due_at) {
- text += `Their features will update immediately and from ${formatUnixToDate(preview.due_next_cycle?.due_at)} onwards, they will pay any new prices${usagePriceExists ? " (including usage from the last cycle)" : ""}.`;
- }
-
- return (
- <>
-
- {/* You are switching this customer to version {product.version} of{" "}
- {product.name}. */}
- {text}
-
- >
- );
-
- // let text = `The customer is currently on ${currentProduct.name} v${currentProduct.version}. Switching to v${product.version} will update the customer's features immediately, and from ${formatUnixToDate(preview.due_next_cycle.due_at)} onwards they will pay any new prices`;
-
- // if (usagePriceExists) {
- // text += ` (including usage from the last cycle).`;
- // } else {
- // text += `.`;
- // }
-
- return text;
- }
-
- switch (preview?.branch) {
- case AttachBranch.SameCustomEnts:
- return "No changes to prices or subscriptions will be made";
-
- case AttachBranch.Renew:
- if (scheduledProduct) {
- return `${scheduledProduct.product.name} is scheduled to start on ${formatUnixToDate(scheduledProduct.starts_at)}. Renewing ${currentProduct.name} will undo this.`;
- } else {
- return null;
- }
- default:
- return null;
- }
- };
- const description = getAttachDescription();
-
- const hasNoPriceChanges = () => {
- if (preview?.branch === AttachBranch.SameCustomEnts) {
- return true;
- }
-
- if (flags.isFree) {
- return true;
- }
-
- if (preview?.due_today?.total === 0) {
- return true;
- }
-
- return false;
- };
-
- if (!description) {
- return null;
- }
-
- // if (hasNoPriceChanges()) {
- // return (
- //
- //
- //
- //
- //
No changes to prices or subscriptions will be made
- //
- // );
- // }
-
- return (
-
-
-
-
-
{getAttachDescription()}
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/attach-preview/AttachNewItems.tsx b/vite/src/views/customers/customer/product/components/attach-preview/AttachNewItems.tsx
deleted file mode 100644
index e357fffed..000000000
--- a/vite/src/views/customers/customer/product/components/attach-preview/AttachNewItems.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { PriceItem } from "@/components/pricing/attach-pricing-dialog";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { isFeaturePriceItem } from "@/utils/product/getItemType";
-import {
- getFeatureString,
- getPaidFeatureString,
-} from "@/utils/product/product-item/formatProductItem";
-import { useProductContext } from "@/views/products/product/ProductContext";
-
-export const AttachNewItems = () => {
- const { org } = useOrg();
- const { features } = useFeaturesQuery();
- const { attachState } = useProductContext();
- const { preview } = attachState;
-
- if (preview?.new_items) {
- return (
-
-
New items
- {preview.new_items.map((item: any, index: number) => {
- const str = isFeaturePriceItem(item)
- ? getPaidFeatureString({
- item,
- features,
- currency: org?.default_currency,
- })
- : getFeatureString({ item, features });
-
- return (
-
- {str}
-
- );
- })}
-
- );
- }
-};
diff --git a/vite/src/views/customers/customer/product/components/attach-preview/DueNextCycle.tsx b/vite/src/views/customers/customer/product/components/attach-preview/DueNextCycle.tsx
deleted file mode 100644
index 9be945c3a..000000000
--- a/vite/src/views/customers/customer/product/components/attach-preview/DueNextCycle.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import {
- AttachBranch,
- type Feature,
- formatAmount,
- getAmountForQuantity,
- getFeatureInvoiceDescription,
- type Price,
-} from "@autumn/shared";
-import { PriceItem } from "@/components/pricing/attach-pricing-dialog";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { formatUnixToDate } from "@/utils/formatUtils/formatDateUtils";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import { AdjustableOptions } from "./AdjustQuantity";
-
-export const DueNextCycle = () => {
- const { org } = useOrg();
- const { features } = useFeaturesQuery();
- const { attachState, product } = useProductContext();
-
- const preview = attachState.preview;
- const currency = org.default_currency || "USD";
-
- const getPrepaidPrice = ({ option }: { option: any }) => {
- const quantity =
- (option.quantity ?? option.current_quantity) / option.billing_units;
-
- // Handle tiered pricing
- if (option.tiers) {
- const amount = getAmountForQuantity({
- price: {
- config: {
- usage_tiers: option.full_tiers,
- billing_units: option.billing_units,
- },
- } as Price,
- quantity: (option.quantity ?? option.current_quantity) || 0,
- });
-
- return formatAmount({
- amount,
- currency,
- maxFractionDigits: 2,
- });
- }
-
- // Handle fixed pricing
- return formatAmount({
- amount: option.full_price * quantity,
- currency,
- maxFractionDigits: 2,
- });
- };
-
- const branch = attachState.preview?.branch;
-
- if (!preview.due_next_cycle || !preview.due_next_cycle.due_at) return null;
-
- if (
- !preview.due_next_cycle.line_items?.length &&
- !preview.options?.length
- // || preview.options.every((option: any) => option.full_price == option.price)
- )
- return null;
-
- return (
-
-
- Next cycle: {formatUnixToDate(preview.due_next_cycle.due_at)}
-
- {preview.due_next_cycle.line_items.map((item: any) => {
- const { description, price } = item;
- return (
-
- {description}
- {price}
-
- );
- })}
- {branch === AttachBranch.Downgrade ? (
-
- ) : (
- preview.options
- .filter((option: any) => {
- console.log("Option:", option);
- if (!option.interval) return false;
- return true;
- })
- .map((option: any) => {
- const quantity = Math.ceil(
- (option.quantity ?? option.current_quantity) /
- option.billing_units,
- );
- const description = getFeatureInvoiceDescription({
- feature: features.find(
- (f: Feature) => f.id === option.feature_id,
- ) as Feature,
- usage: quantity || 0,
- billingUnits: option.billing_units,
- isPrepaid: true,
- });
-
- return (
-
-
- {product.name} - {description}
-
- {getPrepaidPrice({ option })}
-
- );
- })
- )}
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/attach-preview/DueToday.tsx b/vite/src/views/customers/customer/product/components/attach-preview/DueToday.tsx
deleted file mode 100644
index ff822d0fb..000000000
--- a/vite/src/views/customers/customer/product/components/attach-preview/DueToday.tsx
+++ /dev/null
@@ -1,168 +0,0 @@
-import {
- AttachBranch,
- formatAmount,
- getAmountForQuantity,
- type Price,
-} from "@autumn/shared";
-import { Decimal } from "decimal.js";
-import { PriceItem } from "@/components/pricing/attach-pricing-dialog";
-import { Input } from "@/components/ui/input";
-import { useOrg } from "@/hooks/common/useOrg";
-import { notNullish } from "@/utils/genUtils";
-import { useProductContext } from "@/views/products/product/ProductContext";
-
-export const DueToday = () => {
- const { org } = useOrg();
- const { attachState, product } = useProductContext();
- const { preview, options, setOptions } = attachState;
-
- const dueToday = preview.due_today;
-
- if (!dueToday || preview.branch == AttachBranch.NewVersion) {
- return null;
- }
-
- const dueTodayItems = dueToday.line_items;
- const currency = org?.default_currency || "USD";
- const branch = preview.branch;
-
- const getTotalPrice = () => {
- let total =
- preview?.due_today?.line_items.reduce((acc: any, item: any) => {
- if (item.amount) {
- return acc.plus(item.amount);
- }
- return acc;
- }, new Decimal(0)) || new Decimal(0);
- total = total.toNumber();
-
- options.forEach((option: any) => {
- // Get invoice amount
-
- if (option.tiers) {
- const amount = getAmountForQuantity({
- price: {
- config: {
- usage_tiers: option.tiers,
- billing_units: option.billing_units,
- },
- } as Price,
- quantity: option.quantity || 0,
- });
-
- total = new Decimal(total).plus(amount).toNumber();
- }
-
- if (notNullish(option.price)) {
- total = new Decimal(total)
- .plus(
- new Decimal(option.price).times(
- new Decimal(option.quantity || 0).div(option.billing_units),
- ),
- )
- .toNumber();
- }
- });
- return total;
- };
-
- const getTitle = () => {
- if (branch == AttachBranch.UpdatePrepaidQuantity) {
- return "Update quantity";
- }
-
- return "Due today";
- };
-
- const getPrepaidPrice = ({ option }: { option: any }) => {
- if (notNullish(option.price)) {
- return `x ${formatAmount({
- amount: option.price,
- currency,
- maxFractionDigits: 5,
- })} per `;
- }
-
- if (option.tiers) {
- const start = option.tiers[0].amount;
- const end = option.tiers[option.tiers.length - 1].amount;
- return "x ";
- // return `${formatAmount({
- // amount: start,
- // defaultCurrency: currency,
- // maxFractionDigits: 5,
- // })} - ${formatAmount({
- // amount: end,
- // defaultCurrency: currency,
- // maxFractionDigits: 5,
- // })} `;
- }
-
- return "";
- };
-
- return (
-
-
{getTitle()}
- {dueTodayItems &&
- dueTodayItems.map((item: any) => {
- const { description, price } = item;
- return (
-
- {description}
- {price}
-
- );
- })}
- {/*
*/}
- {options.length > 0 &&
- options.map((option: any, index: number) => {
- const { feature_name, billing_units, quantity, price } = option;
- return (
-
-
- {product.name} - {feature_name}
-
-
- ) => {
- const newOptions = [...options];
- newOptions[index].quantity =
- parseInt(e.target.value) * billing_units;
-
- setOptions(newOptions);
- }}
- className="w-12 h-7"
- />
-
-
- {/* ×{" "} */}
- {/* {formatAmount({
- defaultCurrency: currency,
- amount: price,
- maxFractionDigits: 2,
- })}{" "} */}
- {getPrepaidPrice({ option })}
- {billing_units === 1 ? " " : billing_units} {feature_name}
-
-
-
- );
- })}
- {preview.due_today && (
-
- Total:
-
- {formatAmount({
- amount: getTotalPrice(),
- currency,
- maxFractionDigits: 2,
- })}
-
-
- )}
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/attach-preview/OptionsInput.tsx b/vite/src/views/customers/customer/product/components/attach-preview/OptionsInput.tsx
deleted file mode 100644
index 0740554c9..000000000
--- a/vite/src/views/customers/customer/product/components/attach-preview/OptionsInput.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { PriceItem } from "@/components/pricing/attach-pricing-dialog";
-import { Input } from "@/components/ui/input";
-import { notNullish } from "@/utils/genUtils";
-import { useProductContext } from "@/views/products/product/ProductContext";
-
-export const OptionsInput = () => {
- const { attachState, product } = useProductContext();
- const { options, setOptions } = attachState;
-
- if (!options || options.length == 0) return null;
-
- return (
-
- {options.length > 0 &&
- options.map((option: any, index: number) => {
- const { feature_name, billing_units, quantity, price } = option;
- return (
-
-
- {product.name} - {feature_name}
-
-
- ) => {
- const newOptions = [...options];
- newOptions[index].quantity =
- parseInt(e.target.value) * billing_units;
-
- setOptions(newOptions);
- }}
- className="w-12 h-7"
- />
-
- {/*
- ×{" "}
- {formatAmount({
- defaultCurrency: currency,
- amount: price,
- maxFractionDigits: 2,
- })}{" "}
- per {billing_units === 1 ? " " : billing_units} {feature_name}
- */}
-
-
- );
- })}
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/attach-preview/OptionsResult.tsx b/vite/src/views/customers/customer/product/components/attach-preview/OptionsResult.tsx
deleted file mode 100644
index f39bd7f00..000000000
--- a/vite/src/views/customers/customer/product/components/attach-preview/OptionsResult.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { AttachBranch, formatAmount } from "@autumn/shared";
-import { PriceItem } from "@/components/pricing/attach-pricing-dialog";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useProductContext } from "@/views/products/product/ProductContext";
-
-export const OptionsResult = () => {
- const { org } = useOrg();
- const { attachState, product } = useProductContext();
- const { preview, options } = attachState;
- const currency = org?.default_currency || "USD";
-
- const dueToday = preview.due_today;
- if (!dueToday || preview.branch == AttachBranch.NewVersion) {
- return null;
- }
-
- const getDifference = (option: any) => {
- const {
- current_quantity: currentQuantity,
- quantity: newQuantity,
- price,
- billing_units,
- feature_name,
- } = option;
- const difference = (newQuantity || 0) - currentQuantity;
- const priceForDifference = difference * (price / billing_units);
-
- let title = "";
- if (!currentQuantity) {
- title = ` ${difference} ${feature_name}`;
- } else if (difference > 0) {
- title += ` Additional ${difference} ${feature_name}`;
- } else if (difference < 0) {
- title += ` Less ${-difference} ${feature_name}`;
- } else {
- title = ` ${Math.abs(difference)} ${feature_name}`;
- }
-
- return {
- difference,
- amount: priceForDifference,
- title: `${product.name} - ${title}`,
- };
- };
-
- return (
- <>
- {options && options.length > 0 && (
- <>
- {options.map((option: any, index: number) => {
- // Calculate price for option
- const { feature_name, billing_units, quantity, price } = option;
-
- const proratedAmount = option.proration_amount;
- const currentQuantity = option.current_quantity;
- const { title, difference, amount } = getDifference(option);
-
- return (
- <>
- {proratedAmount > 0 && (
-
-
- {product.name} - {currentQuantity} {feature_name}{" "}
- (Prorated)
-
-
-
- {formatAmount({
- currency,
- amount: proratedAmount,
- maxFractionDigits: 2,
- })}{" "}
-
-
-
- )}
-
- {title}
-
- {formatAmount({
- currency,
- amount: amount,
- maxFractionDigits: 2,
- })}
-
-
- >
- );
- })}
- >
- )}
- >
- );
-};
diff --git a/vite/src/views/customers/customer/product/components/attach-preview/UpdateQuantity.tsx b/vite/src/views/customers/customer/product/components/attach-preview/UpdateQuantity.tsx
deleted file mode 100644
index 0080014b1..000000000
--- a/vite/src/views/customers/customer/product/components/attach-preview/UpdateQuantity.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import {
- AttachBranch,
- formatAmount,
- getAmountForQuantity,
- type Price,
-} from "@autumn/shared";
-import { Decimal } from "decimal.js";
-import { PriceItem } from "@/components/pricing/attach-pricing-dialog";
-import { Input } from "@/components/ui/input";
-import { useOrg } from "@/hooks/common/useOrg";
-import { notNullish } from "@/utils/genUtils";
-import { useProductContext } from "@/views/products/product/ProductContext";
-
-export const UpdateQuantity = () => {
- const { org } = useOrg();
- const { attachState, product } = useProductContext();
- const { preview, options, setOptions } = attachState;
-
- if (preview.branch !== AttachBranch.UpdatePrepaidQuantity) {
- return null;
- }
-
- const currency = org?.default_currency || "USD";
-
- const getPrepaidPrice = ({ option }: { option: any }) => {
- if (notNullish(option.price)) {
- return `x ${formatAmount({
- amount: option.price,
- currency,
- maxFractionDigits: 5,
- })} per `;
- }
-
- if (option.tiers) {
- const start = option.tiers[0].amount;
- const end = option.tiers[option.tiers.length - 1].amount;
- return "x ";
- // return `${formatAmount({
- // amount: start,
- // defaultCurrency: currency,
- // maxFractionDigits: 5,
- // })} - ${formatAmount({
- // amount: end,
- // defaultCurrency: currency,
- // maxFractionDigits: 5,
- // })} `;
- }
-
- return "";
- };
-
- const getTotalPrice = () => {
- return options.reduce((acc: number, option: any) => {
- const currentQuantity = option.current_quantity || 0;
- const newQuantity = option.quantity || 0;
- const difference = newQuantity - currentQuantity;
-
- const isDecrease = newQuantity < currentQuantity;
-
- if (isDecrease && option.config.on_decrease == "none") {
- return acc;
- }
-
- let priceForDifference = 0;
-
- // Handle tiered pricing
- if (option.tiers) {
- // Calculate amount for new quantity
- const newAmount = getAmountForQuantity({
- price: {
- config: {
- usage_tiers: option.tiers,
- billing_units: option.billing_units,
- },
- } as Price,
- quantity: newQuantity,
- });
-
- // Calculate amount for current quantity
- const currentAmount = getAmountForQuantity({
- price: {
- config: {
- usage_tiers: option.tiers,
- billing_units: option.billing_units,
- },
- } as Price,
- quantity: currentQuantity,
- });
-
- priceForDifference = new Decimal(newAmount)
- .minus(currentAmount)
- .toNumber();
- } else {
- // Handle fixed pricing
- const differenceInUnits = difference / option.billing_units;
- priceForDifference = option.price * differenceInUnits;
- }
-
- return acc + priceForDifference;
- }, 0);
- };
-
- return (
-
-
Update prepaid quantity
- {/* {preview.due_today &&
- preview.due_today.line_items.map((item: any) => {
- return (
-
- {item.description}
- {item.price}
-
- );
- })} */}
- {options.length > 0 &&
- options.map((option: any, index: number) => {
- const { feature_name, billing_units, quantity, price } = option;
- return (
-
-
- {product.name} - {feature_name}
-
-
- ) => {
- const newOptions = [...options];
- newOptions[index].quantity =
- parseInt(e.target.value) * billing_units;
-
- setOptions(newOptions);
- }}
- className="w-12 h-7"
- />
-
-
- {/* ×{" "} */}
- {/* {formatAmount({
- defaultCurrency: currency,
- amount: price,
- maxFractionDigits: 2,
- })}{" "} */}
- {getPrepaidPrice({ option })}
- {billing_units === 1 ? " " : billing_units} {feature_name}
-
-
-
- );
- })}
-
- Total:
-
- {formatAmount({
- amount: getTotalPrice(),
- currency,
- maxFractionDigits: 2,
- })}
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/hooks/useAttachState.tsx b/vite/src/views/customers/customer/product/hooks/useAttachState.tsx
deleted file mode 100644
index eae14a5b0..000000000
--- a/vite/src/views/customers/customer/product/hooks/useAttachState.tsx
+++ /dev/null
@@ -1,215 +0,0 @@
-import {
- type AttachPreview,
- type FeatureOptions,
- type FullCusProduct,
- isCanceled,
- type ProductItem,
- type ProductV2,
- UsageModel,
-} from "@autumn/shared";
-import { useEffect, useState } from "react";
-import { notNullish } from "@/utils/genUtils";
-import { isFeatureItem } from "@/utils/product/getItemType";
-import { isOneOffProduct } from "@/utils/product/priceUtils";
-import { sortProductItems } from "@/utils/productUtils";
-
-// export type FrontendProduct = ProductV2 & {
-// isActive: boolean;
-// options: FeatureOptions[];
-// isCanceled: boolean;
-// };
-
-export enum AttachCase {
- AddOn = "Add On",
- OneOff = "One Off",
- Active = "Active",
- Custom = "Custom",
- Checkout = "Checkout",
-}
-
-const productHasPrepaid = (items: ProductItem[]) => {
- return items.some(
- (item) =>
- item.usage_model === UsageModel.Prepaid && notNullish(item.interval),
- );
-};
-
-const productIsAddOn = (product: ProductV2) => {
- return product.is_add_on;
-};
-
-const productIsFree = (product: ProductV2) => {
- return product.items.every((item) => isFeatureItem(item));
-};
-
-export const useAttachState = ({
- product,
- cusProduct,
- setProduct,
- initialProductRef,
-}: {
- product: ProductV2 | null;
- setProduct: (product: ProductV2) => void;
-
- cusProduct: FullCusProduct | undefined | null;
- initialProductRef: React.RefObject;
-}) => {
- const [preview, setPreview] = useState(null);
- const [options, setOptions] = useState<
- (FeatureOptions & {
- full_price: number;
- billing_units: number;
- })[]
- >([]);
-
- const [itemsChanged, setItemsChanged] = useState(false);
- const [flags, setFlags] = useState({
- hasPrepaid: product ? productHasPrepaid(product.items) : false,
- isAddOn: product ? productIsAddOn(product) : false,
- isFree: product ? productIsFree(product) : false,
-
- isCanceled: cusProduct ? isCanceled({ cusProduct }) : false,
-
- isOneOff: product
- ? isOneOffProduct(product.items, product.is_add_on)
- : false,
- });
-
- useEffect(() => {
- if (preview?.options) {
- setOptions(preview.options);
- }
- }, [preview]);
-
- const initFlags = () => {
- setFlags({
- hasPrepaid: product ? productHasPrepaid(product.items) : false,
- isAddOn: product ? productIsAddOn(product) : false,
- isFree: product ? productIsFree(product) : false,
- isCanceled: cusProduct ? isCanceled({ cusProduct }) : false,
- isOneOff: product
- ? isOneOffProduct(product.items, product.is_add_on)
- : false,
- });
- };
-
- useEffect(() => {
- if (!product) {
- return;
- }
-
- const sortedProduct = {
- ...product,
- items: sortProductItems(product.items),
- };
-
- if (JSON.stringify(product.items) !== JSON.stringify(sortedProduct.items)) {
- setProduct(sortedProduct);
- }
-
- initFlags();
-
- const hasItemsChanged =
- JSON.stringify({
- items: sortedProduct.items,
- free_trial: sortedProduct.free_trial,
- }) !==
- JSON.stringify({
- items: initialProductRef.current?.items || [],
- free_trial: initialProductRef.current?.free_trial || null,
- });
-
- console.log("[UAS] effect", {
- changed: hasItemsChanged,
- refLen: initialProductRef.current?.items?.length,
- hasPrepaid: productHasPrepaid(product.items),
- });
-
- setItemsChanged(hasItemsChanged);
- }, [product]);
-
- const getButtonDisabled = () => {
- if (flags.isOneOff) return false;
-
- if (cusProduct && !itemsChanged && !flags.isCanceled) {
- if (flags.hasPrepaid) {
- return false;
- }
-
- if (flags.isAddOn) {
- return false;
- }
-
- return true;
- }
-
- return false;
- };
-
- const getAttachCase = () => {
- if (!product) {
- return null;
- }
-
- if (product?.is_add_on) {
- return AttachCase.AddOn;
- }
-
- if (isOneOffProduct(product.items, product.is_add_on)) {
- return AttachCase.OneOff;
- }
-
- if (cusProduct && !itemsChanged) {
- return AttachCase.Active;
- }
-
- if (itemsChanged) {
- return AttachCase.Custom;
- }
- };
-
- const getButtonText = () => {
- const result = (() => {
- if (cusProduct && !itemsChanged) {
- if (flags.isOneOff) {
- return "Attach Plan";
- }
-
- if (flags.hasPrepaid) {
- return "Update prepaid quantity";
- }
- }
-
- if (flags.isCanceled) {
- return "Renew Plan";
- }
-
- return "Attach Plan";
- })();
-
- console.log("[BTN]", {
- text: result,
- cusId: cusProduct?.id,
- changed: itemsChanged,
- prepaid: flags.hasPrepaid,
- });
- return result;
- };
-
- return {
- itemsChanged,
- buttonDisabled: getButtonDisabled(),
- buttonText: getButtonText(),
- attachCase: getAttachCase(),
-
- preview,
- setPreview,
- options,
- setOptions,
-
- flags,
- setFlags,
-
- cusProduct,
- };
-};
diff --git a/vite/src/views/customers/customer/product/multi-attach/MultiAttachDialog.tsx b/vite/src/views/customers/customer/product/multi-attach/MultiAttachDialog.tsx
deleted file mode 100644
index 28c057afb..000000000
--- a/vite/src/views/customers/customer/product/multi-attach/MultiAttachDialog.tsx
+++ /dev/null
@@ -1,368 +0,0 @@
-import { formatAmount, type ProductV2 } from "@autumn/shared";
-import type { CheckoutResult } from "autumn-js";
-import { ArrowUpRightFromSquare, Loader2, Plus, X } from "lucide-react";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import {
- CustomDialogBody,
- CustomDialogContent,
- CustomDialogFooter,
-} from "@/components/general/modal-components/DialogContentWrapper";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Button } from "@/components/ui/button";
-import { Dialog, DialogHeader, DialogTitle } from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useOrgStripeQuery } from "@/hooks/queries/useOrgStripeQuery";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { formatUnixToDate } from "@/utils/formatUtils/formatDateUtils";
-import { getBackendErr } from "@/utils/genUtils";
-import { getStripeInvoiceLink } from "@/utils/linkUtils";
-import { useCusQuery } from "../../hooks/useCusQuery";
-import { InvoiceCustomerButton } from "../components/InvoiceCustomerButton";
-import { getCusProductMinQuantity } from "../utils/getCusProductMinQuantity";
-import { MultiAtttachLines } from "./MultiAttachLines";
-import { AddRewardButton, MultiAttachRewards } from "./MultiAttachRewards";
-
-export const MultiAttachDialog = ({
- open,
- setOpen,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
-}) => {
- // const { customer, cusMutate, products, org } = useCustomerContext();
- const { org } = useOrg();
- const { customer, products, refetch } = useCusQuery();
- const { stripeAccount } = useOrgStripeQuery();
- const env = useEnv();
-
- const axiosInstance = useAxiosInstance();
-
- const [attachRewards, setAttachRewards] = useState([]);
- const [checkoutLoading, setCheckoutLoading] = useState(false);
- const [productOptions, setProductOptions] = useState([]);
- const [checkoutResult, setCheckoutResult] = useState(
- null,
- );
- const [attachLoading, setAttachLoading] = useState(false);
-
- const defaultCurrency = org?.default_currency || "usd";
-
- // const { data: subData } = useAxiosSWR({
- // url: `/customers/${customer.id}/sub`,
- // });
-
- const subDiscounts = [];
-
- const getDefaultProductOptions = () => {
- return [{ product_id: null, quantity: 1 }];
- };
-
- useEffect(() => {
- setProductOptions(getDefaultProductOptions());
- setCheckoutResult(null);
- }, [open]);
-
- useEffect(() => {
- if (open) {
- handleChange();
- }
- }, [productOptions, attachRewards]);
-
- const isValidAttach = () => {
- if (productOptions.length === 0) {
- return false;
- }
-
- for (const option of productOptions) {
- if (!option.product_id) {
- return false;
- }
-
- if (isNaN(parseFloat(option.quantity))) {
- return false;
- }
- }
-
- for (const reward of attachRewards) {
- if (!reward.reward_id) {
- return false;
- }
- }
-
- return true;
- };
-
- const handleChange = async () => {
- if (isValidAttach()) {
- setCheckoutLoading(true);
- try {
- const { data } = await axiosInstance.post("/v1/checkout", {
- customer_id: customer.id,
- products: productOptions,
- reward: attachRewards.map((r) => r.reward_id),
- });
-
- setCheckoutResult(data);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to preview checkout"));
- } finally {
- setCheckoutLoading(false);
- }
- }
- };
-
- const handleAttachClicked = async ({
- enableProductImmediately,
- useInvoice,
- setLoading,
- }: {
- enableProductImmediately: false;
- useInvoice: boolean;
- setLoading: (loading: boolean) => void;
- }) => {
- if (checkoutResult?.url && !useInvoice) {
- window.open(checkoutResult.url, "_blank");
- // setOpen(false);
- return;
- }
-
- for (const option of productOptions) {
- if (!option.product_id) {
- toast.error("Can't leave plan empty");
- return;
- }
- }
-
- setLoading(true);
-
- try {
- const { data } = await axiosInstance.post("/v1/attach", {
- customer_id: customer.id,
- products: productOptions,
- invoice: useInvoice,
- enable_product_immediately: useInvoice
- ? enableProductImmediately
- : undefined,
- finalize_invoice: useInvoice ? false : undefined,
- reward: attachRewards?.map((r) => r.reward_id) || [],
- });
-
- if (data.invoice) {
- window.open(
- getStripeInvoiceLink({
- stripeInvoice: data.invoice,
- env,
- accountId: stripeAccount?.id,
- }),
- "_blank",
- );
- }
-
- await refetch();
- toast.success("Plans attached successfully");
- setOpen(false);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to attach plans"));
- console.log(error);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
-
-
-
-
-
- Attach Products
-
-
-
Products
-
Quantity
- {productOptions.map((option, index) => (
- <>
-
{
- setProductOptions((prev) => {
- const newOptions = [...prev];
-
- const minQuantity = getCusProductMinQuantity({
- customer,
- productId: value,
- });
-
- newOptions[index] = {
- product_id: value,
- quantity: Math.max(
- newOptions[index].quantity,
- minQuantity,
- ),
- };
- return newOptions;
- });
- }}
- value={option.product_id || undefined}
- >
-
-
-
-
- {products
- .filter(
- (p: ProductV2) =>
- !productOptions
- .map((o, i) =>
- i !== index ? o.product_id : null,
- )
- .includes(p.id),
- )
- .map((product: ProductV2) => (
-
- {product.name}
-
- ))}
-
-
-
- {
- setProductOptions((prev) => {
- const newOptions = [...prev];
- newOptions[index] = {
- product_id: newOptions[index].product_id,
- quantity: parseInt(e.target.value),
- };
- return newOptions;
- });
- }}
- />
- }
- onClick={() => {
- if (productOptions.length === 1) {
- toast.error("Must attach at least one plan");
- return;
- }
- setProductOptions((prev) => {
- const newOptions = [...prev];
- newOptions.splice(index, 1);
- return newOptions;
- });
- }}
- />
-
- >
- ))}
-
-
-
}
- onClick={() => {
- setProductOptions((prev) => [
- ...prev,
- { product_id: null, quantity: 1 },
- ]);
- }}
- >
- New Product
-
- {attachRewards.length === 0 && subDiscounts.length === 0 && (
-
- )}
- {checkoutLoading && (
-
-
-
- )}
-
-
-
- {checkoutResult && (
-
-
-
-
-
Total:
-
- {formatAmount({
- currency: defaultCurrency,
- amount: checkoutResult.total,
- maxFractionDigits: 2,
- })}
-
-
- {checkoutResult.next_cycle && (
-
-
-
- Due next cycle (
- {formatUnixToDate(checkoutResult.next_cycle?.starts_at)}
- )
-
-
-
- {formatAmount({
- amount: checkoutResult.next_cycle?.total,
- currency: defaultCurrency,
- })}
-
-
- )}
-
- )}
-
-
-
-
- 0}
- />
-
- {
- handleAttachClicked({
- useInvoice: false,
- enableProductImmediately: false,
- setLoading: setAttachLoading,
- });
- }}
- endIcon={ }
- disabled={checkoutLoading || attachLoading}
- disableStartIcon
- >
- {checkoutResult?.url ? "Checkout Page" : "Attach Plans"}
-
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/multi-attach/MultiAttachLines.tsx b/vite/src/views/customers/customer/product/multi-attach/MultiAttachLines.tsx
deleted file mode 100644
index 76dbd56a0..000000000
--- a/vite/src/views/customers/customer/product/multi-attach/MultiAttachLines.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import * as AccordionPrimitive from "@radix-ui/react-accordion";
-import type { CheckoutResult } from "autumn-js";
-import { ChevronDown } from "lucide-react";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
-} from "@/components/ui/accordion";
-import { cn } from "@/lib/utils";
-
-export function MultiAtttachLines({
- checkoutResult,
-}: {
- checkoutResult: CheckoutResult;
-}) {
- return (
-
-
-
-
-
-
- {checkoutResult?.lines
- // .filter((line) => line.amount != 0)
- .map((line, index) => {
- return (
-
-
- {line.description}
-
-
- {new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: checkoutResult?.currency,
- }).format(line.amount)}
-
-
- );
- })}
-
-
-
- );
-}
-
-function CustomAccordionTrigger({
- className,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
-
- {children}
-
-
- );
-}
diff --git a/vite/src/views/customers/customer/product/multi-attach/MultiAttachRewards.tsx b/vite/src/views/customers/customer/product/multi-attach/MultiAttachRewards.tsx
deleted file mode 100644
index de389ad1c..000000000
--- a/vite/src/views/customers/customer/product/multi-attach/MultiAttachRewards.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import { type Reward, RewardType } from "@autumn/shared";
-import { Plus, X } from "lucide-react";
-import { toast } from "sonner";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Button } from "@/components/ui/button";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
-
-export const AddRewardButton = ({
- setAttachRewards,
-}: {
- setAttachRewards: (rewards: any) => void;
-}) => {
- return (
- }
- onClick={() => {
- setAttachRewards((prev: any) => [
- ...prev,
- { reward_id: null, quantity: 1 },
- ]);
- }}
- >
- Add Rewards
-
- );
-};
-
-export const MultiAttachRewards = ({
- attachRewards,
- setAttachRewards,
-}: {
- attachRewards: any;
- setAttachRewards: (rewards: any) => void;
-}) => {
- const { org } = useOrg();
- const { rewards } = useRewardsQuery();
- const subDiscounts: any[] = [];
-
- const noRewards = attachRewards.length === 0 && subDiscounts.length === 0;
-
- if (!rewards || !attachRewards || noRewards) return null;
-
- const formatReward = (reward: Reward) => {
- if (reward.type === RewardType.PercentageDiscount) {
- return (
- <>
- {reward.name}{" "}
-
- - {reward.discount_config?.discount_value}% off
-
- >
- );
- } else if (reward.type === RewardType.FixedDiscount) {
- return (
- <>
- {reward.name}{" "}
-
- - {reward.discount_config?.discount_value}{" "}
- {(org.default_currency || "usd").toUpperCase()} off
-
- >
- );
- }
- };
-
- const getSubDiscountName = (subDiscount: any) => {
- const correspondingReward = rewards.find(
- (r: Reward) => r.id === subDiscount.coupon.id,
- );
-
- const name =
- correspondingReward?.name ||
- subDiscount.coupon.name ||
- subDiscount.coupon.id;
-
- const amountOff = subDiscount.coupon.percent_off
- ? `${subDiscount.coupon.percent_off}% off`
- : `${subDiscount.coupon.amount_off} ${org.default_currency?.toUpperCase()} off`;
-
- return `${name} - ${amountOff}`;
- };
-
- const filteredRewards = rewards.filter((r: Reward) => {
- if (
- r.type === RewardType.PercentageDiscount ||
- r.type === RewardType.FixedDiscount
- ) {
- const subDiscount = subDiscounts.find((sd: any) => sd.coupon.id === r.id);
-
- if (subDiscount) {
- return false;
- }
- return true;
- }
-
- return false;
- });
-
- return (
-
-
-
Rewards
-
- {subDiscounts.map((subDiscount: any, index: number) => (
-
-
- {getSubDiscountName(subDiscount)}
-
-
- ))}
- {attachRewards.map((attachReward: any, index: number) => (
-
- {
- for (let i = 0; i < attachRewards.length; i++) {
- if (i === index) continue;
- if (attachRewards[i].reward_id === value) {
- toast.error("Reward already added");
- return;
- }
- }
- setAttachRewards((prev: any) =>
- prev.map((r: any, i: number) =>
- i === index ? { ...r, reward_id: value } : r,
- ),
- );
- }}
- >
-
-
- {rewards.find(
- (r: any) => r.id === attachReward.reward_id,
- ) ? (
- formatReward(
- rewards.find(
- (r: any) => r.id === attachReward.reward_id,
- ) as Reward,
- )
- ) : (
- Select a reward
- )}
-
-
-
- {filteredRewards.map((r: any) => (
-
- {formatReward(r)}
-
- ))}
-
-
- {
- setAttachRewards((prev: any) =>
- prev.filter(
- (_: any, index: number) =>
- index !== attachRewards.length - 1,
- ),
- );
- }}
- >
-
-
-
- ))}
-
-
-
-
- );
-};
diff --git a/vite/src/views/customers/customer/product/multi-attach/useMultiAttachProvider.tsx b/vite/src/views/customers/customer/product/multi-attach/useMultiAttachProvider.tsx
deleted file mode 100644
index e69de29bb..000000000
diff --git a/vite/src/views/customers/customer/product/utils/getCusProductMinQuantity.ts b/vite/src/views/customers/customer/product/utils/getCusProductMinQuantity.ts
deleted file mode 100644
index 6abc0d7ed..000000000
--- a/vite/src/views/customers/customer/product/utils/getCusProductMinQuantity.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { FullCusProduct, type FullCustomer, notNullish } from "@autumn/shared";
-
-export const getCusProductMinQuantity = ({
- customer,
- productId,
-}: {
- customer: FullCustomer;
- productId: string;
-}) => {
- const cusProducts = customer.customer_products;
- const entityCount = cusProducts.filter(
- (cusProduct) =>
- cusProduct.product_id === productId &&
- notNullish(cusProduct.internal_entity_id),
- ).length;
- return entityCount || 1;
-};
diff --git a/vite/src/views/customers2/components/sheets/SubscriptionUpdateTestSheet.tsx b/vite/src/views/customers2/components/sheets/SubscriptionUpdateTestSheet.tsx
deleted file mode 100644
index 79c660dcf..000000000
--- a/vite/src/views/customers2/components/sheets/SubscriptionUpdateTestSheet.tsx
+++ /dev/null
@@ -1,1747 +0,0 @@
-import {
- type Entity,
- type Feature,
- FreeTrialDuration,
- type FrontendProduct,
- type FullCusProduct,
- type FullCustomer,
- getProductItemDisplay,
- isCustomerProductTrialing,
- type ProductItem,
- type ProductV2,
- stripeToAtmnAmount,
- type UpdateSubscriptionV0Params,
-} from "@autumn/shared";
-import { Check, Copy, PencilSimple } from "@phosphor-icons/react";
-import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
-import type { AxiosError } from "axios";
-import { useEffect, useMemo, useState } from "react";
-import { useNavigate } from "react-router";
-import { toast } from "sonner";
-import { AttachProductLineItems } from "@/components/forms/attach-product/attach-product-line-items";
-import { AttachProductTotals } from "@/components/forms/attach-product/attach-product-totals";
-import { useUpdateSubscriptionPreview } from "@/components/forms/update-subscription/use-update-subscription-preview";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { Button } from "@/components/v2/buttons/Button";
-import { IconButton } from "@/components/v2/buttons/IconButton";
-import { LoadingShimmerText } from "@/components/v2/LoadingShimmerText";
-import { SheetHeader } from "@/components/v2/sheets/InlineSheet";
-import { useOrgStripeQuery } from "@/hooks/queries/useOrgStripeQuery";
-import { usePrepaidItems } from "@/hooks/stores/useProductStore";
-import { useSheetStore } from "@/hooks/stores/useSheetStore";
-import { useSubscriptionById } from "@/hooks/stores/useSubscriptionStore";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
-import { pushPage } from "@/utils/genUtils";
-import { getStripeInvoiceLink } from "@/utils/linkUtils";
-import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
-
-/**
- * TEST SHEET: SubscriptionUpdateTestSheet
- *
- * This is an isolated test sheet for testing the subscription update flow.
- * It calls:
- * - POST /v1/subscriptions/preview_update - to get a billing plan preview
- * - POST /v1/subscriptions/update - to execute the update
- *
- * Usage: Open this sheet with an itemId (cusProduct id) and optional customizedProduct in data
- */
-
-interface PrepaidEditorProps {
- prepaidItems: Array<{
- feature_id?: string | null;
- feature?: { internal_id: string } | undefined;
- billing_units?: number | null;
- }>;
- prepaidOptions: Record;
- onPrepaidChange: (featureId: string, quantity: number) => void;
-}
-
-function PrepaidEditor({
- prepaidItems,
- prepaidOptions,
- onPrepaidChange,
-}: PrepaidEditorProps) {
- if (prepaidItems.length === 0) return null;
-
- return (
-
-
-
Prepaid Quantities
-
-
- {prepaidItems.map((item) => {
- const featureId = item.feature_id ?? item.feature?.internal_id ?? "";
- const billingUnits = item.billing_units ?? 1;
- const inputId = `prepaid-${featureId}`;
- return (
-
-
-
- {featureId}
-
-
- (billing_units: {billingUnits})
-
-
-
- onPrepaidChange(featureId, parseInt(e.target.value, 10) || 0)
- }
- className="w-20 px-2 py-1 border border-border rounded text-sm bg-transparent"
- />
-
- );
- })}
-
-
- );
-}
-
-interface FreeTrialEditorProps {
- cusProduct: FullCusProduct;
- trialLength: number | null;
- trialDuration: FreeTrialDuration;
- trialCardRequired: boolean;
- removeTrial: boolean;
- onTrialLengthChange: (length: number | null) => void;
- onTrialDurationChange: (duration: FreeTrialDuration) => void;
- onTrialCardRequiredChange: (required: boolean) => void;
- onRemoveTrialChange: (remove: boolean) => void;
-}
-
-function FreeTrialEditor({
- cusProduct,
- trialLength,
- trialDuration,
- trialCardRequired,
- removeTrial,
- onTrialLengthChange,
- onTrialDurationChange,
- onTrialCardRequiredChange,
- onRemoveTrialChange,
-}: FreeTrialEditorProps) {
- const isCurrentlyTrialing = isCustomerProductTrialing(cusProduct);
-
- return (
-
-
-
Free Trial
-
-
- {/* Current Trial Status */}
-
- Current Status:
- {isCurrentlyTrialing ? (
-
- Trialing (ends:{" "}
- {new Date(cusProduct.trial_ends_at!).toLocaleDateString()})
-
- ) : (
-
- Not Trialing
-
- )}
-
-
- {/* Remove Trial (only show if currently trialing) */}
- {isCurrentlyTrialing ? (
-
- {
- onRemoveTrialChange(e.target.checked);
- if (e.target.checked) {
- onTrialLengthChange(null); // Clear new trial if removing
- }
- }}
- className="rounded border-border"
- />
-
- Remove Trial (pass free_trial: null)
-
-
- ) : null}
-
- {/* Set New Trial */}
- {!removeTrial ? (
- <>
-
-
- Trial Length
-
-
- onTrialLengthChange(
- e.target.value ? parseInt(e.target.value, 10) : null,
- )
- }
- className="w-20 px-2 py-1 border border-border rounded text-sm bg-transparent"
- />
-
- onTrialDurationChange(e.target.value as FreeTrialDuration)
- }
- className="px-2 py-1 border border-border rounded text-sm bg-transparent"
- >
- Day(s)
- Month(s)
- Year(s)
-
-
-
-
- onTrialCardRequiredChange(e.target.checked)}
- className="rounded border-border"
- />
-
- Card Required
-
-
- >
- ) : null}
-
- {/* Preview of what will be sent */}
-
- {removeTrial ? (
-
- Will send: free_trial: null (removes trial)
-
- ) : trialLength ? (
-
- Will send: free_trial:{" "}
- {`{ length: ${trialLength}, duration: "${trialDuration}", card_required: ${trialCardRequired} }`}
-
- ) : (
-
- No free_trial param (undefined) - existing trial preserved
-
- )}
-
-
-
- );
-}
-
-interface BillingPlanData {
- autumn?: {
- freeTrialPlan?: {
- freeTrial?: {
- length?: number;
- duration?: string;
- } | null;
- trialEndsAt?: number;
- };
- insertCustomerProducts?: Array<{
- product?: { name?: string };
- status?: string;
- starts_at?: number;
- trial_ends_at?: number;
- customer_entitlements?: Array<{
- feature_id?: string;
- balance?: number;
- next_reset_at?: number;
- entitlement?: { feature?: { name?: string } };
- }>;
- }>;
- updateCustomerProduct?: {
- customerProduct?: { product?: { name?: string } };
- updates?: Record;
- };
- customPrices?: unknown[];
- customEntitlements?: unknown[];
- };
- stripe?: {
- subscriptionAction?: {
- type?: string;
- stripeSubscriptionId?: string;
- params?: {
- items?: Array<{
- id?: string;
- price?: string;
- quantity?: number;
- deleted?: boolean;
- }>;
- trial_end?: number | "now";
- proration_behavior?: string;
- cancel_at_period_end?: boolean;
- };
- };
- subscriptionScheduleAction?: {
- type?: string;
- stripeSubscriptionScheduleId?: string;
- params?: {
- customer?: string;
- start_date?: number | "now";
- end_behavior?: string;
- phases?: Array<{
- start_date?: number;
- end_date?: number;
- trial_end?: number;
- items?: Array<{
- price?: string;
- quantity?: number;
- }>;
- }>;
- };
- };
- invoiceAction?: {
- addLineParams?: {
- lines?: Array<{
- description?: string;
- amount?: number;
- }>;
- };
- };
- invoiceItemsAction?: {
- createInvoiceItems?: Array<{
- description?: string;
- amount?: number;
- customer?: string;
- subscription?: string;
- }>;
- };
- };
-}
-
-interface PreviewResultProps {
- data: unknown;
- isLoading: boolean;
- error: Error | null;
-}
-
-function PreviewResult({ data, isLoading, error }: PreviewResultProps) {
- const billingPlan = data as BillingPlanData | null;
- const [copied, setCopied] = useState(false);
-
- const handleCopy = () => {
- if (data) {
- navigator.clipboard.writeText(JSON.stringify(data, null, 2));
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- }
- };
-
- return (
-
-
-
Billing Plan Preview
- {data ? (
-
- {copied ? (
- <>
-
- Copied!
- >
- ) : (
- <>
-
- Copy
- >
- )}
-
- ) : null}
-
-
- {isLoading ? (
-
- Loading preview...
-
- ) : null}
-
- {error ? (
-
- Error: {error.message}
-
- ) : null}
-
- {!data && !isLoading && !error ? (
-
- No preview data yet
-
- ) : null}
-
- {billingPlan && !isLoading ? (
-
- {/* Free Trial Plan */}
- {billingPlan.autumn?.freeTrialPlan ? (
-
-
- 🎁 Free Trial Plan
-
-
- {billingPlan.autumn.freeTrialPlan.freeTrial ? (
-
- Duration:
-
- {billingPlan.autumn.freeTrialPlan.freeTrial.length}{" "}
- {billingPlan.autumn.freeTrialPlan.freeTrial.duration}
- {Number(
- billingPlan.autumn.freeTrialPlan.freeTrial.length,
- ) > 1
- ? "s"
- : ""}
-
-
- ) : null}
- {billingPlan.autumn.freeTrialPlan.trialEndsAt ? (
-
- Trial ends at:
- {(() => {
- const trialEndsFormatted = formatUnixToDateTime(
- billingPlan.autumn.freeTrialPlan.trialEndsAt,
- );
- return (
-
- {trialEndsFormatted.date} {trialEndsFormatted.time}
-
- );
- })()}
-
- ) : null}
-
-
- ) : null}
-
- {/* Insert Customer Products */}
- {billingPlan.autumn?.insertCustomerProducts &&
- billingPlan.autumn.insertCustomerProducts.length > 0 ? (
-
-
- 📥 Inserting Customer Product
-
- {billingPlan.autumn.insertCustomerProducts.map((cp, index) => {
- const startsAtFormatted = formatUnixToDateTime(cp.starts_at);
- const trialEndsAtFormatted = formatUnixToDateTime(
- cp.trial_ends_at,
- );
- return (
-
-
- {cp.product?.name || "Unknown Product"}
-
-
- {cp.status ? (
-
- Status:
- {cp.status}
-
- ) : null}
- {cp.starts_at ? (
-
- Starts at:
-
- {startsAtFormatted.date} {startsAtFormatted.time}
-
-
- ) : null}
- {cp.trial_ends_at ? (
-
- Trial ends at:
-
- {trialEndsAtFormatted.date}{" "}
- {trialEndsAtFormatted.time}
-
-
- ) : null}
- {cp.customer_entitlements &&
- cp.customer_entitlements.length > 0 ? (
-
-
Entitlements:
-
- {cp.customer_entitlements.map((ent, entIndex) => {
- const nextResetFormatted = ent.next_reset_at
- ? formatUnixToDateTime(ent.next_reset_at)
- : null;
- return (
-
-
- {ent.entitlement?.feature?.name ||
- ent.feature_id}
- : {ent.balance}
-
- {nextResetFormatted ? (
-
- (resets:{" "}
-
- {nextResetFormatted.date}{" "}
- {nextResetFormatted.time}
-
- )
-
- ) : null}
-
- );
- })}
-
-
- ) : null}
-
-
- );
- })}
-
- ) : null}
-
- {/* Update Customer Product */}
- {(() => {
- const updateCusProduct = billingPlan.autumn?.updateCustomerProduct;
- if (!updateCusProduct) return null;
- return (
-
-
- ✏️ Updating Customer Product
-
-
-
- {updateCusProduct.customerProduct?.product?.name ||
- "Unknown Product"}
-
- {updateCusProduct.updates ? (
-
- Updates:
-
- {JSON.stringify(updateCusProduct.updates)}
-
-
- ) : null}
-
-
- );
- })()}
-
- {/* Stripe Subscription Action */}
- {billingPlan.stripe?.subscriptionAction ? (
-
-
- 💳 Stripe Subscription Action
-
-
-
- Type:
-
- {billingPlan.stripe.subscriptionAction.type || "none"}
-
-
- {billingPlan.stripe.subscriptionAction.stripeSubscriptionId ? (
-
- Sub:
-
- {
- billingPlan.stripe.subscriptionAction
- .stripeSubscriptionId
- }
-
-
- ) : null}
-
- {/* Subscription Items */}
- {billingPlan.stripe.subscriptionAction.params?.items &&
- billingPlan.stripe.subscriptionAction.params.items.length > 0 ? (
-
-
- Items:
-
- {billingPlan.stripe.subscriptionAction.params.items.map(
- (item, index) => (
-
- {item.deleted ? (
- <>
-
- {item.id || "Unknown item"}
-
- 🗑️ delete
- >
- ) : (
- <>
-
- {item.price || item.id || "Unknown"}
-
-
- qty: {item.quantity ?? 1}
-
- >
- )}
-
- ),
- )}
-
- ) : null}
- {/* Other params */}
- {billingPlan.stripe.subscriptionAction.params?.trial_end ? (
-
- Trial ends:
-
- {billingPlan.stripe.subscriptionAction.params.trial_end ===
- "now"
- ? "now"
- : new Date(
- (billingPlan.stripe.subscriptionAction.params
- .trial_end as number) * 1000,
- ).toLocaleDateString()}
-
-
- ) : null}
- {billingPlan.stripe.subscriptionAction.params ? (
-
-
- View raw params
-
-
- {JSON.stringify(
- billingPlan.stripe.subscriptionAction.params,
- null,
- 2,
- )}
-
-
- ) : null}
-
- ) : null}
-
- {/* Stripe Subscription Schedule Action */}
- {billingPlan.stripe?.subscriptionScheduleAction ? (
-
-
- 📅 Stripe Subscription Schedule Action
-
-
-
- Type:
-
- {billingPlan.stripe.subscriptionScheduleAction.type ||
- "none"}
-
-
- {billingPlan.stripe.subscriptionScheduleAction
- .stripeSubscriptionScheduleId ? (
-
- Schedule:
-
- {
- billingPlan.stripe.subscriptionScheduleAction
- .stripeSubscriptionScheduleId
- }
-
-
- ) : null}
-
- {/* Schedule-level start_date */}
- {(() => {
- const scheduleParams =
- billingPlan.stripe.subscriptionScheduleAction.params;
- if (!scheduleParams) return null;
- const scheduleStartDate =
- typeof scheduleParams.start_date === "number"
- ? formatUnixToDateTime(scheduleParams.start_date * 1000)
- : null;
- return (
-
- {scheduleStartDate ? (
-
- Starts:
-
- {scheduleStartDate.date} {scheduleStartDate.time}
-
-
- ) : scheduleParams.start_date === "now" ? (
-
- Starts:
- now
-
- ) : null}
- {scheduleParams.end_behavior ? (
-
- End behavior:
-
- {scheduleParams.end_behavior}
-
-
- ) : null}
-
- );
- })()}
- {/* Phases */}
- {billingPlan.stripe.subscriptionScheduleAction.params?.phases &&
- billingPlan.stripe.subscriptionScheduleAction.params.phases
- .length > 0 ? (
-
-
- Phases:
-
- {billingPlan.stripe.subscriptionScheduleAction.params.phases.map(
- (phase, phaseIndex) => {
- const startDate = phase.start_date
- ? formatUnixToDateTime(phase.start_date * 1000)
- : null;
- const endDate = phase.end_date
- ? formatUnixToDateTime(phase.end_date * 1000)
- : null;
- const trialEnd = phase.trial_end
- ? formatUnixToDateTime(phase.trial_end * 1000)
- : null;
- return (
-
-
- Phase {phaseIndex + 1}
-
- {" "}
- (
- {startDate
- ? `${startDate.date} ${startDate.time}`
- : "schedule start"}
- {" → "}
- {endDate
- ? `${endDate.date} ${endDate.time}`
- : "ongoing"}
- )
-
-
- {trialEnd ? (
-
-
- Trial ends:{" "}
-
-
- {trialEnd.date} {trialEnd.time}
-
-
- ) : null}
- {phase.items && phase.items.length > 0 ? (
-
- {phase.items.map((item, itemIndex) => (
-
-
- {item.price || "Unknown price"}
-
-
- qty: {item.quantity ?? 1}
-
-
- ))}
-
- ) : (
-
- No items
-
- )}
-
- );
- },
- )}
-
- ) : null}
-
-
- View raw params
-
-
- {JSON.stringify(
- billingPlan.stripe.subscriptionScheduleAction.params,
- null,
- 2,
- )}
-
-
-
- ) : null}
-
- {/* Stripe Invoice Action */}
- {billingPlan.stripe?.invoiceAction ? (
-
-
- 🧾 Stripe Invoice Action
-
-
- {/* Immediate Line Items (addLineParams) */}
- {billingPlan.stripe.invoiceAction.addLineParams?.lines &&
- billingPlan.stripe.invoiceAction.addLineParams.lines.length >
- 0 ? (
-
-
- Immediate charges:
-
-
- {billingPlan.stripe.invoiceAction.addLineParams.lines.map(
- (
- line: {
- description?: string;
- amount?: number;
- },
- index: number,
- ) => {
- const amount = line.amount
- ? stripeToAtmnAmount({
- amount: line.amount,
- currency: "usd",
- })
- : 0;
- return (
-
-
- {line.description || "Line item"}
-
- = 0 ? "text-green-400" : "text-red-400"
- }
- >
- ${amount.toFixed(2)}
-
-
- );
- },
- )}
-
-
- ) : (
-
- No immediate line items
-
- )}
-
-
-
- View raw params
-
-
- {JSON.stringify(billingPlan.stripe.invoiceAction, null, 2)}
-
-
-
- ) : null}
-
- {/* Stripe Invoice Items Action - Deferred charges added to next cycle */}
- {billingPlan.stripe?.invoiceItemsAction?.createInvoiceItems &&
- billingPlan.stripe.invoiceItemsAction.createInvoiceItems.length >
- 0 ? (
-
-
- ⏱️ Stripe Invoice Items Action (Added to next cycle)
-
-
- {billingPlan.stripe.invoiceItemsAction.createInvoiceItems.map(
- (
- item: {
- description?: string;
- amount?: number;
- customer?: string;
- subscription?: string;
- },
- index: number,
- ) => {
- const amount = item.amount
- ? stripeToAtmnAmount({
- amount: item.amount,
- currency: "usd",
- })
- : 0;
- return (
-
-
- {item.description || "Invoice item"}
-
- = 0 ? "text-amber-400" : "text-red-400"
- }
- >
- ${amount.toFixed(2)}
-
-
- );
- },
- )}
-
-
- These items will appear on the customer's next invoice
-
-
-
- View raw params
-
-
- {JSON.stringify(
- billingPlan.stripe.invoiceItemsAction,
- null,
- 2,
- )}
-
-
-
- ) : null}
-
- {/* Empty Stripe section indicator */}
- {billingPlan.stripe &&
- !billingPlan.stripe.subscriptionAction &&
- !billingPlan.stripe.invoiceAction &&
- !billingPlan.stripe.invoiceItemsAction ? (
-
- No Stripe actions required
-
- ) : null}
-
- {/* Raw JSON toggle */}
-
-
- View raw JSON
-
-
- {JSON.stringify(data, null, 2)}
-
-
-
- ) : null}
-
- );
-}
-
-interface UpdateResultProps {
- data: unknown;
- isLoading: boolean;
- error: Error | null;
-}
-
-function UpdateResult({ data, isLoading, error }: UpdateResultProps) {
- if (!data && !isLoading && !error) return null;
-
- return (
-
-
-
Update Response
-
- {isLoading ? (
-
Updating...
- ) : null}
- {error ? (
-
- Error: {error.message}
-
- ) : null}
- {data !== null && data !== undefined && !isLoading ? (
-
-
✓ Success
-
- {JSON.stringify(data, null, 2)}
-
-
- ) : null}
-
- );
-}
-
-function useSubscriptionUpdatePreview({
- body,
- enabled,
-}: {
- body: UpdateSubscriptionV0Params | null;
- enabled: boolean;
-}) {
- const axiosInstance = useAxiosInstance();
-
- // Debounce the body to avoid too many API calls
- const [debouncedBody, setDebouncedBody] = useState(body);
-
- useEffect(() => {
- const timer = setTimeout(() => {
- setDebouncedBody(body);
- }, 300);
- return () => clearTimeout(timer);
- }, [body]);
-
- const isDebouncing = JSON.stringify(body) !== JSON.stringify(debouncedBody);
-
- const query = useQuery({
- queryKey: [
- "subscription-update-preview-test",
- JSON.stringify(debouncedBody),
- ],
- queryFn: async () => {
- if (!debouncedBody) return null;
- const response = await axiosInstance.post(
- "/v1/subscriptions/preview_update",
- debouncedBody,
- );
- return response.data;
- },
- enabled: enabled && !!debouncedBody,
- retry: false,
- });
-
- return {
- ...query,
- isLoading: query.isLoading || isDebouncing,
- };
-}
-
-interface SubscriptionUpdateParams {
- body: UpdateSubscriptionV0Params;
- useInvoice?: boolean;
- enableProductImmediately?: boolean;
-}
-
-function useSubscriptionUpdate({
- customerId,
- onInvoiceCreated,
-}: {
- customerId?: string;
- onInvoiceCreated?: (invoiceLink: string) => void;
-}) {
- const axiosInstance = useAxiosInstance();
- const queryClient = useQueryClient();
- const { closeSheet } = useSheetStore();
-
- return useMutation({
- mutationFn: async ({
- body,
- useInvoice,
- enableProductImmediately,
- }: SubscriptionUpdateParams) => {
- const requestBody = {
- ...body,
- invoice: useInvoice,
- enable_product_immediately: useInvoice
- ? enableProductImmediately
- : undefined,
- finalize_invoice: useInvoice ? false : undefined,
- force_checkout:
- useInvoice && enableProductImmediately === false ? true : undefined,
- };
-
- const response = await axiosInstance.post(
- "/v1/subscriptions/update",
- requestBody,
- );
- return response.data;
- },
- onSuccess: (data) => {
- if (data?.invoice) {
- onInvoiceCreated?.(data.invoice);
- toast.success("Invoice created successfully");
- } else if (data?.checkout_url) {
- toast.success("Redirecting to checkout...");
- window.open(data.checkout_url, "_blank");
- } else {
- toast.success("Subscription updated successfully");
- }
- closeSheet();
- if (customerId) {
- queryClient.invalidateQueries({ queryKey: ["customer", customerId] });
- }
- },
- onError: (error) => {
- toast.error(
- (error as AxiosError<{ message: string }>)?.response?.data?.message ??
- "Failed to update subscription",
- );
- console.error("Update failed:", error);
- },
- });
-}
-
-function SheetContent({
- cusProduct,
- productV2,
- customizedProduct,
-}: {
- cusProduct: FullCusProduct;
- productV2: ProductV2;
- customizedProduct: FrontendProduct | undefined;
-}) {
- const navigate = useNavigate();
- const { customer, features } = useCusQuery();
- const customerId = customer?.id ?? customer?.internal_id;
- const entityId = cusProduct?.entity_id ?? undefined;
-
- // Stripe + invoice handling
- const { stripeAccount } = useOrgStripeQuery();
- const env = useEnv();
-
- const product = customizedProduct?.id ? customizedProduct : productV2;
- const { prepaidItems } = usePrepaidItems({ product });
-
- // Get display info for custom items
- const getItemDisplay = (item: ProductItem) => {
- return getProductItemDisplay({
- item,
- features: (features as Feature[]) ?? [],
- currency: "usd",
- });
- };
-
- // Handle Edit Plan - navigates to plan editor
- const handleEditPlan = () => {
- if (!cusProduct || !customer) return;
-
- const entity = (customer as FullCustomer).entities?.find(
- (e: Entity) =>
- e.internal_id === cusProduct.internal_entity_id ||
- e.id === cusProduct.entity_id,
- );
-
- pushPage({
- path: `/customers/${customer.id || customer.internal_id}/${cusProduct.product_id}`,
- queryParams: {
- id: cusProduct.id,
- entity_id: entity ? entity.id || entity.internal_id : undefined,
- version: String(cusProduct.product.version),
- },
- navigate,
- });
- };
-
- // Get initial prepaid values from the current subscription
- // Divide by billing_units to show values in "billing units" (e.g., 200 instead of 20000)
- const initialPrepaidOptions = useMemo(() => {
- return cusProduct.options.reduce(
- (acc, option) => {
- // Find the corresponding prepaid item to get billing_units
- const prepaidItem = prepaidItems.find(
- (item) =>
- (item.feature_id ?? item.feature?.internal_id) ===
- option.feature_id,
- );
- const billingUnits = prepaidItem?.billing_units ?? 1;
- // Divide by billing_units so input shows "200" not "20000"
- acc[option.feature_id] = Math.round(option.quantity / billingUnits);
- return acc;
- },
- {} as Record,
- );
- }, [cusProduct.options, prepaidItems]);
-
- const [prepaidOptions, setPrepaidOptions] = useState>(
- initialPrepaidOptions,
- );
-
- const [planCustomStartDate, _setPlanCustomStartDate] = useState<
- number | null
- >(null);
- const [planCustomEndDate, _setPlanCustomEndDate] = useState(
- null,
- );
- const [billingCycleAnchor, _setBillingCycleAnchor] = useState(
- null,
- );
-
- // Free trial state
- const [trialLength, setTrialLength] = useState(null);
- const [trialDuration, setTrialDuration] = useState(
- FreeTrialDuration.Day,
- );
- const [trialCardRequired, setTrialCardRequired] = useState(true);
- const [removeTrial, setRemoveTrial] = useState(false);
-
- // Version update state
- const [targetVersion, setTargetVersion] = useState(null);
-
- const handlePrepaidChange = (featureId: string, quantity: number) => {
- setPrepaidOptions((prev) => ({
- ...prev,
- [featureId]: quantity,
- }));
- };
-
- // Build the request body
- const requestBody = useMemo(() => {
- if (!customerId) return null;
-
- const body: UpdateSubscriptionV0Params = {
- customer_id: customerId,
- product_id: product?.id,
- entity_id: entityId,
- customer_product_id: cusProduct.id ?? cusProduct.internal_product_id,
- };
-
- // Add options only if they have changed from initial values
- // Multiply by billing_units to match what useUpdateSubscriptionBodyBuilder does
- if (prepaidItems.length > 0) {
- const options = prepaidItems
- .map((item) => {
- const featureId = item.feature_id ?? item.feature?.internal_id ?? "";
- const inputQuantity = prepaidOptions[featureId];
- const initialQuantity = initialPrepaidOptions[featureId];
- const billingUnits = item.billing_units ?? 1;
- // Only include if changed from initial value
- if (
- inputQuantity !== undefined &&
- inputQuantity !== null &&
- featureId &&
- inputQuantity !== initialQuantity
- ) {
- // Multiply by billing_units - input is in "billing units", API expects total quantity
- return {
- feature_id: featureId,
- quantity: inputQuantity * billingUnits,
- };
- }
- return null;
- })
- .filter(Boolean);
-
- if (options.length > 0) {
- body.options = options as Array<{
- feature_id: string;
- quantity: number;
- }>;
- }
- }
-
- // Add custom items if we have a customized product
- if (customizedProduct?.items) {
- body.items = customizedProduct.items;
- }
-
- // Add free trial logic:
- // 1. If removeTrial is true, pass null to remove the trial
- // 2. If trialLength is set, use the new trial settings
- // 3. If customizedProduct has free_trial, use that
- // 4. Otherwise, don't include free_trial (undefined = preserve existing)
- if (removeTrial) {
- body.free_trial = null;
- } else if (trialLength) {
- body.free_trial = {
- length: trialLength,
- duration: trialDuration,
- card_required: trialCardRequired,
- };
- }
-
- // Add custom plan dates if set (epoch milliseconds)
- if (planCustomStartDate) {
- body.plan_custom_start_date = planCustomStartDate;
- }
- if (planCustomEndDate) {
- body.plan_custom_end_date = planCustomEndDate;
- }
- if (billingCycleAnchor) {
- body.billing_cycle_anchor = billingCycleAnchor;
- }
-
- // Add version if set (for version updates)
- if (targetVersion !== null) {
- body.version = targetVersion;
- }
-
- return body;
- }, [
- customerId,
- product?.id,
- entityId,
- cusProduct.id,
- cusProduct.internal_product_id,
- prepaidItems,
- prepaidOptions,
- initialPrepaidOptions,
- customizedProduct?.items,
- customizedProduct?.free_trial,
- planCustomStartDate,
- planCustomEndDate,
- billingCycleAnchor,
- removeTrial,
- trialLength,
- trialDuration,
- trialCardRequired,
- targetVersion,
- ]);
-
- // Preview query - fires when body changes
- const previewQuery = useSubscriptionUpdatePreview({
- body: requestBody,
- enabled: !!requestBody,
- });
-
- // Compute freeTrial value for preview
- const previewFreeTrial = useMemo(() => {
- if (removeTrial) {
- return null;
- }
- if (trialLength) {
- return {
- length: trialLength,
- duration: trialDuration,
- card_required: trialCardRequired,
- unique_fingerprint: false,
- };
- }
- return undefined;
- }, [removeTrial, trialLength, trialDuration, trialCardRequired]);
-
- // Checkout preview query with free trial support
- const checkoutPreviewQuery = useUpdateSubscriptionPreview({
- customerId,
- product,
- entityId,
- prepaidOptions: prepaidOptions ?? undefined,
- version: product?.version,
- freeTrial: previewFreeTrial,
- });
-
- // Update mutation with invoice handling
- const updateMutation = useSubscriptionUpdate({
- customerId,
- onInvoiceCreated: (stripeInvoice) => {
- const invoiceLink = getStripeInvoiceLink({
- stripeInvoice,
- env,
- accountId: stripeAccount?.id,
- });
- window.open(invoiceLink, "_blank");
- },
- });
-
- const handleConfirm = () => {
- if (!requestBody) return;
- updateMutation.mutate({ body: requestBody, useInvoice: false });
- };
-
- const handleInvoiceUpdate = ({
- enableProductImmediately,
- }: {
- enableProductImmediately: boolean;
- }) => {
- if (!requestBody) return;
- updateMutation.mutate({
- body: requestBody,
- useInvoice: true,
- enableProductImmediately,
- });
- };
-
- return (
-
-
- }
- >
- Edit Plan
-
-
-
-
- {/* Current Customer Product Options */}
- {cusProduct.options && cusProduct.options.length > 0 ? (
-
-
-
Current Options
-
-
- {cusProduct.options.map((option) => (
-
- {option.feature_id}
- {option.quantity}
-
- ))}
-
-
- ) : null}
-
- {/* Request Body Display */}
-
-
-
Request Body
-
-
-
- customer_id:
- {requestBody?.customer_id}
-
-
- product_id:
- {requestBody?.product_id}
-
- {requestBody?.entity_id ? (
-
- entity_id:
- {requestBody.entity_id}
-
- ) : null}
- {requestBody?.options && requestBody.options.length > 0 ? (
-
- options:
-
- {requestBody.options
- .map((o) => `${o.feature_id}: ${o.quantity}`)
- .join(", ")}
-
-
- ) : null}
- {requestBody?.items && requestBody.items.length > 0 ? (
-
-
items:
-
- {requestBody.items.map((item, index) => {
- const display = getItemDisplay(item as ProductItem);
- return (
-
-
- {display.primary_text}
-
- {display.secondary_text ? (
-
- {display.secondary_text}
-
- ) : null}
-
- );
- })}
-
-
- ) : null}
- {requestBody?.free_trial === null ? (
-
- free_trial:
-
- null (removing trial)
-
-
- ) : requestBody?.free_trial ? (
-
- free_trial:
-
-
- {requestBody.free_trial.length}{" "}
- {requestBody.free_trial.duration}
- {Number(requestBody.free_trial.length) > 1 ? "s" : ""}
-
-
- (card_required:{" "}
- {requestBody.free_trial.card_required ? "true" : "false"})
-
-
-
- ) : null}
- {requestBody?.plan_custom_start_date ? (
-
-
- plan_custom_start_date:{" "}
-
-
- {requestBody.plan_custom_start_date}
-
-
- ) : null}
- {requestBody?.plan_custom_end_date ? (
-
- plan_custom_end_date:
-
- {requestBody.plan_custom_end_date}
-
-
- ) : null}
- {requestBody?.billing_cycle_anchor ? (
-
- billing_cycle_anchor:
-
- {requestBody.billing_cycle_anchor}
-
-
- ) : null}
-
-
- View raw JSON
-
-
- {JSON.stringify(requestBody, null, 2)}
-
-
-
-
-
- {/* Prepaid Editor */}
-
-
- {/* Free Trial Editor */}
-
-
- {/* Version Update Editor */}
-
-
-
Version Update
-
-
-
- Current Version:
-
- {cusProduct.product.version}
-
-
-
-
- Target Version:
-
- {
- const value = e.target.value;
- setTargetVersion(value === "" ? null : parseInt(value, 10));
- }}
- className="w-20 px-2 py-1 border border-border rounded text-sm bg-transparent font-mono"
- />
- {targetVersion !== null && (
- setTargetVersion(null)}
- className="text-xs text-t-secondary hover:text-t-primary"
- >
- Clear
-
- )}
-
-
- {targetVersion !== null ? (
-
- Will send: version: {targetVersion}
-
- ) : (
-
- No version param (quantity update only)
-
- )}
-
-
-
-
- {/* Checkout Preview Response (same as SubscriptionUpdateSheet) */}
-
-
-
Checkout Preview Response
-
- {checkoutPreviewQuery.isLoading ? (
-
- ) : (
-
- )}
-
-
- {/* Preview Result */}
-
-
- {/* Update Result */}
-
-
-
- {/* Footer Actions */}
-
- {/* Send an Invoice Button with Dropdown */}
-
-
-
- Send an Invoice
-
-
-
-
-
- handleInvoiceUpdate({ enableProductImmediately: true })
- }
- className="px-4 py-3 text-left text-sm hover:bg-accent"
- >
- Enable plan immediately
-
- Enable the plan immediately and redirect to Stripe to finalize
- the invoice
-
-
-
- handleInvoiceUpdate({ enableProductImmediately: false })
- }
- className="px-4 py-3 text-left text-sm hover:bg-accent border-t"
- >
- Enable plan after payment
-
- Generate an invoice link for the customer. The plan will be
- enabled after they pay the invoice
-
-
-
-
-
-
- {/* Confirm Update Button */}
-
- {updateMutation.isPending ? "Updating..." : "Confirm Update"}
-
-
- {/* Refresh Preview Button */}
-
previewQuery.refetch()}
- disabled={!requestBody || previewQuery.isLoading}
- >
- Refresh Preview
-
-
-
- );
-}
-
-/**
- * Main sheet component.
- *
- * To use this sheet, you need to:
- * 1. Add "subscription-update-v2" to the SheetType union in useSheetStore.ts
- * 2. Add a case for it in CustomerSheets.tsx
- * 3. Or, for quick testing, temporarily replace SubscriptionUpdateSheet import
- *
- * Example trigger:
- * setSheet({
- * type: "subscription-update-v2",
- * itemId: cusProduct.id,
- * data: { customizedProduct: product } // optional
- * })
- */
-export function SubscriptionUpdateTestSheet() {
- const itemId = useSheetStore((s) => s.itemId);
- const sheetData = useSheetStore((s) => s.data);
-
- const { cusProduct, productV2 } = useSubscriptionById({ itemId });
-
- const customizedProduct = sheetData?.customizedProduct as
- | FrontendProduct
- | undefined;
-
- if (!cusProduct) {
- return (
-
-
-
- No customer product found for itemId: {itemId}
-
-
- );
- }
-
- if (!productV2) {
- return (
-
-
-
- );
- }
-
- return (
-
- );
-}
diff --git a/vite/src/views/customers2/components/table/customer-feature-usage/CustomerFeatureBalance.tsx b/vite/src/views/customers2/components/table/customer-feature-usage/CustomerFeatureBalance.tsx
deleted file mode 100644
index 852033948..000000000
--- a/vite/src/views/customers2/components/table/customer-feature-usage/CustomerFeatureBalance.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import {
- AllowanceType,
- FeatureType,
- type FullCusEntWithFullCusProduct,
-} from "@autumn/shared";
-import { useCustomerContext } from "../../../customer/CustomerContext";
-
-const BalanceWrapper = ({ children }: { children: React.ReactNode }) => {
- return (
-
- {children}
-
- );
-};
-
-export const CusEntBalance = ({
- cusEnt,
-}: {
- cusEnt: FullCusEntWithFullCusProduct;
-}) => {
- const { entityId } = useCustomerContext();
- const ent = cusEnt.entitlement;
- const feature = ent.feature;
- const rollovers = cusEnt.rollovers;
-
- if (feature.type === FeatureType.Boolean) {
- return null;
- }
-
- if (ent.allowance_type === AllowanceType.Unlimited) {
- return Unlimited ;
- }
-
- if (entityId && cusEnt.entities?.[entityId]) {
- const entityBalance = cusEnt.entities?.[entityId]?.balance;
- const rolloverAmount = rollovers
- .filter((x) => x.entities?.[entityId])
- .reduce(
- (sum, rollover) => sum + (rollover.entities?.[entityId]?.balance || 0),
- 0,
- );
-
- return (
-
-
- {entityBalance}
- {rolloverAmount > 0 && (
-
- {" + "}
- {rolloverAmount} (rolled over)
-
- )}
-
-
- );
- }
-
- if (cusEnt.entities) {
- const totalBalance = Object.values(cusEnt.entities).reduce(
- (sum, entity) => sum + (entity.balance || 0),
- 0,
- );
-
- const rolloverAmount = cusEnt.rollovers.reduce((sum, rollover) => {
- return (
- sum +
- Object.values(rollover.entities).reduce(
- (entitySum: number, entity: { balance: number }) =>
- entitySum + (entity.balance || 0),
- 0,
- )
- );
- }, 0);
-
- return (
-
-
- {totalBalance}
- {rolloverAmount > 0 && (
-
- {" + "}
- {rolloverAmount} (rolled over)
-
- )}
-
-
- );
- }
-
- const rolloverAmount = cusEnt.rollovers.reduce((sum, rollover) => {
- return sum + (rollover.balance || 0);
- }, 0);
-
- return (
-
-
- {cusEnt.balance}
- {rolloverAmount > 0 && (
- + {rolloverAmount} (rolled over)
- )}
- {cusEnt.replaceables.length > 0 && (
-
- {` (${cusEnt.replaceables.length} free)`}
-
- )}
-
-
- );
-};
diff --git a/vite/src/views/customers2/components/table/customer-invoices/CustomerInvoicesShowAllButton.tsx b/vite/src/views/customers2/components/table/customer-invoices/CustomerInvoicesShowAllButton.tsx
deleted file mode 100644
index 3c758bed9..000000000
--- a/vite/src/views/customers2/components/table/customer-invoices/CustomerInvoicesShowAllButton.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { ArrowRightIcon } from "@phosphor-icons/react";
-import { useNavigate } from "react-router";
-import { Button } from "@/components/v2/buttons/Button";
-import { useCustomerContext } from "@/views/customers2/customer/CustomerContext";
-
-export function CustomerInvoicesShowAllButton() {
- const navigate = useNavigate();
- const { customer } = useCustomerContext();
- return (
- {
- navigate(`/customers/${customer.id}/invoices`);
- }}
- >
- Show all
-
-
- );
-}
diff --git a/vite/src/views/customers2/components/table/customer-products/AttachProductDropdown.tsx b/vite/src/views/customers2/components/table/customer-products/AttachProductDropdown.tsx
deleted file mode 100644
index 04c25092f..000000000
--- a/vite/src/views/customers2/components/table/customer-products/AttachProductDropdown.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { PlusIcon } from "@phosphor-icons/react";
-import { Search } from "lucide-react";
-import { useState } from "react";
-import { useNavigate } from "react-router";
-import SmallSpinner from "@/components/general/SmallSpinner";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { Button } from "@/components/v2/buttons/Button";
-import { Input } from "@/components/v2/inputs/Input";
-import { navigateTo } from "@/utils/genUtils";
-import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
-import { useCustomerContext } from "@/views/customers2/customer/CustomerContext";
-
-export function AttachProductDropdown() {
- const { entityId } = useCustomerContext();
- const { products, customer } = useCusQuery();
-
- const [searchQuery, setSearchQuery] = useState("");
- const [open, setOpen] = useState(false);
- const navigate = useNavigate();
-
- const filteredProducts = products.filter((product: ProductV2) => {
- if (product.archived) return false;
-
- return product.name.toLowerCase().includes(searchQuery.toLowerCase());
- });
-
- const handleAddProduct = async (productId: string) => {
- navigateTo(
- `/customers/${customer.id || customer.internal_id}/${productId}${
- entityId ? `?entity_id=${entityId}` : ""
- }`,
- navigate,
- );
- };
-
- return (
-
-
-
-
- Attach Plan
-
-
-
-
-
- setSearchQuery(e.target.value)}
- onClick={(e) => e.stopPropagation()}
- onKeyDown={(e) => e.stopPropagation()}
- autoFocus
- />
-
- {filteredProducts.length === 0 ? (
- No new products found
- ) : (
- filteredProducts.map((product: ProductV2) => (
-
- ))
- )}
-
-
- );
-}
-
-const DropdownProductItem = ({
- product,
- handleAddProduct,
-}: {
- product: ProductV2;
- handleAddProduct: (productId: string) => Promise;
-}) => {
- const [isLoading, setIsLoading] = useState(false);
- return (
- {
- e.stopPropagation();
- e.preventDefault();
- setIsLoading(true);
- await handleAddProduct(product.id);
- }}
- >
- {isLoading && }
-
- {product.name}
- ({product.id})
-
-
- );
-};
diff --git a/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsSelectFeatures.tsx b/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsSelectFeatures.tsx
deleted file mode 100644
index aed42f4fb..000000000
--- a/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsSelectFeatures.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Check, ChevronDown } from "lucide-react";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { Button } from "@/components/v2/buttons/Button";
-import { cn } from "@/lib/utils";
-
-export function CustomerUsageAnalyticsSelectFeatures({
- availableFeatures,
- selectedFeatures,
- setSelectedFeatures,
-}: {
- availableFeatures: string[];
- selectedFeatures: string[];
- setSelectedFeatures: (features: string[]) => void;
-}) {
- const toggleFeature = (feature: string) => {
- if (selectedFeatures.includes(feature)) {
- setSelectedFeatures(selectedFeatures.filter((f) => f !== feature));
- } else {
- setSelectedFeatures([...selectedFeatures, feature]);
- }
- };
-
- const displayText =
- selectedFeatures.length === availableFeatures.length
- ? "All features"
- : selectedFeatures.length === 1
- ? selectedFeatures[0]
- : `${selectedFeatures.length} features`;
-
- return (
-
-
-
- {displayText}
-
-
-
-
- {availableFeatures.map((feature) => {
- const isSelected = selectedFeatures.includes(feature);
- return (
- toggleFeature(feature)}
- >
-
- {feature}
-
- );
- })}
-
-
- );
-}
diff --git a/vite/src/views/customers2/hooks/useCustomerRawEvents.ts b/vite/src/views/customers2/hooks/useCustomerRawEvents.ts
deleted file mode 100644
index cd7d400b0..000000000
--- a/vite/src/views/customers2/hooks/useCustomerRawEvents.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { ErrCode } from "@autumn/shared";
-import { useParams } from "react-router";
-import { usePostSWR } from "@/services/useAxiosSwr";
-
-export const useCustomerRawEvents = ({
- interval = "30d",
-}: {
- interval?: "24h" | "7d" | "30d" | "90d";
-}) => {
- const { customer_id } = useParams();
-
- const { data, isLoading, error } = usePostSWR({
- url: `/query/raw`,
- data: {
- customer_id: customer_id || null,
- interval,
- },
- queryKey: ["customer-raw-events", customer_id, interval],
- options: {
- refreshInterval: 0,
- onError: (error) => {
- if (error.code === ErrCode.ClickHouseDisabled) {
- return error;
- }
- },
- },
- });
-
- return {
- rawEvents: data?.rawEvents?.data || [],
- isLoading,
- error: error?.code === ErrCode.ClickHouseDisabled ? null : error,
- };
-};
diff --git a/vite/src/views/developer/api-keys/components/APIKeyTable.tsx b/vite/src/views/developer/api-keys/components/APIKeyTable.tsx
deleted file mode 100644
index 5d03c0a42..000000000
--- a/vite/src/views/developer/api-keys/components/APIKeyTable.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { ApiKey } from "@autumn/shared";
-import { useMemo } from "react";
-import { Table } from "@/components/general/table";
-import { useProductTable } from "@/views/products/hooks/useProductTable";
-import { createAPIKeyTableColumns } from "./APIKeyTableColumns";
-
-export const APIKeyTable = ({ apiKeys }: { apiKeys: ApiKey[] }) => {
- const columns = useMemo(() => createAPIKeyTableColumns(), []);
-
- const apiKeyTable = useProductTable({
- data: apiKeys || [],
- columns,
- options: {
- globalFilterFn: "includesString",
- enableGlobalFilter: true,
- },
- });
-
- const enableSorting = false;
-
- const emptyStateText =
- "API keys are used to securely authenticate your requests from your server.";
-
- return (
-
-
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/main-sidebar/SidebarGroup.tsx b/vite/src/views/main-sidebar/SidebarGroup.tsx
deleted file mode 100644
index f1fc8f4db..000000000
--- a/vite/src/views/main-sidebar/SidebarGroup.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { cn } from "@/lib/utils";
-import { keyToTitle } from "@/utils/formatUtils/formatTextUtils";
-import { NavButton } from "./NavButton";
-import { useSidebarContext } from "./SidebarContext";
-
-export const SidebarGroup = ({
- value,
- productGroup,
- subTabs,
-}: {
- value: string;
- productGroup: boolean;
- subTabs: { title: string; value: string }[];
-}) => {
- const { expanded } = useSidebarContext();
- return (
-
- {expanded && (
-
- {subTabs.map((subTab, index) => (
-
- ))}
-
- )}
-
- );
-};
diff --git a/vite/src/views/main-sidebar/SidebarTop.tsx b/vite/src/views/main-sidebar/SidebarTop.tsx
deleted file mode 100644
index 9ae89401b..000000000
--- a/vite/src/views/main-sidebar/SidebarTop.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { OrgDropdown } from "./components/OrgDropdown";
-
-export const SidebarTop = () => {
- return (
-
-
-
- );
-};
diff --git a/vite/src/views/main-sidebar/components/UserButton.tsx b/vite/src/views/main-sidebar/components/UserButton.tsx
deleted file mode 100644
index e8ecc4856..000000000
--- a/vite/src/views/main-sidebar/components/UserButton.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { LogOut, Settings } from "lucide-react";
-import { useState } from "react";
-import { useNavigate } from "react-router";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { deleteUser, signOut, useSession } from "@/lib/auth-client";
-
-export const UserButton = () => {
- const navigate = useNavigate();
- const { data: session } = useSession();
- const user = session?.user;
- const [isLoading, setIsLoading] = useState(false);
-
- if (!user) return null;
-
- const handleSignOut = async () => {
- setIsLoading(true);
- try {
- await signOut();
- } catch (error) {
- console.error("Sign out failed:", error);
- } finally {
- setIsLoading(false);
- }
- };
-
- const handleDeleteAccount = async () => {
- await deleteUser();
- };
-
- const getInitials = (name: string) => {
- return name
- .split(" ")
- .map((part) => part[0])
- .join("")
- .toUpperCase()
- .slice(0, 2);
- };
-
- const displayName = user.name || user.email?.split("@")[0] || "User";
- const initials = getInitials(displayName);
-
- return (
-
-
-
- {/* Shimmer effect */}
-
-
- {/* Avatar content */}
- {user.image ? (
-
- ) : (
- {initials}
- )}
-
-
-
-
- navigate("/settings")}
- className="cursor-pointer flex items-center gap-2 text-sm rounded-sm text-t2"
- >
-
- Organization Settings
-
-
-
-
- {isLoading ? "Signing out..." : "Sign out"}
-
-
- Delete Account
-
-
-
- );
-};
diff --git a/vite/src/views/main-sidebar/components/deploy-button/DeployToProdButton.tsx b/vite/src/views/main-sidebar/components/deploy-button/DeployToProdButton.tsx
deleted file mode 100644
index 90cf23a15..000000000
--- a/vite/src/views/main-sidebar/components/deploy-button/DeployToProdButton.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { ArrowRightIcon } from "@phosphor-icons/react";
-import { useState } from "react";
-import { IconButton } from "@/components/v2/buttons/IconButton";
-import { cn } from "@/lib/utils";
-import { DeployToProdDialog } from "./DeployToProdDialog";
-
-interface DeployToProdButtonProps {
- expanded: boolean;
-}
-
-export const DeployToProdButton = ({ expanded }: DeployToProdButtonProps) => {
- const [showDeployDialog, setShowDeployDialog] = useState(false);
-
- return (
- <>
-
- }
- iconOrientation={expanded ? "right" : "center"}
- onClick={() => setShowDeployDialog(true)}
- className={cn(
- "w-full overflow-hidden",
- expanded ? "justify-between" : "justify-center pl-3!",
- )}
- >
-
- Deploy to Production
-
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/onboarding/ConnectStripe.tsx b/vite/src/views/onboarding/ConnectStripe.tsx
deleted file mode 100644
index 7a5e852ef..000000000
--- a/vite/src/views/onboarding/ConnectStripe.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-"use client";
-
-import { AppEnv } from "@autumn/shared";
-import { faStripeS } from "@fortawesome/free-brands-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { useState } from "react";
-import { useNavigate, useSearchParams } from "react-router";
-import { toast } from "sonner";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import SmallSpinner from "@/components/general/SmallSpinner";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/v2/selects/Select";
-import { useOrg } from "@/hooks/common/useOrg";
-import { cn } from "@/lib/utils";
-import { OrgService } from "@/services/OrgService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { stripeCurrencyCodes } from "@/utils/constants/stripeCurrencyCodes";
-import { getBackendErr } from "@/utils/genUtils";
-
-function ConnectStripe({
- className,
- onboarding,
-}: {
- className?: string;
- onboarding?: boolean;
-}) {
- const axiosInstance = useAxiosInstance({ env: AppEnv.Live });
-
- const navigate = useNavigate();
- const { org, mutate, isLoading: isOrgLoading } = useOrg();
- const [searchParams] = useSearchParams();
- const redirect = searchParams.get("redirect");
-
- const [testApiKey, setTestApiKey] = useState("");
- const [liveApiKey, setLiveApiKey] = useState("");
- const [successUrl, setSuccessUrl] = useState("https://useautumn.com");
- const [defaultCurrency, setDefaultCurrency] = useState("USD");
- const [isLoading, setIsLoading] = useState(false);
-
- const handleConnectStripe = async () => {
- if (!testApiKey || !successUrl || !defaultCurrency) {
- toast.error("Please fill in all fields");
- return;
- }
-
- if (!successUrl.startsWith("http") && !successUrl.startsWith("https")) {
- toast.error("Success URL must start with http or https");
- return;
- }
-
- setIsLoading(true);
-
- try {
- await OrgService.connectStripe(axiosInstance, {
- testApiKey,
- liveApiKey: onboarding ? testApiKey : liveApiKey,
- successUrl,
- defaultCurrency,
- });
-
- toast.success("Successfully connected to Stripe");
- await mutate();
- if (redirect && !onboarding) {
- navigate(redirect);
- }
- } catch (error) {
- console.log("Failed to connect Stripe", error);
- toast.error(getBackendErr(error, "Failed to connect Stripe"));
- }
-
- setIsLoading(false);
- };
-
- const [isDisconnecting, setIsDisconnecting] = useState(false);
- const handleDisconnectStripe = async () => {
- try {
- setIsDisconnecting(true);
- await OrgService.disconnectStripe(axiosInstance);
- await mutate();
- toast.success("Successfully disconnected from Stripe");
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to disconnect Stripe"));
- }
- setIsDisconnecting(false);
- };
-
- if (isOrgLoading) {
- return ;
- }
-
- if (org?.stripe_connected) {
- return (
-
-
Stripe Connected ✅
-
- Disconnect Stripe
-
-
- );
- }
-
- return (
-
-
-
- Stripe Test Secret API Key
- setTestApiKey(e.target.value)}
- />
-
-
- {!onboarding && (
-
- Stripe Live Secret API Key
- setLiveApiKey(e.target.value)}
- />
-
- )}
-
-
- Success URL after Stripe payment
- setSuccessUrl(e.target.value)}
- />
-
-
- Currency
-
-
-
-
-
- }
- >
- Connect Stripe
-
-
-
-
- );
-}
-
-export default ConnectStripe;
-
-export const CurrencySelect = ({
- defaultCurrency,
- setDefaultCurrency,
- className,
- disabled,
-}: {
- defaultCurrency: string;
- setDefaultCurrency: (currency: string) => void;
- className?: string;
- disabled?: boolean;
-}) => {
- return (
- setDefaultCurrency(value.toUpperCase())}
- disabled={disabled}
- >
-
-
-
-
- {stripeCurrencyCodes.map((currency) => (
-
- {currency.currency} - {currency.code}
-
- ))}
-
-
- );
-};
diff --git a/vite/src/views/onboarding/ConnectStripeOAuth.tsx b/vite/src/views/onboarding/ConnectStripeOAuth.tsx
deleted file mode 100644
index f4a2c11bd..000000000
--- a/vite/src/views/onboarding/ConnectStripeOAuth.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export default function ConnectStripeOAuth() {}
diff --git a/vite/src/views/onboarding/OnboardingView.tsx b/vite/src/views/onboarding/OnboardingView.tsx
deleted file mode 100644
index 793ae02c5..000000000
--- a/vite/src/views/onboarding/OnboardingView.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-"use client";
-
-import { AutumnProvider } from "autumn-js/react";
-import { Book } from "lucide-react";
-import { useEffect, useRef, useState } from "react";
-import { useSearchParams } from "react-router";
-import Step from "@/components/general/OnboardingStep";
-import { useSession } from "@/lib/auth-client";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useAxiosSWR } from "@/services/useAxiosSwr";
-import { useEnv } from "@/utils/envUtils";
-import LoadingScreen from "../general/LoadingScreen";
-import AttachProduct from "./onboarding-steps/AttachProduct";
-import AutumnProviderStep from "./onboarding-steps/AutumnProvider";
-import CheckAccessStep from "./onboarding-steps/CheckAccess";
-import { ConnectStripeStep } from "./onboarding-steps/ConnectStripe";
-import { CreateSecretKey } from "./onboarding-steps/CreateSecretKey";
-import EnvStep from "./onboarding-steps/Env";
-import Install from "./onboarding-steps/Install";
-import IntegrationGuideStep from "./onboarding-steps/IntegrationGuide";
-import MountHandler from "./onboarding-steps/MountHandler";
-import { ProductList } from "./onboarding-steps/ProductList";
-import { SampleApp } from "./onboarding-steps/SampleApp";
-
-function OnboardingView() {
- const env = useEnv();
-
- const [searchParams] = useSearchParams();
- const token = searchParams.get("token");
- const [apiKey, setApiKey] = useState("");
- const [showIntegrationSteps, setShowIntegrationSteps] = useState(false);
- const [loading, setLoading] = useState(true);
- const { data } = useSession();
-
- const axiosInstance = useAxiosInstance();
- const hasHandledToken = useRef(false);
-
- const orgId = data?.session?.activeOrganizationId;
-
- const {
- data: productData,
- mutate: productMutate,
- isLoading: productLoading,
- } = useAxiosSWR({
- url: `/products/data`,
- env: env,
- withAuth: true,
- });
-
- useEffect(() => {
- const handleToken = async () => {
- try {
- await axiosInstance.post("/onboarding", {
- token,
- });
-
- await productMutate();
- } catch (error) {
- console.error(error);
- } finally {
- setLoading(false);
- }
- };
-
- if (token && !hasHandledToken.current) {
- hasHandledToken.current = true;
- handleToken();
- }
- }, [searchParams, token, axiosInstance, productMutate]);
-
- useEffect(() => {
- if (orgId && !token) {
- setLoading(false);
- }
- }, [orgId, token]);
-
- if (loading || productLoading) {
- return ;
- }
-
- return (
-
-
- {productData && (
- <>
-
-
-
-
-
-
-
- {showIntegrationSteps && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You're all set! Autumn is tracking your customers'
- usage, what they have access to and how much they should
- be billed. Go to the Customers tab to manage
- your users, and read our{" "}
-
- Documentation
-
- {" "}
- to learn more about what you can do with Autumn.
-
- }
- >
-
-
-
- )}
- >
- )}
-
-
- );
-}
-
-export default OnboardingView;
diff --git a/vite/src/views/onboarding/StripePage.tsx b/vite/src/views/onboarding/StripePage.tsx
deleted file mode 100644
index edaf584fe..000000000
--- a/vite/src/views/onboarding/StripePage.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import ConnectStripe from "./ConnectStripe";
-
-export default function StripePage() {
- return (
- <>
-
-
-
-
- Please connect your Stripe account
-
-
- Your credentials will be encrypted and stored safely
-
-
-
-
-
- >
- );
-}
diff --git a/vite/src/views/onboarding/components/CodeBlock.tsx b/vite/src/views/onboarding/components/CodeBlock.tsx
deleted file mode 100644
index e9bc18b88..000000000
--- a/vite/src/views/onboarding/components/CodeBlock.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import Prism from "prismjs";
-import "prism-themes/themes/prism-vsc-dark-plus.css";
-import "prismjs/components/prism-json";
-import "prismjs/components/prism-bash";
-import "prismjs/components/prism-go";
-import "prismjs/components/prism-typescript";
-import "prismjs/components/prism-python";
-import "prismjs/components/prism-ruby";
-import "prismjs/components/prism-dart";
-import "prismjs/components/prism-rust";
-import "prismjs/components/prism-c";
-import { useEffect, useRef, useState } from "react";
-import { cn } from "@/lib/utils";
-import OptionButton, { CopyButton } from "./OptionButton";
-// import OptionButton from './OptionButton'
-// import CopyButton from '../CopyButton/CopyButton'
-
-export type Language =
- | "javascript"
- | "go"
- | "python"
- | "typescript"
- | "ruby"
- | "dart"
- | "rust"
- | "json"
- | "c"
- | "bash";
-
-export type CodeSnippetDetails = {
- title: string;
- language: Language;
- displayLanguage: Language;
- content: string;
-};
-
-interface CodeBlockProps {
- snippets: CodeSnippetDetails[];
- className?: string;
-}
-
-const CodeBlock = ({ snippets, className }: CodeBlockProps) => {
- const codeRef = useRef(null);
- const [index, setIndex] = useState(0);
-
- const selected = snippets[index];
-
- useEffect(() => {
- if (codeRef.current) {
- Prism.highlightElement(codeRef.current);
- }
- }, [snippets, selected]);
-
- return (
-
-
-
- {snippets.map((snippet, i) => (
- setIndex(i)}
- />
- ))}
-
-
-
-
-
-
- {selected.content}
-
-
-
-
- );
-};
-
-export default CodeBlock;
diff --git a/vite/src/views/onboarding/components/OptionButton.tsx b/vite/src/views/onboarding/components/OptionButton.tsx
deleted file mode 100644
index 1d419b0c5..000000000
--- a/vite/src/views/onboarding/components/OptionButton.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Check, Copy } from "lucide-react";
-import { useState } from "react";
-import { cn } from "@/lib/utils";
-
-export default function OptionButton({
- title,
- selected,
- onClick,
-}: {
- title: string;
- selected: boolean;
- onClick: () => void;
-}) {
- return (
-
- {title}
-
- );
-}
-
-export function CopyButton({ content }: { content: string }) {
- const [copied, setCopied] = useState(false);
-
- const handleCopy = async () => {
- await navigator.clipboard.writeText(content);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- };
-
- return (
-
- {copied ? : }
-
- );
-}
diff --git a/vite/src/views/onboarding/hooks/useCreateOrg.tsx b/vite/src/views/onboarding/hooks/useCreateOrg.tsx
deleted file mode 100644
index 5c51cb6c3..000000000
--- a/vite/src/views/onboarding/hooks/useCreateOrg.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useEffect, useRef } from "react";
-import { toast } from "sonner";
-import { useOrg } from "@/hooks/common/useOrg";
-import {
- authClient,
- useListOrganizations,
- useSession,
-} from "@/lib/auth-client";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { slugify } from "@/utils/formatUtils/formatTextUtils";
-
-export const useCreateOrg = ({
- productMutate,
-}: {
- productMutate: () => Promise;
-}) => {
- const axiosInstance = useAxiosInstance();
- const hasCreatedOrg = useRef(false);
-
- const { data: session } = useSession();
- const { data: organizations, isPending } = useListOrganizations();
- const { mutate } = useOrg();
-
- const getOrgSlug = () => {
- const randomDigits = Math.floor(10000000 + Math.random() * 90000000);
- return `${slugify(session?.user!.name || "org")}_${randomDigits}`;
- };
-
- useEffect(() => {
- const createDefaultOrg = async () => {
- if (hasCreatedOrg.current || isPending || !session?.user) return;
- hasCreatedOrg.current = true;
- // Either set first org active, or create a new org
- try {
- if (organizations && organizations.length > 0) {
- await authClient.organization.setActive({
- organizationId: organizations[0]?.id,
- });
- } else {
- const { data, error } = await authClient.organization.create({
- name: `${session?.user.name}'s Org`,
- slug: getOrgSlug(),
- });
-
- if (error) throw error;
-
- await authClient.organization.setActive({
- organizationId: data?.id,
- });
-
- await mutate();
- await productMutate();
- }
- } catch (error: any) {
- toast.error(`Error initializing org: ${error.message}`);
- }
- };
- createDefaultOrg();
-
- // if (!org && setActive) {
- // createDefaultOrg();
- // }
- }, [session, axiosInstance, productMutate]);
-};
diff --git a/vite/src/views/onboarding/onboarding-steps/AttachProduct.tsx b/vite/src/views/onboarding/onboarding-steps/AttachProduct.tsx
deleted file mode 100644
index 4ec1bef92..000000000
--- a/vite/src/views/onboarding/onboarding-steps/AttachProduct.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import type { Product } from "@autumn/shared";
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { useState } from "react";
-import Step from "@/components/general/OnboardingStep";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import CodeBlock from "../components/CodeBlock";
-
-const attachCodeNextjs = (productId: string, apiKey: string) => {
- return `// app/page.tsx
-
- import { useCustomer } from 'autumn-js/react';
-
- export default function PurchaseButton() {
- const { attach } = useCustomer();
-
- return (
- {
- await attach({ productId: "${productId || "pro"}" });
- }}
- >
- Upgrade to ${productId || "Pro"}
-
- );
- }
-`;
-};
-
-export default function AttachProduct({
- products,
- apiKey,
- number,
-}: {
- products: Product[];
- apiKey: string;
- number: number;
-}) {
- const [selectedProductId, setSelectedProductId] = useState(
- products.length > 0 ? products[0].id! : "",
- );
-
- return (
-
-
- The{" "}
-
-
- /attach
-
-
- {" "}
- endpoint will return a Stripe Checkout URL. Once paid, the user will
- be granted access to the features you defined above.
-
-
-
-
-
-
-
- {products.map((product) => (
-
- {product.name || product.id}
-
- ))}
-
-
-
-
- }
- >
-
-
- );
-}
diff --git a/vite/src/views/onboarding/onboarding-steps/AutumnProvider.tsx b/vite/src/views/onboarding/onboarding-steps/AutumnProvider.tsx
deleted file mode 100644
index 0286aed66..000000000
--- a/vite/src/views/onboarding/onboarding-steps/AutumnProvider.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { ArrowUpRightFromSquare } from "lucide-react";
-import Step from "@/components/general/OnboardingStep";
-import CodeBlock from "../components/CodeBlock";
-
-const react = () => {
- return `import { AutumnProvider } from "autumn-js/react";
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode,
-}) {
- return (
-
-
-
- {children}
-
-
-
- );
-}
-`;
-};
-
-export default function AutumnProviderStep({ number }: { number: number }) {
- return (
-
- Wrap your root layout with the AutumnProvider component, and pass in
- your backend URL.
-
- }
- >
- {/* */}
- {/*
- You can do this directly from your frontend using the Publishable
- API Key.
-
*/}
-
- {/*
*/}
-
- {/*
*/}
- {/*
*/}
-
- );
-}
diff --git a/vite/src/views/onboarding/onboarding-steps/CheckAccess.tsx b/vite/src/views/onboarding/onboarding-steps/CheckAccess.tsx
deleted file mode 100644
index d87ac5aa1..000000000
--- a/vite/src/views/onboarding/onboarding-steps/CheckAccess.tsx
+++ /dev/null
@@ -1,240 +0,0 @@
-import type { Feature, Product } from "@autumn/shared";
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { useState } from "react";
-import { CodeDisplay } from "@/components/general/CodeDisplay";
-import Step from "@/components/general/OnboardingStep";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { FeatureTypeBadge } from "@/views/products/features/components/FeatureTypeBadge";
-import CodeBlock from "../components/CodeBlock";
-
-const checkAccessCode = (
- apiKey: string,
- id: string,
- isProduct: boolean,
-) => `// app/page.tsx
-
-import { useCustomer } from "autumn-js/react";
-
-export default function CheckAccess() {
- const { customer, check } = useCustomer();
-
- const handleCheckAccess = () => {
- const { data } = check({ ${isProduct ? "productId" : "featureId"}: "${id}" });
-
- ${
- isProduct
- ? `// Check if customer has an active product
- if (data?.allowed) {
- alert("You have access to ${id}");
- } else {
- alert("You don't have access to ${id}");
- }`
- : `// Check feature balance
- if (data?.allowed) {
- alert("You have access to ${id}. Balance: " + data?.balance);
- } else {
- alert("You don't have access to ${id}");
- }`
- }
- };
-
- return Check Access ;
-}
-`;
-
-const checkAccessCodeTypescript = (
- apiKey: string,
- id: string,
- isProduct: boolean,
-) => `import { Autumn } from "autumn-js";
-
-const autumn = new Autumn({ secretKey: "am_sk_..." });
-
-let { data } = await autumn.check({
- customer_id: "user_123",
- ${isProduct ? "product_id" : "feature_id"}: "${id}",
-});
-
-if (!data?.allowed) {
- console.log("User does not have access to ${id}");
-}
-`;
-
-const usageEventCode = (
- apiKey: string,
- id: string,
- isProduct: boolean,
-) => `import { Autumn } from "autumn-js";
-
-const autumn = new Autumn({ secretKey: "am_sk_..." });
-
-autumn.track({
- customer_id: "user_123",
- "feature_id": "${id}",
- "value": 1
-});
-`;
-
-export default function CheckAccessStep({
- apiKey,
- features,
- number,
- products,
-}: {
- apiKey: string;
- features: Feature[];
- number: number;
- products: Product[];
-}) {
- const [isProduct, setIsProduct] = useState(true);
- const [selectedFeature, setSelectedFeature] = useState(
- features.length > 0 ? features[0] : undefined,
- );
- const [selectedProductId, setSelectedProductId] = useState(
- products.length > 0 ? products[0].id! : "",
- );
-
- const selectedId = isProduct ? selectedProductId : selectedFeature?.id || "";
- const selectedItems = isProduct ? products : features;
-
- return (
-
-
- setIsProduct(true)}
- className={`px-3 py-1 text-sm rounded-xs ${
- isProduct
- ? "bg-blue-500 text-white"
- : "bg-gray-200 text-gray-700 hover:bg-gray-300"
- }`}
- >
- Product
-
- setIsProduct(false)}
- className={`px-3 py-1 text-sm rounded-xs ${
- !isProduct
- ? "bg-blue-500 text-white"
- : "bg-gray-200 text-gray-700 hover:bg-gray-300"
- }`}
- >
- Feature
-
-
-
- Check whether a customer can access a{" "}
- {isProduct ? "plan" : "feature"} by calling the{" "}
-
-
- /check
-
-
- {" "}
- endpoint.
-
- {!isProduct && selectedFeature?.type === "metered" && (
-
- If it's a metered (usage-based) feature, send us the usage
- data by calling the{" "}
-
-
- /track
-
-
- {" "}
- endpoint.
-
- )}
-
-
- setSelectedFeature(features.find((f) => f.id === value))
- }
- >
-
- {selectedId ? (
-
-
- {isProduct
- ? products.find((p) => p.id === selectedId)?.name
- : selectedFeature?.name}
-
- {!isProduct && selectedFeature && (
-
- )}
-
- ) : (
-
- )}
-
-
- {selectedItems.map((item) => (
-
-
- {item.name}
-
-
- ))}
-
-
-
- >
- }
- >
-
-
- {isProduct === false && selectedFeature?.type === "metered" && (
-
- )}
-
-
- );
-}
diff --git a/vite/src/views/onboarding/onboarding-steps/ConnectStripe.tsx b/vite/src/views/onboarding/onboarding-steps/ConnectStripe.tsx
deleted file mode 100644
index 386ed8b91..000000000
--- a/vite/src/views/onboarding/onboarding-steps/ConnectStripe.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { AppEnv } from "@autumn/shared";
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { useState } from "react";
-import { toast } from "sonner";
-import Step from "@/components/general/OnboardingStep";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { OrgService } from "@/services/OrgService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import ConnectStripe, { CurrencySelect } from "../ConnectStripe";
-
-export const ConnectStripeStep = ({
- mutate,
- productData,
- number,
-}: {
- mutate: () => Promise;
- productData: any;
- number: number;
-}) => {
- const [testApiKey, setTestApiKey] = useState("");
-
- const [loading, setLoading] = useState(false);
-
- const axiosInstance = useAxiosInstance({ env: AppEnv.Live });
-
- const handleConnectStripe = async () => {
- setLoading(true);
- try {
- await OrgService.connectStripe(axiosInstance, {
- testApiKey,
- liveApiKey: testApiKey,
- successUrl: `https://useautumn.com`,
- });
-
- toast.success("Successfully connected to Stripe");
- await mutate();
- } catch (error) {
- console.log("Failed to connect Stripe", error);
- toast.error(getBackendErr(error, "Failed to connect Stripe"));
- }
-
- setLoading(false);
- };
-
- // console.log("productData", productData);
- const stripeConnected = productData?.org.stripe_connected;
- return (
-
-
- Paste in your{" "}
-
- Stripe Test Secret Key
-
- {" "}
-
-
- }
- >
-
- setTestApiKey(e.target.value)}
- disabled={stripeConnected}
- />
- {/* */}
-
- Connect Stripe
-
-
-
- {/* */}
-
- );
-};
diff --git a/vite/src/views/onboarding/onboarding-steps/CreateOrg.tsx b/vite/src/views/onboarding/onboarding-steps/CreateOrg.tsx
deleted file mode 100644
index 9f449a89e..000000000
--- a/vite/src/views/onboarding/onboarding-steps/CreateOrg.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import { useState } from "react";
-import ConfettiExplosion from "react-confetti-explosion";
-import { toast } from "sonner";
-import Step from "@/components/general/OnboardingStep";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-
-// export const CreateOrgStep = ({
-// number,
-// pollForOrg,
-// }: {
-// number: number;
-// pollForOrg: () => Promise;
-// }) => {
-
-// const env = useEnv();
-// const axios = useAxiosInstance({ env });
-
-// const [isExploding, setIsExploding] = useState(false);
-// const [loading, setLoading] = useState(false);
-// const [fields, setFields] = useState({
-// name: org?.name || "",
-// slug: "",
-// });
-
-// const handleCreateOrg = async () => {
-// setLoading(true);
-
-// try {
-// if (!createOrganization) {
-// toast.error("Error creating organization");
-// return;
-// }
-
-// const org = await createOrganization({
-// name: fields.name,
-// });
-
-// // Create org in Autumn
-// const res = await axios.post("/organization", {
-// orgId: org.id,
-// });
-
-// console.log("Org created in Autumn", res);
-
-// await setActive({ organization: org.id });
-// // await pollForOrg();
-// toast.success(`Created your organization: ${org.name}`);
-// setIsExploding(true);
-// } catch (error: any) {
-// if (error.message) {
-// toast.error(error.message);
-// } else {
-// toast.error("Error creating organization");
-// }
-// }
-// setLoading(false);
-// };
-
-// return (
-//
-//
-//
-//
-// 👋
-//
-// Welcome to Autumn
-//
-//
-//
-//
-// Create an organization to get started and integrate pricing within 5
-// minutes.
-//
-// >
-// }
-// >
-// {/* */}
-//
-// {
-// const newFields = { ...fields, name: e.target.value };
-// setFields(newFields);
-// }}
-// />
-// }
-// >
-// Create Organization
-//
-
-// {isExploding && (
-// {
-// console.log("complete");
-// }}
-// />
-// )}
-//
-// {/*
*/}
-//
-// );
-// };
diff --git a/vite/src/views/onboarding/onboarding-steps/CreateSecretKey.tsx b/vite/src/views/onboarding/onboarding-steps/CreateSecretKey.tsx
deleted file mode 100644
index 1527f7c74..000000000
--- a/vite/src/views/onboarding/onboarding-steps/CreateSecretKey.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import { CheckIcon, CopyIcon } from "lucide-react";
-import { useState } from "react";
-import { toast } from "sonner";
-import Step from "@/components/general/OnboardingStep";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { DevService } from "@/services/DevService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { DevContext } from "@/views/developer/DevContext";
-
-export const CreateSecretKey = ({
- apiKey,
- setApiKey,
- number,
-}: {
- apiKey: string;
- setApiKey: (apiKey: string) => void;
- number: number;
-}) => {
- const env = useEnv();
- const [apiKeyName, setApiKeyName] = useState("");
- const [apiCreated, setApiCreated] = useState(false);
-
- const [loading, setLoading] = useState(false);
- const [copied, setCopied] = useState(false);
- const axiosInstance = useAxiosInstance({ env });
-
- const handleCreate = async () => {
- console.log("creating api key", apiKeyName ? apiKeyName : name);
- setLoading(true);
- try {
- const { api_key } = await DevService.createAPIKey(axiosInstance, {
- name: apiKeyName ? apiKeyName : name,
- });
-
- setApiKey(api_key);
- } catch (error) {
- console.log("Error:", error);
- toast.error("Failed to create API key");
- }
-
- setLoading(false);
- };
-
- return (
-
- Create a secret key to authenticate your requests to the Autumn API.
-
- }
- >
- {/*
-
- Your Publishable Key is safe for
- frontend use. It's limited to non-sensitive operations, like
- getting a Stripe Checkout URL and feature access checks.
-
-
- Your Secret Key belongs on your
- backend server and has full API access, including for sending usage
- events.
-
-
*/}
-
- {},
- onboarding: true,
- apiKeyName,
- setApiKeyName,
- apiCreated,
- setApiCreated,
- }}
- >
-
-
-
- );
-};
-
-{
- /*
- {env === AppEnv.Sandbox ? (
-
- ) : (
-
- )}
-
*/
-}
diff --git a/vite/src/views/onboarding/onboarding-steps/Env.tsx b/vite/src/views/onboarding/onboarding-steps/Env.tsx
deleted file mode 100644
index acb86f01d..000000000
--- a/vite/src/views/onboarding/onboarding-steps/Env.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { CodeDisplay } from "@/components/general/CodeDisplay";
-import Step from "@/components/general/OnboardingStep";
-import CodeBlock from "../components/CodeBlock";
-
-const envCode = `AUTUMN_SECRET_KEY=am_sk_1234567890`;
-
-export default function EnvStep({ number }: { number: number }) {
- return (
-
- Paste the secret key into your .env file.
- >
- }
- >
- {/* Check Feature Access */}
-
-
-
-
- );
-}
diff --git a/vite/src/views/onboarding/onboarding-steps/Install.tsx b/vite/src/views/onboarding/onboarding-steps/Install.tsx
deleted file mode 100644
index 1940e6097..000000000
--- a/vite/src/views/onboarding/onboarding-steps/Install.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { CodeDisplay } from "@/components/general/CodeDisplay";
-import Step from "@/components/general/OnboardingStep";
-import CodeBlock from "../components/CodeBlock";
-
-const installCode = `npm install autumn-js`;
-const installCodePnpm = `pnpm install autumn-js`;
-const installCodeYarn = `yarn add autumn-js`;
-export default function CheckAccessStep({ number }: { number: number }) {
- return (
-
- Install the Autumn SDK to your project.
- >
- }
- >
- {/* Check Feature Access */}
-
-
-
-
- );
-}
diff --git a/vite/src/views/onboarding/onboarding-steps/IntegrationGuide.tsx b/vite/src/views/onboarding/onboarding-steps/IntegrationGuide.tsx
deleted file mode 100644
index 6b82fab31..000000000
--- a/vite/src/views/onboarding/onboarding-steps/IntegrationGuide.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { AlertTriangle, ArrowUpRightFromSquare, Code } from "lucide-react";
-import Step from "@/components/general/OnboardingStep";
-import { Button } from "@/components/ui/button";
-
-interface IntegrationGuideStepProps {
- number: number;
- showIntegrationSteps: boolean;
- setShowIntegrationSteps: (show: boolean) => void;
-}
-
-export default function IntegrationGuideStep({
- number,
- showIntegrationSteps,
- setShowIntegrationSteps,
-}: IntegrationGuideStepProps) {
- return (
-
-
- When you're ready, follow the steps below to embed Autumn into your
- application.
-
- >
- }
- >
-
- {!showIntegrationSteps && (
-
setShowIntegrationSteps(true)}
- variant="outline"
- >
-
- Show integration guide
-
- )}
-
- {showIntegrationSteps && (
-
-
- {/*
*/}
-
-
- This guide is for React & Node.js. Read our{" "}
-
- docs
-
- {" "}
- if your stack is different.
-
-
-
-
- )}
-
-
- );
-}
diff --git a/vite/src/views/onboarding/onboarding-steps/MountHandler.tsx b/vite/src/views/onboarding/onboarding-steps/MountHandler.tsx
deleted file mode 100644
index 99af99ff7..000000000
--- a/vite/src/views/onboarding/onboarding-steps/MountHandler.tsx
+++ /dev/null
@@ -1,227 +0,0 @@
-import { ArrowUpRightFromSquare } from "lucide-react";
-import Step from "@/components/general/OnboardingStep";
-import CodeBlock from "../components/CodeBlock";
-
-const nextjs = () => {
- return `// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@/lib/auth";
-
-export const { GET, POST } = autumnHandler({
- identify: async (request) => {
- const session = await auth.api.getSession({
- headers: request.headers,
- });
-
- return {
- customerId: session?.user.id,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});
-`;
-};
-
-const remix = () => {
- return `// app/routes/api.autumn.$.ts
-
-import { autumnHandler } from "autumn-js/remix";
-import { auth } from "../lib/auth.server";
-
-export const { loader, action } = autumnHandler({
- identify: async (args) => {
- const session = await auth.api.getSession({
- headers: args.request.headers,
- });
-
- return {
- customerId: session?.user.id,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});
-`;
-};
-
-const Tanstack = () => {
- return `// routes/api/autumn.$.ts
-
-import { createAPIFileRoute } from "@tanstack/react-start/api";
-import { auth } from "~/lib/auth";
-import { autumnHandler } from "autumn-js/tanstack";
-
-const handler = autumnHandler({
- identify: async ({ request }) => {
- const session = await auth.api.getSession({
- headers: request.headers,
- });
-
- return {
- customerId: session?.user.id,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});
-
-export const APIRoute = createAPIFileRoute("/api/autumn/$")(handler);
-`;
-};
-
-const hono = () => {
- return `//index.ts
-
-import { autumnHandler } from "autumn-js/hono";
-
-app.use(
- "/api/autumn/*",
- autumnHandler({
- identify: async (c: Context) => {
- const session = await auth.api.getSession({
- headers: c.req.raw.headers,
- });
-
- return {
- customerId: session?.user.id,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
- })
-);
-`;
-};
-
-const express = () => {
- return `//index.ts
-
-import { autumnHandler } from "autumn-js/express";
-
-app.use(express.json()); // need to parse request body before autumnHandler
-app.use(
- "/api/autumn",
- autumnHandler({
- identify: async (req) => {
- const session = await auth.api.getSession({
- headers: fromNodeHeaders(req.headers),
- });
-
- return {
- customerId: session?.user.id,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
- })
-);
-`;
-};
-
-const fastify = () => {
- return `//index.ts
-
-import { autumnHandler } from "autumn-js/fastify";
-
-fastify.route({
- method: ["GET", "POST"],
- url: "/api/autumn/*",
- handler: autumnHandler({
- identify: async (request) => {
- const session = await auth.api.getSession({
- headers: request.headers as any,
- });
-
- return {
- customerId: session?.user.id,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
- }),
-});
-`;
-};
-
-export default function MountHandler({ number }: { number: number }) {
- return (
-
- This creates routes in the /api/autumn/* path, used by Autumn's
- React library. The handler takes in an identify function that returns
- a customerId.
-
-
- This example shows the customerId being resolved from better-auth.
-
- }
- >
- {/* */}
- {/*
- You can do this directly from your frontend using the Publishable
- API Key.
-
*/}
-
- {/*
*/}
-
- {/*
*/}
- {/*
*/}
-
- );
-}
diff --git a/vite/src/views/onboarding/onboarding-steps/ProductList.tsx b/vite/src/views/onboarding/onboarding-steps/ProductList.tsx
deleted file mode 100644
index 9ce9c7239..000000000
--- a/vite/src/views/onboarding/onboarding-steps/ProductList.tsx
+++ /dev/null
@@ -1,323 +0,0 @@
-import {
- Product,
- type ProductItem,
- type ProductV2,
- products,
-} from "@autumn/shared";
-import { Check } from "lucide-react";
-import { useEffect, useState } from "react";
-import { useSearchParams } from "react-router";
-import { toast } from "sonner";
-import Step from "@/components/general/OnboardingStep";
-import { PageSectionHeader } from "@/components/general/PageSectionHeader";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { getBackendErr } from "@/utils/genUtils";
-import { ProductsContext } from "@/views/products/ProductsContext";
-import { CreateFreeTrial } from "@/views/products/product/free-trial/CreateFreeTrial";
-import { ManageProduct } from "@/views/products/product/ManageProduct";
-import { ProductContext } from "@/views/products/product/ProductContext";
-import CreateProduct from "@/views/products/products/components/CreateProductDialog";
-import { ProductsTable } from "@/views/products/products/components/ProductsTable";
-
-export const ProductList = ({
- data,
- mutate,
-}: {
- data: any;
- mutate: () => Promise;
-}) => {
- const env = useEnv();
-
- const [searchParams] = useSearchParams();
- const token = searchParams.get("token");
-
- const [product, setProduct] = useState(data.products[0]);
- const [features, setFeatures] = useState(data.features);
- const [open, setOpen] = useState(false);
- const [originalProduct, setOriginalProduct] = useState(null);
- const [entityFeatureIds, setEntityFeatureIds] = useState([]);
-
- useEffect(() => {
- setFeatures(data.features);
- setEntityFeatureIds(
- Array.from(
- new Set(
- product?.items
- .filter((item: ProductItem) => item.entity_feature_id != null)
- .map((item: ProductItem) => item.entity_feature_id),
- ),
- ),
- );
- }, [data.features, product]);
-
- if (!data.products) return null;
-
- return (
- 0
- ? "Your plans"
- : "Create your plans"
- }
- number={1}
- description={
-
- Create products for any free products, paid products and any add-on or
- top up products that your application offers.
-
- }
- >
-
-
-
- {/* Test Data */}
- {
- await mutate();
- setProduct(newProduct);
- setOpen(true);
- }}
- />
- >
- }
- className="pr-0 border-l"
- />
-
- {
- const selectedProduct = data.products.find(
- (p: ProductV2) => p.id === id,
- );
- setProduct(selectedProduct);
- // setEntityFeatureIds([]);
- setOriginalProduct(JSON.parse(JSON.stringify(selectedProduct)));
- setOpen(true);
- }}
- />
-
-
- );
-};
-
-export const EditProductDialog = ({
- product,
- features,
- setProduct,
- setFeatures,
- mutate,
- open,
- setOpen,
- originalProduct,
- entityFeatureIds,
- setEntityFeatureIds,
-}: {
- product: any;
- setProduct: (product: any) => void;
- features: any[];
- setFeatures: (features: any[]) => void;
- mutate: () => Promise;
- open: boolean;
- setOpen: (open: boolean) => void;
- originalProduct: any;
- entityFeatureIds: string[];
- setEntityFeatureIds: (entityFeatureIds: string[]) => void;
-}) => {
- const env = useEnv();
- const axiosInstance = useAxiosInstance();
- const [createProductLoading, setCreateProductLoading] = useState(false);
- const [freeTrialModalOpen, setFreeTrialModalOpen] = useState(false);
-
- // Store the original product state when modal opens
- const handleOpenChange = async (newOpen: boolean) => {
- if (!newOpen && open && product?.id) {
- // Modal is being closed, check if there are changes
- const hasChanges =
- originalProduct &&
- JSON.stringify(product) !== JSON.stringify(originalProduct);
-
- if (hasChanges) {
- // Only update if there are changes
- updateProduct();
- }
- }
- setOpen(newOpen);
- };
-
- const updateProduct = async () => {
- setCreateProductLoading(true);
- try {
- const res = await ProductService.updateProduct(
- axiosInstance,
- product.id,
- product,
- );
- toast.success("Plan updated successfully");
- await mutate();
- setOpen(false);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to update plan"));
- }
- setCreateProductLoading(false);
- };
-
- const handleFreeTrialClick = () => {
- if (product?.free_trial) {
- // Delete the free trial
- setProduct({ ...product, free_trial: null });
- } else {
- // Open the free trial modal
- setFreeTrialModalOpen(true);
- }
- };
-
- return (
-
-
-
- {/* Edit Product */}
-
-
-
-
-
-
-
-
- setProduct({
- ...product,
- is_default: !product?.is_default,
- })
- }
- className={`min-w-32 flex items-center gap-2 ${
- product?.is_default ? "bg-stone-100" : ""
- }`}
- >
- {product?.is_default && (
-
-
-
- )}
- Default
-
-
-
- This product is enabled by default for all new users
-
-
-
-
-
- setProduct({ ...product, is_add_on: !product?.is_add_on })
- }
- className={`min-w-32 flex items-center gap-2 ${
- product?.is_add_on ? "bg-stone-100" : ""
- }`}
- >
- {product?.is_add_on && (
-
-
-
- )}
- Add-on
-
-
-
- This product is an add-on that can be bought together with
- your base products
-
-
-
- {product?.free_trial && (
-
-
-
- )}
- Free Trial
-
-
-
- Update Product
-
-
-
-
-
- );
-};
-
- );
-};
diff --git a/vite/src/views/onboarding/onboarding-steps/SampleApp.tsx b/vite/src/views/onboarding/onboarding-steps/SampleApp.tsx
deleted file mode 100644
index be7795bd2..000000000
--- a/vite/src/views/onboarding/onboarding-steps/SampleApp.tsx
+++ /dev/null
@@ -1,520 +0,0 @@
-import { useCustomer } from "autumn-js/react";
-import {
- ArrowUpRightFromSquare,
- Check,
- ChevronDown,
- ChevronRight,
- Code,
- Lock,
- Send,
-} from "lucide-react";
-import { useEffect, useState } from "react";
-import { useSearchParams } from "react-router";
-import { toast } from "sonner";
-import CheckDialog from "@/components/autumn/paywall-dialog";
-import PricingTable from "@/components/autumn/pricing-table";
-import Step from "@/components/general/OnboardingStep";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { cn } from "@/lib/utils";
-import { useEnv } from "@/utils/envUtils";
-import CodeBlock from "@/views/onboarding/components/CodeBlock";
-
-export const SampleApp = ({
- data,
- number,
- mutate,
-}: {
- data: any;
- number: number;
- mutate: () => Promise;
-}) => {
- const env = useEnv();
-
- const [searchParams] = useSearchParams();
- const token = searchParams.get("token");
-
- const [open, setOpen] = useState(false);
- const [checkData, setCheckData] = useState(null);
- const [trackData, setTrackData] = useState(null);
- const [showCodeSection, setShowCodeSection] = useState(true);
- const [showCheckSnippet, setShowCheckSnippet] = useState(true);
- const [showTrackSnippet, setShowTrackSnippet] = useState(true);
- const [showCustomerSnippet, setShowCustomerSnippet] = useState(true);
- const [lastUsedFeature, setLastUsedFeature] = useState({
- featureId: data.features?.[0]?.id,
- value: 1,
- });
-
- const { customer, openBillingPortal } = useCustomer();
-
- if (!data.products) return null;
-
- return (
-
- Learn how Autumn works by playing with the sample app, generated from
- the products you set up.
-
- }
- >
-
-
-
-
-
-
- Show example app
-
-
-
-
-
- Sample App
- {
- setShowCodeSection(!showCodeSection);
- }}
- className="self-start bg-zinc-800 text-white mr-7"
- >
-
- {showCodeSection ? "Hide Code" : "Show Code"}
-
-
-
-
- Every time you use a feature, we{" "}
-
- check
- {" "}
- for access permission, then{" "}
-
- track
- {" "}
- the usage. Test using features, hitting usage limits,
- upgrade and downgrade flows, and making changes to your
- products.
-
-
-
-
-
-
-
-
- Available Features
-
-
-
- {data.features
- ?.filter(
- (feature: any) => customer?.features?.[feature.id],
- )
- .concat(
- data.features?.filter(
- (feature: any) => !customer?.features?.[feature.id],
- ) || [],
- )
- .map((feature: any, index: number) => {
- const customerFeature =
- customer?.features?.[feature.id];
- return (
-
- );
- })}
-
-
-
- {/* Products List */}
-
-
-
Billing
- {customer?.stripe_id && (
- {
- const { error } = await openBillingPortal();
- if (error) {
- toast.error(error.message);
- }
- }}
- >
- Manage Billing
-
- )}
-
-
-
-
-
-
- {/* Code Snippets Toggle Button */}
- {showCodeSection && (
-
- {/* Code Snippets */}
-
-
-
-
-
- Check feature
-
-
- setShowCheckSnippet(!showCheckSnippet)
- }
- className="h-6 w-6 p-0"
- >
- {showCheckSnippet ? (
-
- ) : (
-
- )}
-
-
- {showCheckSnippet && (
-
{
-if ( !allowed({ featureId: '${
- lastUsedFeature?.featureId ||
- data.features?.[0]?.id ||
- "feature-id"
- }' }) ) {
- alert('Feature not allowed');
- }
-} `
- : "// Click 'Send' on a feature"
- }`,
- },
- {
- title: "Node.js",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- checkData
- ? `import { Autumn } from 'autumn-js';
-
-const autumn = new Autumn({
- secretKey: 'am_sk_1234567890'
-});
-
-const { data } = await autumn.check({
- customerId: 'user_123',
- featureId: '${
- lastUsedFeature?.featureId || data.features?.[0]?.id || "feature-id"
- }'
-});
-`
- : "// Click 'Send' on a feature"
- }`,
- },
- {
- title: "Response",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- checkData
- ? JSON.stringify(checkData, null, 2)
- : "// Click 'Send' on a feature"
- }`,
- },
- ]}
- />
- )}
-
-
-
-
- Track usage
-
- setShowTrackSnippet(!showTrackSnippet)
- }
- className="h-6 w-6 p-0"
- >
- {showTrackSnippet ? (
-
- ) : (
-
- )}
-
-
- {showTrackSnippet && (
-
{
- await track({
- featureId: '${
- lastUsedFeature?.featureId || data.features?.[0]?.id || "feature-id"
- }',
- value: ${lastUsedFeature?.value || 1}
- });
-};`
- : "// Click 'Send' on a feature"
- }`,
- },
- {
- title: "Node.js",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- trackData
- ? `import { Autumn } from 'autumn-js';
-
-const autumn = new Autumn({
- secretKey: 'am_sk_1234567890'
-});
-
-const response = await autumn.track({
- featureId: '${
- lastUsedFeature?.featureId || data.features?.[0]?.id || "feature-id"
- }',
- value: ${lastUsedFeature?.value || 1}
-});
-`
- : "// Click 'Send' on a feature"
- }`,
- },
- {
- title: "Response",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- trackData
- ? JSON.stringify(trackData, null, 2)
- : "// Click 'Send' on a feature"
- }`,
- },
- ]}
- />
- )}
-
-
-
-
-
- Customer data
-
-
- setShowCustomerSnippet(!showCustomerSnippet)
- }
- className="h-6 w-6 p-0"
- >
- {showCustomerSnippet ? (
-
- ) : (
-
- )}
-
-
- {showCustomerSnippet && (
-
{
- //refresh customer data after feature is used
- await refetch();
-};`,
- },
- {
- title: "Node.js",
- language: "typescript",
- displayLanguage: "typescript",
- content: `import { Autumn } from 'autumn-js';
-
-const autumn = new Autumn({
- secretKey: 'am_sk_1234567890'
-});
-
-const { customer } = await autumn.customers.get('user_123');`,
- },
- {
- title: "Response",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- customer
- ? JSON.stringify(customer, null, 2)
- : "// Customer data will appear here"
- }`,
- },
- ]}
- />
- )}
-
-
-
-
- )}
-
-
-
-
-
-
- );
-};
-
-const FeatureUsageItem = ({
- feature,
- customerFeature,
- onCheckData,
- onTrackData,
- onFeatureUsed,
-}: {
- feature: any;
- customerFeature: any;
- onCheckData: (data: any) => void;
- onTrackData: (data: any) => void;
- onFeatureUsed: (feature: any) => void;
-}) => {
- const { check, track, refetch } = useCustomer();
- const [trackValue, setTrackValue] = useState(1);
-
- if (feature.type === "boolean") {
- return (
-
-
- {feature.name || `Feature ${feature.id || "Unknown"}`}
- {customerFeature ? (
-
- ) : (
-
- )}
-
-
- );
- }
- return (
-
-
-
-
- {feature.name || `Feature ${feature.id || "Unknown"}`}
-
- {!customerFeature && }
-
-
-
-
- setTrackValue(e.target.value)}
- className="w-12 h-6 px-1 text-xs bg-white border rounded-xs text-center [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
- disabled={!customerFeature}
- />
- {
- onFeatureUsed({
- featureId: customerFeature.id,
- value: Number(trackValue),
- });
- const { data: checkResponse } = await check({
- featureId: customerFeature.id,
- });
- onCheckData(checkResponse);
-
- if (checkResponse?.allowed) {
- toast.success(`Used ${trackValue} ${feature.name}`);
- const { data: trackResponse } = await track({
- featureId: customerFeature.id,
- value: Number(trackValue),
- });
- onTrackData(trackResponse);
- await refetch();
- }
- }}
- >
-
-
-
- {customerFeature && (
-
- {customerFeature.unlimited
- ? "unlimited"
- : `${customerFeature.usage}${
- customerFeature.included_usage > 0
- ? ` / ${customerFeature.included_usage}`
- : ""
- }`}
-
- )}
-
-
- );
-};
diff --git a/vite/src/views/onboarding2/AttachProduct.tsx b/vite/src/views/onboarding2/AttachProduct.tsx
deleted file mode 100644
index 472d985d7..000000000
--- a/vite/src/views/onboarding2/AttachProduct.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import type { Product } from "@autumn/shared";
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { useState } from "react";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import CodeBlock from "../onboarding/components/CodeBlock";
-import Step from "./Step";
-
-const attachCodeNextjs = (productId: string, apiKey: string) => {
- return `import { useCustomer } from 'autumn-js/react';
-
-export default function PurchaseButton() {
- const { attach } = useCustomer();
-
- return (
- {
- await attach({ productId: "${productId || "pro"}" });
- }}
- >
- Upgrade to ${productId || "Pro"}
-
- );
-}
-`;
-};
-
-export default function AttachProduct({
- products,
- apiKey,
- number,
-}: {
- products: Product[];
- apiKey: string;
- number: number;
-}) {
- const [selectedProductId, setSelectedProductId] = useState(
- products.length > 0 ? products[0].id! : "",
- );
-
- return (
-
-
- The{" "}
-
-
- /attach
-
-
- {" "}
- endpoint will return a Stripe Checkout URL. Once paid, the user will
- be granted access to the features you defined above.
-
-
-
-
-
-
-
- {products.map((product) => (
-
- {product.name || product.id}
-
- ))}
-
-
-
-
- }
- >
-
-
- );
-}
diff --git a/vite/src/views/onboarding2/AutumnProvider.tsx b/vite/src/views/onboarding2/AutumnProvider.tsx
deleted file mode 100644
index f7be0dd93..000000000
--- a/vite/src/views/onboarding2/AutumnProvider.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { ArrowUpRightFromSquare } from "lucide-react";
-import CodeBlock from "../onboarding/components/CodeBlock";
-import Step from "./Step";
-
-const nextjs = () => {
- return `// app/layout.tsx
-import { AutumnProvider } from "autumn-js/react";
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode,
-}) {
- return (
-
-
-
- {children}
-
-
-
- );
-}
-`;
-};
-const vite = () => {
- return `// main.tsx
-import { AutumnProvider } from "autumn-js/react";
-
-createRoot(document.getElementById("root")!).render(
- // backendUrl is the URL of your server (eg. hono)
-
-
-
-);
-`;
-};
-const reactRouter = () => {
- return `// root.tsx
-import { AutumnProvider } from "autumn-js/react";
-export function Layout({ children }: { children: React.ReactNode }) {
- return (
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
- );
-}
-`;
-};
-
-export default function AutumnProviderStep({ number }: { number: number }) {
- return (
-
- Wrap your root layout with the AutumnProvider component, and pass in
- your backend URL. Works with any React framework.
-
- }
- >
-
-
- );
-}
diff --git a/vite/src/views/onboarding2/Env.tsx b/vite/src/views/onboarding2/Env.tsx
deleted file mode 100644
index 4cbd1eea2..000000000
--- a/vite/src/views/onboarding2/Env.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { CodeDisplay } from "@/components/general/CodeDisplay";
-import Step from "@/components/general/OnboardingStep";
-import CodeBlock from "../onboarding/components/CodeBlock";
-
-const envCode = `AUTUMN_SECRET_KEY=am_sk_1234567890`;
-
-export default function EnvStep() {
- return (
-
-
-
- );
-}
diff --git a/vite/src/views/onboarding2/Install.tsx b/vite/src/views/onboarding2/Install.tsx
deleted file mode 100644
index 3e17b8865..000000000
--- a/vite/src/views/onboarding2/Install.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { ArrowUpRightFromSquare } from "lucide-react";
-import { CodeDisplay } from "@/components/general/CodeDisplay";
-import Step from "@/components/general/OnboardingStep";
-import CodeBlock from "../onboarding/components/CodeBlock";
-
-const installCode = `npm install autumn-js`;
-const installCodePnpm = `pnpm install autumn-js`;
-const installCodeYarn = `yarn add autumn-js`;
-export default function Install() {
- return (
-
-
-
- );
-}
diff --git a/vite/src/views/onboarding2/MountHandler.tsx b/vite/src/views/onboarding2/MountHandler.tsx
deleted file mode 100644
index 85bdc0f95..000000000
--- a/vite/src/views/onboarding2/MountHandler.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-import { ArrowUpRightFromSquare } from "lucide-react";
-import CodeBlock from "../onboarding/components/CodeBlock";
-import Step from "./Step";
-
-const nextjs = () => {
- return `// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@/lib/auth";
-
-export const { GET, POST } = autumnHandler({
- identify: async (request) => {
- return {
- customerId: "demo_user_id", // your internal customer id
- customerData: {
- name: "John Doe",
- email: "john.doe@example.com",
- },
- };
- },
-});
-`;
-};
-
-const remix = () => {
- return `// app/routes/api.autumn.$.ts
-
-import { autumnHandler } from "autumn-js/remix";
-import { auth } from "../lib/auth.server";
-
-export const { loader, action } = autumnHandler({
- identify: async (args) => {
- return {
- customerId: "demo_user_id", // your internal customer id
- customerData: {
- name: "John Doe",
- email: "john.doe@example.com",
- },
- };
- },
-});
-`;
-};
-
-const Tanstack = () => {
- return `// routes/api/autumn.$.ts
-
-import { createAPIFileRoute } from "@tanstack/react-start/api";
-import { auth } from "~/lib/auth";
-import { autumnHandler } from "autumn-js/tanstack";
-
-const handler = autumnHandler({
- identify: async ({ request }) => {
- return {
- customerId: "demo_user_id", // your internal customer id
- customerData: {
- name: "John Doe",
- email: "john.doe@example.com",
- },
- };
- },
-});
-
-export const APIRoute = createAPIFileRoute("/api/autumn/$")(handler);
-`;
-};
-
-const hono = () => {
- return `//index.ts
-
-import { autumnHandler } from "autumn-js/hono";
-
-app.use(
- "/api/autumn/*",
- autumnHandler({
- identify: async (c: Context) => {
- return {
- customerId: "demo_user_id", // your internal customer id
- customerData: {
- name: "John Doe",
- email: "john.doe@example.com",
- },
- };
- },
- })
-);
-`;
-};
-
-const express = () => {
- return `//index.ts
-
-import { autumnHandler } from "autumn-js/express";
-
-app.use(express.json()); // need to parse request body before autumnHandler
-app.use(
- "/api/autumn",
- autumnHandler({
- identify: async (req) => {
- return {
- customerId: "demo_user_id", // your internal customer id
- customerData: {
- name: "John Doe",
- email: "john.doe@example.com",
- },
- };
- },
- })
-);
-`;
-};
-
-const fastify = () => {
- return `//index.ts
-
-import { autumnHandler } from "autumn-js/fastify";
-
-fastify.route({
- method: ["GET", "POST"],
- url: "/api/autumn/*",
- handler: autumnHandler({
- identify: async (request) => {
- return {
- customerId: "demo_user_id", // your internal customer id
- customerData: {
- name: "John Doe",
- email: "john.doe@example.com",
- },
- };
- },
- }),
-});
-`;
-};
-
-export default function MountHandler({ number }: { number: number }) {
- return (
-
- Mounts routes on the /api/autumn/* path which is used by Autumn's
- React library. Requires an identify function that returns the
- customerId for authentication.
-
- }
- >
-
-
- );
-}
diff --git a/vite/src/views/onboarding2/OnboardingView2.tsx b/vite/src/views/onboarding2/OnboardingView2.tsx
deleted file mode 100644
index c13e7d107..000000000
--- a/vite/src/views/onboarding2/OnboardingView2.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { parseAsBoolean, parseAsString, useQueryStates } from "nuqs";
-import { useEffect, useRef, useState } from "react";
-import { useSearchParams } from "react-router";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useSession } from "@/lib/auth-client";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useAxiosSWR } from "@/services/useAxiosSwr";
-import LoadingScreen from "../general/LoadingScreen";
-import { useOnboardingQueryState } from "./hooks/useOnboardingQueryState";
-import IntegrateAutumn from "./integrate/IntegrateAutumn";
-import { ModelPricing } from "./model-pricing/ModelPricing";
-import { useListProducts } from "./model-pricing/usePricingTable";
-
-export default function OnboardingView2() {
- // const [queryStates, setQueryStates] = useQueryStates(
- // {
- // page: parseAsString.withDefault("pricing"),
- // reactTypescript: parseAsBoolean.withDefault(true),
- // frontend: parseAsString.withDefault(""),
- // backend: parseAsString.withDefault(""),
- // auth: parseAsString.withDefault(""),
- // customerType: parseAsString.withDefault("user"),
- // productId: parseAsString.withDefault(""),
- // token: parseAsString.withDefault(""),
- // },
- // {
- // history: "push",
- // }
- // );
-
- const { queryStates, setQueryStates } = useOnboardingQueryState();
-
- const [loading, setLoading] = useState(true);
- const axiosInstance = useAxiosInstance();
- const hasHandledToken = useRef(false);
- const { data } = useSession();
- const orgId = data?.session?.activeOrganizationId;
-
- const {
- products: autumnProducts,
- isLoading: isAutumnLoading,
- mutate: refetchAutumnProducts,
- } = useListProducts({ customerId: "onboarding_demo_user" });
-
- const { isLoading: productsLoading } = useProductsQuery();
- const { isLoading: orgLoading } = useOrg();
-
- useEffect(() => {
- const handleToken = async () => {
- try {
- await axiosInstance.post("/onboarding", {
- token: queryStates.token,
- });
-
- // await productMutate();
- await refetchAutumnProducts();
- } catch (error) {
- console.error(error);
- } finally {
- setLoading(false);
- }
- };
-
- if (queryStates.token && !hasHandledToken.current) {
- hasHandledToken.current = true;
- handleToken();
- }
- }, [queryStates.token, axiosInstance]);
-
- useEffect(() => {
- if (orgId && !queryStates.token) {
- setLoading(false);
- }
- }, [orgId, queryStates.token]);
-
- if (isAutumnLoading || loading || productsLoading || orgLoading)
- return ;
-
- return (
- <>
- {queryStates.page === "integrate" ? (
-
- ) : (
- {
- // await productMutate();
- // await mutateAutumnProducts();
- // }}
- // mutateAutumnProducts={mutateAutumnProducts}
- autumnProducts={autumnProducts}
- refetchAutumnProducts={refetchAutumnProducts}
- // productCounts={productCounts}
- // mutateCounts={mutateCounts}
- // queryStates={queryStates}
- // setQueryStates={setQueryStates}
- />
- )}
- >
- );
-}
diff --git a/vite/src/views/onboarding2/SampleApp.tsx b/vite/src/views/onboarding2/SampleApp.tsx
deleted file mode 100644
index 3d4ff0d60..000000000
--- a/vite/src/views/onboarding2/SampleApp.tsx
+++ /dev/null
@@ -1,523 +0,0 @@
-import { PaywallDialog, PricingTable, useCustomer } from "autumn-js/react";
-import {
- ArrowUpRightFromSquare,
- Check,
- ChevronDown,
- ChevronRight,
- Code,
- Lock,
- Send,
-} from "lucide-react";
-import { useCallback, useState } from "react";
-import { useSearchParams } from "react-router";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { cn } from "@/lib/utils";
-import CodeBlock from "@/views/onboarding/components/CodeBlock";
-
-export const SampleApp = () => {
- const [searchParams] = useSearchParams();
- const [open, setOpen] = useState(false);
- const [checkData, setCheckData] = useState(null);
- const [trackData, setTrackData] = useState(null);
- const [showCodeSection, setShowCodeSection] = useState(true);
- const [showCheckSnippet, setShowCheckSnippet] = useState(true);
- const [showTrackSnippet, setShowTrackSnippet] = useState(true);
- const [showCustomerSnippet, setShowCustomerSnippet] = useState(true);
-
- const { features, isLoading } = useFeaturesQuery();
-
- const [lastUsedFeature, setLastUsedFeature] = useState({
- featureId: features?.[0]?.id,
- value: 1,
- });
-
- const { customer, openBillingPortal } = useCustomer();
-
- if (isLoading) return null;
- return (
-
-
-
-
- Show example app
-
-
-
-
-
- Sample App
- {
- setShowCodeSection(!showCodeSection);
- }}
- className="self-start bg-zinc-800 text-white mr-7"
- >
-
- {showCodeSection ? "Hide Code" : "Show Code"}
-
-
-
-
- Every time you use a feature, we{" "}
-
- check
- {" "}
- for access permission, then{" "}
-
- track
- {" "}
- the usage. Test using features, hitting usage limits, upgrade and
- downgrade flows, and making changes to your products.
-
-
-
-
-
-
-
-
Available Features
-
-
- {features
- ?.filter((feature: any) => customer?.features?.[feature.id])
- .concat(
- features?.filter(
- (feature: any) => !customer?.features?.[feature.id],
- ) || [],
- )
- .map((feature: any, index: number) => {
- const customerFeature = customer?.features?.[feature.id];
- return (
-
- );
- })}
-
-
-
- {/* Products List */}
-
-
-
Billing
- {customer?.stripe_id && (
- {
- const { error } = await openBillingPortal();
- if (error) {
- toast.error(error.message);
- }
- }}
- >
- Manage Billing
-
- )}
-
-
-
-
-
-
- {/* Code Snippets Toggle Button */}
- {showCodeSection && (
-
- {/* Code Snippets */}
-
-
-
-
- Check feature
- setShowCheckSnippet(!showCheckSnippet)}
- className="h-6 w-6 p-0"
- >
- {showCheckSnippet ? (
-
- ) : (
-
- )}
-
-
- {showCheckSnippet && (
-
{
-if ( !allowed({ featureId: '${
- lastUsedFeature?.featureId ||
- features?.[0]?.id ||
- "feature-id"
- }' }) ) {
- alert('Feature not allowed');
- }
-} `
- : "// Click 'Send' on a feature"
- }`,
- },
- {
- title: "Node.js",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- checkData
- ? `import { Autumn } from 'autumn-js';
-
-const autumn = new Autumn({
- secretKey: 'am_sk_1234567890'
-});
-
-const { data } = await autumn.check({
- customerId: 'user_123',
- featureId: '${
- lastUsedFeature?.featureId || features?.[0]?.id || "feature-id"
- }'
-});
-`
- : "// Click 'Send' on a feature"
- }`,
- },
- {
- title: "Response",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- checkData
- ? JSON.stringify(checkData, null, 2)
- : "// Click 'Send' on a feature"
- }`,
- },
- ]}
- />
- )}
-
-
-
-
- Track usage
- setShowTrackSnippet(!showTrackSnippet)}
- className="h-6 w-6 p-0"
- >
- {showTrackSnippet ? (
-
- ) : (
-
- )}
-
-
- {showTrackSnippet && (
-
{
- await track({
- featureId: '${
- lastUsedFeature?.featureId || features?.[0]?.id || "feature-id"
- }',
- value: ${lastUsedFeature?.value || 1}
- });
-};`
- : "// Click 'Send' on a feature"
- }`,
- },
- {
- title: "Node.js",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- trackData
- ? `import { Autumn } from 'autumn-js';
-
-const autumn = new Autumn({
- secretKey: 'am_sk_1234567890'
-});
-
-const response = await autumn.track({
- featureId: '${
- lastUsedFeature?.featureId || features?.[0]?.id || "feature-id"
- }',
- value: ${lastUsedFeature?.value || 1}
-});
-`
- : "// Click 'Send' on a feature"
- }`,
- },
- {
- title: "Response",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- trackData
- ? JSON.stringify(trackData, null, 2)
- : "// Click 'Send' on a feature"
- }`,
- },
- ]}
- />
- )}
-
-
-
-
- Customer data
-
- setShowCustomerSnippet(!showCustomerSnippet)
- }
- className="h-6 w-6 p-0"
- >
- {showCustomerSnippet ? (
-
- ) : (
-
- )}
-
-
- {showCustomerSnippet && (
-
{
- //refresh customer data after feature is used
- await refetch();
-};`,
- },
- {
- title: "Node.js",
- language: "typescript",
- displayLanguage: "typescript",
- content: `import { Autumn } from 'autumn-js';
-
-const autumn = new Autumn({
- secretKey: 'am_sk_1234567890'
-});
-
-const { customer } = await autumn.customers.get('user_123');`,
- },
- {
- title: "Response",
- language: "typescript",
- displayLanguage: "typescript",
- content: `${
- customer
- ? JSON.stringify(customer, null, 2)
- : "// Customer data will appear here"
- }`,
- },
- ]}
- />
- )}
-
-
-
-
- )}
-
-
-
- );
-};
-
-// Wrapper component to handle PaywallDialog independently
-const PaywallWrapper = ({
- featureId,
- onClose,
-}: {
- featureId: string;
- onClose: () => void;
-}) => {
- const [paywallOpen, setPaywallOpen] = useState(true);
-
- const handlePaywallClose = useCallback(
- (open: boolean) => {
- setPaywallOpen(open);
- if (!open) {
- onClose();
- }
- },
- [onClose],
- );
-
- return (
-
- );
-};
-
-const FeatureUsageItem = ({
- feature,
- customerFeature,
- onCheckData,
- onTrackData,
- onFeatureUsed,
-}: {
- feature: any;
- customerFeature: any;
- onCheckData: (data: any) => void;
- onTrackData: (data: any) => void;
- onFeatureUsed: (feature: any) => void;
-}) => {
- const { check, track, refetch } = useCustomer();
- const [trackValue, setTrackValue] = useState(1);
- const [showPaywall, setShowPaywall] = useState(false);
-
- if (feature.type === "boolean") {
- return (
-
-
- {feature.name || `Feature ${feature.id || "Unknown"}`}
- {customerFeature ? (
-
- ) : (
-
- )}
-
-
- );
- }
- return (
-
-
-
-
- {feature.name || `Feature ${feature.id || "Unknown"}`}
-
- {!customerFeature && }
-
-
-
-
- setTrackValue(e.target.value)}
- className="w-12 h-6 px-1 text-xs bg-white border rounded-xs text-center [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
- disabled={!customerFeature}
- />
- {
- // Prevent event bubbling to parent dialog
- e.stopPropagation();
-
- onFeatureUsed({
- featureId: customerFeature.id,
- value: Number(trackValue),
- });
-
- // Check without dialog first
- const { data: checkResponse } = check({
- featureId: customerFeature.id,
- });
-
- onCheckData(checkResponse);
-
- if (checkResponse?.allowed) {
- toast.success(`Used ${trackValue} ${feature.name}`);
- const { data: trackResponse } = await track({
- featureId: customerFeature.id,
- value: Number(trackValue),
- });
- onTrackData(trackResponse);
- await refetch();
- } else {
- // Show custom paywall if not allowed
- setShowPaywall(true);
- }
- }}
- >
-
-
-
- {customerFeature && (
-
- {customerFeature.unlimited
- ? "unlimited"
- : `${customerFeature.usage}${
- customerFeature.included_usage > 0
- ? ` / ${customerFeature.included_usage}`
- : ""
- }`}
-
- )}
-
- {showPaywall && (
-
setShowPaywall(false)}
- />
- )}
-
- );
-};
diff --git a/vite/src/views/onboarding2/Step.tsx b/vite/src/views/onboarding2/Step.tsx
deleted file mode 100644
index f81b21109..000000000
--- a/vite/src/views/onboarding2/Step.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Wallet } from "lucide-react";
-import type React from "react";
-import { cn } from "@/lib/utils";
-
-interface StepProps {
- title: string;
- children: React.ReactNode;
- className?: string;
- description?: React.ReactNode;
- number?: number;
-}
-
-function Step({ title, children, className, description, number }: StepProps) {
- return (
-
-
-
- {number || "1"}
-
-
-
{title}
-
-
{description}
-
- {children}
-
- );
-}
-
-export default Step;
diff --git a/vite/src/views/onboarding2/hooks/useOnboardingQueryState.tsx b/vite/src/views/onboarding2/hooks/useOnboardingQueryState.tsx
deleted file mode 100644
index 01383c99e..000000000
--- a/vite/src/views/onboarding2/hooks/useOnboardingQueryState.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { parseAsBoolean, parseAsString, useQueryStates } from "nuqs";
-
-export const useOnboardingQueryState = () => {
- const [queryStates, setQueryStates] = useQueryStates(
- {
- page: parseAsString.withDefault("pricing"),
- reactTypescript: parseAsBoolean.withDefault(true),
- frontend: parseAsString.withDefault(""),
- backend: parseAsString.withDefault(""),
- auth: parseAsString.withDefault(""),
- customerType: parseAsString.withDefault("user"),
- productId: parseAsString.withDefault(""),
- token: parseAsString.withDefault(""),
- },
- {
- history: "push",
- },
- );
-
- return { queryStates, setQueryStates };
-};
diff --git a/vite/src/views/onboarding2/integrate/AITools.tsx b/vite/src/views/onboarding2/integrate/AITools.tsx
deleted file mode 100644
index 658d71d8b..000000000
--- a/vite/src/views/onboarding2/integrate/AITools.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { Download, ExternalLink, Info } from "lucide-react";
-import { toast } from "sonner";
-import CopyButton from "@/components/general/CopyButton";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Button } from "@/components/ui/button";
-import CodeBlock from "@/views/onboarding/components/CodeBlock";
-import { CodeSpan } from "./components/CodeSpan";
-import { InfoBox } from "./components/InfoBox";
-import { StepHeader } from "./StepHeader";
-
-export const AITools = () => {
- // MCP configuration for Autumn
- const mcpConfig = {
- name: "Autumn Docs",
- url: "https://docs.useautumn.com/mcp",
- headers: {},
- };
-
- // Base64 encode the configuration for Cursor's install URL
- const encodedConfig = btoa(JSON.stringify(mcpConfig));
- const cursorInstallUrl = `cursor://anysphere.cursor-deeplink/mcp/install?name=Autumn%20Docs&config=${encodedConfig}`;
-
- // Manual JSON configuration for copy-paste
- const manualConfig = JSON.stringify(
- {
- mcpServers: {
- autumn: mcpConfig,
- },
- },
- null,
- 2,
- );
-
- const handleCursorInstall = () => {
- window.open(cursorInstallUrl, "_blank");
- };
-
- return (
-
- {/*
*/}
-
-
-
-
-
- Add Autumn to your AI tools
-
-
-
-
- Install our MCP with Cursor or Claude Code to use AI to integrate
- Autumn.
-
-
-
- Cursor
-
- }
- >
- Open in Cursor
-
-
-
-
Claude Code
-
{
- navigator.clipboard.writeText(
- "claude mcp add --transport http autumn-docs https://docs.useautumn.com/mcp",
- );
- toast.success(
- "Copied command to clipboard. Paste in your terminal to install.",
- );
- }}
- variant="outline"
- className="w-fit text-t2"
- startIcon={
-
- }
- >
- Copy installation command
-
-
-
-
- When using Cursor or Claude, prompt the model to use the
- `autumn-docs` MCP to integrate Autumn.
-
-
-
-
-
- {/* One-click install for Cursor */}
-
-
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/ConnectStripeStep.tsx b/vite/src/views/onboarding2/integrate/ConnectStripeStep.tsx
deleted file mode 100644
index 9a8272436..000000000
--- a/vite/src/views/onboarding2/integrate/ConnectStripeStep.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { AppEnv } from "@autumn/shared";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { useOrg } from "@/hooks/common/useOrg";
-import { OrgService } from "@/services/OrgService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { StepHeader } from "./StepHeader";
-
-export const ConnectStripeStep = () => {
- const { org, mutate: mutateOrg } = useOrg();
-
- const [testApiKey, setTestApiKey] = useState("");
-
- const [loading, setLoading] = useState(false);
-
- const axiosInstance = useAxiosInstance({ env: AppEnv.Live });
-
- const handleConnectStripe = async () => {
- setLoading(true);
- try {
- await OrgService.connectStripe(axiosInstance, {
- secret_key: testApiKey,
- });
-
- toast.success("Successfully connected to Stripe");
- await mutateOrg();
- } catch (error) {
- console.log("Failed to connect Stripe", error);
- toast.error(getBackendErr(error, "Failed to connect Stripe"));
- }
-
- setLoading(false);
- };
-
- // console.log("productData", productData);
- const stripeConnected = org?.stripe_connected;
- return (
-
-
-
- Stripe is required to checkout and add your products to customers. Grab
- your API key{" "}
-
- here
-
- .
-
-
- setTestApiKey(e.target.value)}
- disabled={stripeConnected}
- />
-
-
- Connect Stripe
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/IntegrateAutumn.tsx b/vite/src/views/onboarding2/integrate/IntegrateAutumn.tsx
deleted file mode 100644
index cea497c16..000000000
--- a/vite/src/views/onboarding2/integrate/IntegrateAutumn.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { AutumnProvider } from "autumn-js/react";
-import { ArrowLeftIcon } from "lucide-react";
-import { Button } from "@/components/ui/button";
-import { notNullish } from "@/utils/genUtils";
-import { useOnboardingQueryState } from "../hooks/useOnboardingQueryState";
-import { AITools } from "./AITools";
-import { IntegrateContext } from "./IntegrateContext";
-import { AddAutumnProvider } from "./integration-steps/AddAutumnProvider";
-import { AutumnHandler } from "./integration-steps/AutumnHandler";
-import { CheckoutPricingTable } from "./integration-steps/CheckoutPricingTable";
-import { EnvStep } from "./integration-steps/EnvStep";
-import { Install } from "./integration-steps/Install";
-import { NextSteps } from "./NextSteps";
-import { SelectStack } from "./SelectStack";
-
-export default function IntegrateAutumn() {
- const { queryStates, setQueryStates } = useOnboardingQueryState();
- const stackSelected =
- queryStates.frontend && queryStates.backend && queryStates.auth;
-
- return (
-
-
-
-
-
{
- setQueryStates({
- page: "pricing",
- });
- }}
- >
-
- Create your pricing plans
-
-
Integrate Autumn
-
- Let's integrate Autumn and get your first customer onto one of
- your plans
-
-
-
-
-
-
- {stackSelected && queryStates.reactTypescript && (
- <>
-
-
-
-
-
-
-
-
- >
- )}
-
-
-
-
- );
-}
diff --git a/vite/src/views/onboarding2/integrate/IntegrateContext.tsx b/vite/src/views/onboarding2/integrate/IntegrateContext.tsx
deleted file mode 100644
index c7c96bd38..000000000
--- a/vite/src/views/onboarding2/integrate/IntegrateContext.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createContext, useContext } from "react";
-
-export const IntegrateContext = createContext(null);
-
-export const useIntegrateContext = () => {
- const context = useContext(IntegrateContext);
-
- if (context === undefined) {
- throw new Error(
- "useProductContext must be used within a ProductContextProvider",
- );
- }
-
- return context;
-};
diff --git a/vite/src/views/onboarding2/integrate/NextSteps.tsx b/vite/src/views/onboarding2/integrate/NextSteps.tsx
deleted file mode 100644
index b4db684d0..000000000
--- a/vite/src/views/onboarding2/integrate/NextSteps.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Button } from "@/components/ui/button";
-import { SampleApp } from "../SampleApp";
-import { useIntegrateContext } from "./IntegrateContext";
-import { StepHeader } from "./StepHeader";
-
-export const NextSteps = () => {
- const { data, mutate } = useIntegrateContext();
- return (
- <>
-
-
Next Steps} />
-
- Congrats on setting up Autumn! The next steps are to learn how to use
- Autumn to check if a user has access to features in your application,
- and track usage for those features. Learn how to do so{" "}
-
- here
-
- .
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/SelectStack.tsx b/vite/src/views/onboarding2/integrate/SelectStack.tsx
deleted file mode 100644
index f501d7a39..000000000
--- a/vite/src/views/onboarding2/integrate/SelectStack.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Button } from "@/components/ui/button";
-import { InfoBox } from "./components/InfoBox";
-import { useIntegrateContext } from "./IntegrateContext";
-import { StepHeader } from "./StepHeader";
-import { SelectFrameworks } from "./select-stack/SelectFrameworks";
-
-export const SelectStack = () => {
- const { queryStates, setQueryStates } = useIntegrateContext();
-
- const tabClassName = `rounded-xs h-8 data-[state=active]:bg-stone-100 data-[state=active]:text-t2 data-[state=active]:shadow-inner data-[state=active]:border`;
- return (
-
-
-
-
- Help us customize the integration guide for your specific tech stack.
- Click{" "}
- {
- setQueryStates({ ...queryStates, reactTypescript: false });
- }}
- className="underline cursor-pointer"
- >
- here
- {" "}
- if you're not using a React + Typescript backend stack.
-
-
- {queryStates.reactTypescript ? (
-
- ) : (
- <>
-
-
- This onboarding guide shows how to set up Autumn using our
- frontend components / hooks on React and server-side framework
- adaptors.
-
-
- In your case, you should integrate with Autumn's API directly on
- the backend. We have SDKs for Typescript and Python. Learn how to
- do so{" "}
-
- here
-
- .
-
-
-
{
- setQueryStates({ ...queryStates, reactTypescript: true });
- }}
- >
- Learn how to integrate Autumn with React + Typescript
-
- >
- )}
-
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/StackEnums.tsx b/vite/src/views/onboarding2/integrate/StackEnums.tsx
deleted file mode 100644
index e4bfbf497..000000000
--- a/vite/src/views/onboarding2/integrate/StackEnums.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-export enum Backend {
- Nextjs = "nextjs",
- ReactRouter = "react-router",
- Express = "express",
- Elysia = "elysia",
- Hono = "hono",
- Other = "other",
-}
-
-export enum Frontend {
- Nextjs = "nextjs",
- ReactRouter = "react-router",
- Vite = "vite",
- Other = "other",
-}
diff --git a/vite/src/views/onboarding2/integrate/StepHeader.tsx b/vite/src/views/onboarding2/integrate/StepHeader.tsx
deleted file mode 100644
index 581797757..000000000
--- a/vite/src/views/onboarding2/integrate/StepHeader.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-export const StepHeader = ({
- number,
- title,
-}: {
- number: number;
- title: React.ReactNode;
-}) => {
- return (
-
-
- {number}
-
-
{title}
-
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/components/CodeSpan.tsx b/vite/src/views/onboarding2/integrate/components/CodeSpan.tsx
deleted file mode 100644
index 92e23dd0b..000000000
--- a/vite/src/views/onboarding2/integrate/components/CodeSpan.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { cn } from "@/lib/utils";
-
-export const CodeSpan = ({
- children,
- className,
-}: {
- children: React.ReactNode;
- className?: string;
-}) => {
- return (
-
- {children}
-
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/AddAutumnProvider.tsx b/vite/src/views/onboarding2/integrate/integration-steps/AddAutumnProvider.tsx
deleted file mode 100644
index 8d19b3715..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/AddAutumnProvider.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import CodeBlock from "@/views/onboarding/components/CodeBlock";
-import { CodeSpan } from "../components/CodeSpan";
-import { InfoBox } from "../components/InfoBox";
-import { useIntegrateContext } from "../IntegrateContext";
-import { Backend, Frontend } from "../StackEnums";
-import { StepHeader } from "../StepHeader";
-
-const nextjsAutumnProvider = ({
- includeBackendUrl,
-}: {
- includeBackendUrl: boolean;
-}) => {
- const backendUrlStr = includeBackendUrl
- ? ` backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL}`
- : "";
- return `// layout.tsx
-
-import { AutumnProvider, PricingTable } from "autumn-js/react";
-
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode;
-}>) {
- return (
-
-
-
- {children}
-
-
-
- );
-}`;
-};
-
-const rr7AutumnProvider = ({
- includeBackendUrl,
-}: {
- includeBackendUrl: boolean;
-}) => {
- const backendUrlStr = includeBackendUrl
- ? ` backendUrl={import.meta.env.VITE_BACKEND_URL}`
- : "";
- return `// root.tsx
-
-import { AutumnProvider } from "autumn-js/react";
-export function Layout({ children }: { children: React.ReactNode }) {
- return (
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
- );
-}`;
-};
-
-const viteAutumnProvider = () => {
- return `// main.tsx
-import { AutumnProvider } from "autumn-js/react";
-
-createRoot(document.getElementById("root")!).render(
-
-
-
-);`;
-};
-
-const otherAutumnProvider = () => {
- return `// main.tsx
-import { AutumnProvider } from "autumn-js/react";
-
-// 1. Simply wrap the root of your app in the AutumnProvider component
-// 2. Pass in your server's URL to the backendUrl prop
-
-createRoot(document.getElementById("root")!).render(
-
-
-
-
-
-);`;
-};
-
-const getSnippet = (queryStates: any) => {
- const { backend, frontend } = queryStates;
- if (frontend === Frontend.Nextjs) {
- return nextjsAutumnProvider({
- includeBackendUrl: backend !== Backend.Nextjs,
- });
- } else if (frontend === Frontend.ReactRouter) {
- return rr7AutumnProvider({
- includeBackendUrl: backend === Backend.Nextjs,
- });
- } else if (frontend === Frontend.Vite) {
- return viteAutumnProvider();
- } else {
- return otherAutumnProvider();
- }
-};
-
-export const AddAutumnProvider = () => {
- const { queryStates } = useIntegrateContext();
- return (
-
-
- Wrap your React app in {" "}
-
- }
- />
-
- This allows you to use our React hooks and components in your app. If
- your server URL is different to your client, you will need to pass in
- the backend URL as a prop.
-
-
- {queryStates.auth === "supabase" && (
-
-
- The examples above assume that Supabase auth is implemented using
- server-side cookie authentication, following the guide{" "}
-
- here
-
- .
-
-
- If you need to set {"Bearer "} in
- your request headers to authenticate, you can use the{" "}
- getBearerToken prop in the{" "}
- AutumnProvider component.
-
-
- )}
-
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/AutumnHandler.tsx b/vite/src/views/onboarding2/integrate/integration-steps/AutumnHandler.tsx
deleted file mode 100644
index 920ea6a2f..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/AutumnHandler.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-import CodeBlock from "@/views/onboarding/components/CodeBlock";
-import { CodeSpan } from "../components/CodeSpan";
-import { useIntegrateContext } from "../IntegrateContext";
-import { Backend } from "../StackEnums";
-import { StepHeader } from "../StepHeader";
-import {} from "./handlerSnippets";
-import {
- elysiaBetterAuth,
- elysiaClerk,
- elysiaOther,
-} from "./snippets/elysiaHandler";
-import {
- expressBetterAuth,
- expressClerk,
- expressOther,
- expressSupabase,
-} from "./snippets/expressHandler";
-import { general } from "./snippets/general";
-import {
- honoBetterAuth,
- honoClerk,
- honoOther,
- honoSupabase,
-} from "./snippets/honoHandler";
-import {
- nextjsBetterAuthOrg,
- nextjsBetterAuthUser,
- nextjsClerkOrg,
- nextjsClerkUser,
- nextjsOther,
- nextjsSupabaseOrg,
- nextjsSupabaseUser,
-} from "./snippets/nextjsHandler";
-import {
- rr7BetterAuth,
- rr7Clerk,
- rr7Other,
- rr7Supabase,
-} from "./snippets/rr7Handler";
-
-const snippet = () => {
- return {
- [Backend.Nextjs]: {
- ["better_auth"]: {
- user: nextjsBetterAuthUser,
- org: nextjsBetterAuthOrg,
- },
- ["supabase"]: {
- user: nextjsSupabaseUser,
- org: nextjsSupabaseOrg,
- },
- ["clerk"]: {
- user: nextjsClerkUser,
- org: nextjsClerkOrg,
- },
- ["other"]: {
- user: nextjsOther,
- org: nextjsOther,
- },
- },
- [Backend.ReactRouter]: {
- ["better_auth"]: {
- user: rr7BetterAuth("user"),
- org: rr7BetterAuth("org"),
- },
- ["supabase"]: {
- user: rr7Supabase("user"),
- org: rr7Supabase("org"),
- },
- ["clerk"]: {
- user: rr7Clerk("user"),
- org: rr7Clerk("org"),
- },
- ["other"]: {
- user: rr7Other("user"),
- org: rr7Other("org"),
- },
- },
- [Backend.Hono]: {
- ["clerk"]: {
- user: honoClerk("user"),
- org: honoClerk("org"),
- },
- ["supabase"]: {
- user: honoSupabase("user"),
- org: honoSupabase("org"),
- },
- ["better_auth"]: {
- user: honoBetterAuth("user"),
- org: honoBetterAuth("org"),
- },
- ["other"]: {
- user: honoOther("user"),
- org: honoOther("org"),
- },
- },
- [Backend.Express]: {
- ["better_auth"]: {
- user: expressBetterAuth("user"),
- org: expressBetterAuth("org"),
- },
- ["clerk"]: {
- user: expressClerk("user"),
- org: expressClerk("org"),
- },
- ["supabase"]: {
- user: expressSupabase("user"),
- org: expressSupabase("org"),
- },
- ["other"]: {
- user: expressOther("user"),
- org: expressOther("org"),
- },
- },
- [Backend.Elysia]: {
- ["better_auth"]: {
- user: elysiaBetterAuth("user"),
- org: elysiaBetterAuth("org"),
- },
- ["clerk"]: {
- user: elysiaClerk("user"),
- org: elysiaClerk("org"),
- },
- ["other"]: {
- user: elysiaOther("user"),
- org: elysiaOther("org"),
- },
- },
- } as any;
-};
-
-export const AutumnHandler = () => {
- const { queryStates } = useIntegrateContext();
-
- const getSnippetContent = () => {
- const backendLang = queryStates.backend;
- const authProvider = queryStates.auth;
- const customerType = queryStates.customerType;
-
- const templates = snippet();
-
- let template = templates[backendLang]?.[authProvider]?.[customerType] || "";
-
- template = template.trim("\n");
-
- if (!template) {
- return general();
- }
-
- return template;
- };
-
- return (
-
-
- Mount autumnHandler to your backend
-
- }
- />
-
- autumnHandler mounts routes on the{" "}
- /api/autumn/* paths which allows our React hooks
- and components to interact with the Autumn API directly.
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/CheckoutPricingTable.tsx b/vite/src/views/onboarding2/integrate/integration-steps/CheckoutPricingTable.tsx
deleted file mode 100644
index 3aeca0d45..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/CheckoutPricingTable.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import CodeBlock from "@/views/onboarding/components/CodeBlock";
-import { CodeSpan } from "../components/CodeSpan";
-import { InfoBox } from "../components/InfoBox";
-import { useIntegrateContext } from "../IntegrateContext";
-import { StepHeader } from "../StepHeader";
-
-const nextjsSnippet = () => {
- return `import { PricingTable } from "autumn-js/react";
-
-export default function Home() {
- return (
-
- );
-}`;
-};
-
-const getSnippet = (queryStates: any) => {
- return nextjsSnippet();
-};
-
-export const CheckoutPricingTable = () => {
- const { queryStates } = useIntegrateContext();
- return (
-
-
- Drop in {" "}
-
- }
- />
-
- Display a pricing table with the products you have created and let your
- customers choose a product.
-
-
-
-
- {
- "Our component is completely customisable by installing it as a shadcn component. "
- }
- Learn how to do so{" "}
-
- here
-
- .
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/CreateSecretKey.tsx b/vite/src/views/onboarding2/integrate/integration-steps/CreateSecretKey.tsx
deleted file mode 100644
index 94637a8fc..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/CreateSecretKey.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { CheckIcon, CopyIcon, PlusIcon } from "lucide-react";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { DevService } from "@/services/DevService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-
-export const CreateSecretKey = ({
- apiKey,
- setApiKey,
-}: {
- apiKey: string;
- setApiKey: (apiKey: string) => void;
-}) => {
- const env = useEnv();
- // const [apiKeyName, setApiKeyName] = useState("");
- const [apiCreated, setApiCreated] = useState(false);
-
- const [loading, setLoading] = useState(false);
- const [copied, setCopied] = useState(false);
- const axiosInstance = useAxiosInstance({ env });
-
- const handleCreate = async () => {
- setLoading(true);
- try {
- const { api_key } = await DevService.createAPIKey(axiosInstance, {
- name: "Autumn Onboarding",
- });
-
- setApiKey(api_key);
- } catch (error) {
- console.log("Error:", error);
- toast.error("Failed to create API key");
- }
-
- setLoading(false);
- };
-
- return (
-
- );
-};
-
-{
- /*
- {env === AppEnv.Sandbox ? (
-
- ) : (
-
- )}
-
*/
-}
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/EnvStep.tsx b/vite/src/views/onboarding2/integrate/integration-steps/EnvStep.tsx
deleted file mode 100644
index 7898c7fbb..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/EnvStep.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useState } from "react";
-import CodeBlock from "@/views/onboarding/components/CodeBlock";
-import { CodeSpan } from "../components/CodeSpan";
-import { StepHeader } from "../StepHeader";
-import { CreateSecretKey } from "./CreateSecretKey";
-
-export const EnvStep = () => {
- const [apiKey, setApiKey] = useState("");
- return (
- <>
-
-
- Add the Autumn secret key to your {".env"} {" "}
- file
-
- }
- />
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/Install.tsx b/vite/src/views/onboarding2/integrate/integration-steps/Install.tsx
deleted file mode 100644
index 0d1ed15e9..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/Install.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import CodeBlock from "@/views/onboarding/components/CodeBlock";
-import { StepHeader } from "../StepHeader";
-
-const installCode = `npm install autumn-js`;
-const installCodePnpm = `pnpm add autumn-js`;
-const installCodeYarn = `yarn add autumn-js`;
-const installCodeBun = `bun add autumn-js`;
-
-export const Install = () => {
- return (
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/handlerSnippets.ts b/vite/src/views/onboarding2/integrate/integration-steps/handlerSnippets.ts
deleted file mode 100644
index d5477ad2b..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/handlerSnippets.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-export const nextjsBetterAuthUser = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@/lib/auth";
-import { headers } from "next/headers";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- return {
- customerId: session?.user.id,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});
-`;
-
-export const nextjsBetterAuthOrg = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@/lib/auth";
-import { headers } from "next/headers";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- return {
- customerId: session?.session.activeOrganizationId,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});
-`;
-
-export const nextjsBetterAuthOther = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@/lib/auth";
-import { headers } from "next/headers";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- // From the request, retrieve the ID you want to use as the customer ID
- const customerId = "customer_id";
-
- return {
- customerId,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});
-`;
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/snippets/betterAuth.tsx b/vite/src/views/onboarding2/integrate/integration-steps/snippets/betterAuth.tsx
deleted file mode 100644
index 0392ccff5..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/snippets/betterAuth.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-export const betterAuthSnippet = (
- customerType: "user" | "org",
- headersString: string,
- tabLevel: number = 0,
-) => {
- const tabs = " ".repeat(tabLevel);
-
- let snippet = ``;
- if (customerType === "user") {
- snippet = `${tabs}const session = await auth.api.getSession({
-${tabs} headers: ${headersString},
-${tabs}});
-${tabs}
-${tabs}return {
-${tabs} customerId: session?.user.id,
-${tabs} customerData: {
-${tabs} name: session?.user.name,
-${tabs} email: session?.user.email,
-${tabs} },
-${tabs}};`;
- } else {
- snippet = `${tabs}const session = await auth.api.getSession({
-${tabs} headers: ${headersString},
-${tabs}});
-${tabs}
-${tabs}return {
-${tabs} customerId: session?.session.activeOrganizationId,
-${tabs} customerData: { name: "", email: "" }
-${tabs}};`;
- }
-
- return snippet;
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/snippets/elysiaHandler.tsx b/vite/src/views/onboarding2/integrate/integration-steps/snippets/elysiaHandler.tsx
deleted file mode 100644
index 381e8aede..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/snippets/elysiaHandler.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { betterAuthSnippet } from "./betterAuth";
-
-export const elysiaBetterAuth = (customerType: "user" | "org") => {
- return `import { autumnHandler } from "autumn-js/elysia";
-import { auth } from "./auth";
-
-const app = new Elysia()
- .use(cors())
- .mount(auth.handler)
- .use(
- autumnHandler({
- identify: async (context) => {
-${betterAuthSnippet(customerType, "context.headers", 4)}
- },
- })
- )
- .listen(8000);
-
- `;
-};
-
-export const elysiaClerk = (customerType: "user" | "org") => {
- return `import { clerkPlugin } from "elysia-clerk";
-import { autumnHandler } from "autumn-js/backend";
-
-const app = new Elysia()
- .use(cors())
- .use(clerkPlugin())
- .all("*", async (ctx: any) => {
- console.log("Request received");
- })
- .all("/api/autumn/*", async (ctx: any) => {
- const auth = ctx.auth();
-
- let body = null;
- if (ctx.request.method !== "GET") {
- body = await ctx.request.json();
- }
-
- const { statusCode, response } = await autumnHandler({
- customerId: ${customerType === "user" ? "auth.userId" : "auth.orgId"},
- customerData: { name: "", email: "" },
- request: {
- url: ctx.request.url,
- method: ctx.request.method,
- body: body,
- },
- });
-
- ctx.set.status = statusCode;
- return response;
- })
- .listen(8000);`;
-};
-
-export const elysiaOther = (customerType: "user" | "org") => {
- return `import { autumnHandler } from "autumn-js/elysia";
-import { auth } from "./auth";
-
-const app = new Elysia()
- .use(cors())
- .mount(auth.handler)
- .use(
- autumnHandler({
- identify: async (context) => {
- const customerId = "your_customer_id"; // Authenticate and get customer ID from your DB
-
- return {
- customerId,
- customerData: { name: "", email: "" },
- };
- },
- })
- )
- .listen(8000);
-
- `;
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/snippets/expressHandler.tsx b/vite/src/views/onboarding2/integrate/integration-steps/snippets/expressHandler.tsx
deleted file mode 100644
index ceba1dfa6..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/snippets/expressHandler.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { betterAuthSnippet } from "./betterAuth";
-import { clerkSnippet, supabaseSnippet } from "./honoHandler";
-
-export const expressBetterAuth = (customerType: "user" | "org") => {
- return `import { autumnHandler } from "autumn-js/express";
-import { auth } from "@/lib/auth";
-
-app.use(express.json()); // need to parse request body before autumnHandler
-app.use(
- "/api/autumn",
- autumnHandler({
- identify: async (req) => {
-${betterAuthSnippet(customerType, "req.headers", 3)}
- },
- })
-);`;
-};
-
-export const expressClerk = (customerType: "user" | "org") => {
- return `import { autumnHandler } from "autumn-js/express";
-import { clerkMiddleware, getAuth } from "@clerk/express";
-
-app.use(express.json()); // need to parse request body before autumnHandler
-app.use(clerkMiddleware());
-app.use(
- "/api/autumn",
- autumnHandler({
- identify: async (req) => {
- ${clerkSnippet(customerType, "req")}
- },
- })
-);`;
-};
-
-export const expressSupabase = (customerType: "user" | "org") => {
- return `import { autumnHandler } from "autumn-js/express";
-import { createClient } from "./lib/supabase";
-
-app.use(express.json()); // need to parse request body before autumnHandler
-app.use(
- "/api/autumn",
- autumnHandler({
- identify: async (req, res) => {
- ${supabaseSnippet(customerType, "createClient({ req, res })")}
- },
- })
-);`;
-};
-
-export const expressOther = (customerType: "user" | "org") => {
- return `import { autumnHandler } from "autumn-js/express";
-
-app.use(express.json()); // need to parse request body before autumnHandler
-app.use(
- "/api/autumn",
- autumnHandler({
- identify: async (req, res) => {
- const customerId = "your_customer_id"; // Get customer id from your database
-
- return {
- customerId,
- customerData: { name: "", email: "" },
- };
- },
- })
-);`;
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/snippets/general.tsx b/vite/src/views/onboarding2/integrate/integration-steps/snippets/general.tsx
deleted file mode 100644
index e81685254..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/snippets/general.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-export const general = () => {
- return `import { autumnHandler } from "autumn-js/backend";
-
-// 1. autumnHandler takes in request properties and returns a response
-// 2. Simply mount the handler onto the /api/autumn/* path in your backend
-// 3. Call autumnHandler and pass in the required parameters
-// 4. Return the response from the autumnHandler
-
-// Example using autumnHandler with Hono & Clerk
-import { autumnHandler } from "autumn-js/backend";
-import { clerkMiddleware, getAuth } from "@hono/clerk-auth";
-
-app.use("*", clerkMiddleware());
-app.use(
- "/api/autumn/*",
- async (c) => {
- const auth = getAuth(c);
-
- if (!auth?.userId) {
- return c.json({ message: "Unauthorized" }, 401);
- }
-
- let body = null;
- if (c.req.method !== "GET") {
- body = await c.req.json();
- }
-
- const { statusCode, response } = await autumnHandler({
- customerId: auth.userId,
- customerData: { name: "", email: "" },
- request: {
- url: c.req.url,
- method: c.req.method,
- body: body,
- },
- });
-
- return c.json(response, statusCode);
- }
-);`;
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/snippets/honoHandler.tsx b/vite/src/views/onboarding2/integrate/integration-steps/snippets/honoHandler.tsx
deleted file mode 100644
index db8226b2e..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/snippets/honoHandler.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { betterAuthSnippet } from "./betterAuth";
-
-export const clerkSnippet = (
- customerType: "user" | "org",
- reqParam: string = "c",
-) => {
- if (customerType === "user") {
- return `const auth = getAuth(${reqParam});
-
- if (!auth?.userId) return null;
-
- return {
- customerId: auth.userId,
- customerData: { name: "", email: "" },
- };
- `;
- }
-
- return `const auth = getAuth(${reqParam});
-
- if (!auth?.userId || !auth?.orgId) return null;
-
- return {
- customerId: auth.orgId,
- customerData: { name: "", email: "" },
- };
- `;
-};
-
-export const honoClerk = (customerType: "user" | "org") => {
- return `import { autumnHandler } from "autumn-js/hono";
-import { clerkMiddleware, getAuth } from "@hono/clerk-auth";
-
-app.use("*", clerkMiddleware());
-app.use(
- "/api/autumn/*",
- autumnHandler({
- identify: async (c: Context) => {
- ${clerkSnippet(customerType)}
- },
- })
-);`;
-};
-
-export const supabaseSnippet = (
- customerType: "user" | "org",
- supabaseInit: string = "getSupabase(c)",
-) => {
- if (customerType === "user") {
- return `const supabase = ${supabaseInit};
-
- const { data, error } = await supabase.auth.getUser();
-
- if (!data?.user?.id) return null;
-
- return {
- customerId: data.user.id,
- customerData: { name: "", email: "" },
- };`;
- }
-
- return `const supabase = ${supabaseInit};
-
- const { data, error } = await supabase.auth.getUser();
-
- if (!data?.user?.id) return null;
-
- const orgId = "users_org_id"; // Get the orgId from your DB
-
- return {
- customerId: orgId,
- customerData: { name: "", email: "" },
- };`;
-};
-
-export const honoSupabase = (customerType: "user" | "org") => {
- return `// index.ts
-
-import { autumnHandler } from "autumn-js/hono";
-import { getSupabase, supabaseMiddleware } from "./middleware/auth.middleware.js";
-
-app.use("*", supabaseMiddleware());
-app.use(
- "/api/autumn/*",
- autumnHandler({
- identify: async (c: Context) => {
- ${supabaseSnippet(customerType)}
- },
- })
-);`;
-};
-export const honoBetterAuth = (customerType: "user" | "org") => {
- return `// index.ts
-
-import { autumnHandler } from "autumn-js/hono";
-import { auth } from "@/lib/auth"
-
-app.use(
- "/api/autumn/*",
- autumnHandler({
- identify: async (c: Context) => {
-${betterAuthSnippet(customerType, "c.req.raw.headers", 3)}
- },
- })
-);`;
-};
-export const honoOther = (customerType: "user" | "org") => {
- return `// index.ts
-
-import { autumnHandler } from "autumn-js/hono";
-import { auth } from "@/lib/auth"
-
-app.use(
- "/api/autumn/*",
- autumnHandler({
- identify: async (c: Context) => {
- const customerId = "your_customer_id"; // Get customer id from your database
-
- return {
- customerId,
- customerData: { name: "", email: "" },
- };
- },
- })
-);`;
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/snippets/nextjsHandler.tsx b/vite/src/views/onboarding2/integrate/integration-steps/snippets/nextjsHandler.tsx
deleted file mode 100644
index b295fd420..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/snippets/nextjsHandler.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-export const nextjsBetterAuthUser = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@/lib/auth";
-import { headers } from "next/headers";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- return {
- customerId: session?.user.id,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});
-`;
-
-export const nextjsBetterAuthOrg = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@/lib/auth";
-import { headers } from "next/headers";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- return {
- customerId: session?.session.activeOrganizationId,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});
-`;
-
-// Supabase Auth
-export const nextjsSupabaseUser = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { createClient } from "@/utils/supabase/server";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const supabase = await createClient();
- const { data, error } = await supabase.auth.getUser();
-
- if (error || !data?.user) {
- return null;
- }
-
- return {
- customerId: data.user.id,
- customerData: {
- name: data.user.user_metadata?.name,
- email: data.user.email,
- },
- };
- },
-});`;
-
-export const nextjsSupabaseOrg = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { createClient } from "@/utils/supabase/server";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const supabase = await createClient();
- const { data, error } = await supabase.auth.getUser();
-
- if (error || !data?.user) {
- return null;
- }
-
- // Get the orgId of the user from your DB
- const customerId = "users_org_id";
-
- return {
- customerId,
- customerData: {
- name: data.user.user_metadata?.name,
- email: data.user.email,
- },
- };
- },
-});
-`;
-
-export const nextjsClerkUser = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@clerk/nextjs/server";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const { userId } = await auth();
-
- if (!userId) return null;
-
- return {
- customerId: userId,
- // To store the customer name and email
- customerData: { name: "", email: "" },
- };
- },
-});
-`;
-
-export const nextjsClerkOrg = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@clerk/nextjs/server";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const { userId, orgId } = await auth();
-
- if (!userId || !orgId) return null;
-
- return {
- customerId: orgId,
- // To store the customer name and email
- customerData: { name: "", email: "" },
- };
- },
-});
-`;
-
-export const nextjsOther = `
-// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-
-export const { GET, POST } = autumnHandler({
- identify: async (request) => {
- // Authenticate the request and get the customer ID
- const customerId = "customer_id";
- return {
- customerId,
- customerData: { name: "", email: "" },
- };
- },
-});
-`;
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/snippets/rr7Handler.tsx b/vite/src/views/onboarding2/integrate/integration-steps/snippets/rr7Handler.tsx
deleted file mode 100644
index 6211117a7..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/snippets/rr7Handler.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { betterAuthSnippet } from "./betterAuth";
-import { supabaseAuthSnippet } from "./supabaseAuth";
-
-export const rr7BetterAuth = (customerType: "user" | "org") => {
- return `// app/routes/api.autumn.tsx
-
-import { autumnHandler } from "autumn-js/react-router";
-import { auth } from "@/lib/auth";
-
-const handler = autumnHandler({
- identify: async (args) => {
-${betterAuthSnippet(customerType, "args.request.headers", 2)}
- },
-});
-
-export const loader = handler.loader;
-export const action = handler.action;`;
-};
-export const rr7Supabase = (customerType: "user" | "org") => {
- return `// app/routes/api.autumn.tsx
-
-import { autumnHandler } from "autumn-js/react-router";
-import { createClient } from "@/utils/supabase/server";
-
-const handler = autumnHandler({
- identify: async (args) => {
- ${supabaseAuthSnippet({ customerType })}
- },
-});
-
-export const loader = handler.loader;
-export const action = handler.action;`;
-};
-
-export const clerkSnippet = (customerType: "user" | "org") => {
- if (customerType === "user") {
- return `const { userId } = await getAuth(args);
-
- if (!userId) return null;
-
- return {
- customerId: userId,
- customerData: { name: "", email: "" },
- };`;
- }
-
- return `const { userId, orgId } = await getAuth(args);
-
- if (!userId || !orgId) return null;
-
- return {
- customerId: orgId,
- customerData: { name: "", email: "" },
- };`;
-};
-export const rr7Clerk = (customerType: "user" | "org") => {
- return `// app/routes/api.autumn.tsx
-
-import { autumnHandler } from "autumn-js/react-router";
-import { getAuth } from "@clerk/react-router/ssr.server";
-
-const handler = autumnHandler({
- identify: async (args) => {
- ${clerkSnippet(customerType)}
- },
-});
-
-export const loader = handler.loader;
-export const action = handler.action;`;
-};
-export const rr7Other = (customerType: "user" | "org") => {
- return `// app/routes/api.autumn.tsx
-
-import { autumnHandler } from "autumn-js/react-router";
-
-const handler = autumnHandler({
- identify: async (args) => {
- const customerId = "your_customer_id"; // Get customer id from your database
-
- return {
- customerId,
- customerData: { name: "", email: "" },
- };
- },
-});
-
-export const loader = handler.loader;
-export const action = handler.action;`;
-};
diff --git a/vite/src/views/onboarding2/integrate/integration-steps/snippets/supabaseAuth.tsx b/vite/src/views/onboarding2/integrate/integration-steps/snippets/supabaseAuth.tsx
deleted file mode 100644
index e15864ca6..000000000
--- a/vite/src/views/onboarding2/integrate/integration-steps/snippets/supabaseAuth.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-export const supabaseAuthSnippet = ({
- backendLang,
- customerType,
-}: {
- backendLang?: string;
- customerType: "user" | "org";
-}) => {
- if (customerType === "user") {
- return `const supabase = await createClient(request);
- const { data, error } = await supabase.auth.getUser();
-
- if (error || !data?.user) return null;
-
- return {
- customerId: "123",
- customerData: {
- name: data.user.user_metadata?.name,
- email: data.user.email,
- },
- };`;
- }
-
- return `const supabase = await createClient(request);
- const { data, error } = await supabase.auth.getUser();
-
- if (error || !data?.user) return null;
-
- // Get the orgId from your DB
- const orgId = "users_org_id";
-
- return {
- customerId: orgId,
- customerData: { name: "", email: "" },
- };`;
-};
diff --git a/vite/src/views/onboarding2/integrate/select-stack/SelectFrameworks.tsx b/vite/src/views/onboarding2/integrate/select-stack/SelectFrameworks.tsx
deleted file mode 100644
index 9d522ed80..000000000
--- a/vite/src/views/onboarding2/integrate/select-stack/SelectFrameworks.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import { Building, CircleUserRound, Code, Fingerprint } from "lucide-react";
-import { useIntegrateContext } from "../IntegrateContext";
-import { Backend, Frontend } from "../StackEnums";
-
-export const SelectFrameworks = () => {
- const { queryStates, setQueryStates } = useIntegrateContext();
-
- const iconSize = 12;
- const frontendOptions = [
- { value: Frontend.Nextjs, label: "Next.js", logo: "nextjs.png" },
- { value: Frontend.ReactRouter, label: "RR7", logo: "react-router.svg" },
- { value: Frontend.Vite, label: "Vite SPA", logo: "vite.svg" },
- {
- value: Frontend.Other,
- label: "Other",
- icon: ,
- },
- ];
-
- const backendOptions = [
- { value: Backend.Nextjs, label: "Next.js", logo: "nextjs.png" },
- {
- value: Backend.ReactRouter,
- label: "RR7",
- logo: "react-router.svg",
- },
- { value: Backend.Hono, label: "Hono", logo: "hono.png" },
- { value: Backend.Express, label: "Express", logo: "express.png" },
- { value: Backend.Elysia, label: "Elysia", logo: "elysia.png" },
- {
- value: Backend.Other,
- label: "Other",
- icon: ,
- },
- ];
-
- const authOptions = [
- { value: "better_auth", label: "Better Auth", logo: "better-auth.png" },
- { value: "supabase", label: "Supabase", logo: "supabase.png" },
- { value: "clerk", label: "Clerk", logo: "clerk.png" },
- {
- value: "other",
- label: "Other",
- icon: ,
- },
- ];
-
- const customerOptions = [
- {
- value: "user",
- label: "Users",
- icon: ,
- },
- {
- value: "org",
- label: "Orgs",
- icon: ,
- },
- ];
- return (
-
-
-
Frontend
-
- {frontendOptions.map((option) => {
- return (
-
- );
- })}
-
-
-
-
Backend
-
- {backendOptions.map((option) => {
- return (
-
- );
- })}
-
-
-
-
-
-
Auth Provider
-
- {authOptions.map((option) => {
- return (
-
- );
- })}
-
-
-
-
Customer Type
-
- {customerOptions.map((option) => {
- return (
-
- );
- })}
-
-
-
-
- );
-};
-
-const FrameworkContainer = ({
- option,
- setQueryStates,
- queryStates,
- type,
-}: {
- option: {
- value: string;
- label: string;
- logo?: string;
- icon?: React.ReactNode;
- };
- setQueryStates: (queryStates: any) => void;
- queryStates: any;
- type: "backend" | "auth" | "customerType" | "frontend";
-}) => {
- const isSelected = queryStates[type] === option.value;
- return (
- setQueryStates({ ...queryStates, [type]: option.value })}
- >
- {option.logo ? (
-
- ) : option.icon ? (
- <>{option.icon}>
- ) : (
-
- )}
-
- {option.label}
-
-
- );
-};
diff --git a/vite/src/views/onboarding2/model-pricing/AddTrialButton.tsx b/vite/src/views/onboarding2/model-pricing/AddTrialButton.tsx
deleted file mode 100644
index 503bc087f..000000000
--- a/vite/src/views/onboarding2/model-pricing/AddTrialButton.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { PlusIcon, X } from "lucide-react";
-import { useState } from "react";
-import { Button } from "@/components/ui/button";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { CreateFreeTrial } from "@/views/products/product/free-trial/CreateFreeTrial";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import { handleAutoSave } from "./model-pricing-utils/modelPricingUtils";
-
-export const AddTrialButton = () => {
- const axiosInstance = useAxiosInstance();
- const { product, setProduct, autoSave, refetch } = useProductContext();
- const [open, setOpen] = useState(false);
-
- return (
- <>
-
- {
- e.preventDefault();
- e.stopPropagation();
- setOpen(true);
- }}
- className="justify-start w-fit p-0 hover:bg-transparent text-t2 font-medium hover:text-t1"
- >
- {product?.free_trial ? (
-
-
- {product?.free_trial?.length} {product.free_trial?.duration} trial
-
-
{
- e.preventDefault();
- e.stopPropagation();
- setProduct({
- ...product,
- free_trial: null,
- });
-
- if (autoSave) {
- handleAutoSave({
- axiosInstance,
- productId: product.id,
- product: { ...product, free_trial: null },
- refetch,
- });
- }
- }}
- className="hover:bg-zinc-300 !h-4 !w-4 text-t3 mt-0.5"
- >
-
-
-
- ) : (
-
- Add Free Trial
-
-
- )}
-
- >
- );
-};
diff --git a/vite/src/views/onboarding2/model-pricing/EditProduct.tsx b/vite/src/views/onboarding2/model-pricing/EditProduct.tsx
deleted file mode 100644
index 4e3678a7e..000000000
--- a/vite/src/views/onboarding2/model-pricing/EditProduct.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-import type { Product } from "@autumn/shared";
-import { SaveIcon } from "lucide-react";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { isFreeProduct } from "@/utils/product/priceUtils";
-import { CreateFreeTrial } from "@/views/products/product/free-trial/CreateFreeTrial";
-import { ProductContext } from "@/views/products/product/ProductContext";
-import { CreateProductItem2 } from "@/views/products/product/product-item/CreateProductItem2";
-import { ProductItemTable } from "@/views/products/product/product-item/ProductItemTable";
-import { ToggleDefaultProduct } from "@/views/products/product/product-sidebar/ToggleDefaultProduct";
-import { updateProduct } from "@/views/products/product/utils/updateProduct";
-import ConfirmNewVersionDialog from "@/views/products/product/versioning/ConfirmNewVersionDialog";
-import { AddTrialButton } from "./AddTrialButton";
-import { EditProductDetails } from "./edit-product/EditProductDetails";
-import { useModelPricingContext } from "./ModelPricingContext";
-import { handleAutoSave } from "./model-pricing-utils/modelPricingUtils";
-
-export const EditProduct = ({
- refetchAutumnProducts,
-}: {
- refetchAutumnProducts: () => Promise;
-}) => {
- const { refetch, counts, products } = useProductsQuery();
- const { productDataState } = useModelPricingContext();
-
- const [showNewVersionDialog, setShowNewVersionDialog] = useState(false);
- const [freeTrialModalOpen, setFreeTrialModalOpen] = useState(false);
-
- const productCount = counts?.[productDataState.product?.id];
-
- const {
- product,
- setProduct,
- entityFeatureIds,
- setEntityFeatureIds,
- actionState,
- } = productDataState;
-
- const [details, setDetails] = useState({
- name: product.name,
- id: product.id,
- });
-
- const axiosInstance = useAxiosInstance();
- const [saveLoading, setSaveLoading] = useState(false);
-
- const runUpdateProduct = async () => {
- setSaveLoading(true);
- await updateProduct({
- axiosInstance,
- product,
- onSuccess: refetch,
- });
- setSaveLoading(false);
- };
-
- const handleSaveClicked = async () => {
- if (productCount?.all > 0) {
- setShowNewVersionDialog(true);
- return;
- }
- await runUpdateProduct();
- };
-
- const hasItems = product.items.length > 0;
- const hasCustomers = productCount?.all > 0;
- const showSaveButton = hasCustomers || product.version > 1;
- const firstProductCreated = products.length > 0;
-
- const handleToggleSettings = async (key: string) => {
- if (!product) return;
-
- const curValue = product[key as keyof Product];
- const newProduct = { ...product, [key]: !curValue };
-
- // Validate
- if (key === "is_default" && !isFreeProduct(product.items)) {
- toast.error("Default plan must be a free plan");
- return;
- }
-
- setProduct(newProduct);
-
- if (!showSaveButton) {
- handleAutoSave({
- axiosInstance,
- productId: product.id ? product.id : details.id,
- product: { ...product, [key]: !curValue },
- refetch,
- });
- }
- };
-
- return (
-
-
-
{
- console.log("Refetching edit product...");
- await Promise.all([refetch(), refetchAutumnProducts()]);
- },
- entityFeatureIds,
- setEntityFeatureIds,
- isOnboarding: true,
- autoSave: !showSaveButton,
- }}
- >
-
-
-
-
-
-
- {showSaveButton && (
- }
- disabled={actionState.disabled}
- onClick={handleSaveClicked}
- isLoading={saveLoading}
- >
- Save Product
-
- )}
-
-
- {firstProductCreated && (
- <>
- {product.items.length === 0 ? (
-
- {/* Next, add items to define what customers with this product
- get access to, and how much they should be charged for it. */}
- Next, add which features your customers can use on this
- product and how much it should cost.
-
- ) : (
-
- )}
-
{" "}
- >
- )}
-
-
-
-
-
-
-
- A default product is enabled by default for all new users,
- typically used for your free product.
-
-
-
-
-
- A product that can be added on top of a customer's main
- product. Eg. one time purchases or top ups.
-
-
-
-
-
- Add a free trial to your product.
-
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding2/model-pricing/ModelPricing.tsx b/vite/src/views/onboarding2/model-pricing/ModelPricing.tsx
deleted file mode 100644
index df46d0a7e..000000000
--- a/vite/src/views/onboarding2/model-pricing/ModelPricing.tsx
+++ /dev/null
@@ -1,321 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { Feature, type Product } from "autumn-js";
-import { AutumnProvider } from "autumn-js/react";
-import { ArrowRight, PlusIcon } from "lucide-react";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import PricingTable from "@/components/autumn/pricing-table";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { cn } from "@/lib/utils";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { slugify } from "@/utils/formatUtils/formatTextUtils";
-import { getBackendErr, nullish } from "@/utils/genUtils";
-import { ProductsContext } from "@/views/products/ProductsContext";
-import { useProductData } from "@/views/products/product/hooks/useProductData";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import ConnectStripeDialog from "../ConnectStripeDialog";
-import { useOnboardingQueryState } from "../hooks/useOnboardingQueryState";
-import { ConnectStripeStep } from "../integrate/ConnectStripeStep";
-import { EditProduct } from "./EditProduct";
-import {
- ModelPricingContext,
- useModelPricingContext,
-} from "./ModelPricingContext";
-import { SelectEditProduct } from "./SelectEditProduct";
-
-const defaultProduct = {
- id: "",
- name: "",
- items: [],
- is_default: false,
- is_add_on: false,
- free_trial: null,
-};
-
-export const ModelPricing = ({
- // data,
- // mutate,
- // mutateAutumnProducts,
- refetchAutumnProducts,
- autumnProducts,
- // productCounts,
- // mutateCounts,
- // queryStates,
- // setQueryStates,
-}: {
- // data: any;
- // mutate: any;
- // mutateAutumnProducts: any;
- refetchAutumnProducts: any;
- autumnProducts: Product[];
- // productCounts: any;
- // mutateCounts: any;
- // queryStates: any;
- // setQueryStates: any;
-}) => {
- const { queryStates, setQueryStates } = useOnboardingQueryState();
- const { products, refetch } = useProductsQuery();
-
- const getCurProduct = () => {
- if (queryStates.productId) {
- const prod = products.find(
- (p: ProductV2) => p.id === queryStates.productId,
- );
-
- if (prod) return prod;
- }
-
- if (products.length > 0) return products[0];
-
- return defaultProduct;
- };
-
- const curProduct = getCurProduct();
-
- const firstItemCreated = autumnProducts.some(
- (p: Product) => p.items.length > 0,
- );
-
- const [editingNewProduct, setEditingNewProduct] = useState(
- nullish(curProduct),
- );
-
- const [connectStripeOpen, setConnectStripeOpen] = useState(false);
-
- const productDataState = useProductData({
- originalProduct: curProduct as any,
- });
-
- const { product } = productDataState;
-
- useEffect(() => {
- if (curProduct && !queryStates.productId) {
- setQueryStates({
- productId: curProduct.id,
- });
- }
- }, [curProduct]);
-
- if (!product) return null;
-
- return (
-
- productDataState,
-
- // For &
- refetch: async () => {
- await Promise.all([refetch(), refetchAutumnProducts()]);
- },
-
- // product,
- // setProduct,
- // mutate,
- // data,
- // productCount: productCounts?.[product?.id ?? ""],
- // queryStates,
- // setQueryStates,
- // mutateAutumnProducts,
- // mutateCounts,
- setConnectStripeOpen,
- }}
- >
-
-
-
-
-
-
-
-
Create your products
-
- To start, model your app's pricing by creating a product for
- your free plans, paid plans and any add-ons or top-ups.
-
-
- {firstItemCreated && (
-
-
- {products.length > 0 && }
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{
- setQueryStates({
- page: "integrate",
- });
- }}
- className="bg-zinc-800 w-fit hover:bg-zinc-700"
- >
- Next: Integrate Autumn
-
-
-
-
-
-
- );
-};
-
-const NewProductPopover = () => {
- const [open, setOpen] = useState(false);
-
- const {
- refetch,
- productDataState: { setProduct },
- } = useModelPricingContext();
-
- const axiosInstance = useAxiosInstance();
- const [details, setDetails] = useState({
- name: "",
- id: "",
- });
-
- const [creating, setCreating] = useState(false);
-
- const handleSave = async () => {
- try {
- setCreating(true);
- await axiosInstance.post("/v1/products", {
- name: details.name,
- id: details.id,
- });
- await refetch();
- const newProduct = {
- ...defaultProduct,
- name: details.name,
- id: details.id,
- };
- setProduct(newProduct);
- setOpen(false);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to create plan"));
- } finally {
- setCreating(false);
- }
- };
-
- return (
-
-
- {}}
- >
-
- New Product
-
-
-
-
-
New Product
-
-
- setDetails({
- ...details,
- name: e.target.value,
- id: slugify(e.target.value),
- })
- }
- />
-
-
-
-
- Create
-
-
-
-
-
- );
-};
-
-// Get latest product
-const getAutumnProducts = () => {
- // const curProductItems = product.items.map((item: any) =>
- // getProductItemResponse({
- // item,
- // features: data.features,
- // currency: "USD",
- // })
- // );
- // return [product, ...autumnProducts];
- // const properties: ProductProperties = {
- // has_trial: notNullish(product.free_trial),
- // is_free: isFreeProduct(product.items),
- // is_one_off: isOneOffProduct(product.items),
- // updateable: product.items.some(
- // (item: any) => item.usage_model == UsageModel.Prepaid
- // ),
- // };
- // const latestProduct = {
- // ...product,
- // items: curProductItems,
- // properties,
- // };
- // const curProducts = autumnProducts.filter(
- // (p: Product) => p.id !== product.id
- // );
- // if (!firstItemCreated) {
- // return [];
- // }
- // const newProducts = [latestProduct, ...curProducts] as any;
- // return sortProductsV2({ products: newProducts }) as Product[];
-};
diff --git a/vite/src/views/onboarding2/model-pricing/ModelPricingContext.tsx b/vite/src/views/onboarding2/model-pricing/ModelPricingContext.tsx
deleted file mode 100644
index f36e1569c..000000000
--- a/vite/src/views/onboarding2/model-pricing/ModelPricingContext.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createContext, useContext } from "react";
-
-export const ModelPricingContext = createContext(null);
-
-export const useModelPricingContext = () => {
- const context = useContext(ModelPricingContext);
-
- return context;
-};
diff --git a/vite/src/views/onboarding2/model-pricing/SelectEditProduct.tsx b/vite/src/views/onboarding2/model-pricing/SelectEditProduct.tsx
deleted file mode 100644
index 70f81fd8f..000000000
--- a/vite/src/views/onboarding2/model-pricing/SelectEditProduct.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import { ChevronDownIcon, Trash } from "lucide-react";
-// import { DeleteProductDialog } from "@/views/products/products/product-row-toolbar/DeleteProductDialog";
-import { useState } from "react";
-import { Button } from "@/components/ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { cn } from "@/lib/utils";
-import { useOnboardingQueryState } from "../hooks/useOnboardingQueryState";
-import { useModelPricingContext } from "./ModelPricingContext";
-
-export const SelectEditProduct = () => {
- const {
- productDataState: { product, setProduct },
- } = useModelPricingContext();
-
- const { products } = useProductsQuery();
- const { setQueryStates } = useOnboardingQueryState();
-
- const [deleteProductOpen, setDeleteProductOpen] = useState(false);
- const [deleteProductSelected, setDeleteProductSelected] = useState(product);
-
- const selectedClassName = "!bg-zinc-100 h-7 border";
- if (products.length > 3) {
- return (
- <>
- {/* */}
-
-
-
- {product?.name || "None"}
-
-
-
-
- {products.map((p: any) => {
- if (!p.name) {
- return null;
- }
- return (
- {
- setProduct(p);
- setQueryStates({
- productId: p.id,
- });
- }}
- className="flex items-center justify-between group"
- >
- {p.name}
- {
- e.stopPropagation();
- e.preventDefault();
- setDeleteProductSelected(p);
- setDeleteProductOpen(true);
- console.log("Delete product selected:", p);
- }}
- >
-
-
-
- );
- })}
-
-
- >
- );
- }
-
- const tabTriggerClass =
- "data-[state=active]:bg-stone-200 data-[state=active]:text-t2 data-[state=active]:font-medium";
-
- return (
- <>
- {/* */}
-
-
- {products.map((p: any) => {
- if (!p.name) {
- return null;
- }
-
- const isSelected = p.id === product.id;
- return (
- {
- setProduct(p);
- setQueryStates({
- productId: p.id,
- });
- }}
- >
- {p.name}
- {isSelected && (
- {
- setDeleteProductOpen(true);
- }}
- >
-
-
- )}
-
- );
- })}
-
-
- >
- );
-};
diff --git a/vite/src/views/onboarding2/model-pricing/edit-product/EditProductDetails.tsx b/vite/src/views/onboarding2/model-pricing/edit-product/EditProductDetails.tsx
deleted file mode 100644
index eff04fb52..000000000
--- a/vite/src/views/onboarding2/model-pricing/edit-product/EditProductDetails.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { slugify } from "@/utils/formatUtils/formatTextUtils";
-import { getBackendErr } from "@/utils/genUtils";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import { useModelPricingContext } from "../ModelPricingContext";
-import { handleAutoSave } from "../model-pricing-utils/modelPricingUtils";
-
-export const EditProductDetails = () => {
- const { productDataState } = useModelPricingContext();
- const { product, setProduct } = productDataState;
-
- const { products, refetch } = useProductsQuery();
-
- const allowCreate = products.length === 0;
- const { autoSave } = useProductContext();
- const [createLoading, setCreateLoading] = useState(false);
-
- const axiosInstance = useAxiosInstance();
- const [details, setDetails] = useState({
- name: product?.name,
- id: product?.id,
- });
-
- useEffect(() => {
- if (product.id) {
- setDetails({
- name: product.name,
- id: product.id,
- });
- }
- }, [product]);
-
- const handleCreateProduct = async () => {
- setCreateLoading(true);
- try {
- await axiosInstance.post("/v1/products", {
- name: details.name,
- id: details.id,
- });
- await refetch();
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to create plan"));
- } finally {
- setCreateLoading(false);
- }
- };
-
- return (
-
-
- Product Name
- {
- if (autoSave && !allowCreate) {
- await handleAutoSave({
- axiosInstance,
- productId: product.id ? product.id : details.id,
- product: {
- ...product,
- name: details.name,
- id: details.id,
- },
- refetch,
- });
- }
- setProduct({
- ...product,
- name: details.name,
- id: details.id,
- });
- }}
- placeholder="Eg. Free Product"
- value={details.name}
- onChange={(e) => {
- const newIdData = allowCreate
- ? {
- id: slugify(e.target.value),
- }
- : {};
- setDetails({
- ...details,
- name: e.target.value,
- ...newIdData,
- });
- }}
- />
-
-
-
-
- Product ID
-
-
-
- The product ID is used to identify the product in the API when
- you're making a payment.
-
-
-
-
-
{
- setDetails({
- ...details,
- id: e.target.value,
- });
- }}
- placeholder="Eg. free_plan"
- />
-
- {allowCreate && (
-
- Create Product
-
- )}
-
- );
-};
diff --git a/vite/src/views/onboarding2/model-pricing/model-pricing-utils/modelPricingUtils.ts b/vite/src/views/onboarding2/model-pricing/model-pricing-utils/modelPricingUtils.ts
deleted file mode 100644
index 7748bdac9..000000000
--- a/vite/src/views/onboarding2/model-pricing/model-pricing-utils/modelPricingUtils.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import type { AxiosInstance } from "axios";
-import { toast } from "sonner";
-import { getBackendErr, notNullish } from "@/utils/genUtils";
-
-export const handleAutoSave = async ({
- axiosInstance,
- productId,
- product,
- refetch,
-}: {
- axiosInstance: AxiosInstance;
- productId: string;
- product: ProductV2;
- refetch?: any;
-}) => {
- if (!productId || !product.id) return;
- try {
- await axiosInstance.post(
- `v1/products/${productId}?upsert=true&disable_version=true`,
- {
- ...product,
- group: notNullish(product.group) ? product.group : undefined,
- },
- );
- if (refetch) {
- await refetch();
- }
- } catch (error) {
- console.log(error);
- toast.error(getBackendErr(error, "Failed to auto save product"));
- }
-};
diff --git a/vite/src/views/onboarding2/model-pricing/model-pricing-utils/productToPricingCard.tsx b/vite/src/views/onboarding2/model-pricing/model-pricing-utils/productToPricingCard.tsx
deleted file mode 100644
index fe7691c7b..000000000
--- a/vite/src/views/onboarding2/model-pricing/model-pricing-utils/productToPricingCard.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export const productToPricingCard = (product: any) => {
- return {
- id: product.id,
- name: product.name,
- items: product.items,
- };
-};
diff --git a/vite/src/views/onboarding2/model-pricing/usePricingTable.tsx b/vite/src/views/onboarding2/model-pricing/usePricingTable.tsx
deleted file mode 100644
index f8d324b8f..000000000
--- a/vite/src/views/onboarding2/model-pricing/usePricingTable.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { useAxiosSWR } from "@/services/useAxiosSwr";
-
-export const useListProducts = ({ customerId }: { customerId: string }) => {
- const { data, isLoading, error, mutate } = useAxiosSWR({
- url: `/v1/products?customer_id=${customerId}`,
- options: {
- refreshInterval: 0,
- },
- });
-
- return {
- products: data?.list || [],
- isLoading,
- error,
- mutate,
- };
-};
diff --git a/vite/src/views/onboarding2/utils/useCustomerReplica.tsx b/vite/src/views/onboarding2/utils/useCustomerReplica.tsx
deleted file mode 100644
index b0b68e038..000000000
--- a/vite/src/views/onboarding2/utils/useCustomerReplica.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { CheckoutParams } from "autumn-js";
-import { toast } from "sonner";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { toSnakeCase } from "@/utils/formatUtils/formatUtils";
-
-const cusData = {
- customer_id: "demo_123",
- customer_data: {
- name: "Demo Customer",
- email: "demo@example.com",
- },
-};
-
-export const useCustomerReplica = () => {
- const axiosInstance = useAxiosInstance();
-
- const checkout = async (params: any) => {
- try {
- const { data } = await axiosInstance.post("/v1/checkout", {
- ...cusData,
- ...toSnakeCase(params),
- });
-
- if (data.url) {
- window.open(data.url, "_blank");
- }
- return data;
- } catch (error: any) {
- toast.error(`Failed to checkout: ${error.message}`);
- return null;
- }
- };
-
- return {
- checkout,
- };
-};
diff --git a/vite/src/views/onboarding3/ConnectStripeDialog.tsx b/vite/src/views/onboarding3/ConnectStripeDialog.tsx
deleted file mode 100644
index 0ac3eedc2..000000000
--- a/vite/src/views/onboarding3/ConnectStripeDialog.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/v2/buttons/Button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/v2/dialogs/Dialog";
-import { Input } from "@/components/v2/inputs/Input";
-import { useAutumnFlags } from "@/hooks/common/useAutumnFlags";
-import { useOrg } from "@/hooks/common/useOrg";
-import { OrgService } from "@/services/OrgService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-
-export default function ConnectStripeDialog({
- open,
- setOpen,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
-}) {
- const { org, mutate: mutateOrg } = useOrg();
- const axiosInstance = useAxiosInstance();
- const flags = useAutumnFlags();
-
- const [testApiKey, setTestApiKey] = useState("");
- const [loading, setLoading] = useState(false);
- const [showSecretKeyInput, setShowSecretKeyInput] = useState(false);
-
- // Check if user can paste secret keys (feature flagged)
- const canPasteSecretKey =
- flags.stripe_key === true || flags.platform === true;
-
- const handleRedirectToOAuth = async () => {
- try {
- const { data } = await axiosInstance.get(
- `/v1/organization/stripe/oauth_url?from_onboarding=true`,
- );
- window.open(data.oauth_url, "_blank");
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to redirect to OAuth"));
- }
- };
-
- const handleConnectStripe = async () => {
- setLoading(true);
- try {
- await OrgService.connectStripe(axiosInstance, {
- secret_key: testApiKey,
- });
-
- toast.success("Successfully connected to Stripe");
- await mutateOrg();
- setOpen(false);
- } catch (error) {
- console.log("Failed to connect Stripe", error);
- toast.error(getBackendErr(error, "Failed to connect Stripe"));
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
-
-
- Connect your Stripe sandbox account
-
-
- {!showSecretKeyInput ? (
-
-
- To preview checkout, please connect your Stripe sandbox account.
-
-
-
-
- Connect via OAuth
-
- {canPasteSecretKey && (
- setShowSecretKeyInput(true)}
- >
- Paste secret key
-
- )}
-
-
- ) : (
-
-
- Grab your test secret key from your API keys page{" "}
-
- here
-
-
-
-
setTestApiKey(e.target.value)}
- disabled={org?.stripe_connected}
- />
-
-
- setShowSecretKeyInput(false)}
- >
- Back
-
-
- Connect Stripe
-
-
-
- )}
-
-
- );
-}
diff --git a/vite/src/views/onboarding3/OnboardingCheckoutDialog.tsx b/vite/src/views/onboarding3/OnboardingCheckoutDialog.tsx
deleted file mode 100644
index 9b3588091..000000000
--- a/vite/src/views/onboarding3/OnboardingCheckoutDialog.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import type { CheckoutResult } from "autumn-js";
-import { useCustomer } from "autumn-js/react";
-import { useEffect, useState } from "react";
-import { Button } from "@/components/v2/buttons/Button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/v2/dialogs/Dialog";
-import { getCheckoutContent } from "@/lib/autumn/checkout-content";
-
-export interface CheckoutDialogProps {
- open: boolean;
- setOpen: (open: boolean) => void;
- checkoutResult: CheckoutResult;
- onComplete?: () => void;
-}
-
-const formatCurrency = ({
- amount,
- currency,
-}: {
- amount: number;
- currency: string;
-}) => {
- return new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: currency,
- }).format(amount);
-};
-
-export default function OnboardingCheckoutDialog(params: CheckoutDialogProps) {
- const { attach } = useCustomer();
- const [checkoutResult, setCheckoutResult] = useState<
- CheckoutResult | undefined
- >(params?.checkoutResult);
- const [loading, setLoading] = useState(false);
-
- useEffect(() => {
- if (params.checkoutResult) {
- setCheckoutResult(params.checkoutResult);
- }
- }, [params.checkoutResult]);
-
- if (!checkoutResult) {
- return null;
- }
-
- const { open, setOpen } = params;
- const { title, message } = getCheckoutContent(checkoutResult);
-
- const handleConfirm = async () => {
- setLoading(true);
- try {
- const options = checkoutResult.options.map((option) => ({
- featureId: option.feature_id,
- quantity: option.quantity,
- }));
-
- await attach({
- productId: checkoutResult.product.id,
- options,
- openInNewTab: true,
- });
-
- params.onComplete?.();
- setOpen(false);
- } catch (error) {
- console.error("Checkout error:", error);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
-
-
- {title}
-
-
-
-
{message}
-
- {/* Product items */}
- {checkoutResult.product.items.filter(
- (item) => item.type !== "feature",
- ).length > 0 && (
-
-
Price
- {checkoutResult.product.items
- .filter((item) => item.type !== "feature")
- .map((item, index) => (
-
-
- {item.feature?.name || "Subscription"}
-
-
- {item.display?.primary_text}{" "}
- {item.display?.secondary_text}
-
-
- ))}
-
- )}
-
- {/* Total */}
- {checkoutResult.total > 0 && (
-
- Total due today
-
- {formatCurrency({
- amount: checkoutResult.total,
- currency: checkoutResult.currency,
- })}
-
-
- )}
-
-
-
-
- Confirm
-
-
-
-
- );
-}
diff --git a/vite/src/views/onboarding3/OnboardingPreview.tsx b/vite/src/views/onboarding3/OnboardingPreview.tsx
deleted file mode 100644
index d4c5b7170..000000000
--- a/vite/src/views/onboarding3/OnboardingPreview.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import { PricingTableContainer } from "@/components/autumn/PricingTableContainer";
-import { PlanTypeBadges } from "@/components/v2/badges/PlanTypeBadges";
-import { Card, CardContent, CardHeader } from "@/components/v2/cards/Card";
-import { Separator } from "@/components/v2/separator";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useFeatureStore } from "@/hooks/stores/useFeatureStore";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { useIsEditingPlan, useSheetStore } from "@/hooks/stores/useSheetStore";
-import { cn } from "@/lib/utils";
-import { BasePriceDisplay } from "../products/plan/components/plan-card/BasePriceDisplay";
-import { DummyPlanFeatureRow } from "../products/plan/components/plan-card/DummyPlanFeatureRow";
-import { PlanCardToolbar } from "../products/plan/components/plan-card/PlanCardToolbar";
-import { PlanFeatureList } from "../products/plan/components/plan-card/PlanFeatureList";
-import { useOnboarding3QueryState } from "./hooks/useOnboarding3QueryState";
-import { useOnboardingStore } from "./store/useOnboardingStore";
-import { getStepNumber } from "./utils/onboardingUtils";
-
-const MAX_PLAN_NAME_LENGTH = 20;
-interface OnboardingPreviewProps {
- setConnectStripeOpen?: (open: boolean) => void;
-}
-
-export const OnboardingPreview = ({
- setConnectStripeOpen,
-}: OnboardingPreviewProps) => {
- // Get step from query state
- const { queryStates } = useOnboarding3QueryState();
- const step = queryStates.step;
-
- // Get state from Zustand
- const playgroundMode = useOnboardingStore((state) => state.playgroundMode);
- const feature = useFeatureStore((state) => state.feature);
- const setSheet = useSheetStore((state) => state.setSheet);
- const isPlanBeingEdited = useIsEditingPlan();
- const handleDeletePlanSuccess = useOnboardingStore(
- (s) => s.handleDeletePlanSuccess,
- );
-
- const product = useProductStore((s) => s.product);
- const { products: allProducts } = useProductsQuery();
-
- const currentStep = getStepNumber(step);
-
- const showBasicInfo = currentStep >= 1;
- const showPricing = currentStep >= 1;
- const showDummyFeature = currentStep === 2;
- const showFeatures = currentStep >= 3;
- const showToolbar = currentStep >= 4;
- const allowAddFeature = currentStep >= 4;
- const showPricingTable = currentStep === 5;
-
- if (!product) {
- return (
-
-
- Loading preview...
-
-
- );
- }
-
- const handleEdit = () => {
- setSheet({ type: "edit-plan" });
- };
-
- // Show preview mode for step 4 (Playground) when in preview mode OR step 5
- if (showPricingTable || (currentStep === 4 && playgroundMode === "preview")) {
- return (
-
- );
- }
-
- return (
-
-
- {/* Absolutely positioned toolbar - CANNOT MOVE */}
- {showToolbar && (
-
- )}
-
- {/* Left content with padding to avoid toolbar */}
-
-
- {showBasicInfo && product?.name ? (
-
-
- {product.name.length > MAX_PLAN_NAME_LENGTH
- ? `${product.name.slice(0, MAX_PLAN_NAME_LENGTH)}...`
- : product.name}
-
-
- ) : (
-
Pro
- )}
-
-
- {playgroundMode === "edit" && product && (
-
-
MAX_PLAN_NAME_LENGTH - 10}
- />
-
- )}
-
-
- {showPricing && (
-
- // }
- // className="mt-2 !opacity-100 pointer-events-none"
- // onClick={handleEdit}
- // disabled={true}
- // >
- // {basePrice?.amount ? (
- //
- // ${basePrice.amount}/
- // {keyToTitle(basePrice.interval ?? "once", {
- // exclusionMap: { one_off: "once" },
- // }).toLowerCase()}
- //
- // ) : (
- // No price set
- // )}
- //
- )}
-
- {showDummyFeature && feature && (
- <>
-
-
- >
- )}
-
- {showFeatures && }
-
- {showFeatures && (
-
- )}
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/OnboardingView3.tsx b/vite/src/views/onboarding3/OnboardingView3.tsx
deleted file mode 100644
index 6347623c5..000000000
--- a/vite/src/views/onboarding3/OnboardingView3.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import { useEffect, useState } from "react";
-import { useSearchParams } from "react-router";
-import { SheetContainer } from "@/components/v2/sheets/InlineSheet";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { cn } from "@/lib/utils";
-import { trackSignUp } from "@/utils/posthogTracking";
-import { ProductContext } from "@/views/products/product/ProductContext";
-import LoadingScreen from "../general/LoadingScreen";
-import { SaveChangesBar } from "../products/plan/components/SaveChangesBar";
-import ConnectStripeDialog from "./ConnectStripeDialog";
-import { ExitButton } from "./components/ExitButton";
-import { IntegrationStep } from "./components/IntegrationStep";
-import { OnboardingStepRenderer } from "./components/OnboardingStepRenderer";
-import { OnboardingSteps } from "./components/OnboardingSteps";
-import { StepHeader } from "./components/StepHeaders";
-import { useAutoSetProductId } from "./hooks/useAutoSetProductId";
-import { useAutoSkipToPlayground } from "./hooks/useAutoSkipToPlayground";
-import { useInitFeatureItem } from "./hooks/useInitFeatureItem";
-import { useOnboarding3QueryState } from "./hooks/useOnboarding3QueryState";
-import { useOnboardingFeatureSync } from "./hooks/useOnboardingFeatureSync";
-import { useOnboardingLogic } from "./hooks/useOnboardingLogic";
-import { useOnboardingProductSync } from "./hooks/useOnboardingProductSync";
-import { useSyncPlaygroundMode } from "./hooks/useSyncPlaygroundMode";
-import { OnboardingPreview } from "./OnboardingPreview";
-import { OnboardingStep } from "./utils/onboardingUtils";
-
-export default function OnboardingContent() {
- const [connectStripeOpen, setConnectStripeOpen] = useState(false);
- const [searchParams, setSearchParams] = useSearchParams();
- const { mutate: refetchOrg } = useOrg();
-
- // Get query data
- const { isLoading: productsLoading } = useProductsQuery();
- const { isLoading: featuresLoading } = useFeaturesQuery();
-
- // Get step from query state
- const { queryStates } = useOnboarding3QueryState();
- const step = queryStates.step;
-
- // Sync product store with products list (like useProductSync but for onboarding)
- // This handles all initialization logic
- useOnboardingProductSync();
-
- // Initialize feature data
- useOnboardingFeatureSync();
-
- // Initialize feature item for Step 3 (handles refresh scenario)
- useInitFeatureItem();
-
- // Auto-skip to playground if steps 1-3 are already complete
- const { isChecking: isCheckingAutoSkip } = useAutoSkipToPlayground();
-
- // Auto-set product_id in query params when entering playground step
- useAutoSetProductId();
-
- // Initialize onboarding logic and store handlers
- useOnboardingLogic();
-
- // Sync playground mode with query params
- useSyncPlaygroundMode();
-
- // Track sign-up event on first mount
- useEffect(() => {
- trackSignUp();
- }, []);
-
- // Handle OAuth success redirect
- useEffect(() => {
- const success = searchParams.get("success");
- if (success === "true") {
- // Refetch org data to get updated stripe_connected status
- refetchOrg();
- // Clear the success param from URL
- searchParams.delete("success");
- setSearchParams(searchParams, { replace: true });
- }
- }, [searchParams, refetchOrg, setSearchParams]);
-
- // Compute loading state
- const isQueryLoading = productsLoading || featuresLoading;
-
- if (isQueryLoading || isCheckingAutoSkip) {
- return ;
- }
-
- return (
- <>
-
- {},
- refetch: async () => {}, // Not needed in onboarding
- }}
- >
- {/* Standard layout for all steps - NO PROP DRILLING! */}
-
- {/* Exit button */}
-
-
-
- {/* Components access Zustand directly - no props! */}
- {step === OnboardingStep.Integration ? (
-
- ) : (
- <>
-
-
- {step === OnboardingStep.Playground && (
-
-
-
- )}
- >
- )}
-
-
-
-
-
-
-
- {step !== OnboardingStep.Integration && (
-
-
-
- )}
-
-
-
-
-
- >
- );
-}
diff --git a/vite/src/views/onboarding3/components/CompletionStep.tsx b/vite/src/views/onboarding3/components/CompletionStep.tsx
deleted file mode 100644
index b2acfd2bb..000000000
--- a/vite/src/views/onboarding3/components/CompletionStep.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { ArrowRight } from "@phosphor-icons/react";
-import { useState } from "react";
-import { Button } from "@/components/v2/buttons/Button";
-import {
- CodeGroup,
- CodeGroupCode,
- CodeGroupContent,
- CodeGroupCopyButton,
- CodeGroupList,
- CodeGroupTab,
-} from "@/components/v2/CodeGroup";
-import { Input } from "@/components/v2/inputs/Input";
-import { SheetSection } from "@/components/v2/sheets/InlineSheet";
-import {
- nodejsCode,
- reactCode,
- responseCode,
-} from "../utils/completionStepCode";
-
-const FeatureTestRow = ({
- label,
- usage,
-}: {
- label: string;
- usage?: string;
-}) => (
-
-
-
- {label}
-
-
-
- {usage && (
-
- {usage}
-
- )}
-
- Send
-
-
-
-);
-
-export const CompletionStep = () => {
- const [activeTab, setActiveTab] = useState("react");
-
- const getCodeForTab = () => {
- return (
- {
- react: reactCode,
- nodejs: nodejsCode,
- response: responseCode,
- }[activeTab] ?? reactCode
- );
- };
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- React
- Node.js
- Response
- navigator.clipboard.writeText(getCodeForTab())}
- />
-
-
- {reactCode}
-
-
- {nodejsCode}
-
-
- {responseCode}
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/onboarding3/components/ExitButton.tsx b/vite/src/views/onboarding3/components/ExitButton.tsx
deleted file mode 100644
index 62f808949..000000000
--- a/vite/src/views/onboarding3/components/ExitButton.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { ArrowLeftIcon } from "lucide-react";
-import { useNavigate } from "react-router";
-import { IconButton } from "@/components/v2/buttons/IconButton";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/v2/tooltips/Tooltip";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { pushPage } from "@/utils/genUtils";
-import { useOnboarding3QueryState } from "../hooks/useOnboarding3QueryState";
-import { OnboardingStep } from "../utils/onboardingUtils";
-
-interface ExitButtonProps {
- position?: "fixed" | "absolute";
-}
-
-export function ExitButton({ position = "absolute" }: ExitButtonProps) {
- const navigate = useNavigate();
- const { org, mutate: mutateOrg } = useOrg();
- const axiosInstance = useAxiosInstance();
- const { queryStates } = useOnboarding3QueryState();
- const step = queryStates.step;
-
- // Hide exit button on integration step (step 5)
- if (step === OnboardingStep.Integration) {
- return null;
- }
-
- const handleExit = async () => {
- if (!org?.onboarded) {
- await axiosInstance.patch("/v1/organization", {
- onboarded: true,
- });
- await mutateOrg();
- }
-
- pushPage({
- navigate,
- path: "/products",
- queryParams: {
- tab: "products",
- },
- preserveParams: true,
- });
- };
-
- return (
-
-
-
- }
- >
- Exit to Dashboard
-
-
-
-
- You can come back at any time by clicking the Onboarding button in
- the sidebar.
-
-
-
-
- );
-}
diff --git a/vite/src/views/onboarding3/components/FeatureConfigurationStep.tsx b/vite/src/views/onboarding3/components/FeatureConfigurationStep.tsx
deleted file mode 100644
index 02c3d3430..000000000
--- a/vite/src/views/onboarding3/components/FeatureConfigurationStep.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { EditPlanFeatureSheet } from "../../products/plan/components/edit-plan-feature/EditPlanFeatureSheet";
-
-export const FeatureConfigurationStep = () => {
- return (
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/FeatureCreationStep.tsx b/vite/src/views/onboarding3/components/FeatureCreationStep.tsx
deleted file mode 100644
index 018b6ccd5..000000000
--- a/vite/src/views/onboarding3/components/FeatureCreationStep.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { useFeatureStore } from "@/hooks/stores/useFeatureStore";
-import { NewFeatureAdvanced } from "@/views/products/plan/components/new-feature/NewFeatureAdvanced";
-import { NewFeatureBehaviour } from "@/views/products/plan/components/new-feature/NewFeatureBehaviour";
-import { NewFeatureDetails } from "../../products/plan/components/new-feature/NewFeatureDetails";
-import { NewFeatureType } from "../../products/plan/components/new-feature/NewFeatureType";
-import { useOnboardingStore } from "../store/useOnboardingStore";
-
-export const FeatureCreationStep = () => {
- const feature = useFeatureStore((s) => s.feature);
- const setFeature = useFeatureStore((s) => s.setFeature);
- const isOnboarding = useOnboardingStore((s) => s.isOnboarding);
-
- return (
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/IntegrationStep.tsx b/vite/src/views/onboarding3/components/IntegrationStep.tsx
deleted file mode 100644
index e677b6784..000000000
--- a/vite/src/views/onboarding3/components/IntegrationStep.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Separator } from "@/components/v2/separator";
-import { BackendSection } from "./integration-step/BackendSection";
-import { EnvSection } from "./integration-step/EnvSection";
-import { FrontendSection } from "./integration-step/FrontendSection";
-import { InstallSection } from "./integration-step/InstallSection";
-import { IntegrationProvider } from "./integration-step/IntegrationContext";
-import { MCPSection } from "./integration-step/MCPSection";
-import { NextStepsSection } from "./integration-step/NextStepsSection";
-import { PricingTableSection } from "./integration-step/PricingTableSection";
-import { StackSelectionSection } from "./integration-step/StackSelectionSection";
-
-export const IntegrationStep = () => {
- return (
-
-
-
-
Integrate Autumn
-
- Let's integrate Autumn and get your first customer onto one of your
- plans
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/OnboardingStepRenderer.tsx b/vite/src/views/onboarding3/components/OnboardingStepRenderer.tsx
deleted file mode 100644
index a00aa79fc..000000000
--- a/vite/src/views/onboarding3/components/OnboardingStepRenderer.tsx
+++ /dev/null
@@ -1,224 +0,0 @@
-import type { ProductItem, ProductV2 } from "@autumn/shared";
-import { productV2ToFeatureItems } from "@autumn/shared";
-import { useEffect, useState } from "react";
-import { useFeatureStore } from "@/hooks/stores/useFeatureStore";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { useSheetStore } from "@/hooks/stores/useSheetStore";
-import { getItemId } from "@/utils/product/productItemUtils";
-import { EditPlanPriceSheet } from "@/views/products/plan/components/EditPlanPriceSheet";
-import { EditPlanSheet } from "@/views/products/plan/components/EditPlanSheet";
-import { ProductItemContext } from "@/views/products/product/product-item/ProductItemContext";
-import { EditPlanFeatureSheet } from "../../products/plan/components/edit-plan-feature/EditPlanFeatureSheet";
-import { NewFeatureSheet } from "../../products/plan/components/new-feature/NewFeatureSheet";
-import { SelectFeatureSheet } from "../../products/plan/components/SelectFeatureSheet";
-import { useOnboarding3QueryState } from "../hooks/useOnboarding3QueryState";
-import { useOnboardingStore } from "../store/useOnboardingStore";
-import { OnboardingStep } from "../utils/onboardingUtils";
-import { FeatureConfigurationStep } from "./FeatureConfigurationStep";
-import { FeatureCreationStep } from "./FeatureCreationStep";
-import { IntegrationStep } from "./IntegrationStep";
-import { PlanDetailsStep } from "./PlanDetailsStep";
-import { AvailableFeatures } from "./playground-step/AvailableFeatures";
-import { QuickStartCodeGroup } from "./playground-step/QuickStartCodeGroup";
-
-export const OnboardingStepRenderer = () => {
- // Get step from query state
- const { queryStates } = useOnboarding3QueryState();
- const step = queryStates.step;
-
- // Get state from Zustand
- const playgroundMode = useOnboardingStore((state) => state.playgroundMode);
- const setLastUsedProductId = useOnboardingStore(
- (state) => state.setLastUsedProductId,
- );
- const feature = useFeatureStore((state) => state.feature);
-
- const product = useProductStore((s) => s.product);
- const setProduct = useProductStore((s) => s.setProduct);
- const sheetType = useSheetStore((s) => s.type);
- const itemId = useSheetStore((s) => s.itemId);
-
- const [trackResponse, setTrackResponse] = useState(null);
- const [checkResponse, setCheckResponse] = useState(null);
-
- const [lastUsedFeatureId, setLastUsedFeatureId] = useState<
- string | undefined
- >(undefined);
-
- // Track product ID changes when in playground mode
- useEffect(() => {
- if (step === OnboardingStep.Playground && product?.id) {
- setLastUsedProductId(product.id);
- }
- }, [product?.id, step, setLastUsedProductId]);
-
- // Don't render overrides when on Integration step or Playground preview mode - allow the step to render normally
- const shouldSkipOverrides =
- step === OnboardingStep.Integration ||
- (step === OnboardingStep.Playground && playgroundMode === "preview");
-
- // Handle all override conditions first (before switch statement)
- if (!shouldSkipOverrides) {
- // Plan editing override
- if (sheetType === "edit-plan") {
- return ;
- }
-
- // New feature creation override
- if (sheetType === "new-feature") {
- return ;
- }
-
- // Select feature override
- if (sheetType === "select-feature") {
- return ;
- }
-
- if (sheetType === "edit-plan-price") {
- return ;
- }
-
- // Existing feature editing override
- if (sheetType === "edit-feature" && itemId) {
- const featureItems = productV2ToFeatureItems({
- items: product?.items || [],
- withBasePrice: true,
- });
- const isCurrentItem = (item: ProductItem, index: number) => {
- const currentItemId = getItemId({ item, itemIndex: index });
- return itemId === currentItemId;
- };
- const currentItem = featureItems.find(isCurrentItem);
-
- const setCurrentItem = (updatedItem: ProductItem) => {
- if (!product || !product.items) return;
-
- const filteredItems = productV2ToFeatureItems({
- items: product.items,
- withBasePrice: true,
- });
-
- const currentItemIndex = filteredItems.findIndex((item, index) => {
- const currentItemId = getItemId({ item, itemIndex: index });
- return itemId === currentItemId;
- });
-
- if (currentItemIndex === -1) return;
-
- const targetItem = filteredItems[currentItemIndex];
-
- // Find this item in the ORIGINAL items array
- const originalIndex = product.items.indexOf(targetItem);
-
- if (originalIndex === -1) return;
-
- // Update that specific index in the original array
- const updatedItems = [...product.items];
- updatedItems[originalIndex] = updatedItem;
-
- setProduct({ ...product, items: updatedItems } as ProductV2);
- };
-
- return (
- {},
- isUpdate: false,
- handleUpdateProductItem: async () => null,
- }}
- >
-
-
- );
- }
- }
-
- // Step-based rendering (fallback when no overrides are active)
- switch (step) {
- case OnboardingStep.PlanDetails:
- return ;
-
- case OnboardingStep.FeatureCreation:
- return ;
-
- case OnboardingStep.FeatureConfiguration: {
- // Find the ProductItem that corresponds to the feature being configured
- // This should be the item with the feature_id matching the current feature
-
- const featureItems = productV2ToFeatureItems({
- items: product?.items || [],
- withBasePrice: false, // Don't include base price when looking for feature items
- });
-
- // Find the item that matches the current feature being configured
- const currentItem =
- featureItems.find((item) => item.feature_id === feature.id) || null;
-
- // Update the item in the product when it changes
- const setCurrentItem = (updatedItem: ProductItem) => {
- if (!product?.items) return;
-
- // Find the index of the item with matching feature_id in the original items array
- const originalIndex = product.items.findIndex(
- (item) => item.feature_id === feature.id,
- );
-
- if (originalIndex === -1) return;
-
- // Update that specific index in the original array
- const updatedItems = [...product.items];
- updatedItems[originalIndex] = updatedItem;
-
- setProduct({ ...product, items: updatedItems } as ProductV2);
- };
-
- return (
- {},
- isUpdate: false,
- handleUpdateProductItem: async () => null,
- }}
- >
-
-
- );
- }
-
- case OnboardingStep.Playground:
- // Preview mode is handled in OnboardingPreview.tsx
- // In preview mode, show the preview sidebar content
- if (playgroundMode === "preview") {
- return (
- <>
-
-
- >
- );
- }
- // In edit mode, handled by the useEffect in useOnboardingLogic that opens edit-plan sheet
- return null;
-
- case OnboardingStep.Integration:
- return ;
-
- default:
- return null;
- }
-};
diff --git a/vite/src/views/onboarding3/components/OnboardingSteps.tsx b/vite/src/views/onboarding3/components/OnboardingSteps.tsx
deleted file mode 100644
index 78475d0a5..000000000
--- a/vite/src/views/onboarding3/components/OnboardingSteps.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { ShortcutButton } from "@/components/v2/buttons/ShortcutButton";
-import { useFeatureStore } from "@/hooks/stores/useFeatureStore";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { cn } from "@/lib/utils";
-import { useOnboarding3QueryState } from "../hooks/useOnboarding3QueryState";
-import { useOnboardingStore } from "../store/useOnboardingStore";
-import { getStepNumber } from "../utils/onboardingUtils";
-
-interface OnboardingStepsProps {
- className?: string;
-}
-
-export const OnboardingSteps = ({ className }: OnboardingStepsProps) => {
- // Get product and feature for validation
- const product = useProductStore((s) => s.product);
- const feature = useFeatureStore((state) => state.feature);
-
- // Get step from query state
- const { queryStates } = useOnboarding3QueryState();
- const step = queryStates.step;
-
- // Get handlers and state from store
- const isButtonLoading = useOnboardingStore((state) => state.isButtonLoading);
- const handleNext = useOnboardingStore((state) => state.handleNext);
- const handleBack = useOnboardingStore((state) => state.handleBack);
- const validateStep = useOnboardingStore((state) => state.validateStep);
-
- const currentStep = getStepNumber(step);
- const totalSteps = 5;
- const nextDisabled = !validateStep?.(step, product, feature);
- const backDisabled = currentStep === 1;
- const nextText = currentStep >= 5 ? "Go to Dashboard" : "Next";
-
- return (
-
-
- {Array.from({ length: totalSteps }).map((_, index) => {
- const isCompleted = index < currentStep;
- return (
-
- );
- })}
-
- {/* Navigation buttons */}
-
-
- Back
-
-
- {nextText}
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/PlanDetailsStep.tsx b/vite/src/views/onboarding3/components/PlanDetailsStep.tsx
deleted file mode 100644
index b663af0b0..000000000
--- a/vite/src/views/onboarding3/components/PlanDetailsStep.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { FormLabel } from "@/components/v2/form/FormLabel";
-import { Input } from "@/components/v2/inputs/Input";
-import { SheetSection } from "@/components/v2/sheets/InlineSheet";
-import { useAutoSlug } from "@/hooks/common/useAutoSlug";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { PlanTypeSection } from "@/views/products/plan/components/edit-plan-details/PlanTypeSection";
-import { BasePriceSection } from "../../products/plan/components/edit-plan-details/BasePriceSection";
-
-export const PlanDetailsStep = () => {
- // Get product state from ProductContext (working copy being edited)
- const product = useProductStore((s) => s.product);
- const baseProduct = useProductStore((s) => s.baseProduct);
- const setProduct = useProductStore((s) => s.setProduct);
-
- // Check if product already exists on backend (has internal_id from database)
- const isExistingProduct = !!baseProduct?.internal_id;
-
- const { setSource, setTarget } = useAutoSlug({
- setState: setProduct,
- sourceKey: "name",
- targetKey: "id",
- disableAutoSlug: isExistingProduct,
- });
-
- return (
- <>
-
-
-
-
- Name
- setSource(e.target.value)}
- className="mb-1"
- />
-
-
- This name will be displayed on Stripe's checkout page
-
-
-
- ID
- setTarget(e.target.value)}
- className="mb-1"
- />
-
- Used to refer to this plan when using Autumn's APIs or SDKs
-
-
- {/* {step === OnboardingStep.Playground && product && (
-
- Description
-
- setProduct({
- ...product,
- description: e.target.value,
- })
- }
- />
-
- )} */}
-
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/onboarding3/components/StepHeaders.tsx b/vite/src/views/onboarding3/components/StepHeaders.tsx
deleted file mode 100644
index ffe2b21b2..000000000
--- a/vite/src/views/onboarding3/components/StepHeaders.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { SheetHeader } from "@/components/v2/sheets/InlineSheet";
-import { useOnboarding3QueryState } from "../hooks/useOnboarding3QueryState";
-import {
- getStepNumber,
- OnboardingStep,
- stepConfig,
-} from "../utils/onboardingUtils";
-import { PlaygroundToolbar } from "./playground-step/PlaygroundToolbar";
-
-export const StepHeader = () => {
- // Get step from query state
- const { queryStates } = useOnboarding3QueryState();
- const step = queryStates.step;
-
- const stepNum = getStepNumber(step);
- const config = stepConfig[step];
-
- if (step === OnboardingStep.Playground) {
- return (
-
-
-
- );
- }
-
- return (
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/BackendSection.tsx b/vite/src/views/onboarding3/components/integration-step/BackendSection.tsx
deleted file mode 100644
index 34d5b04af..000000000
--- a/vite/src/views/onboarding3/components/integration-step/BackendSection.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {
- CodeGroup,
- CodeGroupCode,
- CodeGroupContent,
- CodeGroupCopyButton,
- CodeGroupList,
- CodeGroupTab,
-} from "@/components/v2/CodeGroup";
-import { CodeSpan } from "@/views/onboarding2/integrate/components/CodeSpan";
-import { useIntegrationContext } from "./IntegrationContext";
-import { SectionHeader } from "./SectionHeader";
-import { getBackendSnippet } from "./snippets";
-
-export const BackendSection = () => {
- const {
- selectedBackend,
- selectedAuth,
- customerType,
- secretKey,
- setSecretKey,
- } = useIntegrationContext();
-
- const snippet = getBackendSnippet(
- selectedBackend,
- selectedAuth,
- customerType,
- secretKey,
- );
- return (
-
-
- Mount {"autumnHandler"} to your backend
-
- }
- description={
-
- {"autumnHandler"} mounts routes on the{" "}
- {"/api/autumn/*"} paths which allows our React
- hooks and components to interact with the Autumn API directly.
-
- }
- />
-
-
-
- {/*
-
-
- .env
-
- navigator.clipboard.writeText(
- `AUTUMN_SECRET_KEY=${secretKey}`,
- )
- }
- />
-
-
- {`AUTUMN_SECRET_KEY=${secretKey}`}
-
-
-
*/}
-
-
-
-
-
- {selectedBackend === "nextjs"
- ? "app/api/autumn/[...all]/route.ts"
- : selectedBackend === "rr7"
- ? "app/routes/api.autumn.$.tsx"
- : `${selectedBackend === "express" ? "server" : "app"}.${selectedBackend === "elysia" ? "ts" : "js"}`}
-
- navigator.clipboard.writeText(snippet)}
- />
-
-
- {snippet}
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/ConnectStripeSection.tsx b/vite/src/views/onboarding3/components/integration-step/ConnectStripeSection.tsx
deleted file mode 100644
index ab1f24c6f..000000000
--- a/vite/src/views/onboarding3/components/integration-step/ConnectStripeSection.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { AppEnv } from "@autumn/shared";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/v2/buttons/Button";
-import { Input } from "@/components/v2/inputs/Input";
-import { useOrg } from "@/hooks/common/useOrg";
-import { OrgService } from "@/services/OrgService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { SectionHeader } from "./SectionHeader";
-
-export const ConnectStripeSection = () => {
- const { org, mutate: mutateOrg } = useOrg();
- const [apiKey, setApiKey] = useState("");
- const [loading, setLoading] = useState(false);
- const axiosInstance = useAxiosInstance({ env: AppEnv.Live });
-
- const handleConnectStripe = async () => {
- setLoading(true);
- try {
- await OrgService.connectStripe(axiosInstance, {
- secret_key: apiKey,
- });
-
- toast.success("Successfully connected to Stripe");
- await mutateOrg();
- } catch (error) {
- console.log("Failed to connect Stripe", error);
- toast.error(getBackendErr(error, "Failed to connect Stripe"));
- }
-
- setLoading(false);
- };
-
- const stripeConnected = org?.stripe_connected;
-
- return (
-
-
- Stripe is required to checkout and add your plans to customers. Grab
- your API key{" "}
-
- here
-
- .
-
- }
- />
-
-
-
-
- setApiKey(e.target.value)}
- disabled={stripeConnected}
- className="flex-1"
- />
-
- Connect Stripe
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/EnvSection.tsx b/vite/src/views/onboarding3/components/integration-step/EnvSection.tsx
deleted file mode 100644
index 2f4ad57a1..000000000
--- a/vite/src/views/onboarding3/components/integration-step/EnvSection.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { Check, Copy } from "lucide-react";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/v2/buttons/Button";
-import {
- CodeGroup,
- CodeGroupCode,
- CodeGroupContent,
- CodeGroupCopyButton,
- CodeGroupList,
- CodeGroupTab,
-} from "@/components/v2/CodeGroup";
-import { Input } from "@/components/v2/inputs/Input";
-import { DevService } from "@/services/DevService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { CodeSpan } from "@/views/onboarding2/integrate/components/CodeSpan";
-import { useIntegrationContext } from "./IntegrationContext";
-import { SectionHeader } from "./SectionHeader";
-
-export const EnvSection = () => {
- const { secretKey, setSecretKey } = useIntegrationContext();
- const [loading, setLoading] = useState(false);
- const [copied, setCopied] = useState(false);
- const env = useEnv();
- const axiosInstance = useAxiosInstance({ env });
-
- const handleGenerateKey = async () => {
- setLoading(true);
- try {
- const { api_key } = await DevService.createAPIKey(axiosInstance, {
- name: "Autumn Onboarding",
- });
-
- setSecretKey(api_key);
- } catch (error) {
- console.log("Error:", error);
- toast.error("Failed to create API key");
- }
-
- setLoading(false);
- };
-
- const handleCopy = () => {
- navigator.clipboard.writeText(secretKey);
- setCopied(true);
- setTimeout(() => {
- setCopied(false);
- }, 1000);
- };
-
- return (
-
-
- Add the Autumn secret key to your {".env"} file
-
- }
- className="gap-0"
- />
-
-
-
-
-
- {secretKey ? (
-
- {copied ? (
-
- ) : (
-
- )}
- Copy
-
- ) : (
-
- Generate secret key
-
- )}
-
-
-
-
-
- .env
-
- navigator.clipboard.writeText(
- `AUTUMN_SECRET_KEY=${secretKey}`,
- )
- }
- />
-
-
- {`AUTUMN_SECRET_KEY=${secretKey}`}
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/FrontendSection.tsx b/vite/src/views/onboarding3/components/integration-step/FrontendSection.tsx
deleted file mode 100644
index 1bbe504c7..000000000
--- a/vite/src/views/onboarding3/components/integration-step/FrontendSection.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import {
- CodeGroup,
- CodeGroupCode,
- CodeGroupContent,
- CodeGroupCopyButton,
- CodeGroupList,
- CodeGroupTab,
-} from "@/components/v2/CodeGroup";
-import { CodeSpan } from "@/views/onboarding2/integrate/components/CodeSpan";
-import { useIntegrationContext } from "./IntegrationContext";
-import { SectionHeader } from "./SectionHeader";
-import { getFrontendSnippet } from "./snippets";
-
-export const FrontendSection = () => {
- const { selectedFrontend } = useIntegrationContext();
- const snippet = getFrontendSnippet(selectedFrontend);
- return (
-
-
- Wrap your React app in {" "}
-
- }
- description="This allows you to use our React hooks and components in your app. If your server URL is different to your client, you will need to pass in the backend URL as a prop."
- />
-
-
-
-
-
-
-
- {selectedFrontend === "nextjs"
- ? "layout.tsx"
- : selectedFrontend === "rr7"
- ? "app/root.tsx"
- : "App.tsx"}
-
- navigator.clipboard.writeText(snippet)}
- />
-
-
- {snippet}
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/InstallSection.tsx b/vite/src/views/onboarding3/components/integration-step/InstallSection.tsx
deleted file mode 100644
index 13f50c7dd..000000000
--- a/vite/src/views/onboarding3/components/integration-step/InstallSection.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { useState } from "react";
-import {
- CodeGroup,
- CodeGroupCode,
- CodeGroupContent,
- CodeGroupCopyButton,
- CodeGroupList,
- CodeGroupTab,
-} from "@/components/v2/CodeGroup";
-import { SectionHeader } from "./SectionHeader";
-
-export const InstallSection = () => {
- const [pm, setPm] = useState<"npm" | "pnpm" | "yarn" | "bun">("npm");
-
- return (
-
-
-
-
-
-
-
-
- setPm("npm")}>
- npm
-
- setPm("pnpm")}>
- pnpm
-
- setPm("yarn")}>
- yarn
-
- setPm("bun")}>
- bun
-
- {
- const commands = {
- npm: "npm install autumn-js",
- pnpm: "pnpm add autumn-js",
- yarn: "yarn add autumn-js",
- bun: "bun add autumn-js",
- };
- // Find the active tab by looking for the data-state="active" attribute
- const activeTab =
- document
- .querySelector('[data-state="active"]')
- ?.getAttribute("value") || "npm";
- navigator.clipboard.writeText(
- commands[activeTab as keyof typeof commands],
- );
- }}
- />
-
-
- {`npm install autumn-js`}
-
-
- {`pnpm add autumn-js`}
-
-
- {`yarn add autumn-js`}
-
-
- {`bun add autumn-js`}
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/IntegrationContext.tsx b/vite/src/views/onboarding3/components/integration-step/IntegrationContext.tsx
deleted file mode 100644
index 70cad2df1..000000000
--- a/vite/src/views/onboarding3/components/integration-step/IntegrationContext.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { createContext, type ReactNode, useContext, useState } from "react";
-
-export type FrontendStackType = "nextjs" | "rr7" | "vite" | "general";
-export type BackendStackType =
- | "nextjs"
- | "express"
- | "hono"
- | "elysia"
- | "rr7"
- | "general";
-export type AuthType = "betterauth" | "supabase" | "clerk" | "other";
-export type CustomerType = "user" | "org";
-
-export interface IntegrationContextType {
- selectedFrontend: FrontendStackType;
- setSelectedFrontend: (stack: FrontendStackType) => void;
- selectedBackend: BackendStackType;
- setSelectedBackend: (stack: BackendStackType) => void;
- selectedAuth: AuthType;
- setSelectedAuth: (auth: AuthType) => void;
- customerType: CustomerType;
- setCustomerType: (type: CustomerType) => void;
- secretKey: string;
- setSecretKey: (key: string) => void;
-}
-
-export const IntegrationContext = createContext(
- null,
-);
-
-export const useIntegrationContext = () => {
- const context = useContext(IntegrationContext);
-
- if (!context) {
- throw new Error(
- "useIntegrationContext must be used within an IntegrationProvider",
- );
- }
-
- return context;
-};
-
-export const IntegrationProvider = ({ children }: { children: ReactNode }) => {
- const [selectedFrontend, setSelectedFrontend] =
- useState("nextjs");
- const [selectedBackend, setSelectedBackend] =
- useState("nextjs");
- const [selectedAuth, setSelectedAuth] = useState("betterauth");
- const [customerType, setCustomerType] = useState("user");
- const [secretKey, setSecretKey] = useState("");
-
- return (
-
- {children}
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/MCPSection.tsx b/vite/src/views/onboarding3/components/integration-step/MCPSection.tsx
deleted file mode 100644
index 6a449671d..000000000
--- a/vite/src/views/onboarding3/components/integration-step/MCPSection.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { InfoIcon } from "@phosphor-icons/react";
-import { toast } from "sonner";
-import { IconButton } from "@/components/v2/buttons/IconButton";
-
-export const MCPSection = () => {
- const mcpConfig = {
- name: "Autumn Docs",
- url: "https://docs.useautumn.com/mcp",
- headers: {},
- };
-
- const encodedConfig = btoa(JSON.stringify(mcpConfig));
- const cursorInstallUrl = `cursor://anysphere.cursor-deeplink/mcp/install?name=Autumn%20Docs&config=${encodedConfig}`;
-
- const handleCursorInstall = () => {
- window.open(cursorInstallUrl, "_blank");
- };
-
- const handleCopyClaudeCommand = () => {
- navigator.clipboard.writeText(
- "claude mcp add --transport http autumn-docs https://docs.useautumn.com/mcp",
- );
- toast.success(
- "Copied command to clipboard. Paste in your terminal to install.",
- );
- };
-
- return (
-
-
-
-
- Install our MCP and prompt the model to use the `autumn-docs` MCP to
- integrate Autumn
-
-
-
- }
- >
- Open in Cursor
-
- }
- >
- Copy install command
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/NextStepsSection.tsx b/vite/src/views/onboarding3/components/integration-step/NextStepsSection.tsx
deleted file mode 100644
index c981b6cf2..000000000
--- a/vite/src/views/onboarding3/components/integration-step/NextStepsSection.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { ArrowSquareOutIcon } from "@phosphor-icons/react";
-import { IconButton } from "@/components/v2/buttons/IconButton";
-import {
- Card,
- CardContent,
- CardHeader,
- CardTitle,
-} from "@/components/v2/cards/Card";
-import { cn } from "@/lib/utils";
-import { CodeSpan } from "@/views/onboarding2/integrate/components/CodeSpan";
-import { SectionHeader } from "./SectionHeader";
-
-interface NextStepCardProps {
- title: string;
- description: React.ReactNode;
- href: string;
-}
-
-const NextStepCard = ({ title, description, href }: NextStepCardProps) => {
- return (
-
-
-
- {title}
- }
- variant="skeleton"
- disableActive
- className="pointer-events-none"
- />
-
-
- {description}
-
-
-
- );
-};
-
-export const NextStepsSection = () => {
- return (
-
-
Next Steps}
- description="Congrats on setting up Autumn! The next steps are to learn how to use Autumn to check if a user has access to features in your application, and track usage for those features. Learn how to do so here."
- />
-
-
-
-
- Learn how to check feature access with the{" "}
- check route
- >
- }
- href="https://docs.useautumn.com/features/check"
- />
-
-
- Keep track of your customer's feature usage with the{" "}
- track route
- >
- }
- href="https://docs.useautumn.com/features/tracking-usage"
- />
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/PricingTableSection.tsx b/vite/src/views/onboarding3/components/integration-step/PricingTableSection.tsx
deleted file mode 100644
index acccb88f5..000000000
--- a/vite/src/views/onboarding3/components/integration-step/PricingTableSection.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import {
- CodeGroup,
- CodeGroupCode,
- CodeGroupContent,
- CodeGroupCopyButton,
- CodeGroupList,
- CodeGroupTab,
-} from "@/components/v2/CodeGroup";
-import { CodeSpan } from "@/views/onboarding2/integrate/components/CodeSpan";
-import { SectionHeader } from "./SectionHeader";
-
-export const PricingTableSection = () => {
- const snippet = `import { PricingTable } from "autumn-js/react";
-
-export default function Home() {
- return (
-
- );
-}`;
-
- return (
-
-
- Drop in {" "}
-
- }
- description="This will display your pricing plans in a table format. You can customize the table by passing in the product details as a prop."
- />
-
-
-
-
-
-
- layout.tsx
- navigator.clipboard.writeText(snippet)}
- />
-
-
- {snippet}
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/StackSelectionSection.tsx b/vite/src/views/onboarding3/components/integration-step/StackSelectionSection.tsx
deleted file mode 100644
index 43822c121..000000000
--- a/vite/src/views/onboarding3/components/integration-step/StackSelectionSection.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import { AtIcon, UserCircleIcon } from "@phosphor-icons/react";
-import type { ReactNode } from "react";
-import StackBadge from "@/components/v2/badges/StackBadge";
-import {
- type AuthType,
- type BackendStackType,
- type CustomerType,
- type FrontendStackType,
- useIntegrationContext,
-} from "./IntegrationContext";
-import { SectionHeader } from "./SectionHeader";
-
-type StackOption =
- | {
- value: string;
- label: string;
- asset: string;
- icon?: never;
- }
- | {
- value: string;
- label: string;
- asset?: never;
- icon: ReactNode;
- };
-
-const FRONTEND_OPTIONS: StackOption[] = [
- { value: "nextjs", label: "Next.js", asset: "/frameworks/nextjs.png" },
- { value: "rr7", label: "RR7", asset: "/frameworks/react-router.svg" },
- { value: "vite", label: "Vite SPA", asset: "/frameworks/vite.svg" },
- { value: "general", label: "Other", asset: "/frameworks/react.png" },
-];
-
-const BACKEND_OPTIONS: StackOption[] = [
- {
- value: "nextjs",
- label: "Next.js",
- asset: "/frameworks/nextjs.png",
- },
- { value: "rr7", label: "RR7", asset: "/frameworks/react-router.svg" },
- { value: "hono", label: "Hono", asset: "/frameworks/hono.png" },
- { value: "express", label: "Express", asset: "/frameworks/express.png" },
- { value: "elysia", label: "Elysia", asset: "/frameworks/elysia.png" },
- { value: "general", label: "Other", asset: "/frameworks/nodejs.svg" },
-];
-
-const AUTH_OPTIONS: StackOption[] = [
- {
- value: "betterauth",
- label: "Better Auth",
- asset: "/frameworks/better-auth.png",
- },
- { value: "supabase", label: "Supabase", asset: "/frameworks/supabase.png" },
- { value: "clerk", label: "Clerk", asset: "/frameworks/clerk.png" },
- { value: "other", label: "Other", asset: "/auth-key.png" },
-];
-
-const CUSTOMER_TYPE_OPTIONS: StackOption[] = [
- {
- value: "user",
- label: "Users",
- icon: ,
- },
- {
- value: "org",
- label: "Orgs",
- icon: ,
- },
-];
-
-export const StackSelectionSection = () => {
- const {
- selectedFrontend,
- setSelectedFrontend,
- selectedBackend,
- setSelectedBackend,
- selectedAuth,
- setSelectedAuth,
- customerType,
- setCustomerType,
- } = useIntegrationContext();
-
- return (
-
-
-
-
- {/* Frontend */}
-
-
Frontend
-
- {FRONTEND_OPTIONS.map((option) => (
-
- setSelectedFrontend(option.value as FrontendStackType)
- }
- />
- ))}
-
-
- {/* Backend */}
-
-
Backend
-
- {BACKEND_OPTIONS.map((option) => (
-
- setSelectedBackend(option.value as BackendStackType)
- }
- />
- ))}
-
-
-
- {/* Auth Provider */}
-
-
Auth Provider
-
- {AUTH_OPTIONS.map((option) => (
-
- setSelectedAuth(option.value as AuthType)
- }
- />
- ))}
-
-
-
- {/* Customer Type */}
-
-
Customer Type
-
- {CUSTOMER_TYPE_OPTIONS.map((option) => (
-
- setCustomerType(option.value as CustomerType)
- }
- />
- ))}
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/integration-step/snippets.ts b/vite/src/views/onboarding3/components/integration-step/snippets.ts
deleted file mode 100644
index d2087ad5d..000000000
--- a/vite/src/views/onboarding3/components/integration-step/snippets.ts
+++ /dev/null
@@ -1,485 +0,0 @@
-import type {
- AuthType,
- BackendStackType,
- CustomerType,
- FrontendStackType,
-} from "./IntegrationContext";
-
-export const getBackendSnippet = (
- stack: BackendStackType,
- auth: AuthType,
- customerType: CustomerType,
- _secretKey: string,
-): string => {
- switch (stack) {
- case "nextjs":
- return getNextjsSnippet(auth, customerType);
- case "express":
- return getExpressSnippet(auth, customerType);
- case "hono":
- return getHonoSnippet(auth, customerType);
- case "elysia":
- return getElysiaSnippet(auth, customerType);
- case "rr7":
- return getRR7Snippet(auth, customerType);
- default:
- return getGeneralSnippet();
- }
-};
-
-const getNextjsSnippet = (
- auth: AuthType,
- customerType: CustomerType,
-): string => {
- switch (auth) {
- case "betterauth":
- return customerType === "user"
- ? nextjsBetterAuthUser
- : nextjsBetterAuthOrg;
- case "supabase":
- return customerType === "user" ? nextjsSupabaseUser : nextjsSupabaseOrg;
- case "clerk":
- return customerType === "user" ? nextjsClerkUser : nextjsClerkOrg;
- default:
- return nextjsOther;
- }
-};
-
-const getExpressSnippet = (
- _auth: AuthType,
- customerType: CustomerType,
-): string => {
- return `// server.js or app.js
-
-import express from "express";
-import { autumnHandler } from "autumn-js/backend";
-
-const app = express();
-app.use(express.json());
-
-app.use("/api/autumn/*", async (req, res) => {
- // Your authentication logic here
- const customerId = "${customerType === "user" ? "user_id" : "org_id"}";
-
- const { statusCode, response } = await autumnHandler({
- customerId,
- customerData: { name: "", email: "" },
- request: {
- url: req.url,
- method: req.method,
- body: req.body,
- },
- });
-
- res.status(statusCode).json(response);
-});`;
-};
-
-const getHonoSnippet = (
- _auth: AuthType,
- customerType: CustomerType,
-): string => {
- return `// app.ts
-
-import { Hono } from "hono";
-import { autumnHandler } from "autumn-js/backend";
-
-const app = new Hono();
-
-app.use("/api/autumn/*", async (c) => {
- // Your authentication logic here
- const customerId = "${customerType === "user" ? "user_id" : "org_id"}";
-
- let body = null;
- if (c.req.method !== "GET") {
- body = await c.req.json();
- }
-
- const { statusCode, response } = await autumnHandler({
- customerId,
- customerData: { name: "", email: "" },
- request: {
- url: c.req.url,
- method: c.req.method,
- body: body,
- },
- });
-
- return c.json(response, statusCode);
-});`;
-};
-
-const getElysiaSnippet = (
- _auth: AuthType,
- customerType: CustomerType,
-): string => {
- return `// index.ts
-
-import { Elysia } from "elysia";
-import { autumnHandler } from "autumn-js/backend";
-
-new Elysia()
- .all("/api/autumn/*", async ({ request }) => {
- // Your authentication logic here
- const customerId = "${customerType === "user" ? "user_id" : "org_id"}";
-
- let body = null;
- if (request.method !== "GET") {
- body = await request.json();
- }
-
- const { statusCode, response } = await autumnHandler({
- customerId,
- customerData: { name: "", email: "" },
- request: {
- url: request.url,
- method: request.method,
- body: body,
- },
- });
-
- return new Response(JSON.stringify(response), {
- status: statusCode,
- headers: { "Content-Type": "application/json" }
- });
- })
- .listen(3000);`;
-};
-
-const getRR7Snippet = (_auth: AuthType, customerType: CustomerType): string => {
- return `// app/routes/api.autumn.$.tsx
-
-import { autumnHandler } from "autumn-js/backend";
-import type { ActionFunction, LoaderFunction } from "@remix-run/node";
-
-const handler = async (request: Request) => {
- // Your authentication logic here
- const customerId = "${customerType === "user" ? "user_id" : "org_id"}";
-
- let body = null;
- if (request.method !== "GET") {
- body = await request.json();
- }
-
- const { statusCode, response } = await autumnHandler({
- customerId,
- customerData: { name: "", email: "" },
- request: {
- url: request.url,
- method: request.method,
- body: body,
- },
- });
-
- return new Response(JSON.stringify(response), {
- status: statusCode,
- headers: { "Content-Type": "application/json" },
- });
-};
-
-export const loader: LoaderFunction = ({ request }) => handler(request);
-export const action: ActionFunction = ({ request }) => handler(request);`;
-};
-
-const getGeneralSnippet = (): string => {
- return `import { autumnHandler } from "autumn-js/backend";
-
-// 1. autumnHandler takes in request properties and returns a response
-// 2. Simply mount the handler onto the /api/autumn/* path in your backend
-// 3. Call autumnHandler and pass in the required parameters
-// 4. Return the response from the autumnHandler
-
-// Example using autumnHandler with your framework
-const handleRequest = async (request) => {
- // Your authentication logic here
- const customerId = "user_id_or_org_id";
-
- let body = null;
- if (request.method !== "GET") {
- body = await request.json();
- }
-
- const { statusCode, response } = await autumnHandler({
- customerId,
- customerData: { name: "", email: "" },
- request: {
- url: request.url,
- method: request.method,
- body: body,
- },
- });
-
- return new Response(JSON.stringify(response), {
- status: statusCode,
- headers: { "Content-Type": "application/json" },
- });
-};`;
-};
-
-// Next.js specific snippets
-const nextjsBetterAuthUser = `// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@/lib/auth";
-import { headers } from "next/headers";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- return {
- customerId: session?.user.id,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});`;
-
-const nextjsBetterAuthOrg = `// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@/lib/auth";
-import { headers } from "next/headers";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- return {
- customerId: session?.session.activeOrganizationId,
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});`;
-
-const nextjsSupabaseUser = `// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { createClient } from "@/utils/supabase/server";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const supabase = await createClient();
- const { data, error } = await supabase.auth.getUser();
-
- if (error || !data?.user) {
- return null;
- }
-
- return {
- customerId: data.user.id,
- customerData: {
- name: data.user.user_metadata?.name,
- email: data.user.email,
- },
- };
- },
-});`;
-
-const nextjsSupabaseOrg = `// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { createClient } from "@/utils/supabase/server";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const supabase = await createClient();
- const { data, error } = await supabase.auth.getUser();
-
- if (error || !data?.user) {
- return null;
- }
-
- // Get the orgId of the user from your DB
- const customerId = "users_org_id";
-
- return {
- customerId,
- customerData: {
- name: data.user.user_metadata?.name,
- email: data.user.email,
- },
- };
- },
-});`;
-
-const nextjsClerkUser = `// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@clerk/nextjs/server";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const { userId } = await auth();
-
- if (!userId) return null;
-
- return {
- customerId: userId,
- // To store the customer name and email
- customerData: { name: "", email: "" },
- };
- },
-});`;
-
-const nextjsClerkOrg = `// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-import { auth } from "@clerk/nextjs/server";
-
-export const { GET, POST } = autumnHandler({
- identify: async () => {
- const { userId, orgId } = await auth();
-
- if (!userId || !orgId) return null;
-
- return {
- customerId: orgId,
- // To store the customer name and email
- customerData: { name: "", email: "" },
- };
- },
-});`;
-
-const nextjsOther = `// app/api/autumn/[...all]/route.ts
-
-import { autumnHandler } from "autumn-js/next";
-
-export const { GET, POST } = autumnHandler({
- identify: async (request) => {
- // Authenticate the request and get the customer ID
- const customerId = "customer_id";
- return {
- customerId,
- customerData: { name: "", email: "" },
- };
- },
-});`;
-
-export const getFrontendSnippet = (
- stack: FrontendStackType,
- productId?: string,
- featureId?: string,
-): string => {
- const _actualProductId = productId || "your_product_id";
- const _actualFeatureId = featureId || "your_feature_id";
-
- switch (stack) {
- case "nextjs":
- return `import { AutumnProvider } from "autumn-js/react";
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode,
-}) {
- return (
-
-
-
- {children}
-
-
-
- );
-}`;
- case "rr7":
- return `// app/root.tsx
-
-import { AutumnProvider } from "autumn-js/react";
-import {
- Links,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
-} from "@remix-run/react";
-
-export default function App() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}`;
- default:
- return `import { AutumnProvider } from "autumn-js/react";
-
-// Wrap your app in AutumnProvider
-function App() {
- return (
-
- {/* Your app content */}
-
- );
-}`;
- }
-};
-
-// Usage example showing check() before track()
-export const getUsageSnippet = (featureId?: string): string => {
- const actualFeatureId = featureId || "your_feature_id";
-
- return `import { useCustomer } from "autumn-js/react";
-import { useState } from "react";
-
-function MessageInput() {
- const { customer, check, track } = useCustomer();
- const [message, setMessage] = useState("");
-
- const handleSend = async () => {
- // 1. Check if user has access before tracking
- const { data } = await check({
- featureId: "${actualFeatureId}",
- requiredQuantity: 1
- });
-
- if (!data?.allowed) {
- alert("You've reached your limit!");
- return;
- }
-
- // 2. Track usage after successful check
- await track({
- featureId: "${actualFeatureId}",
- value: 1
- });
-
- // 3. Send the message
- // ... your message sending logic
- };
-
- return (
-
- setMessage(e.target.value)}
- />
-
- Send Message
-
-
- );
-}`;
-};
diff --git a/vite/src/views/onboarding3/components/playground-step/AvailableFeatures.tsx b/vite/src/views/onboarding3/components/playground-step/AvailableFeatures.tsx
deleted file mode 100644
index a8596e174..000000000
--- a/vite/src/views/onboarding3/components/playground-step/AvailableFeatures.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { FeatureType } from "@autumn/shared";
-import { ArrowRightIcon } from "@phosphor-icons/react";
-import { PaywallDialog, useCustomer } from "autumn-js/react";
-import { useState } from "react";
-import { Button } from "@/components/v2/buttons/Button";
-import { Input } from "@/components/v2/inputs/Input";
-import { SheetSection } from "@/components/v2/sheets/InlineSheet";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-
-const FeatureTestRow = ({
- label,
- usage,
- handleSend,
-}: {
- label: string;
- usage?: string;
- handleSend: (value: number) => void;
-}) => {
- const [value, setValue] = useState(0);
-
- return (
-
-
- {label}
-
-
-
{
- const inputValue = e.target.value;
- if (inputValue === "" || inputValue === "-") {
- setValue(0);
- } else {
- const numValue = Number(inputValue);
- if (!Number.isNaN(numValue)) {
- setValue(numValue);
- }
- }
- }}
- placeholder="Enter any amount to test"
- className="text-[13px] flex-1"
- />
- {usage && (
-
- {usage}
-
- )}
-
handleSend(value)}
- variant="secondary"
- size="sm"
- >
- Send
-
-
-
-
- );
-};
-
-export const AvailableFeatures = ({
- onTrackSuccess,
- onCheckSuccess,
- onFeatureUsed,
-}: {
- onTrackSuccess?: (response: any) => void;
- onCheckSuccess?: (response: any) => void;
- onFeatureUsed?: (featureId: string) => void;
-}) => {
- const { customer, track, refetch, check } = useCustomer();
- const { features } = useFeaturesQuery();
-
- return (
-
-
- {Object.keys(customer?.features || {}).length > 0 ? (
- Object.keys(customer?.features ?? {})
- .filter(
- (x) =>
- features.find((f) => f.id === customer?.features[x].id)
- ?.type !== FeatureType.Boolean,
- )
- .map((x) => (
- f.id === customer?.features[x].id)
- ?.name || ""
- }
- usage={
- customer?.features[x].unlimited
- ? "Unlimited"
- : String(customer?.features[x].balance)
- }
- key={x}
- handleSend={async (value) => {
- const featureId = customer?.features[x].id;
-
- // Check the feature access
- const { data: checkResponse, error: checkError } = check({
- featureId: featureId,
- requiredBalance: value,
- dialog: PaywallDialog,
- });
-
- if (!checkError && checkResponse && onCheckSuccess) {
- onCheckSuccess(checkResponse);
- }
- // Notify parent which feature was used
- if (onFeatureUsed && featureId !== undefined) {
- onFeatureUsed(featureId);
- }
-
- if (!checkResponse?.allowed) return;
-
- // Track the usage
- const { data, error } = await track({
- featureId: featureId,
- value: value,
- });
-
- if (!error && data && onTrackSuccess) {
- onTrackSuccess(data);
- }
-
- // Immediately refetch customer data to update balances
- await refetch();
- }}
- />
- ))
- ) : (
-
- Your current plan doesn't have any features. Try purchasing a plan
- in the preview first.
-
- )}
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/playground-step/PlaygroundToolbar.tsx b/vite/src/views/onboarding3/components/playground-step/PlaygroundToolbar.tsx
deleted file mode 100644
index 2c2a4fa93..000000000
--- a/vite/src/views/onboarding3/components/playground-step/PlaygroundToolbar.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import {
- PencilSimpleIcon,
- SquareSplitHorizontalIcon,
-} from "@phosphor-icons/react";
-import { GroupedTabButton } from "@/components/v2/buttons/GroupedTabButton";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/v2/selects/Select";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import CreatePlanDialog from "@/views/products/products/components/CreatePlanDialog";
-import { useOnboarding3QueryState } from "../../hooks/useOnboarding3QueryState";
-import { useOnboardingStore } from "../../store/useOnboardingStore";
-
-export const PlaygroundToolbar = () => {
- // Get products from query
- const { products } = useProductsQuery();
-
- // Get current product and playground mode from stores
- const product = useProductStore((s) => s.product);
- const playgroundMode = useOnboardingStore((s) => s.playgroundMode);
-
- // Get query state setters to update URL
- const { setQueryStates } = useOnboarding3QueryState();
-
- // Get handlers from store
- const handlePlanSelect = useOnboardingStore((s) => s.handlePlanSelect);
- const onCreatePlanSuccess = useOnboardingStore((s) => s.onCreatePlanSuccess);
- return (
-
-
{
- // Update query param, which will sync to store
- setQueryStates({ m: val === "edit" ? "e" : "p" });
- }}
- options={[
- {
- value: "edit",
- label: "Edit Mode",
- icon: ,
- },
- {
- value: "preview",
- label: "Preview Mode",
- icon: (
-
- ),
- },
- ]}
- />
- {playgroundMode === "edit" && (
-
- {products.filter((p) => !p.archived).length > 1 && (
- handlePlanSelect?.(id)}
- >
-
-
-
-
- {products
- .filter((p) => !p.archived)
- .map((prod) => (
-
-
- {prod.name}
-
-
- ))}
-
-
- )}
-
-
- )}
-
- );
-};
diff --git a/vite/src/views/onboarding3/components/playground-step/QuickStartCodeGroup.tsx b/vite/src/views/onboarding3/components/playground-step/QuickStartCodeGroup.tsx
deleted file mode 100644
index 336165de8..000000000
--- a/vite/src/views/onboarding3/components/playground-step/QuickStartCodeGroup.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import type { ProductItem } from "@autumn/shared";
-import type { CheckResult, TrackResult } from "autumn-js";
-import { useCustomer } from "autumn-js/react";
-import { useEffect, useState } from "react";
-import {
- CodeGroup,
- CodeGroupCode,
- CodeGroupContent,
- CodeGroupCopyButton,
- CodeGroupList,
- CodeGroupTab,
-} from "@/components/v2/CodeGroup";
-import { SheetSection } from "@/components/v2/sheets/InlineSheet";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { useOnboardingStore } from "../../store/useOnboardingStore";
-import { getCodeSnippets } from "../../utils/completionStepCode";
-
-type CodeLanguage = "react" | "nodejs" | "response";
-
-const CodeSnippetSection = ({
- title,
- snippets,
- trackResponse,
- checkResponse,
-}: {
- title: string;
- snippets: { react: string; nodejs: string; response: string };
- trackResponse?: TrackResult;
- checkResponse?: CheckResult;
-}) => {
- const [activeLanguage, setActiveLanguage] = useState("react");
-
- // Auto-switch to response tab when responses are available
- useEffect(() => {
- if (trackResponse && title === "Track usage") {
- setActiveLanguage("response");
- }
- if (checkResponse && title === "Check feature access") {
- setActiveLanguage("response");
- }
- }, [trackResponse, checkResponse, title]);
-
- const getCodeForTab = () => {
- // Use dynamic responses for response tabs
- if (activeLanguage === "response") {
- if (title === "Track usage" && trackResponse) {
- return JSON.stringify(trackResponse, null, 2);
- }
- if (title === "Check feature access" && checkResponse) {
- return JSON.stringify(checkResponse, null, 2);
- }
- }
- return snippets[activeLanguage];
- };
-
- return (
-
-
{title}
- setActiveLanguage(val as CodeLanguage)}
- >
-
- React
- Node.js
- Response
- navigator.clipboard.writeText(getCodeForTab())}
- />
-
-
- {snippets.react}
-
-
- {snippets.nodejs}
-
-
-
- {title === "Track usage" && trackResponse
- ? JSON.stringify(trackResponse, null, 2)
- : title === "Check feature access" && checkResponse
- ? JSON.stringify(checkResponse, null, 2)
- : snippets.response}
-
-
-
-
- );
-};
-
-const CustomerSection = () => {
- const { customer } = useCustomer();
- const [activeTab, setActiveTab] = useState("customer");
-
- return (
-
-
Customer
-
-
- Customer Response
-
- navigator.clipboard.writeText(JSON.stringify(customer, null, 2))
- }
- />
-
-
-
- {JSON.stringify(customer, null, 2)}
-
-
-
-
- );
-};
-
-export const QuickStartCodeGroup = ({
- trackResponse,
- checkResponse,
- featureId: usedFeatureId,
-}: {
- trackResponse?: TrackResult;
- checkResponse?: CheckResult;
- featureId?: string;
-}) => {
- const { product } = useProductStore();
- const { features } = useFeaturesQuery();
- const lastUsedProductId = useOnboardingStore(
- (state) => state.lastUsedProductId,
- );
-
- // Use the feature that was actually used (if available), otherwise fallback to first feature
- const firstFeatureItem = product?.items?.find(
- (item: ProductItem) => item.feature_id,
- );
- const featureId = usedFeatureId || firstFeatureItem?.feature_id || undefined;
-
- // Use lastUsedProductId (from pricing card clicks) or fallback to current product
- const productId = lastUsedProductId || product?.id || undefined;
-
- // Get the actual feature name from features list
- const featureName = features.find((f) => f.id === featureId)?.name;
-
- // Generate snippets with actual IDs from onboarding
- const snippets = getCodeSnippets(featureId, productId, featureName);
-
- return (
-
-
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/onboarding3/hooks/actions/useFeatureConfigActions.tsx b/vite/src/views/onboarding3/hooks/actions/useFeatureConfigActions.tsx
deleted file mode 100644
index 10a45f223..000000000
--- a/vite/src/views/onboarding3/hooks/actions/useFeatureConfigActions.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { useCallback } from "react";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useHasChanges, useProductStore } from "@/hooks/stores/useProductStore";
-import { useSheetStore } from "@/hooks/stores/useSheetStore";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { trackOnboardingFeatureConfigured } from "@/utils/posthogTracking";
-import { updateProduct } from "@/views/products/product/utils/updateProduct";
-
-export const useFeatureConfigActions = () => {
- const axiosInstance = useAxiosInstance();
-
- // Get product from product store (working copy to save)
- const product = useProductStore((s) => s.product);
-
- const hasChanges = useHasChanges();
-
- // Get products refetch
- const { refetch: refetchProducts } = useProductsQuery();
-
- // Get sheet store
- const setSheet = useSheetStore((state) => state.setSheet);
-
- // Save product changes before proceeding to playground
- const handleProceed = useCallback(async (): Promise => {
- // If no changes, just open the sheet and proceed
- if (!hasChanges) {
- setSheet({ type: "edit-plan" });
- return true;
- }
-
- const saved = await updateProduct({
- axiosInstance,
- productId: product.id,
- product: product as ProductV2,
- onSuccess: async () => {
- // Refetch products so useInitProductAndFeature can update baseProduct
- await refetchProducts();
- },
- });
-
- if (!saved) return false;
-
- // Track feature configuration completion
- trackOnboardingFeatureConfigured();
-
- // Open edit-plan sheet after successful save
- setSheet({ type: "edit-plan" });
-
- return true;
- }, [hasChanges, axiosInstance, product, setSheet, refetchProducts]);
-
- return {
- handleProceed,
- };
-};
diff --git a/vite/src/views/onboarding3/hooks/actions/useFeatureCreationActions.tsx b/vite/src/views/onboarding3/hooks/actions/useFeatureCreationActions.tsx
deleted file mode 100644
index 62c25ac63..000000000
--- a/vite/src/views/onboarding3/hooks/actions/useFeatureCreationActions.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import type { CreateFeature, Feature, ProductV2 } from "@autumn/shared";
-import {
- CreateFeatureSchema,
- FeatureUsageType,
- featureV1ToDbFeature,
-} from "@autumn/shared";
-import type { AxiosError } from "axios";
-import { useCallback } from "react";
-import { toast } from "sonner";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import {
- useFeatureStore,
- useHasFeatureChanges,
-} from "@/hooks/stores/useFeatureStore";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { FeatureService } from "@/services/FeatureService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { trackOnboardingFeatureCreation } from "@/utils/posthogTracking";
-
-export const useFeatureCreationActions = () => {
- const axiosInstance = useAxiosInstance();
- const { refetch: refetchFeatures } = useFeaturesQuery();
-
- // Get state from feature store
- const feature = useFeatureStore((state) => state.feature);
- const baseFeature = useFeatureStore((state) => state.baseFeature);
- const setBaseFeature = useFeatureStore((state) => state.setBaseFeature);
- const setFeature = useFeatureStore((state) => state.setFeature);
- const hasFeatureChanges = useHasFeatureChanges();
-
- // Get product store state
- const baseProduct = useProductStore((s) => s.baseProduct);
- const setProduct = useProductStore((s) => s.setProduct);
-
- // Create or update feature
- const handleProceed = useCallback(async (): Promise => {
- // Validate feature data
- const result = CreateFeatureSchema.safeParse(feature);
- if (result.error) {
- toast.error("Invalid feature", {
- description: result.error.issues.map((x) => x.message).join(".\n"),
- });
- return false;
- }
-
- // If feature exists and no changes, skip update
- if (baseFeature?.id && !hasFeatureChanges) {
- return true;
- }
-
- try {
- let updatedFeature: CreateFeature;
-
- // If baseFeature exists (update mode), update it
- if (baseFeature?.id) {
- const { data } = await FeatureService.updateFeature(
- axiosInstance,
- baseFeature.id,
- {
- name: feature.name,
- id: feature.id,
- type: feature.type,
- consumable: feature.config?.usage_type === FeatureUsageType.Single,
- event_names: feature.event_names,
- },
- );
-
- updatedFeature = featureV1ToDbFeature({ apiFeature: data });
-
- toast.success(`Feature "${feature.name}" updated successfully!`);
- } else {
- // Create new feature
- const { data } = await FeatureService.createFeature(axiosInstance, {
- name: feature.name,
- id: feature.id,
- type: feature.type,
- consumable: feature.config?.usage_type === FeatureUsageType.Single,
- event_names: feature.event_names,
- });
- updatedFeature = featureV1ToDbFeature({ apiFeature: data });
- toast.success(`Feature "${feature.name}" created successfully!`);
-
- // Track feature creation in onboarding
- trackOnboardingFeatureCreation({
- featureType: feature.type,
- });
- }
-
- console.log("Updated feature", updatedFeature);
- if (!updatedFeature?.id) return false;
-
- await refetchFeatures(); // Refresh features list
-
- // Update both base and working copy after successful save
- setBaseFeature(updatedFeature as Feature);
- setFeature(updatedFeature);
-
- // Refetch product from backend to sync changes (e.g., entitlement updates when feature type changes)
- // This is critical because handleFeatureTypeChanged may update entitlements on the backend
- if (baseProduct?.id) {
- const { data } = await axiosInstance.get<{
- products: ProductV2[];
- groupToDefaults: Record>;
- }>("/products/products");
-
- const syncedProduct = data.products.find(
- (p) => p.id === baseProduct.id,
- );
-
- if (syncedProduct) {
- // Sync both base and working copy with backend state
- useProductStore.getState().setBaseProduct(syncedProduct);
- setProduct(syncedProduct);
- }
- }
-
- // Note: Feature item creation is now handled by useInitFeatureItem when entering Step 3
- // This ensures proper initialization on both normal flow and refresh scenarios
-
- return true;
- } catch (error: unknown) {
- console.error("Failed to create/update feature:", error);
- toast.error(
- getBackendErr(error as AxiosError, "Failed to create/update feature"),
- );
- return false;
- }
- }, [
- feature,
- baseFeature,
- baseProduct,
- axiosInstance,
- refetchFeatures,
- setBaseFeature,
- setFeature,
- setProduct,
- hasFeatureChanges,
- ]);
-
- return {
- handleProceed,
- };
-};
diff --git a/vite/src/views/onboarding3/hooks/actions/usePlanDetailsActions.tsx b/vite/src/views/onboarding3/hooks/actions/usePlanDetailsActions.tsx
deleted file mode 100644
index c623e32ee..000000000
--- a/vite/src/views/onboarding3/hooks/actions/usePlanDetailsActions.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { useCallback } from "react";
-import { toast } from "sonner";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import {
- useHasDetailsChanged,
- useProductStore,
-} from "@/hooks/stores/useProductStore";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { trackOnboardingProductCreation } from "@/utils/posthogTracking";
-import { isPriceItem } from "@/utils/product/getItemType";
-import { updateProduct } from "@/views/products/product/utils/updateProduct";
-import { createProduct } from "../../utils/onboardingUtils";
-
-export const usePlanDetailsActions = () => {
- const axiosInstance = useAxiosInstance();
- const { refetch: refetchProducts } = useProductsQuery();
-
- // Get product from product store (working copy)
- const product = useProductStore((s) => s.product);
- const baseProduct = useProductStore((s) => s.baseProduct);
- const hasDetailsChanged = useHasDetailsChanged();
-
- // Create or update product
- const handleProceed = useCallback(async (): Promise => {
- if (!product) return false;
-
- // If product exists and no details changed, skip update
- if (baseProduct?.internal_id && !hasDetailsChanged) {
- return true;
- }
-
- let newProduct: ProductV2;
-
- // If baseProduct exists (update mode), update it
- if (baseProduct?.id) {
- newProduct = await updateProduct({
- axiosInstance,
- productId: baseProduct.id,
- product: product as ProductV2,
- onSuccess: async () => {},
- });
- toast.success("Plan updated successfully");
- } else {
- // Create new product
- newProduct = await createProduct(product, axiosInstance);
-
- // Track product creation in Amplitude (only on creation, not update)
- if (newProduct) {
- const isPaid =
- newProduct.items?.some((item) => isPriceItem(item)) ?? false;
- const productType = isPaid ? "paid" : "free";
-
- trackOnboardingProductCreation({
- productType,
- });
- }
- }
-
- if (!newProduct) return false;
-
- // Refetch products to get latest from backend
- await refetchProducts();
-
- return true;
- }, [product, baseProduct, axiosInstance, refetchProducts, hasDetailsChanged]);
-
- return {
- handleProceed,
- };
-};
diff --git a/vite/src/views/onboarding3/hooks/actions/useSharedActions.tsx b/vite/src/views/onboarding3/hooks/actions/useSharedActions.tsx
deleted file mode 100644
index 40bd3e081..000000000
--- a/vite/src/views/onboarding3/hooks/actions/useSharedActions.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { useCallback, useMemo } from "react";
-import { useNavigate } from "react-router";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { useSheetStore } from "@/hooks/stores/useSheetStore";
-import { useEnv } from "@/utils/envUtils";
-import { navigateTo } from "@/utils/genUtils";
-import { OnboardingStep } from "../../utils/onboardingUtils";
-import { useOnboarding3QueryState } from "../useOnboarding3QueryState";
-
-interface SharedActionsProps {
- step: OnboardingStep;
- popStep: () => void;
- refetchProducts: () => Promise;
- products: ProductV2[];
-}
-
-export const useSharedActions = ({
- step,
- popStep,
- refetchProducts,
- products,
-}: SharedActionsProps) => {
- const navigate = useNavigate();
- const env = useEnv();
-
- // Get product from product store (working copy)
- const product = useProductStore((s) => s.product);
- const setProduct = useProductStore((s) => s.setProduct);
- const baseProduct = useProductStore((s) => s.baseProduct);
- const setBaseProduct = useProductStore((s) => s.setBaseProduct);
-
- const setSheet = useSheetStore((state) => state.setSheet);
-
- // Get query state setters
- const { setQueryStates } = useOnboarding3QueryState();
-
- // Handle plan selection from dropdown
- const handlePlanSelect = useCallback(
- async (planId: string) => {
- if (!planId || planId === product.id) return;
-
- // Find the product in the already-fetched products list
- const selectedProduct = products.find((p) => p.id === planId);
-
- if (!selectedProduct) {
- console.error("Product not found in products list:", planId);
- return;
- }
-
- // Set both baseProduct and product atomically using getState() to avoid timing issues
- const productStore = useProductStore.getState();
- productStore.setBaseProduct(selectedProduct);
- productStore.setProduct(selectedProduct);
-
- // Update product_id in query params
- setQueryStates({ product_id: planId });
-
- // Keep the edit-plan sheet open
- setSheet({ type: "edit-plan" });
- },
- [product.id, products, setQueryStates, setSheet],
- );
-
- // Handle back navigation
- const handleBack = useCallback(async () => {
- const { closeSheet } = useSheetStore.getState();
- closeSheet();
-
- // If we're on Step 3 (FeatureConfiguration), reset product to baseProduct
- // This removes the half-configured feature item that was added in Step 2
- if (step === OnboardingStep.FeatureConfiguration && baseProduct) {
- setProduct(baseProduct);
- }
-
- // If we're on Step 4 (Playground), discard any changes made and reset to baseProduct
- // This prevents invalid configurations from blocking progress if user goes back to Step 3
- if (step === OnboardingStep.Playground && baseProduct) {
- setProduct(baseProduct);
- }
-
- // Default: pop one step back
- popStep();
- }, [popStep, step, baseProduct, setProduct]);
-
- // Handle create plan success from dialog
- const onCreatePlanSuccess = useCallback(
- async (newProduct: ProductV2) => {
- try {
- // Refetch products to update the list
- // useOnboardingProductSync will sync baseProduct with the backend version
- await refetchProducts();
-
- // Set the newly created product as base and working product
- setBaseProduct(newProduct);
- setProduct(newProduct);
-
- // Update product_id in query params
- setQueryStates({ product_id: newProduct.id });
-
- // Open edit-plan sheet
- setSheet({ type: "edit-plan" });
- } catch (error) {
- console.error("Failed to load new plan:", error);
- navigateTo(`/products/${newProduct.id}`, navigate, env);
- }
- },
- [
- setBaseProduct,
- setProduct,
- refetchProducts,
- setQueryStates,
- setSheet,
- navigate,
- env,
- ],
- );
-
- // Handle delete plan success from dialog
- const handleDeletePlanSuccess = useCallback(async () => {
- // Refetch products - useOnboardingProductSync will automatically:
- // - Redirect to step 1 if no products left
- // - Fallback to products[0] if current product was deleted but others exist
- // - Sync both baseProduct and product
- await refetchProducts();
- }, [refetchProducts]);
-
- return useMemo(
- () => ({
- handlePlanSelect,
- handleBack,
- onCreatePlanSuccess,
- handleDeletePlanSuccess,
- }),
- [
- handlePlanSelect,
- handleBack,
- onCreatePlanSuccess,
- handleDeletePlanSuccess,
- ],
- );
-};
diff --git a/vite/src/views/onboarding3/hooks/actions/useStepActions.tsx b/vite/src/views/onboarding3/hooks/actions/useStepActions.tsx
deleted file mode 100644
index cd1c91248..000000000
--- a/vite/src/views/onboarding3/hooks/actions/useStepActions.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import type { CreateFeature, Feature, ProductV2 } from "@autumn/shared";
-import { useCallback } from "react";
-import { useNavigate } from "react-router";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useFeatureStore } from "@/hooks/stores/useFeatureStore";
-import { useHasChanges, useProductStore } from "@/hooks/stores/useProductStore";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { pushPage } from "@/utils/genUtils";
-import {
- trackOnboardingIntegrationCompleted,
- trackOnboardingPlaygroundCompleted,
-} from "@/utils/posthogTracking";
-import { useOnboardingStore } from "../../store/useOnboardingStore";
-import { getNextStep, OnboardingStep } from "../../utils/onboardingUtils";
-import { useFeatureConfigActions } from "./useFeatureConfigActions";
-import { useFeatureCreationActions } from "./useFeatureCreationActions";
-import { usePlanDetailsActions } from "./usePlanDetailsActions";
-
-interface StepActionsProps {
- // Flow state
- step: OnboardingStep;
- pushStep: (step: OnboardingStep) => void;
- popStep: () => void;
- validateStep: (
- step: OnboardingStep,
- product: ProductV2 | undefined,
- feature: Feature | CreateFeature | null,
- ) => boolean;
- // Shared actions (passed from parent)
- sharedActions: {
- handlePlanSelect: (planId: string) => Promise;
- handleBack: () => Promise;
- onCreatePlanSuccess: (newProduct: ProductV2) => Promise;
- };
-}
-
-export const useStepActions = (props: StepActionsProps) => {
- const { step, pushStep, validateStep, sharedActions } = props;
-
- const navigate = useNavigate();
- const { org, mutate: mutateOrg } = useOrg();
- const axiosInstance = useAxiosInstance();
-
- // Get product from product store
- const product = useProductStore((s) => s.product);
-
- // Get state from Zustand
- const feature = useFeatureStore((state) => state.feature);
- const setIsButtonLoading = useOnboardingStore((s) => s.setIsButtonLoading);
- const hasChanges = useHasChanges();
-
- // Step-specific action hooks (they access state directly now)
- const planDetailsActions = usePlanDetailsActions();
- const featureCreationActions = useFeatureCreationActions();
- const featureConfigActions = useFeatureConfigActions();
-
- // Main navigation handler
- const handleNext = useCallback(async () => {
- if (!validateStep(step, product, feature)) return;
-
- const nextStep = getNextStep(step);
- let canProceed = true;
-
- // Set loading for steps 1 and 2
- if (
- step === OnboardingStep.PlanDetails ||
- step === OnboardingStep.FeatureCreation ||
- (step === OnboardingStep.FeatureConfiguration && hasChanges)
- ) {
- console.log("setting loading to true", step);
- setIsButtonLoading(true);
- }
-
- try {
- // Step 1: Create product
- if (step === OnboardingStep.PlanDetails) {
- canProceed = await planDetailsActions.handleProceed();
- }
-
- // Step 2: Create feature and add to product
- if (step === OnboardingStep.FeatureCreation) {
- canProceed = await featureCreationActions.handleProceed();
- }
-
- // Step 3: Save product changes before moving to playground
- if (
- step === OnboardingStep.FeatureConfiguration &&
- nextStep === OnboardingStep.Playground
- ) {
- canProceed = await featureConfigActions.handleProceed();
- }
-
- if (!canProceed) return;
-
- if (nextStep) {
- // Track playground completion when moving to integration
- if (step === OnboardingStep.Playground) {
- trackOnboardingPlaygroundCompleted();
- }
-
- pushStep(nextStep);
- } else {
- // Track integration completion
- if (step === OnboardingStep.Integration) {
- trackOnboardingIntegrationCompleted();
- }
-
- // Finish onboarding
- if (!org?.onboarded) {
- await axiosInstance.patch("/v1/organization", {
- onboarded: true,
- });
- await mutateOrg();
- }
-
- pushPage({
- navigate,
- path: "/products",
- queryParams: {
- tab: "products",
- },
- preserveParams: true,
- });
- // navigateTo("/products?tab=products", navigate, env);
- }
- } finally {
- // Always clear loading state
- setIsButtonLoading(false);
- }
- }, [
- validateStep,
- step,
- product,
- feature,
- planDetailsActions,
- featureCreationActions,
- featureConfigActions,
- pushStep,
- setIsButtonLoading,
- hasChanges,
- navigate,
- org,
- axiosInstance,
- mutateOrg,
- ]);
-
- return {
- handleNext,
- handleBack: sharedActions.handleBack,
- handlePlanSelect: sharedActions.handlePlanSelect,
- onCreatePlanSuccess: sharedActions.onCreatePlanSuccess,
- };
-};
diff --git a/vite/src/views/onboarding3/hooks/useAutoSetProductId.ts b/vite/src/views/onboarding3/hooks/useAutoSetProductId.ts
deleted file mode 100644
index 3f8146e82..000000000
--- a/vite/src/views/onboarding3/hooks/useAutoSetProductId.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { useEffect, useRef } from "react";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { OnboardingStep } from "../utils/onboardingUtils";
-import { useOnboarding3QueryState } from "./useOnboarding3QueryState";
-
-/**
- * Auto-sets product_id in query params when entering the playground step
- * This prevents product switching when the products array order changes
- */
-export const useAutoSetProductId = () => {
- const { queryStates, setQueryStates } = useOnboarding3QueryState();
- const baseProduct = useProductStore((s) => s.baseProduct);
- const hasSetProductId = useRef(false);
-
- useEffect(() => {
- // Only run in playground step
- if (queryStates.step !== OnboardingStep.Playground) {
- return;
- }
-
- // If product_id is already set in query params, don't override it
- if (queryStates.product_id) {
- hasSetProductId.current = true;
- return;
- }
-
- // If we have a baseProduct and haven't set the product_id yet, set it
- if (baseProduct?.id && !hasSetProductId.current) {
- setQueryStates({ product_id: baseProduct.id });
- hasSetProductId.current = true;
- }
- }, [
- queryStates.step,
- queryStates.product_id,
- baseProduct?.id,
- setQueryStates,
- ]);
-};
diff --git a/vite/src/views/onboarding3/hooks/useAutoSkipToPlayground.ts b/vite/src/views/onboarding3/hooks/useAutoSkipToPlayground.ts
deleted file mode 100644
index f84fa864d..000000000
--- a/vite/src/views/onboarding3/hooks/useAutoSkipToPlayground.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * Hook that was previously used to auto-skip to playground if steps 1-3 were complete.
- * Now disabled since onboarding always creates a new product from scratch.
- *
- * Returns isChecking: false (never checking since we always start fresh)
- */
-export const useAutoSkipToPlayground = () => {
- // Onboarding always starts fresh - no auto-skipping
- return { isChecking: false };
-};
diff --git a/vite/src/views/onboarding3/hooks/useInitFeatureItem.tsx b/vite/src/views/onboarding3/hooks/useInitFeatureItem.tsx
deleted file mode 100644
index 70262106d..000000000
--- a/vite/src/views/onboarding3/hooks/useInitFeatureItem.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import type { ProductItem } from "@autumn/shared";
-import { useEffect } from "react";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useFeatureStore } from "@/hooks/stores/useFeatureStore";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { createProductItem, OnboardingStep } from "../utils/onboardingUtils";
-import { useOnboarding3QueryState } from "./useOnboarding3QueryState";
-
-/**
- * Hook to ensure Step 3 has a valid feature item that matches baseFeature
- * Runs whenever on Step 3 and ensures the feature item is in sync
- */
-export const useInitFeatureItem = () => {
- // Get step from query state
- const { queryStates } = useOnboarding3QueryState();
- const step = queryStates.step;
-
- // Get features from query
- const { features } = useFeaturesQuery();
-
- // Get product state from store
- const product = useProductStore((state) => state.product);
- const setProduct = useProductStore((state) => state.setProduct);
-
- // Get state from Zustand
- const baseFeature = useFeatureStore((state) => state.baseFeature);
-
- // biome-ignore lint/correctness/useExhaustiveDependencies: Don't depend on setProduct
- useEffect(() => {
- if (!product?.items || !baseFeature?.id) return;
-
- // Find existing feature item (any item with matching feature_id)
- // This includes both priced and non-priced items
- const existingFeatureItemIndex = product.items.findIndex(
- (item: ProductItem) => item.feature_id === baseFeature.id,
- );
-
- const updatedItems = [...product.items];
-
- let needsUpdate = false;
- if (existingFeatureItemIndex === -1) {
- // Create feature item only on Step 3
- if (step === OnboardingStep.FeatureConfiguration && features?.length) {
- updatedItems.push(createProductItem(baseFeature));
- needsUpdate = true;
- }
- }
-
- // else if (
- // updatedItems[existingFeatureItemIndex].feature_id !== baseFeature.id
- // ) {
- // // Update feature_id if it changed
- // updatedItems[existingFeatureItemIndex] = {
- // ...updatedItems[existingFeatureItemIndex],
- // feature_id: baseFeature.id,
- // };
- // needsUpdate = true;
- // }
-
- if (needsUpdate) {
- const updatedProduct = { ...product, items: updatedItems };
- // Only update product (working copy), NOT baseProduct
- // baseProduct should remain as the backend state
- // This allows back navigation to properly reset product to baseProduct
- setProduct(updatedProduct);
- }
- }, [step, product?.items, baseFeature?.id, features?.length]);
-};
diff --git a/vite/src/views/onboarding3/hooks/useOnboardingData.tsx b/vite/src/views/onboarding3/hooks/useOnboardingData.tsx
deleted file mode 100644
index 2f0b59aae..000000000
--- a/vite/src/views/onboarding3/hooks/useOnboardingData.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { useInitProductAndFeature } from "./useInitProductAndFeature";
-
-/**
- * Hook to manage onboarding initialization
- *
- * Key architecture:
- * - Product state is managed in useProductStore (baseProduct and working product)
- * - This hook just initializes the feature item for step 3
- */
-export const useOnboardingData = () => {
- useInitProductAndFeature();
-};
diff --git a/vite/src/views/onboarding3/hooks/useOnboardingFeatureSync.tsx b/vite/src/views/onboarding3/hooks/useOnboardingFeatureSync.tsx
deleted file mode 100644
index 0168e4e9d..000000000
--- a/vite/src/views/onboarding3/hooks/useOnboardingFeatureSync.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { FeatureType } from "@autumn/shared";
-import { useEffect, useRef } from "react";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useFeatureStore } from "@/hooks/stores/useFeatureStore";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-import { useSheetStore } from "@/hooks/stores/useSheetStore";
-
-/**
- * Hook to sync feature store with backend features during onboarding
- * Only syncs features AFTER a product has been created (has an internal_id from backend)
- * Re-syncs when product changes to ensure feature store matches product
- */
-export const useOnboardingFeatureSync = () => {
- const { features } = useFeaturesQuery();
- const product = useProductStore((s) => s.product);
- const sheetType = useSheetStore((s) => s.type);
-
- // Feature store
- const baseFeature = useFeatureStore((state) => state.baseFeature);
- const setBaseFeature = useFeatureStore((state) => state.setBaseFeature);
- const setFeature = useFeatureStore((state) => state.setFeature);
-
- const lastProductId = useRef(undefined);
-
- // Initialize/sync feature whenever product changes
- useEffect(() => {
- if (!features || features.length === 0) return;
-
- // Only sync after product has been created (has internal_id from backend)
- // This ensures we don't auto-select features for a product that doesn't exist yet
- if (!product?.internal_id) return;
-
- // Don't sync while creating a new feature or selecting a feature
- // This allows the new feature sheet to manage its own feature state
- if (sheetType === "new-feature" || sheetType === "select-feature") return;
-
- // Check if product changed
- const productChanged = lastProductId.current !== product.id;
- lastProductId.current = product.id;
-
- // Re-sync feature if:
- // 1. Product changed, OR
- // 2. No feature is currently set
- if (!productChanged && baseFeature) return;
-
- // Step 1: Get feature IDs that are in the product
- const featureIdsInProduct =
- product?.items
- ?.filter((item) => item.feature_id)
- .map((item) => item.feature_id) || [];
-
- // Step 2: Filter features - if product has features, use only those
- let candidateFeatures = features;
- if (featureIdsInProduct.length > 0) {
- candidateFeatures = features.filter((f) =>
- featureIdsInProduct.includes(f.id),
- );
- }
-
- // Step 3: Sort by metered features first
- candidateFeatures.sort((a, b) => {
- const aIsBoolean = a.type === FeatureType.Boolean;
- const bIsBoolean = b.type === FeatureType.Boolean;
- if (aIsBoolean && !bIsBoolean) return 1;
- if (!aIsBoolean && bIsBoolean) return -1;
- return 0;
- });
-
- // Step 4: Take the first feature
- const featureToLoad = candidateFeatures[0];
-
- if (featureToLoad) {
- setBaseFeature(featureToLoad);
- setFeature(featureToLoad);
- }
- }, [
- features,
- product?.id,
- product?.internal_id,
- product?.items,
- baseFeature,
- setBaseFeature,
- setFeature,
- sheetType,
- ]);
-};
diff --git a/vite/src/views/onboarding3/hooks/useOnboardingFlow.tsx b/vite/src/views/onboarding3/hooks/useOnboardingFlow.tsx
deleted file mode 100644
index 39265faf2..000000000
--- a/vite/src/views/onboarding3/hooks/useOnboardingFlow.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { useCallback, useEffect, useMemo, useState } from "react";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useOnboarding3QueryState } from "./useOnboarding3QueryState";
-import { useOnboardingSteps } from "./useOnboardingSteps";
-
-interface OnboardingFlowState {
- playgroundMode: "edit" | "preview";
- hasCompletedOnboarding: boolean;
-}
-
-export const useOnboardingFlow = () => {
- const { features, isLoading: featuresLoading } = useFeaturesQuery();
- const { products, isLoading: productsLoading } = useProductsQuery();
- const { queryStates, setQueryStates } = useOnboarding3QueryState();
- const { validateStep } = useOnboardingSteps();
-
- const [flowState, setFlowState] = useState({
- playgroundMode: "edit",
- hasCompletedOnboarding: false,
- });
-
- // Check if user has completed onboarding
- const hasCompletedOnboarding = useMemo(() => {
- if (productsLoading || featuresLoading || !products || !features) {
- return false;
- }
- return products.length >= 1 && features.length >= 1;
- }, [products, features, productsLoading, featuresLoading]);
-
- // Update flow state when onboarding completion status changes
- useEffect(() => {
- setFlowState((prev) => ({
- ...prev,
- hasCompletedOnboarding,
- }));
- }, [hasCompletedOnboarding]);
-
- // Auto-skip to playground if user has completed onboarding
- // NOTE: Commented out as per user request - may be used later
- // useEffect(() => {
- // if (onboardingProcessed.current || productsLoading || featuresLoading) {
- // return;
- // }
-
- // if (hasCompletedOnboarding) {
- // // Jump directly to step 4 (Playground) in preview mode
- // pushStep(OnboardingStep.FeatureCreation);
- // pushStep(OnboardingStep.FeatureConfiguration);
- // pushStep(OnboardingStep.Playground);
-
- // setFlowState((prev) => ({
- // ...prev,
- // playgroundMode: "preview",
- // }));
- // }
-
- // onboardingProcessed.current = true;
- // }, [hasCompletedOnboarding, productsLoading, featuresLoading, pushStep]);
-
- const pushStep = useCallback(
- (step: typeof queryStates.step) => {
- setQueryStates({ step });
- },
- [setQueryStates],
- );
-
- const popStep = useCallback(() => {
- // Implement backwards navigation based on current step
- const stepOrder = [
- "plan_details",
- "feature_creation",
- "feature_configuration",
- "playground",
- "integration",
- ];
- const currentIndex = stepOrder.indexOf(queryStates.step);
- if (currentIndex > 0) {
- setQueryStates({
- step: stepOrder[currentIndex - 1] as typeof queryStates.step,
- });
- }
- }, [queryStates.step, setQueryStates]);
-
- const setPlaygroundMode = useCallback((mode: "edit" | "preview") => {
- setFlowState((prev) => ({
- ...prev,
- playgroundMode: mode,
- }));
- }, []);
-
- return {
- step: queryStates.step,
- pushStep,
- popStep,
- validateStep,
- playgroundMode: flowState.playgroundMode,
- setPlaygroundMode,
- hasCompletedOnboarding: flowState.hasCompletedOnboarding,
- isLoading: productsLoading || featuresLoading,
- };
-};
diff --git a/vite/src/views/onboarding3/hooks/useOnboardingLogic.tsx b/vite/src/views/onboarding3/hooks/useOnboardingLogic.tsx
deleted file mode 100644
index 821f3a03b..000000000
--- a/vite/src/views/onboarding3/hooks/useOnboardingLogic.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import { useEffect, useRef } from "react";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useSheetStore } from "@/hooks/stores/useSheetStore";
-import { useOnboardingStore } from "../store/useOnboardingStore";
-import { OnboardingStep } from "../utils/onboardingUtils";
-import { useSharedActions } from "./actions/useSharedActions";
-import { useStepActions } from "./actions/useStepActions";
-import { useOnboardingFlow } from "./useOnboardingFlow";
-
-/**
- * Main orchestrator for onboarding logic
- *
- * This hook handles side effects and auto-initialization.
- * Most state should be accessed directly from Zustand in components!
- *
- * Only returns action handlers (handleNext, handleBack, etc.)
- */
-export const useOnboardingLogic = () => {
- const hasInitializedResumability = useRef(false);
-
- // Get queries
- const { products, refetch: refetchProducts } = useProductsQuery();
- const { features } = useFeaturesQuery();
-
- // Set isOnboarding flag on mount
- useEffect(() => {
- const { setIsOnboarding } = useOnboardingStore.getState();
- setIsOnboarding(true);
-
- return () => {
- setIsOnboarding(false);
- };
- }, []);
-
- // Get flow state
- const flowHook = useOnboardingFlow();
-
- // Get state from sheet store
- const setSheet = useSheetStore((s) => s.setSheet);
- const closeSheet = useSheetStore((s) => s.closeSheet);
-
- // Initialize shared actions (handles plan selection, back navigation, etc.)
- const sharedActions = useSharedActions({
- step: flowHook.step,
- popStep: flowHook.popStep,
- refetchProducts,
- products: products || [],
- });
-
- // Initialize with existing data ONLY if user has completed onboarding AND auto-skipped to playground
- useEffect(() => {
- if (
- !hasInitializedResumability.current &&
- flowHook.hasCompletedOnboarding &&
- !flowHook.isLoading &&
- flowHook.step === OnboardingStep.Playground &&
- flowHook.playgroundMode === "preview"
- ) {
- // Additional safeguard: Double-check that we actually have both products and features
- if (
- products &&
- features &&
- products.length >= 1 &&
- features.length >= 1
- ) {
- hasInitializedResumability.current = true;
- }
- }
- }, [
- flowHook.hasCompletedOnboarding,
- flowHook.isLoading,
- flowHook.step,
- flowHook.playgroundMode,
- products,
- features,
- ]);
-
- // Auto-open edit-plan sheet when entering step 4 (Playground) in edit mode
- // Close sheet when leaving step 4 or entering Integration
- useEffect(() => {
- if (
- flowHook.step === OnboardingStep.Playground &&
- flowHook.playgroundMode === "edit"
- ) {
- setSheet({ type: "edit-plan" });
- } else if (
- flowHook.step === OnboardingStep.Integration ||
- flowHook.step === OnboardingStep.FeatureConfiguration
- ) {
- closeSheet();
- }
- }, [flowHook.step, flowHook.playgroundMode, setSheet, closeSheet]);
-
- // Create actions hook (pass shared actions)
- const stepActionsHook = useStepActions({
- step: flowHook.step,
- pushStep: flowHook.pushStep,
- popStep: flowHook.popStep,
- validateStep: flowHook.validateStep,
- sharedActions,
- });
-
- // Set handlers in store so components can access them directly
- useEffect(() => {
- const {
- setHandleNext,
- setHandleBack,
- setHandlePlanSelect,
- setOnCreatePlanSuccess,
- setHandleDeletePlanSuccess,
- setValidateStep,
- } = useOnboardingStore.getState();
-
- setHandleNext(stepActionsHook.handleNext);
- setHandleBack(stepActionsHook.handleBack);
- setHandlePlanSelect(stepActionsHook.handlePlanSelect);
- setOnCreatePlanSuccess(stepActionsHook.onCreatePlanSuccess);
- setHandleDeletePlanSuccess(sharedActions.handleDeletePlanSuccess);
- setValidateStep(flowHook.validateStep);
- }, [
- stepActionsHook.handleNext,
- stepActionsHook.handleBack,
- stepActionsHook.handlePlanSelect,
- stepActionsHook.onCreatePlanSuccess,
- sharedActions.handleDeletePlanSuccess,
- flowHook.validateStep,
- ]);
-};
diff --git a/vite/src/views/onboarding3/hooks/useOnboardingProductSync.ts b/vite/src/views/onboarding3/hooks/useOnboardingProductSync.ts
deleted file mode 100644
index 6840f993f..000000000
--- a/vite/src/views/onboarding3/hooks/useOnboardingProductSync.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useEffect, useRef } from "react";
-import { useFeatureStore } from "@/hooks/stores/useFeatureStore";
-import { useProductStore } from "@/hooks/stores/useProductStore";
-
-/**
- * Initializes stores for onboarding
- * Always starts fresh with default values to ensure new product/feature are created
- */
-export const useOnboardingProductSync = () => {
- const hasInitialized = useRef(false);
-
- useEffect(() => {
- if (hasInitialized.current) return;
-
- // Always reset to defaults - onboarding always creates new product and feature
- const { reset: resetProduct } = useProductStore.getState();
- const { reset: resetFeature } = useFeatureStore.getState();
- resetProduct();
- resetFeature();
- hasInitialized.current = true;
- }, []);
-};
diff --git a/vite/src/views/onboarding3/hooks/useOnboardingSteps.tsx b/vite/src/views/onboarding3/hooks/useOnboardingSteps.tsx
deleted file mode 100644
index 415658392..000000000
--- a/vite/src/views/onboarding3/hooks/useOnboardingSteps.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import type { CreateFeature, Feature, ProductV2 } from "@autumn/shared";
-import {
- FeatureType,
- FeatureUsageType,
- isPriceItem,
- productV2ToBasePrice,
- productV2ToPlanType,
-} from "@autumn/shared";
-import { useHasChanges } from "@/hooks/stores/useProductStore";
-import { OnboardingStep } from "../utils/onboardingUtils";
-import { useOnboarding3QueryState } from "./useOnboarding3QueryState";
-
-export const useOnboardingSteps = () => {
- // Use query state instead of local state
- const { queryStates } = useOnboarding3QueryState();
- const step = queryStates.step;
- const hasChanges = useHasChanges();
-
- // Step validation
- const validateStep = (
- currentStep: OnboardingStep,
- product: ProductV2 | undefined,
- feature: Feature | CreateFeature | null,
- ): boolean => {
- switch (currentStep) {
- case OnboardingStep.PlanDetails: {
- // Return false if product is null or undefined
- if (!product) return false;
-
- // Basic validation for name and ID
- const basicValid =
- product?.name?.trim() !== "" && product?.id?.trim() !== "";
- if (!basicValid) return false;
-
- // Base price validation - safely check if product has items
- if (!product.items) return false;
-
- const planType = productV2ToPlanType({ product });
- if (!planType) return false;
-
- const basePrice = productV2ToBasePrice({
- product: product as unknown as ProductV2,
- });
- const hasBasePriceItem = product.items.some((item) =>
- isPriceItem(item),
- );
-
- // If base price is unchecked (no price item), that's valid
- if (!hasBasePriceItem) return true;
-
- // If base price is checked, it needs a valid price amount
- return basePrice?.price != null && basePrice.price > 0;
- }
- case OnboardingStep.FeatureCreation:
- return (
- (feature &&
- feature?.name?.trim() !== "" &&
- feature?.id?.trim() !== "" &&
- feature?.type !== null &&
- (feature.type === FeatureType.Boolean ||
- feature?.config?.usage_type === FeatureUsageType.Continuous ||
- feature?.config?.usage_type === FeatureUsageType.Single)) ??
- false
- );
- case OnboardingStep.FeatureConfiguration:
- return true;
- case OnboardingStep.Playground:
- return !hasChanges;
- case OnboardingStep.Integration:
- return true;
- default:
- return false;
- }
- };
-
- return {
- step,
- validateStep,
- };
-};
diff --git a/vite/src/views/onboarding3/hooks/useSyncPlaygroundMode.tsx b/vite/src/views/onboarding3/hooks/useSyncPlaygroundMode.tsx
deleted file mode 100644
index 7714a0fa2..000000000
--- a/vite/src/views/onboarding3/hooks/useSyncPlaygroundMode.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { useEffect } from "react";
-import { useOnboardingStore } from "../store/useOnboardingStore";
-import { OnboardingStep } from "../utils/onboardingUtils";
-import { useOnboarding3QueryState } from "./useOnboarding3QueryState";
-
-/**
- * One-way sync: query param → store
- * Query param is the source of truth
- */
-export const useSyncPlaygroundMode = () => {
- const { queryStates, setQueryStates } = useOnboarding3QueryState();
- const setPlaygroundMode = useOnboardingStore((s) => s.setPlaygroundMode);
-
- const isPlaygroundStep = queryStates.step === OnboardingStep.Playground;
-
- // ONE-WAY sync: query param → store (only in playground step)
- useEffect(() => {
- if (isPlaygroundStep) {
- // Initialize m param if not present
- if (!queryStates.m) {
- setQueryStates({ m: "e" });
- } else if (queryStates.m === "e") {
- setPlaygroundMode("edit");
- } else if (queryStates.m === "p") {
- setPlaygroundMode("preview");
- }
- } else if (queryStates.m !== null) {
- // Remove 'm' param when not in playground step
- setQueryStates({ m: null });
- }
- }, [
- queryStates.m,
- queryStates.step,
- setPlaygroundMode,
- setQueryStates,
- isPlaygroundStep,
- ]);
-};
diff --git a/vite/src/views/onboarding3/utils/completionStepCode.ts b/vite/src/views/onboarding3/utils/completionStepCode.ts
deleted file mode 100644
index 03fdb84eb..000000000
--- a/vite/src/views/onboarding3/utils/completionStepCode.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-export const getCodeSnippets = (
- featureId?: string,
- productId?: string,
- featureName?: string,
-) => {
- const actualFeatureId = featureId || "your_feature_id";
- const actualProductId = productId || "your_product_id";
- const actualFeatureName = featureName || "Your Feature";
-
- return {
- allowed: {
- react: `import { useCustomer } from 'autumn-js/react';
-
-const { check } = useCustomer();
-
-const handleCheckFeature = async () => {
- const { data } = await check({
- featureId: '${actualFeatureId}',
- requiredQuantity: 1
- });
- if (!data?.allowed) {
- alert('Feature not allowed');
- }
-}`,
- nodejs: `import { Autumn } from 'autumn-js';
-
-const autumn = new Autumn({
- apiKey: process.env.AUTUMN_API_KEY
-});
-
-const { data, error } = await autumn.check({
- customerId: 'cust_123',
- featureId: '${actualFeatureId}'
-});`,
- response: `{
- "allowed": true,
- "feature": {
- "id": "${actualFeatureId}",
- "name": "${actualFeatureName}",
- "type": "limit"
- }
-}`,
- },
- checkout: {
- react: `import {
- useCustomer,
- CheckoutDialog
-} from "autumn-js/react";
-
-export default function PurchaseButton() {
- const { checkout } = useCustomer();
-
- return (
- {
- await checkout({
- productId: "${actualProductId}",
- dialog: CheckoutDialog,
- });
- }}
- >
- Upgrade to Pro
-
- );
-}`,
- nodejs: `import { Autumn } from 'autumn-js';
-
-const autumn = new Autumn({
- apiKey: process.env.AUTUMN_API_KEY
-});
-
-const session = await autumn.checkout({
- customerId: 'cust_123',
- productId: '${actualProductId}',
- successUrl: 'https://example.com/success',
- cancelUrl: 'https://example.com/cancel'
-});
-
-console.log(session.checkout_url);`,
- response: `{
- "url": "https://checkout.stripe.com/c/pay/cs_test_123",
- "customer_id": "cust_123",
- "product": {
- "id": "${actualProductId}",
- "name": "Your Plan",
- "items": [
- {
- "type": "price",
- "interval": "month",
- "price": 20
- }
- ]
- }
-}`,
- },
- track: {
- react: `import { useCustomer } from 'autumn-js/react';
-
-const { track } = useCustomer();
-
-const handleAction = async () => {
- await track({
- featureId: '${actualFeatureId}',
- value: 1,
- });
-}`,
- nodejs: `import { Autumn } from 'autumn-js';
-
-const autumn = new Autumn({
- apiKey: process.env.AUTUMN_API_KEY
-});
-
-// 1. Check if user has access first
-const checkResult = await autumn.check({
- customerId: 'user-123',
- featureId: '${actualFeatureId}',
- requiredQuantity: 1
-});
-
-if (!checkResult.data?.allowed) {
- throw new Error("User has reached their limit");
-}
-
-// 2. Track usage after successful check
-await autumn.track({
- customerId: 'user-123',
- featureId: '${actualFeatureId}',
- value: 1,
-});`,
- response: `{
- "id": "evt_2w5dzidzFD1cESxOGnn9frVuVcm",
- "code": "event_received",
- "customer_id": "user_123",
- "feature_id": "${actualFeatureId}"
-}`,
- },
- };
-};
-
-// Backward compatibility - use default snippets
-export const codeSnippets = getCodeSnippets();
-export const reactCode = codeSnippets.allowed.react;
-export const nodejsCode = codeSnippets.allowed.nodejs;
-export const responseCode = codeSnippets.allowed.response;
diff --git a/vite/src/views/onboarding4/TemplateDetailView.tsx b/vite/src/views/onboarding4/TemplateDetailView.tsx
deleted file mode 100644
index b75bc3019..000000000
--- a/vite/src/views/onboarding4/TemplateDetailView.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { ArrowLeft, Check, ExternalLink } from "lucide-react";
-import { Button } from "@/components/v2/buttons/Button";
-import { cn } from "@/lib/utils";
-import type { PricingTier, TemplateConfig } from "./templateConfigs";
-
-interface TemplateDetailViewProps {
- template: TemplateConfig;
- onBack: () => void;
- onCopyPlans: () => void;
-}
-
-function PricingTierCard({ tier }: { tier: PricingTier }) {
- return (
-
-
- {tier.name}
- {tier.description && (
- {tier.description}
- )}
-
-
-
-
- {tier.price}
-
- {tier.interval && (
- /{tier.interval}
- )}
-
-
-
- {tier.features.map((feature) => (
-
-
- {feature}
-
- ))}
-
-
- );
-}
-
-function Tag({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
- );
-}
-
-export function TemplateDetailView({
- template,
- onBack,
- onCopyPlans,
-}: TemplateDetailViewProps) {
- return (
-
- {/* Back button */}
-
-
- Back to templates
-
-
- {/* Header */}
-
-
-
- {template.name}
-
- by {template.company}
-
-
-
- {template.tags.map((tag) => (
- {tag}
- ))}
-
-
-
- {template.description}
-
-
-
- {/* Pricing tiers */}
-
-
Pricing Tiers
-
- {template.pricingTiers.map((tier) => (
-
- ))}
-
-
-
- {/* Actions */}
-
-
- Copy plans to Autumn
-
- window.open(template.websiteUrl, "_blank")}
- >
-
- View pricing page
-
-
-
- );
-}
diff --git a/vite/src/views/onboarding4/TemplatesDialog.tsx b/vite/src/views/onboarding4/TemplatesDialog.tsx
deleted file mode 100644
index 8e36599a2..000000000
--- a/vite/src/views/onboarding4/TemplatesDialog.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import { useState } from "react";
-import { useNavigate } from "react-router";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from "@/components/v2/dialogs/Dialog";
-import { cn } from "@/lib/utils";
-import { pushPage } from "@/utils/genUtils";
-import { TemplateDetailView } from "./TemplateDetailView";
-import { TEMPLATE_CONFIGS, type TemplateConfig } from "./templateConfigs";
-
-interface TemplateCardProps {
- template: TemplateConfig;
- onClick: () => void;
-}
-
-function TemplateCard({ template, onClick }: TemplateCardProps) {
- return (
-
-
- {template.name}
-
-
- {template.tags.slice(0, 2).map((tag) => (
-
- {tag}
-
- ))}
- {template.tags.length > 2 && (
-
- +{template.tags.length - 2}
-
- )}
-
-
- );
-}
-
-interface TemplatesDialogProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function TemplatesDialog({ open, onOpenChange }: TemplatesDialogProps) {
- const navigate = useNavigate();
- const [selectedTemplate, setSelectedTemplate] =
- useState(null);
-
- const handleClose = (isOpen: boolean) => {
- if (!isOpen) {
- setSelectedTemplate(null);
- }
- onOpenChange(isOpen);
- };
-
- const handleCopyPlans = () => {
- handleClose(false);
- pushPage({ path: "/products", navigate });
- };
-
- return (
-
-
- {selectedTemplate ? (
- setSelectedTemplate(null)}
- onCopyPlans={handleCopyPlans}
- />
- ) : (
- <>
-
- Choose a template
-
-
- {TEMPLATE_CONFIGS.map((template) => (
- setSelectedTemplate(template)}
- />
- ))}
-
- >
- )}
-
-
- );
-}
diff --git a/vite/src/views/onboarding4/components/ChatInputWithBeam.tsx b/vite/src/views/onboarding4/components/ChatInputWithBeam.tsx
deleted file mode 100644
index e2a79ce68..000000000
--- a/vite/src/views/onboarding4/components/ChatInputWithBeam.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { ReactNode } from "react";
-import { BorderBeam } from "@/components/ui/border-beam";
-
-interface ChatInputWithBeamProps {
- children: ReactNode;
- showBeam?: boolean;
-}
-
-export function ChatInputWithBeam({
- children,
- showBeam = true,
-}: ChatInputWithBeamProps) {
- return (
-
- {children}
- {showBeam && (
-
- )}
-
- );
-}
diff --git a/vite/src/views/onboarding4/preview/index.ts b/vite/src/views/onboarding4/preview/index.ts
deleted file mode 100644
index ac9fef011..000000000
--- a/vite/src/views/onboarding4/preview/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export { PreviewCreditSchemaCard } from "./PreviewCreditSchemaCard";
-export { PreviewFeatureIcon } from "./PreviewFeatureIcon";
-export { PreviewFeatureRow } from "./PreviewFeatureRow";
-export { PreviewPlanCard } from "./PreviewPlanCard";
-export { PreviewPlanHeader } from "./PreviewPlanHeader";
-export {
- type PreviewProduct,
- type PreviewProductItem,
- transformToPreviewProducts,
-} from "./previewTypes";
diff --git a/vite/src/views/products/ProductConfig.tsx b/vite/src/views/products/ProductConfig.tsx
deleted file mode 100644
index ba1eb23c1..000000000
--- a/vite/src/views/products/ProductConfig.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { useState } from "react";
-import { FormLabel as FieldLabel } from "@/components/v2/form/FormLabel";
-import { Input } from "@/components/v2/inputs/Input";
-import { useAutoSlug } from "@/hooks/common/useAutoSlug";
-
-export const ProductConfig = ({
- product,
- setProduct,
- isUpdate = false,
-}: {
- product: ProductV2;
- setProduct: (product: ProductV2) => void;
- isUpdate?: boolean;
-}) => {
- const [idEdit, setIdEdit] = useState(false);
-
- const { setSource, setTarget } = useAutoSlug({
- state: product,
- setState: setProduct,
- sourceKey: "name",
- targetKey: "id",
- disableAutoSlug: isUpdate,
- });
-
- return (
-
-
- Name
- {
- setSource(e.target.value);
- }}
- />
-
-
-
- );
-};
diff --git a/vite/src/views/products/components/PlanTypeBadge.tsx b/vite/src/views/products/components/PlanTypeBadge.tsx
deleted file mode 100644
index ff1c54a26..000000000
--- a/vite/src/views/products/components/PlanTypeBadge.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Badge } from "@/components/ui/badge";
-import { cn } from "@/lib/utils";
-
-export const PlanTypeBadge = ({ product }: { product: any }) => {
- const badgeType =
- product.is_default &&
- product.free_trial &&
- !product.free_trial.card_required
- ? "default trial"
- : product.is_default
- ? "default"
- : product.is_add_on
- ? "add-on"
- : null;
-
- if (!badgeType) return null;
-
- return (
-
- {badgeType}
-
- );
-};
diff --git a/vite/src/views/products/features/components/CreateFeature.tsx b/vite/src/views/products/features/components/CreateFeature.tsx
deleted file mode 100644
index 9f71f757c..000000000
--- a/vite/src/views/products/features/components/CreateFeature.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import {
- type CreateFeature as CreateFeatureType,
- FeatureType,
-} from "@autumn/shared";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import {
- CustomDialogBody,
- CustomDialogContent,
-} from "@/components/general/modal-components/DialogContentWrapper";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { FeatureService } from "@/services/FeatureService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { getDefaultFeature } from "../utils/defaultFeature";
-import { CreateFeatureFooter } from "./CreateFeatureFooter";
-import { FeatureConfig } from "./FeatureConfig";
-
-export const CreateFeature = ({
- onSuccess,
- setOpen,
- open,
- entityCreate,
- handleBack,
-}: {
- onSuccess?: (newFeature: CreateFeatureType) => Promise;
- setOpen: (open: boolean) => void;
- open: boolean;
- entityCreate?: boolean;
- handleBack?: () => void;
-}) => {
- const axiosInstance = useAxiosInstance();
- const { refetch } = useFeaturesQuery();
- const [feature, setFeature] = useState(getDefaultFeature(entityCreate));
- const [eventNameInput, setEventNameInput] = useState("");
- const [eventNameChanged, setEventNameChanged] = useState(true);
-
- useEffect(() => {
- if (open) {
- setFeature(getDefaultFeature(entityCreate));
- }
- }, [open, entityCreate]);
-
- const updateConfig = () => {
- const config: any = structuredClone(feature.config);
- return config;
- };
-
- const getEventNames = () => {
- const eventNames = feature.event_names || [];
- if (
- feature.type === FeatureType.Metered &&
- eventNameInput.trim() &&
- eventNames.length === 0
- ) {
- return [eventNameInput.trim()];
- }
- return eventNames;
- };
-
- const handleCreateFeature = async () => {
- if (!feature.name || !feature.id || !feature.type) {
- toast.error("Please fill out all fields");
- return;
- }
-
- try {
- const { data: createdFeature } = await FeatureService.createFeature(
- axiosInstance,
- {
- name: feature.name,
- id: feature.id,
- type: feature.type,
- config: updateConfig(),
- event_names: getEventNames(),
- },
- );
-
- await refetch();
- if (onSuccess) {
- await onSuccess(createdFeature);
- } else {
- setOpen(false);
- }
- } catch (error: unknown) {
- toast.error(
- getBackendErr(error as AxiosError, "Failed to create feature"),
- );
- }
- };
-
- return (
- <>
-
-
- Create Feature
-
-
-
-
-
-
- >
- );
-};
-
-export const CreateFeatureDialog = () => {
- const [open, setOpen] = useState(false);
-
- return (
-
-
-
- Feature
-
-
-
-
-
-
- );
-};
-
-//
-//
-// Create Feature
-//
-// {/* {}}
-// setSelectedFeature={() => {}}
-// setOpen={setOpen}
-// open={open}
-// /> */}
-//
-{
- /*
-
- Create Feature
-
- */
-}
diff --git a/vite/src/views/products/features/components/CreateFeatureFooter.tsx b/vite/src/views/products/features/components/CreateFeatureFooter.tsx
deleted file mode 100644
index 62aeeb407..000000000
--- a/vite/src/views/products/features/components/CreateFeatureFooter.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { ArrowLeft } from "lucide-react";
-import { useState } from "react";
-import { CustomDialogFooter } from "@/components/general/modal-components/DialogContentWrapper";
-import { Button } from "@/components/ui/button";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-
-export const CreateFeatureFooter = ({
- handleBack,
- handleCreate,
-}: {
- handleBack?: () => void;
- handleCreate: () => Promise;
-}) => {
- const [loading, setLoading] = useState(false);
- return (
-
- {handleBack ? (
- }
- >
- Back
-
- ) : (
-
- )}
- {
- setLoading(true);
- await handleCreate();
- setLoading(false);
- }}
- isLoading={loading}
- >
- Create
-
-
- );
-};
diff --git a/vite/src/views/products/features/components/FeatureConfig.tsx b/vite/src/views/products/features/components/FeatureConfig.tsx
deleted file mode 100644
index 6ee8314b1..000000000
--- a/vite/src/views/products/features/components/FeatureConfig.tsx
+++ /dev/null
@@ -1,368 +0,0 @@
-import {
- FeatureType,
- FeatureUsageType,
- type MeteredConfig,
-} from "@autumn/shared";
-import { PlusIcon, XIcon } from "lucide-react";
-import { useState } from "react";
-import { useHotkeys } from "react-hotkeys-hook";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { cn } from "@/lib/utils";
-import { slugify } from "@/utils/formatUtils/formatTextUtils";
-import { nullish } from "@/utils/genUtils";
-import { SelectFeatureType } from "./SelectFeatureType";
-import { SelectFeatureUsageType } from "./SelectFeatureUsageType";
-
-export function FeatureConfig({
- feature,
- setFeature,
- eventNameInput,
- setEventNameInput,
- isUpdate = false,
- eventNameChanged,
- setEventNameChanged,
- open,
-}: {
- feature: any;
- setFeature: any;
- eventNameInput: string;
- setEventNameInput: any;
- isUpdate?: boolean;
- eventNameChanged: boolean;
- setEventNameChanged: any;
- open: boolean;
-}) {
- const [fields, setFields] = useState(
- feature.name
- ? {
- name: feature.name,
- id: feature.id,
- }
- : {
- name: "",
- id: "",
- },
- );
-
- const [meteredConfig, setMeteredConfig] = useState(
- feature.type === FeatureType.Metered
- ? feature.config
- : {
- filters: [
- {
- property: "",
- operator: "",
- value: [],
- },
- ],
- usage_type: FeatureUsageType.Single,
- },
- );
-
- const [eventNames, setEventNames] = useState(
- feature.event_names || [],
- );
-
- const [showEventName, setShowEventName] = useState(
- feature.event_names && feature.event_names.length > 0,
- );
- const [idChanged, setIdChanged] = useState(!!feature.id);
-
- // Helper function to update meteredConfig and sync to parent
- const updateMeteredConfig = (newConfig: MeteredConfig) => {
- setMeteredConfig(newConfig);
- if (feature.type === FeatureType.Metered) {
- setFeature({ ...feature, config: newConfig });
- }
- };
-
- // Helper function to update event names and sync to parent
- const updateEventNames = (newEventNames: string[]) => {
- setEventNames(newEventNames);
- setFeature({ ...feature, event_names: newEventNames });
- };
-
- const showNameAndId = () => {
- if (nullish(feature.type)) {
- return false;
- }
-
- if (
- feature.type === FeatureType.Metered &&
- nullish(feature.config?.usage_type)
- ) {
- return false;
- }
-
- return true;
- };
-
- return (
-
-
- Features are the parts of your application that customers get access to
- when purchasing a product
-
-
- {feature.type === FeatureType.Metered && (
-
- )}
-
- {showNameAndId() && (
- <>
-
-
- {/* Filter */}
- {feature.type === FeatureType.Metered && (
- <>
-
-
Event Name
-
-
-
- Event names are only required if you want to link one event
- from your application to multiple feature balances. Read more{" "}
-
- here.
-
-
-
-
-
-
-
- ) : (
-
- )
- }
- onClick={() => {
- setShowEventName(!showEventName);
- }}
- >
- event_name
-
-
-
- Link feature to multiple separate events
-
-
-
- >
- )}
- >
- )}
-
- );
-}
-
-export const FilterInput = ({
- eventNames,
- setEventNames,
- eventNameInput,
- setEventNameInput,
- setEventNameChanged,
-}: {
- eventNames: string[];
- setEventNames: (eventNames: string[]) => void;
- eventNameInput: string;
- setEventNameInput: any;
- setEventNameChanged: any;
-}) => {
- const [inputFocused, setInputFocused] = useState(false);
-
- const enterClicked = () => {
- if (eventNameInput.trim()) {
- setEventNames([...eventNames, eventNameInput.trim()]);
- setEventNameInput("");
- setEventNameChanged(true);
- }
- };
-
- const onRemoveClicked = (index: number) => {
- const newEventNames = [...eventNames];
- newEventNames.splice(index, 1);
- setEventNames(newEventNames);
- };
-
- useHotkeys("enter", enterClicked, {
- enableOnFormTags: ["input"],
- enabled: inputFocused,
- });
- return (
-
- {eventNames.map((value: string, index: number) => (
-
- {value}
- onRemoveClicked(index)}
- >
-
-
-
- ))}
-
setInputFocused(true)}
- onBlur={() => setInputFocused(false)}
- value={eventNameInput}
- onChange={(e) => {
- setEventNameInput(e.target.value);
- setEventNameChanged(true);
- }}
- >
-
- );
-};
-
-{
- /* {featureType === FeatureType.Metered && (
-
-
-
{
- setMeteredConfig({
- ...meteredConfig,
- usage_type: value as FeatureUsageType,
- });
- }}
- >
-
-
-
- Single Use
-
-
-
- Continuous Use
-
-
-
- {meteredConfig.usage_type === FeatureUsageType.Continuous
- ? "For features used on an ongoing basis, like 'seats' or 'storage'"
- : "For features that are consumed and refilled like 'credits' or 'API calls'"}
-
-
-
-
-
-
- Single use features can have a reset period, to refill a
- balance every month, day etc. Existing usage is also
- typically reset on upgrades.
-
-
- Continuous use features don't have a reset period. They
- can be prorated (eg, if a seat is purchased halfway during
- the month, it'll cost half the price).
-
-
-
-
-
-
-
- )} */
-}
-
-{
- /*
-
- Metered
- Boolean
-
-
- {featureType == FeatureType.Metered &&
- "A usage-based feature that you want to track"}
- {featureType == FeatureType.Boolean &&
- "A feature flag that can be either enabled or disabled"}
-
- */
-}
diff --git a/vite/src/views/products/features/components/FeatureTypeBadge.tsx b/vite/src/views/products/features/components/FeatureTypeBadge.tsx
deleted file mode 100644
index b2896e7ae..000000000
--- a/vite/src/views/products/features/components/FeatureTypeBadge.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { type Feature, FeatureType, FeatureUsageType } from "@autumn/shared";
-import { Badge } from "@/components/ui/badge";
-import { cn } from "@/lib/utils";
-
-export function FeatureTypeBadge(feature: Feature) {
- const badgeType =
- feature.type === FeatureType.Boolean
- ? "boolean"
- : feature.config?.usage_type === FeatureUsageType.Continuous
- ? "allocated"
- : "consumable";
-
- return (
-
- {badgeType}
-
- );
-}
diff --git a/vite/src/views/products/features/components/FeaturesTable.tsx b/vite/src/views/products/features/components/FeaturesTable.tsx
deleted file mode 100644
index 6a7ce7693..000000000
--- a/vite/src/views/products/features/components/FeaturesTable.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import { type Feature, FeatureType } from "@autumn/shared";
-import { useState } from "react";
-import { AdminHover } from "@/components/general/AdminHover";
-import CopyButton from "@/components/general/CopyButton";
-import { Item, Row } from "@/components/general/TableGrid";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
-import { useProductsQueryState } from "../../hooks/useProductsQueryState";
-import { FeatureRowToolbar } from "../feature-row-toolbar/FeatureRowToolbar";
-import { FeatureTypeBadge } from "./FeatureTypeBadge";
-import UpdateFeatureSheet from "./UpdateFeatureSheet";
-
-export const FeaturesTable = () => {
- const { features } = useFeaturesQuery();
- const { queryStates } = useProductsQueryState();
-
- const [open, setOpen] = useState(false);
- const [selectedFeature, setSelectedFeature] = useState(null);
-
- const getMeteredEventNames = (feature: Feature) => {
- if (feature.type !== FeatureType.Metered) return "";
-
- if (!feature.event_names || feature.event_names.length === 0) return "";
-
- return feature.event_names.join(", ");
- };
-
- const handleRowClick = (id: string) => {
- const feature = features.find((feature: Feature) => feature.id === id);
- if (!feature) return;
- setSelectedFeature(feature);
- setOpen(true);
- };
-
- const filteredFeatures = features.filter((feature) => {
- if (feature.type === FeatureType.CreditSystem) return false;
- return queryStates.showArchivedFeatures
- ? feature.archived
- : !feature.archived;
- });
-
- return (
-
-
- {features && features.length > 0 ? (
-
- - Name
- - ID
- - Type
- - Event Names
- - Created At
-
-
- ) : (
-
- {queryStates.showArchivedFeatures
- ? "You haven't archived any features yet."
- : "Define the features of your application you want to charge for."}
-
- )}
-
- {filteredFeatures.map((feature: Feature) => (
-
handleRowClick(feature.id)}
- >
- -
-
-
- {feature.name}
-
-
-
- -
-
-
- {feature.id}
-
-
-
- -
-
-
- -
-
{getMeteredEventNames(feature)}
-
- -
- {formatUnixToDateTime(feature.created_at).date}
-
- -
-
-
-
- ))}
-
- );
-};
diff --git a/vite/src/views/products/features/components/SelectFeatureType.tsx b/vite/src/views/products/features/components/SelectFeatureType.tsx
deleted file mode 100644
index 6dadd048a..000000000
--- a/vite/src/views/products/features/components/SelectFeatureType.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import {
- ApiFeatureType,
- type CreateFeature,
- FeatureType,
-} from "@autumn/shared";
-import { ArrowUp01, Flag } from "lucide-react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { SelectType } from "@/components/general/SelectType";
-import { defaultMeteredConfig } from "../utils/defaultFeatureConfig";
-
-export const SelectFeatureType = ({
- feature,
- setFeature,
-}: {
- feature: CreateFeature;
- setFeature: any;
-}) => {
- const featureType = feature.type;
- const usageType = feature.config?.usage_type;
-
- const setFeatureType = (type: ApiFeatureType) => {
- // 1. If type is boolean
- if (type === ApiFeatureType.Boolean) {
- setFeature({
- ...feature,
- type: ApiFeatureType.Boolean,
- config: undefined,
- });
- } else {
- setFeature({
- ...feature,
- type: FeatureType.Metered,
- config: { ...defaultMeteredConfig, usage_type: type },
- });
- }
- };
- return (
-
-
Feature Type
-
- }
- isSelected={featureType === FeatureType.Metered}
- onClick={() => {
- const curConfig = feature.config;
- setFeature({
- ...feature,
- type: FeatureType.Metered,
- config: {
- ...defaultMeteredConfig,
- usage_type: curConfig?.usage_type ?? null,
- },
- });
- }}
- />
- }
- isSelected={featureType === FeatureType.Boolean}
- onClick={() =>
- setFeature({
- ...feature,
- type: FeatureType.Boolean,
- config: undefined,
- })
- }
- />
- {/* }
- isSelected={featureType === FeatureType.Boolean}
- onClick={() => setFeatureType(ApiFeatureType.Boolean)}
- /> */}
-
-
- );
-};
diff --git a/vite/src/views/products/features/components/SelectFeatureUsageType.tsx b/vite/src/views/products/features/components/SelectFeatureUsageType.tsx
deleted file mode 100644
index 6bd4c6c85..000000000
--- a/vite/src/views/products/features/components/SelectFeatureUsageType.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import {
- ApiFeatureType,
- type CreateFeature,
- FeatureType,
- FeatureUsageType,
-} from "@autumn/shared";
-import { Clock, Zap } from "lucide-react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { SelectType } from "@/components/general/SelectType";
-import { defaultMeteredConfig } from "../utils/defaultFeatureConfig";
-
-export const SelectFeatureUsageType = ({
- feature,
- setFeature,
-}: {
- feature: CreateFeature;
- setFeature: any;
-}) => {
- const featureType = feature.type;
- const usageType = feature.config?.usage_type;
-
- const setFeatureType = (type: ApiFeatureType) => {
- // 1. If type is boolean
- if (type === ApiFeatureType.Boolean) {
- setFeature({
- ...feature,
- type: ApiFeatureType.Boolean,
- config: undefined,
- });
- } else {
- setFeature({
- ...feature,
- type: FeatureType.Metered,
- config: { ...defaultMeteredConfig, usage_type: type },
- });
- }
- };
- return (
-
-
Usage Type
-
- }
- isSelected={
- featureType === FeatureType.Metered &&
- usageType === FeatureUsageType.Single
- }
- onClick={() => setFeatureType(ApiFeatureType.SingleUsage)}
- />
- }
- isSelected={
- featureType === FeatureType.Metered &&
- usageType === FeatureUsageType.Continuous
- }
- onClick={() => setFeatureType(ApiFeatureType.ContinuousUse)}
- />
- {/* }
- isSelected={featureType === FeatureType.Boolean}
- onClick={() => setFeatureType(ApiFeatureType.Boolean)}
- /> */}
-
-
- );
-};
diff --git a/vite/src/views/products/features/components/UpdateFeature.tsx b/vite/src/views/products/features/components/UpdateFeature.tsx
deleted file mode 100644
index 42c88a8ae..000000000
--- a/vite/src/views/products/features/components/UpdateFeature.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { FeatureType } from "@autumn/shared";
-import { CircleArrowUp } from "lucide-react";
-import { useEffect, useRef, useState } from "react";
-import { toast } from "sonner";
-import {
- CustomDialogBody,
- CustomDialogContent,
- CustomDialogFooter,
-} from "@/components/general/modal-components/DialogContentWrapper";
-import { Button } from "@/components/ui/button";
-import { Dialog, DialogTitle } from "@/components/ui/dialog";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { FeatureService } from "@/services/FeatureService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { FeatureConfig } from "@/views/products/features/components/FeatureConfig";
-
-export default function UpdateFeature({
- open,
- setOpen,
- selectedFeature,
- setSelectedFeature,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
- selectedFeature: any;
- setSelectedFeature: (feature: any) => void;
-}) {
- const { refetch } = useFeaturesQuery();
- const axiosInstance = useAxiosInstance();
- const [updateLoading, setUpdateLoading] = useState(false);
- const [eventNameInput, setEventNameInput] = useState("");
- const [eventNameChanged, setEventNameChanged] = useState(true);
-
- const originalFeature = useRef(selectedFeature);
-
- useEffect(() => {
- if (open) {
- originalFeature.current = selectedFeature;
- }
- }, [open, selectedFeature]);
-
- useEffect(() => {
- if (open) {
- setEventNameInput("");
- setEventNameChanged(true);
- }
- }, [open]);
-
- const updateConfig = () => {
- const config: any = structuredClone(selectedFeature.config);
- return config;
- };
-
- const getEventNames = () => {
- const eventNames = selectedFeature.event_names || [];
- if (
- selectedFeature.type === FeatureType.Metered &&
- eventNameInput.trim() &&
- eventNames.length === 0
- ) {
- return [eventNameInput.trim()];
- }
- return eventNames;
- };
-
- const handleUpdateFeature = async () => {
- setUpdateLoading(true);
- const originalId = originalFeature.current.id;
-
- try {
- await FeatureService.updateFeature(axiosInstance, originalId, {
- ...selectedFeature,
- id: selectedFeature.id,
- type: selectedFeature.type,
- name: selectedFeature.name,
- config: updateConfig(),
- event_names: getEventNames(),
- });
-
- await refetch();
- setOpen(false);
- } catch (error) {
- console.log(error);
- toast.error(getBackendErr(error, "Failed to update feature"));
- }
- setUpdateLoading(false);
- };
-
- return (
-
-
-
- Update Feature
-
-
-
-
- handleUpdateFeature()}
- variant="add"
- startIcon={ }
- >
- Update Feature
-
-
-
-
- );
-}
diff --git a/vite/src/views/products/features/credit-systems/CreditSystemsTable.tsx b/vite/src/views/products/features/credit-systems/CreditSystemsTable.tsx
deleted file mode 100644
index e9b5f74f9..000000000
--- a/vite/src/views/products/features/credit-systems/CreditSystemsTable.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { type Feature, FeatureType } from "@autumn/shared";
-import { useState } from "react";
-import { AdminHover } from "@/components/general/AdminHover";
-import { Item, Row } from "@/components/general/TableGrid";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
-import { useProductsQueryState } from "../../hooks/useProductsQueryState";
-import { FeatureRowToolbar } from "../feature-row-toolbar/FeatureRowToolbar";
-import UpdateCreditSystemSheet from "./components/UpdateCreditSystemSheet";
-
-export const CreditSystemsTable = () => {
- const { features } = useFeaturesQuery();
- const [selectedCreditSystem, setSelectedCreditSystem] =
- useState(null);
- const [open, setOpen] = useState(false);
-
- const { queryStates } = useProductsQueryState();
-
- const creditSystems = features.filter((feature) => {
- if (queryStates.showArchivedFeatures)
- return feature.type === FeatureType.CreditSystem && feature.archived;
-
- return feature.type === FeatureType.CreditSystem && !feature.archived;
- });
-
- const handleRowClick = (id: string) => {
- const creditSystem = creditSystems.find(
- (creditSystem: Feature) => creditSystem.id === id,
- );
- if (!creditSystem) return;
- setSelectedCreditSystem(creditSystem);
- setOpen(true);
- };
-
- return (
- <>
-
-
- {creditSystems && creditSystems.length > 0 ? (
-
- - Credits Name
- - Credits ID
- - Features
- - Created At
-
-
- ) : (
-
- Create a credit system to manage usage across multiple features.
-
- )}
-
- {creditSystems.map((creditSystem: Feature) => (
- handleRowClick(creditSystem.id)}
- >
- -
-
-
- {creditSystem.name}
-
-
-
- -
-
{creditSystem.id}
-
- -
-
- {creditSystem.config.schema
- .map((schema: any) => schema.metered_feature_id)
- .join(", ")}
-
-
- -
- {formatUnixToDateTime(creditSystem.created_at).date}
-
- -
- {/*
*/}
-
-
-
- ))}
- >
- );
-};
diff --git a/vite/src/views/products/features/feature-row-toolbar/FeatureRowToolbar.tsx b/vite/src/views/products/features/feature-row-toolbar/FeatureRowToolbar.tsx
deleted file mode 100644
index 051ef0eef..000000000
--- a/vite/src/views/products/features/feature-row-toolbar/FeatureRowToolbar.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Feature } from "@autumn/shared";
-import { ArchiveRestore, Delete } from "lucide-react";
-import { useState } from "react";
-import { ToolbarButton } from "@/components/general/table-components/ToolbarButton";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { DeleteFeatureDialog } from "./DeleteFeatureDialog";
-
-export const FeatureRowToolbar = ({ feature }: { feature: Feature }) => {
- const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
- const [dropdownOpen, setDropdownOpen] = useState(false);
-
- return (
- <>
-
-
-
-
-
-
- {
- e.stopPropagation();
- e.preventDefault();
- setDeleteDialogOpen(true);
- setDropdownOpen(false);
- }}
- >
-
- {feature.archived ? "Unarchive" : "Delete"}
- {feature.archived ? (
-
- ) : (
-
- )}
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/products/plan/components/ManagePlan.tsx b/vite/src/views/products/plan/components/ManagePlan.tsx
deleted file mode 100644
index b3a66c643..000000000
--- a/vite/src/views/products/plan/components/ManagePlan.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import PlanCard from "./plan-card/PlanCard";
-
-export const ManagePlan = () => {
- return (
-
- );
-};
diff --git a/vite/src/views/products/plan/components/plan-card/DeleteFeatureRowDialog.tsx b/vite/src/views/products/plan/components/plan-card/DeleteFeatureRowDialog.tsx
deleted file mode 100644
index 1c4ef173f..000000000
--- a/vite/src/views/products/plan/components/plan-card/DeleteFeatureRowDialog.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import type { ProductItem } from "@autumn/shared";
-import { Button } from "@/components/v2/buttons/Button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/v2/dialogs/Dialog";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-
-export const DeleteFeatureRowDialog = ({
- open,
- setOpen,
- item,
- onDelete,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
- item: ProductItem;
- onDelete: (item: ProductItem) => void;
-}) => {
- const { features } = useFeaturesQuery();
- const featureName = features.find((f) => f.id === item.feature_id)?.name;
-
- return (
-
-
-
- Delete "{featureName}"
-
- Please confirm that you want to delete this feature.
-
-
-
- setOpen(false)}>
- Cancel
-
- setOpen(false)}>
- Delete
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/ManageProduct.tsx b/vite/src/views/products/product/ManageProduct.tsx
deleted file mode 100644
index 135056a8f..000000000
--- a/vite/src/views/products/product/ManageProduct.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { AdminHover } from "@/components/general/AdminHover";
-import { SelectEntity } from "@/views/customers/customer/components/customer-header/SelectEntity";
-import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
-import { useProductContext } from "./ProductContext";
-import { ProductItemTable } from "./product-item/ProductItemTable";
-
-export const ManageProduct = ({
- hideAdminHover = false,
-}: {
- hideAdminHover?: boolean;
-}) => {
- const { product, entityId, isCusProductView } = useProductContext();
- const { customer } = useCusQuery({ enabled: isCusProductView });
-
- return (
-
-
-
-
-
-
- {product.name}
-
-
-
-
- {/*
*/}
- {customer && (
-
- )}
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/ProductProps.tsx b/vite/src/views/products/product/ProductProps.tsx
deleted file mode 100644
index c32456237..000000000
--- a/vite/src/views/products/product/ProductProps.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import React from "react";
-import CopyButton from "@/components/general/CopyButton";
-import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useProductCountsQuery } from "./hooks/queries/useProductCountsQuery";
-import { useProductQuery } from "./hooks/useProductQuery";
-import { useProductContext } from "./ProductContext";
-import { ToggleDefaultProduct } from "./product-sidebar/ToggleDefaultProduct";
-
-export const ProductProps = () => {
- const axiosInstance = useAxiosInstance();
- const { product, setProduct } = useProductContext();
- const { refetch } = useProductQuery();
- const { counts } = useProductCountsQuery();
-
- const [groupModalOpen, setGroupModalOpen] = React.useState(false);
- const [tempGroup, setTempGroup] = React.useState(product.group || "");
-
- return (
-
-
-
-
Product ID
-
- {product.id}
-
-
-
-
Customers
-
-
-
- {counts?.active ?? 0} active
-
-
-
-
- Canceled: {counts?.canceled}
-
- {counts?.trialing > 0 && (
-
- Trialing: {counts?.trialing}
-
- )}
- {counts?.custom > 0 && (
-
- Custom: {counts?.custom}
-
- )}
-
-
-
-
-
-
Default
-
-
- This product will be enabled by default for all new users,
- typically used for your free product
-
-
-
-
-
-
-
-
Add On
-
-
- This product is an add-on that can be bought together with your
- base products (eg, for top ups)
-
-
-
-
-
-
-
-
Group
-
{
- setTempGroup(product.group || "");
- setGroupModalOpen(true);
- }}
- >
- {product.group || No group }
-
-
-
-
-
-
- Edit Product Group
-
-
- Assign this product to a group. Customers will be able to have
- active subscriptions from different product groups at the same
- time. This can alter your existing upgrade and downgrade logic, so
- read the docs{" "}
-
- here
- {" "}
- to understand how this works.
-
-
-
setTempGroup(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === "Enter") {
- setProduct({
- ...product,
- group: tempGroup,
- });
- setGroupModalOpen(false);
- }
- }}
- />
-
- {
- setProduct({
- ...product,
- group: tempGroup,
- });
- setGroupModalOpen(false);
- }}
- >
- Save
-
-
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/ProductSidebar.tsx b/vite/src/views/products/product/ProductSidebar.tsx
deleted file mode 100644
index d8aa840ac..000000000
--- a/vite/src/views/products/product/ProductSidebar.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { Minus, Plus } from "lucide-react";
-import { useState } from "react";
-import { SideAccordion } from "@/components/general/SideAccordion";
-import { Accordion } from "@/components/ui/accordion";
-import { isOneOffProduct } from "@/utils/product/priceUtils";
-import { AttachButton } from "@/views/customers/customer/product/components/AttachButton";
-import { CustomerProductBadge } from "@/views/customers/customer/product/components/CustomerProductBadge";
-import { UpdateProductButton } from "./components/UpdateProductButton";
-import { CreateFreeTrial } from "./free-trial/CreateFreeTrial";
-import { FreeTrialView } from "./free-trial/FreeTrialView";
-import { useProductContext } from "./ProductContext";
-import { ProductProps } from "./ProductProps";
-import { ProductVersions } from "./ProductVersions";
-import { EntitiesSidebar } from "./product-item/EntitiesSidebar";
-
-export default function ProductSidebar() {
- const { product, setProduct, isCusProductView } = useProductContext();
- const [freeTrialModalOpen, setFreeTrialModalOpen] = useState(false);
- const [entitiesOpen, setEntitiesOpen] = useState(false);
- const [accordionValues, setAccordionValues] = useState([
- "properties",
- "versions",
- "free-trial",
- "entities",
- ]);
-
- const handleFreeTrialModalOpen = () => {
- setFreeTrialModalOpen(!freeTrialModalOpen);
- };
- const handleDeleteFreeTrial = async () => {
- setProduct({ ...product, free_trial: null });
- };
-
- const handleAccordionToggle = (value: string) => {
- setAccordionValues((prev) =>
- prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value],
- );
- };
-
- return (
-
-
- {isCusProductView && }
-
-
-
-
-
-
-
setEntitiesOpen(!entitiesOpen)}
- buttonIcon={ }
- >
-
-
-
-
-
-
-
-
:
- }
- disabledReason={
- isOneOffProduct(product.items, product.is_add_on)
- ? "Can't add a free trial to an a one time plan"
- : undefined
- }
- >
-
- {product.free_trial ? (
-
- ) : (
- Add a free trial to this plan.
- )}
-
-
-
-
- {isCusProductView ?
:
}
-
-
-
- );
-}
diff --git a/vite/src/views/products/product/ProductVersions.tsx b/vite/src/views/products/product/ProductVersions.tsx
deleted file mode 100644
index 8be4ee77c..000000000
--- a/vite/src/views/products/product/ProductVersions.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { useNavigate } from "react-router";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useEnv } from "@/utils/envUtils";
-import { formatUnixToDate } from "@/utils/formatUtils/formatDateUtils";
-import { getRedirectUrl } from "@/utils/genUtils";
-import { useProductQuery, useProductQueryState } from "./hooks/useProductQuery";
-import { useProductContext } from "./ProductContext";
-import { CountAndMigrate } from "./versioning/CountAndMigrate";
-
-export const ProductVersions = () => {
- const { setQueryStates } = useProductQueryState();
- const { product, isCusProductView } = useProductContext();
- const { numVersions } = useProductQuery();
-
- return (
-
-
-
-
Latest Version
-
v{numVersions}
-
-
-
Created At
-
- {formatUnixToDate(product.created_at, true)}
-
-
-
-
Version History
- {numVersions > 1 ? (
-
{
- setQueryStates({
- version: parseInt(value),
- });
- // Set query states...
- // navigate(
- // getRedirectUrl(
- // `${customer ? `/customers/${customer.id}` : "/products"}/${
- // product.id
- // }?version=${value}`,
- // env
- // )
- // );
- }}
- >
-
-
-
-
- {Array.from({ length: numVersions }, (_, i) => i + 1)
- .reverse()
- .map((version) => (
- {
- setQueryStates({
- version: version,
- });
- }}
- >
- v{version}
-
- ))}
-
-
- ) : (
-
None
- )}
-
- {!isCusProductView &&
}
-
-
- );
-};
diff --git a/vite/src/views/products/product/ProductView.tsx b/vite/src/views/products/product/ProductView.tsx
deleted file mode 100644
index 373eb7978..000000000
--- a/vite/src/views/products/product/ProductView.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import { useParams } from "react-router";
-import ErrorScreen from "@/views/general/ErrorScreen";
-import LoadingScreen from "@/views/general/LoadingScreen";
-import ProductViewBreadcrumbs from "./components/ProductViewBreadcrumbs";
-import { UpdateProductButton } from "./components/UpdateProductButton";
-import { useProductChangedAlert } from "./hooks/useProductChangedAlert";
-import { useProductData } from "./hooks/useProductData";
-import { useProductQuery } from "./hooks/useProductQuery";
-import { ManageProduct } from "./ManageProduct";
-import { ProductContext } from "./ProductContext";
-import ProductSidebar from "./ProductSidebar";
-import ConfirmNewVersionDialog from "./versioning/ConfirmNewVersionDialog";
-
-function ProductView() {
- const { product_id } = useParams();
-
- const { product: originalProduct, isLoading, error } = useProductQuery();
-
- const {
- product,
- setProduct,
- hasChanges,
- entityFeatureIds,
- setEntityFeatureIds,
- actionState,
- } = useProductData({ originalProduct });
-
- const { modal } = useProductChangedAlert({ hasChanges });
- const [showNewVersionDialog, setShowNewVersionDialog] = useState(false);
-
- if (isLoading) return ;
- if (error) {
- return (
-
- {error ? error.message : `Plan ${product_id} not found`}
-
- );
- }
-
- if (!product) return;
-
- return (
-
-
-
- {modal}
-
- );
-}
-
-export default ProductView;
diff --git a/vite/src/views/products/product/components/ProductViewBreadcrumbs.tsx b/vite/src/views/products/product/components/ProductViewBreadcrumbs.tsx
deleted file mode 100644
index 15b48b85d..000000000
--- a/vite/src/views/products/product/components/ProductViewBreadcrumbs.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useNavigate } from "react-router";
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbList,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-import { useEnv } from "@/utils/envUtils";
-import { navigateTo } from "@/utils/genUtils";
-import { useProductContext } from "../ProductContext";
-
-export default function ProductViewBreadcrumbs() {
- const { product } = useProductContext();
- const env = useEnv();
- const navigate = useNavigate();
-
- return (
-
-
- navigateTo("/products", navigate, env)}
- className="cursor-pointer"
- >
- Plans
-
-
-
- {product.name ? product.name : product.id}
-
-
-
- );
-}
diff --git a/vite/src/views/products/product/components/UpdateProductButton.tsx b/vite/src/views/products/product/components/UpdateProductButton.tsx
deleted file mode 100644
index 6ef758fc3..000000000
--- a/vite/src/views/products/product/components/UpdateProductButton.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { Upload } from "lucide-react";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useProductContext } from "@/views/products/product/ProductContext";
-import { useProductCountsQuery } from "../hooks/queries/useProductCountsQuery";
-import { useProductQuery } from "../hooks/useProductQuery";
-import { updateProduct } from "../utils/updateProduct";
-
-export const UpdateProductButton = () => {
- const [buttonLoading, setButtonLoading] = useState(false);
- const axiosInstance = useAxiosInstance();
-
- const { actionState, product, setShowNewVersionDialog } = useProductContext();
- const { counts, isLoading } = useProductCountsQuery();
-
- const { refetch } = useProductQuery();
-
- const handleUpdateClicked = async () => {
- if (isLoading) toast.error("Plan counts are loading");
-
- if (counts?.all > 0) {
- setShowNewVersionDialog(true);
- return;
- }
-
- setButtonLoading(true);
- const success = await updateProduct({
- axiosInstance,
- product,
- onSuccess: async () => {
- await refetch();
- },
- });
-
- setButtonLoading(false);
- };
-
- return (
- }
- >
- {actionState.buttonText}
-
- );
-};
diff --git a/vite/src/views/products/product/free-trial/CreateFreeTrial.tsx b/vite/src/views/products/product/free-trial/CreateFreeTrial.tsx
deleted file mode 100644
index 519566f1e..000000000
--- a/vite/src/views/products/product/free-trial/CreateFreeTrial.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { FreeTrialDuration } from "@autumn/shared";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { handleAutoSave } from "@/views/onboarding2/model-pricing/model-pricing-utils/modelPricingUtils";
-import { useProductContext } from "../ProductContext";
-// import { FrontendFreeTrial } from "@autumn/shared";
-import { FreeTrialConfig } from "./FreeTrialConfig";
-
-export const CreateFreeTrial = ({
- open,
- setOpen,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
-}) => {
- const [loading] = useState(false);
- const { product, setProduct, autoSave, refetch } = useProductContext();
-
- const axiosInstance = useAxiosInstance();
-
- const [freeTrial, setFreeTrial] = useState({
- length: 7,
- unique_fingerprint: false,
- duration: FreeTrialDuration.Day,
- card_required: true,
- });
-
- const handleCreateFreeTrial = async () => {
- const lengthInt = parseInt(freeTrial.length as any);
- if (isNaN(lengthInt)) {
- toast.error("Invalid length");
- return;
- }
-
- setProduct({
- ...product,
- free_trial: {
- length: lengthInt,
- unique_fingerprint: freeTrial.unique_fingerprint,
- duration: freeTrial.duration,
- card_required: freeTrial.card_required,
- },
- });
- if (autoSave) {
- handleAutoSave({
- axiosInstance,
- productId: product.id,
- product: {
- ...product,
- free_trial: {
- length: lengthInt,
- unique_fingerprint: freeTrial.unique_fingerprint,
- duration: freeTrial.duration,
- card_required: freeTrial.card_required,
- },
- },
- refetch,
- });
- }
- setOpen(false);
- };
-
- return (
-
-
- Create Free Trial
-
-
-
-
-
- Create Free Trial
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/free-trial/FreeTrialConfig.tsx b/vite/src/views/products/product/free-trial/FreeTrialConfig.tsx
deleted file mode 100644
index e61eb230e..000000000
--- a/vite/src/views/products/product/free-trial/FreeTrialConfig.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { type CreateFreeTrial, FreeTrialDuration } from "@autumn/shared";
-import { useEffect, useState } from "react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Checkbox } from "@/components/ui/checkbox";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-
-export const FreeTrialConfig = ({
- freeTrial,
- setFreeTrial,
-}: {
- freeTrial: CreateFreeTrial;
- setFreeTrial: (freeTrial: CreateFreeTrial) => void;
-}) => {
- const [fields, setFields] = useState({
- length: freeTrial?.length || 7,
- unique_fingerprint: freeTrial?.unique_fingerprint || false,
- duration: freeTrial?.duration || FreeTrialDuration.Day,
- card_required: freeTrial?.card_required ?? true,
- });
-
- useEffect(() => {
- setFreeTrial(fields);
- }, [fields, setFreeTrial]);
-
- if (!freeTrial) return;
-
- return (
-
-
- Length
-
- setFields({
- ...fields,
- length: e.target.value as unknown as number,
- })
- }
- type="number"
- endContent={
-
- setFields({
- ...fields,
- duration: value as FreeTrialDuration,
- })
- }
- >
-
-
- {freeTrial.duration}
-
-
-
- {Object.values(FreeTrialDuration).map((duration) => (
-
- {duration}
-
- ))}
-
-
- }
- />
-
-
-
- setFields({ ...fields, unique_fingerprint: checked as boolean })
- }
- />
-
- Only allow one free trial per customer{" "}
- fingerprint
-
-
-
-
-
- setFields({ ...fields, card_required: checked as boolean })
- }
- />
- Card required
-
-
- );
-};
diff --git a/vite/src/views/products/product/free-trial/FreeTrialView.tsx b/vite/src/views/products/product/free-trial/FreeTrialView.tsx
deleted file mode 100644
index 00aa1ef7d..000000000
--- a/vite/src/views/products/product/free-trial/FreeTrialView.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-export const FreeTrialView = ({ product }: { product: any }) => {
- return (
- <>
- {product.free_trial && (
- <>
-
-
-
-
- Length{" "}
-
-
- {product.free_trial.length} {product.free_trial.duration}
- {product.free_trial.length > 1 ? "s" : ""}
-
-
-
-
- Limit by Fingerprint
-
-
- {product.free_trial.unique_fingerprint ? (
- True
- ) : (
- "False"
- )}
-
-
-
-
-
- Card Required
-
-
- {product.free_trial.card_required ? (
- True
- ) : (
- "False"
- )}
-
-
-
-
- >
- )}
- {/* {!product.free_trial && } */}
- >
- );
-};
diff --git a/vite/src/views/products/product/hooks/NavigationBlockerModal.tsx b/vite/src/views/products/product/hooks/NavigationBlockerModal.tsx
deleted file mode 100644
index a365829b6..000000000
--- a/vite/src/views/products/product/hooks/NavigationBlockerModal.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import type React from "react";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-
-interface NavigationBlockerModalProps {
- isOpen: boolean;
- onConfirm: () => void;
- onCancel: () => void;
-}
-
-export const NavigationBlockerModal: React.FC = ({
- isOpen,
- onConfirm,
- onCancel,
-}) => {
- return (
- !open && onCancel()}>
-
-
- Unsaved Changes
-
- Are you sure you want to leave without updating the plan? Your
- changes will be lost.
-
-
-
-
- Cancel
-
-
- Leave Page
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/hooks/useProductChangedAlert.tsx b/vite/src/views/products/product/hooks/useProductChangedAlert.tsx
deleted file mode 100644
index a3c2ff82e..000000000
--- a/vite/src/views/products/product/hooks/useProductChangedAlert.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import React, { useCallback, useState } from "react";
-import { NavigationBlockerModal } from "./NavigationBlockerModal";
-import { useBlocker } from "./useBlocker";
-
-export const useProductChangedAlert = ({
- hasChanges,
-}: {
- hasChanges: boolean;
-}) => {
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [resolveConfirm, setResolveConfirm] = useState<
- ((value: boolean) => void) | null
- >(null);
-
- const showConfirmModal = useCallback((): Promise => {
- return new Promise((resolve) => {
- setResolveConfirm(() => resolve);
- setIsModalOpen(true);
- });
- }, []);
-
- const handleConfirm = useCallback(() => {
- setIsModalOpen(false);
- if (resolveConfirm) {
- resolveConfirm(true);
- setResolveConfirm(null);
- }
- }, [resolveConfirm]);
-
- const handleCancel = useCallback(() => {
- setIsModalOpen(false);
- if (resolveConfirm) {
- resolveConfirm(false);
- setResolveConfirm(null);
- }
- }, [resolveConfirm]);
-
- useBlocker(hasChanges, showConfirmModal);
-
- const modal = React.createElement(NavigationBlockerModal, {
- isOpen: isModalOpen,
- onConfirm: handleConfirm,
- onCancel: handleCancel,
- });
-
- return { modal };
-};
diff --git a/vite/src/views/products/product/hooks/useProductData.tsx b/vite/src/views/products/product/hooks/useProductData.tsx
deleted file mode 100644
index 01125c571..000000000
--- a/vite/src/views/products/product/hooks/useProductData.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import type { ProductItem, ProductV2 } from "@autumn/shared";
-import { useEffect, useRef, useState } from "react";
-import { sortProductItems } from "@/utils/productUtils";
-
-export const useProductData = ({
- originalProduct,
-}: {
- originalProduct: ProductV2 | null;
-}) => {
- const initialProductRef = useRef(null);
-
- const [hasChanges, setHasChanges] = useState(false);
- const [product, setProduct] = useState(null);
- const [entityFeatureIds, setEntityFeatureIds] = useState([]);
-
- useEffect(() => {
- const initEntityFeatureIds = (product: ProductV2 | null) => {
- if (!product) return [];
- return Array.from(
- new Set(
- product.items
- .filter((item: ProductItem) => item.entity_feature_id != null)
- .map((item: ProductItem) => item.entity_feature_id as string),
- ),
- );
- };
-
- if (originalProduct) {
- const sortedProduct = {
- ...originalProduct,
- items: sortProductItems(originalProduct.items),
- };
-
- initialProductRef.current = structuredClone(sortedProduct);
- setEntityFeatureIds(initEntityFeatureIds(sortedProduct));
- setProduct(sortedProduct);
- }
- }, [originalProduct]);
-
- useEffect(() => {
- if (!product) return;
-
- const sortedProduct = {
- ...product,
- items: sortProductItems(product.items),
- };
-
- if (JSON.stringify(product.items) !== JSON.stringify(sortedProduct.items)) {
- setProduct(sortedProduct);
- }
-
- const originalProduct = initialProductRef.current;
-
- if (!originalProduct || !sortedProduct) {
- setHasChanges(false);
- return;
- }
-
- const {
- is_default: _is_default,
- is_add_on: _is_add_on,
- ...rest
- } = sortedProduct;
-
- const {
- is_default: _originalIsDefault,
- is_add_on: _originalIsAddOn,
- ...originalRest
- } = originalProduct;
-
- const hasChanged = JSON.stringify(rest) !== JSON.stringify(originalRest);
-
- setHasChanges(hasChanged);
- }, [product]);
-
- const isNewProduct =
- initialProductRef.current?.items?.length === 0 &&
- !initialProductRef.current?.free_trial;
-
- const actionState = {
- disabled: !hasChanges,
- buttonText: isNewProduct ? "Create Plan" : "Update Plan",
- tooltipText: !hasChanges
- ? isNewProduct
- ? "Add entitlements and prices to create a new plan"
- : `Make a change to the entitlements or prices to update ${product?.name}`
- : isNewProduct
- ? `Create a new product: ${product?.name} `
- : `Save changes to product: ${product?.name}`,
- };
-
- return {
- product,
- setProduct,
- hasChanges,
- entityFeatureIds,
- setEntityFeatureIds,
- actionState,
- isNewProduct,
- };
-};
diff --git a/vite/src/views/products/product/prices/CreateFixedPrice.tsx b/vite/src/views/products/product/prices/CreateFixedPrice.tsx
deleted file mode 100644
index a3a9eacd0..000000000
--- a/vite/src/views/products/product/prices/CreateFixedPrice.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import {
- intervalsDifferent,
- type ProductItem,
- ProductItemInterval,
- UpdateProductSchema,
-} from "@autumn/shared";
-import { AlertTriangle, ArrowRight } from "lucide-react";
-import { useEffect, useState } from "react";
-import { useNavigate } from "react-router";
-import { toast } from "sonner";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { WarningBox } from "@/components/general/modal-components/WarningBox";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { useOrg } from "@/hooks/common/useOrg";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { getBackendErr, getRedirectUrl } from "@/utils/genUtils";
-import { isPriceItem } from "@/utils/product/getItemType";
-import { useProductCountsQuery } from "../hooks/queries/useProductCountsQuery";
-import { useProductContext } from "../ProductContext";
-import { useProductItemContext } from "../product-item/ProductItemContext";
-import { SelectCycle } from "../product-item/product-item-config/components/feature-price/SelectBillingCycle";
-
-function CreateFixedPrice() {
- const { item, setItem, selectedIndex } = useProductItemContext();
- const { org } = useOrg();
- const { product, hasChanges } = useProductContext();
- const { counts } = useProductCountsQuery();
-
- const [copyLoading, setCopyLoading] = useState(false);
- const axiosInstance = useAxiosInstance();
- const env = useEnv();
- const navigate = useNavigate();
-
- const curFixedPrice = product.items.find(
- (item: ProductItem, index: number) => {
- const isSameItem = selectedIndex && selectedIndex == index;
- return !isSameItem && isPriceItem(item) && item.interval;
- },
- );
-
- const newVariantMap: Record = {
- [ProductItemInterval.Year]: "annual",
- [ProductItemInterval.SemiAnnual]: "semi-annual",
- [ProductItemInterval.Quarter]: "quarterly",
- [ProductItemInterval.Month]: "monthly",
- [ProductItemInterval.Week]: "weekly",
- [ProductItemInterval.Day]: "daily",
- [ProductItemInterval.Minute]: "minute",
- [ProductItemInterval.Hour]: "hourly",
- };
-
- const intervalWarning = () => {
- if (!curFixedPrice || !item.interval) {
- return null;
- }
-
- const intervalsDiff = intervalsDifferent({
- intervalA: {
- interval: item.interval,
- intervalCount: item.interval_count,
- },
- intervalB: {
- interval: curFixedPrice.interval,
- intervalCount: curFixedPrice.interval_count,
- },
- });
-
- if (!intervalsDiff) {
- return null;
- }
-
- const newIntervalText =
- item.interval == ProductItemInterval.Year
- ? "an annual"
- : `a ${newVariantMap[item.interval! as ProductItemInterval]}`;
-
- if (item.interval_count > 1) {
- return `A fixed price already exists on this product. If you're looking to create a version with a different interval, you should create a new product instead.`;
- }
-
- return `A fixed price already exists on this product. If you're looking to create ${newIntervalText} version, you should create a new product instead.`;
- };
-
- const copyProductClicked = async () => {
- setCopyLoading(true);
-
- try {
- if (hasChanges) {
- if (counts?.all > 0) {
- toast.error("Please save the current changes to your plan first");
- return;
- }
-
- await ProductService.updateProduct(axiosInstance, product.id, {
- ...UpdateProductSchema.parse(product),
- items: product.items,
- free_trial: product.free_trial,
- });
- }
-
- const variantName = newVariantMap[item.interval! as ProductItemInterval];
- const newId = `${product.id}_${variantName}`;
- await ProductService.copyProduct(axiosInstance, product.id, {
- id: newId,
- name: `${product.name} - ${variantName}`,
- env: env,
- });
-
- await navigate(getRedirectUrl(`/products/${newId}`, env));
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to update plan"));
- } finally {
- setCopyLoading(false);
- }
- };
-
- return (
-
- {/*
Rates
*/}
-
-
-
Fixed Price
-
- {
- setItem({ ...item, price: e.target.value });
- }}
- placeholder="30.00"
- type="number"
- step="any"
- className="min-w-36"
- endContent={
-
- {org?.default_currency?.toUpperCase() || "USD"}
-
- }
- />
-
-
-
-
-
- {intervalWarning() && (
-
-
-
{intervalWarning()}
-
-
- }
- isLoading={copyLoading}
- onClick={copyProductClicked}
- >
- Copy Product
-
-
-
-
- )}
-
-
- );
-}
-
-export default CreateFixedPrice;
diff --git a/vite/src/views/products/product/prices/PricingConfig.tsx b/vite/src/views/products/product/prices/PricingConfig.tsx
deleted file mode 100644
index 80a791c21..000000000
--- a/vite/src/views/products/product/prices/PricingConfig.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-import { BillingInterval, BillWhen, PriceType } from "@autumn/shared";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { invalidNumber } from "@/utils/genUtils";
-
-export const PricingConfig = ({
- priceConfig,
- setPriceConfig,
- isUpdate = false,
-}: {
- priceConfig?: any;
- setPriceConfig: any;
- isUpdate?: boolean;
-}) => {
- const defaultFixedConfig = {
- type: PriceType.Fixed,
- amount: "",
- interval: BillingInterval.Month,
- };
-
- const defaultUsageConfig = {
- type: PriceType.Usage,
- internal_feature_id: "",
- feature_id: "",
- bill_when: BillWhen.EndOfPeriod,
- interval: BillingInterval.Month,
- billing_units: "",
- usage_tiers: [
- {
- from: 0,
- to: -1,
- amount: 0.0,
- },
- ],
- should_prorate: false,
- };
-
- const [priceType, setPriceType] = useState(
- priceConfig?.type || PriceType.Fixed,
- );
-
- const [name, setName] = useState(priceConfig?.name || "");
- const [usageTiers, setUsageTiers] = useState([]);
- const [fixedConfig, setFixedConfig] = useState(
- priceConfig && priceConfig.type == PriceType.Fixed
- ? priceConfig
- : defaultFixedConfig,
- );
- const [usageConfig, setUsageConfig]: any = useState(
- priceConfig?.config && priceConfig.config.type == PriceType.Usage
- ? priceConfig.config
- : defaultUsageConfig,
- );
-
- const [originalPrice, _] = useState(priceConfig);
-
- useEffect(() => {
- setPriceConfig(
- fixedConfig,
- // {
- // ...originalPrice,
- // name: name,
- // config: priceType === PriceType.Fixed ? fixedConfig : usageConfig,
-
- // }
- );
- }, [
- fixedConfig,
- usageConfig,
- priceType,
- name,
- setPriceConfig,
- originalPrice,
- ]);
-
- return (
-
- {/*
- Name
- setName(e.target.value)}
- placeholder="Price Name"
- />
-
*/}
-
setPriceType(value as PriceType)}
- defaultValue={priceType}
- >
- {/*
- Flat Fee
- Usage Based
-
- */}
- {/* */}
- {/*
-
-
- */}
-
-
- );
-};
-
-// Validate usage price config
-export const validateUsageConfig = (usageConfig: any) => {
- const config = { ...usageConfig };
- const { bill_when, interval, billing_units } = config;
-
- if (!config.internal_feature_id) {
- toast.error("Please select an entitlement");
- return null;
- }
-
- if (config.usage_tiers.slice(0, -1).some((tier: any) => tier.to < 0)) {
- toast.error("There can be no negative values in your pricing tiers");
- return null;
- }
-
- if (bill_when === BillWhen.StartOfPeriod) {
- if (!interval) {
- toast.error("Please fill out all fields");
- return null;
- }
-
- config.interval = interval;
- }
-
- if (
- bill_when === BillWhen.EndOfPeriod ||
- bill_when === BillWhen.StartOfPeriod
- ) {
- if (!billing_units || invalidNumber(billing_units)) {
- toast.error("Please fill out all fields");
- return null;
- }
-
- config.billing_units = parseFloat(billing_units);
- } else {
- config.billing_units = null;
- }
-
- // Check individual tier validity
- for (let i = 0; i < config.usage_tiers.length; i++) {
- const tier = config.usage_tiers[i];
- if (invalidNumber(tier.from) || invalidNumber(tier.amount)) {
- toast.error("Please fill out all tier fields");
- return null;
- }
-
- // Check current tier's range is valid
- if (
- parseFloat(tier.to) !== -1 &&
- parseFloat(tier.to) < parseFloat(tier.from)
- ) {
- toast.error(
- "Each tier's 'to' value must be greater than its 'from' value",
- );
- return null;
- }
-
- config.usage_tiers[i].from = parseFloat(config.usage_tiers[i].from);
- config.usage_tiers[i].to = parseFloat(config.usage_tiers[i].to);
- config.usage_tiers[i].amount = parseFloat(config.usage_tiers[i].amount);
- }
-
- config.should_prorate = config.should_prorate || false;
- return config;
-};
-
-// Validate fixed price config
-export const validateFixedConfig = (fixedConfig: any) => {
- const config = { ...fixedConfig };
-
- if (invalidNumber(config.amount) || !config.interval) {
- toast.error("Please fill out all fields");
- return null;
- }
- config.amount = parseFloat(config.amount.toString());
- return config;
-};
-
-export const validateConfig = (price: any, prices: any) => {
- const priceType = price.config.type;
- let config = null;
-
- if (!price.name) {
- toast.error("Please fill out a name");
- return null;
- }
-
- if (priceType === PriceType.Fixed) {
- config = validateFixedConfig(price.config);
- } else if (priceType === PriceType.Usage) {
- config = validateUsageConfig(price.config);
- }
- return config;
-};
diff --git a/vite/src/views/products/product/prices/PricingTypeBadge.tsx b/vite/src/views/products/product/prices/PricingTypeBadge.tsx
deleted file mode 100644
index f1e34afdb..000000000
--- a/vite/src/views/products/product/prices/PricingTypeBadge.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { PriceType } from "@autumn/shared";
-import { Badge } from "@/components/ui/badge";
-
-interface PricingTypeBadgeProps {
- type: string;
-}
-
-export function PricingTypeBadge({ type }: PricingTypeBadgeProps) {
- const variant = type === PriceType.Fixed ? "purple" : "blue";
-
- return {type} ;
-}
diff --git a/vite/src/views/products/product/product-item/CreateProductItem.tsx b/vite/src/views/products/product/product-item/CreateProductItem.tsx
deleted file mode 100644
index 885d2b9e3..000000000
--- a/vite/src/views/products/product/product-item/CreateProductItem.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-import {
- type FrontendProductItem,
- Infinite,
- type ProductItem,
- ProductItemInterval,
-} from "@autumn/shared";
-import { Plus } from "lucide-react";
-
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import { Dialog, DialogTrigger } from "@/components/ui/dialog";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { getFeature } from "@/utils/product/entitlementUtils";
-import { isFeatureItem, isFeaturePriceItem } from "@/utils/product/getItemType";
-import { validateProductItem } from "@/utils/product/product-item/validateProductItem";
-import { itemsHaveSameInterval } from "@/utils/product/productItemUtils";
-import { useProductContext } from "../ProductContext";
-import { CreateItemDialogContent } from "./create-product-item/CreateItemDialogContent";
-import { emptyPriceItem } from "./create-product-item/defaultItemConfigs";
-import { ProductItemContext } from "./ProductItemContext";
-import { useSteps } from "./useSteps";
-import { CreateItemStep } from "./utils/CreateItemStep";
-
-export const defaultProductItem: ProductItem = {
- feature_id: null,
-
- included_usage: null,
-
- interval: ProductItemInterval.Month,
-
- // Price config
- price: null,
- tiers: null,
- billing_units: 1,
-
- // Others
- entity_feature_id: null,
- reset_usage_when_enabled: true,
-};
-
-const useMergeFeatureItem = ({
- item,
- setOpen,
-}: {
- item: ProductItem;
- setOpen: (open: boolean) => void;
-}) => {
- const { product, setProduct } = useProductContext();
- const getOtherFeaturePriceItemIndex = () => {
- return product.items.findIndex(
- (i: any) =>
- i.feature_id === item.feature_id &&
- isFeaturePriceItem(i) &&
- itemsHaveSameInterval({ item1: i, item2: item }),
- );
- };
-
- const shouldMergeFeatureItem = () => {
- const otherItemIndex = getOtherFeaturePriceItemIndex();
- const isFeature = isFeatureItem(item);
- return isFeature && otherItemIndex !== -1;
- };
-
- const mergeFeatureItem = () => {
- const otherItemIndex = getOtherFeaturePriceItemIndex();
- const newItems = [...product.items];
- const newIncludedUsage = parseFloat(item.included_usage?.toString() || "0");
- if (isNaN(newIncludedUsage)) {
- toast.error("You must set an included usage for this item");
- return;
- }
-
- newItems[otherItemIndex] = {
- ...newItems[otherItemIndex],
- included_usage: newIncludedUsage,
- };
-
- setProduct({ ...product, items: newItems });
- setOpen(false);
- };
-
- return {
- shouldMergeFeatureItem,
- mergeFeatureItem,
- };
-};
-
-const useMergeFeaturePriceItem = ({
- item,
- setOpen,
-}: {
- item: ProductItem;
- setOpen: (open: boolean) => void;
-}) => {
- const { features } = useFeaturesQuery();
- const { product, setProduct } = useProductContext();
-
- const getOtherFeatureItemIndex = () => {
- return product.items.findIndex(
- (i: any) =>
- i.feature_id === item.feature_id &&
- isFeatureItem(i) &&
- itemsHaveSameInterval({ item1: i, item2: item }),
- );
- };
-
- const shouldMergeFeaturePriceItem = () => {
- const otherItemIndex = getOtherFeatureItemIndex();
- const isFeaturePrice = isFeaturePriceItem(item);
- return isFeaturePrice && otherItemIndex !== -1 && item.feature_id;
- };
-
- const mergeFeaturePriceItem = () => {
- const otherItemIndex = getOtherFeatureItemIndex();
- const otherItem = product.items[otherItemIndex];
-
- // 1. If unlimited, don't allow this item
- if (otherItem.included_usage === Infinite) {
- toast.error(
- `Cannot create a priced feature when you have another item for this feature (${otherItem.feature_id}) which is unlimited`,
- );
- return;
- }
-
- const newItems = [...product.items];
- const newItem = validateProductItem({
- item: item as FrontendProductItem,
- features,
- });
- if (!newItem) return;
-
- newItems[otherItemIndex] = {
- ...newItem,
- included_usage: otherItem.included_usage,
- };
-
- setProduct({ ...product, items: newItems });
- setOpen(false);
- };
-
- return {
- shouldMergeFeaturePriceItem,
- mergeFeaturePriceItem,
- };
-};
-
-export function CreateProductItem() {
- const { features } = useFeaturesQuery();
- const { product, setProduct } = useProductContext();
-
- const [open, setOpen] = useState(false);
- const [showCreateFeature, setShowCreateFeature] = useState(false);
- const [item, setItem] = useState(defaultProductItem);
-
- const { shouldMergeFeatureItem, mergeFeatureItem } = useMergeFeatureItem({
- item,
- setOpen,
- });
- const { shouldMergeFeaturePriceItem, mergeFeaturePriceItem } =
- useMergeFeaturePriceItem({
- item,
- setOpen,
- });
-
- const warning = () => {
- if (shouldMergeFeatureItem() && item.feature_id) {
- const feature = getFeature(item.feature_id, features);
- return `You already have a usage-based price for ${feature?.name}. Adding this feature will merge it with that price, and create an overage price (eg 100 free, then $0.5 thereafter)`;
- }
-
- if (shouldMergeFeaturePriceItem() && item.feature_id) {
- const feature = getFeature(item.feature_id, features);
- return `You already have a feature item for ${feature?.name}. Adding a variable price will merge it with that item, and create an overage price (eg 100 free, then $0.5 thereafter)`;
- }
-
- return null;
- };
-
- const handleCreateProductItem = async (entityFeatureId?: string) => {
- if (shouldMergeFeatureItem()) {
- mergeFeatureItem();
- return;
- }
-
- if (shouldMergeFeaturePriceItem()) {
- mergeFeaturePriceItem();
- return;
- }
-
- const validatedItem = validateProductItem({
- item: {
- ...(item as FrontendProductItem),
- entity_feature_id: entityFeatureId
- ? entityFeatureId
- : item.entity_feature_id,
- },
- features,
- });
-
- if (!validatedItem) return;
-
- const newItems = [...product.items, validatedItem];
- setProduct({ ...product, items: newItems });
-
- setOpen(false);
- };
-
- const stepState = useSteps({
- initialStep: CreateItemStep.CreateItem,
- });
-
- return (
-
-
-
-
- setItem(defaultProductItem)}
- startIcon={ }
- >
- Feature
-
-
-
- setItem(emptyPriceItem)}
- startIcon={ }
- >
- Price
-
-
-
-
-
-
- );
-}
diff --git a/vite/src/views/products/product/product-item/CreateProductItem2.tsx b/vite/src/views/products/product/product-item/CreateProductItem2.tsx
deleted file mode 100644
index fc4e334d4..000000000
--- a/vite/src/views/products/product/product-item/CreateProductItem2.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { type ProductItem, ProductItemInterval } from "@autumn/shared";
-import { PlusIcon } from "lucide-react";
-
-import { useState } from "react";
-import { Button } from "@/components/ui/button";
-import { Dialog, DialogTrigger } from "@/components/ui/dialog";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { cn } from "@/lib/utils";
-import { validateProductItem } from "@/utils/product/product-item/validateProductItem";
-import { useProductContext } from "../ProductContext";
-import { CreateItemDialogContent } from "./create-product-item/CreateItemDialogContent";
-import { defaultPriceItem } from "./create-product-item/defaultItemConfigs";
-import { ProductItemContext } from "./ProductItemContext";
-import { useSteps } from "./useSteps";
-import { CreateItemStep } from "./utils/CreateItemStep";
-
-const defaultProductItem: ProductItem = {
- feature_id: null,
-
- included_usage: null,
-
- interval: ProductItemInterval.Month,
-
- // Price config
- price: null,
- tiers: null,
- billing_units: 1,
-
- // Others
- entity_feature_id: null,
- reset_usage_when_enabled: true,
-};
-
-export function CreateProductItem2({
- classNames,
-}: {
- classNames?: {
- button?: string;
- };
-}) {
- const { features } = useFeaturesQuery();
- const [open, setOpen] = useState(false);
- const [showCreateFeature, setShowCreateFeature] = useState(false);
- const [item, setItem] = useState(defaultProductItem);
- const { product, setProduct } = useProductContext();
-
- const stepState = useSteps({ initialStep: CreateItemStep.SelectItemType });
-
- const handleCreateProductItem = async (entityFeatureId?: string) => {
- const validatedItem = validateProductItem({
- item: {
- isPrice: false,
- ...item,
- entity_feature_id: entityFeatureId
- ? entityFeatureId
- : item.entity_feature_id,
- },
- features,
- });
-
- if (!validatedItem) return;
-
- const newItems = [...product.items, validatedItem];
- const newProduct = { ...product, items: newItems };
- setProduct(newProduct);
- setTimeout(() => {
- setItem({
- ...defaultProductItem,
- feature_id: null,
- });
- }, 400);
-
- setOpen(false);
- // setFirstItemCreated(true);
- return newProduct;
- };
-
- return (
-
-
-
-
- }
- onClick={() => setItem(defaultProductItem)}
- >
- Add Feature
-
-
-
- }
- onClick={() => setItem(defaultPriceItem)}
- >
- Add Price
-
-
-
-
-
-
- );
-}
diff --git a/vite/src/views/products/product/product-item/EntitiesDropdown.tsx b/vite/src/views/products/product/product-item/EntitiesDropdown.tsx
deleted file mode 100644
index b46bcea1b..000000000
--- a/vite/src/views/products/product/product-item/EntitiesDropdown.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import {
- type Feature,
- FeatureUsageType,
- type ProductItem,
-} from "@autumn/shared";
-import { CheckIcon, PlusIcon } from "lucide-react";
-import { useState } from "react";
-import { toast } from "sonner";
-import { CustomDialogContent } from "@/components/general/modal-components/DialogContentWrapper";
-import { Button } from "@/components/ui/button";
-import { Dialog, DialogTrigger } from "@/components/ui/dialog";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { cn } from "@/lib/utils";
-import { CreateFeature } from "@/views/products/features/components/CreateFeature";
-import { useProductContext } from "../ProductContext";
-
-export const EntitiesDropdown = ({
- open,
- setOpen,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
-}) => {
- return (
-
-
-
-
-
-
-
-
- );
-};
-
-export const EntitiesDropdownContent = () => {
- const { features } = useFeaturesQuery();
- const { product, entityFeatureIds, setEntityFeatureIds } =
- useProductContext();
-
- const [createFeatureOpen, setCreateFeatureOpen] = useState(false);
-
- const continuousUseFeatures = features.filter(
- (feature: Feature) =>
- feature.config?.usage_type === FeatureUsageType.Continuous,
- );
-
- return (
- <>
- {continuousUseFeatures.map((item: Feature, index: number) => (
- {
- setEntityFeatureIds((prev: any) => {
- const currentIds = Array.isArray(prev) ? prev : [];
- if (currentIds.includes(item.id)) {
- // Check if any product items are using this entity
- const itemsUsingEntity =
- product.items?.filter(
- (productItem: ProductItem) =>
- productItem.entity_feature_id === item.id,
- ) || [];
-
- if (itemsUsingEntity.length > 0) {
- toast.error(
- "Please delete all items under this entity first",
- );
- return currentIds;
- }
-
- return currentIds.filter((id) => id !== item.id);
- } else {
- return [...currentIds, item.id];
- }
- });
- }}
- >
-
-
{item.name}
- {entityFeatureIds.includes(item.id) && (
-
-
-
- )}
-
-
- ))}
-
-
- e.preventDefault()}>
- }
- className="w-full p-0 h-auto font-normal text-primary"
- >
- Create Feature Entity
-
-
-
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/products/product/product-item/EntitiesSidebar.tsx b/vite/src/views/products/product/product-item/EntitiesSidebar.tsx
deleted file mode 100644
index 6485011ec..000000000
--- a/vite/src/views/products/product/product-item/EntitiesSidebar.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useProductContext } from "../ProductContext";
-import { EntitiesDropdown } from "./EntitiesDropdown";
-
-export const EntitiesSidebar = ({
- open,
- setOpen,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
-}) => {
- const { entityFeatureIds } = useProductContext();
-
- return (
- <>
-
- {entityFeatureIds.length > 0 ? (
-
-
-
-
- Features{" "}
-
-
-
-
- {entityFeatureIds.length === 1
- ? entityFeatureIds[0]
- : `${entityFeatureIds.length}`}
-
-
-
-
-
-
- ) : (
- Assign product items to a sub-group
- )}
- >
- );
-};
diff --git a/vite/src/views/products/product/product-item/ProductItemConfig.tsx b/vite/src/views/products/product/product-item/ProductItemConfig.tsx
deleted file mode 100644
index 64487e0a1..000000000
--- a/vite/src/views/products/product/product-item/ProductItemConfig.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import {
- type Feature,
- FeatureType,
- FeatureUsageType,
- ProductItemFeatureType,
-} from "@autumn/shared";
-import { useEffect, useState } from "react";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { cn } from "@/lib/utils";
-import { shouldShowProrationConfig } from "@/utils/product/productItemUtils";
-
-import { ConfigWithFeature } from "./components/ConfigWithFeature";
-import { useProductItemContext } from "./ProductItemContext";
-import { PriceItemConfig } from "./product-item-config/PriceItemConfig";
-
-export const ProductItemConfig = () => {
- // HOOKS
-
- const { features } = useFeaturesQuery();
- const { item, setItem } = useProductItemContext();
-
- useEffect(() => {
- const feature = features.find((f: Feature) => f.id == item.feature_id);
-
- if (feature) {
- if (feature.type == FeatureType.Boolean) {
- setItem({
- feature_id: item.feature_id,
- feature_type: ProductItemFeatureType.Static,
- });
- } else {
- const showProration = shouldShowProrationConfig({ item, features });
- const resetUsageWhenEnabled =
- feature.config?.usage_type == FeatureUsageType.Continuous
- ? false
- : item.reset_usage_when_enabled;
-
- const newItem = {
- ...item,
- feature_type: feature.config?.usage_type,
- reset_usage_when_enabled: resetUsageWhenEnabled,
- };
-
- // Only manage proration config if this item should show proration
- if (showProration) {
- // Preserve existing config and manage proration parts
- const existingConfig = item.config || {};
- const newConfig = {
- ...existingConfig,
- on_increase: existingConfig.on_increase,
- on_decrease: existingConfig.on_decrease,
- };
-
- if (Object.keys(newConfig).length > 0) {
- newItem.config = newConfig;
- }
- }
-
- setItem(newItem);
- }
- }
- }, [item.feature_id, item.usage_model]);
-
- // const isFeature = isFeatureItem(item);
- const isPrice = item.isPrice;
- // isPriceItem(item) && "w-sm",
- // isFeaturePriceItem(item) && "!w-sm",
- // isFeaturePriceItem(item) && item.tiers?.length > 1 && "w-sm"
- return (
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/ProductItemRow.tsx b/vite/src/views/products/product/product-item/ProductItemRow.tsx
deleted file mode 100644
index 64aca0567..000000000
--- a/vite/src/views/products/product/product-item/ProductItemRow.tsx
+++ /dev/null
@@ -1,298 +0,0 @@
-// ProductItemRow.tsx
-import {
- type BillingInterval,
- type Feature,
- FeatureType,
- formatAmount,
- getFeatureName,
- Infinite,
- type ProductItem,
- ProductItemType,
-} from "@autumn/shared";
-import { DollarSign, Flag } from "lucide-react";
-import { AdminHover } from "@/components/general/AdminHover";
-import { Badge } from "@/components/ui/badge";
-import { cn } from "@/lib/utils";
-import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
-import { formatIntervalText } from "@/utils/formatUtils/formatTextUtils";
-import { notNullish } from "@/utils/genUtils";
-import { getFeature } from "@/utils/product/entitlementUtils";
-import { isFeatureItem, isPriceItem } from "@/utils/product/getItemType";
-import { getItemType, intervalIsNone } from "@/utils/product/productItemUtils";
-
-interface ProductItemRowProps {
- item: ProductItem;
- index: number;
- isOnboarding: boolean;
- features: Feature[];
- org: any;
- onRowClick: (item: ProductItem, index: number) => void;
- className?: string;
-}
-
-export const ProductItemRow = ({
- item,
- index,
- isOnboarding,
- features,
- org,
- onRowClick,
- className,
-}: ProductItemRowProps) => {
- const getName = ({
- featureId,
- units,
- }: {
- featureId: string;
- units: number | string | null | undefined;
- }) => {
- const feature = getFeature(featureId, features);
-
- if (!feature) return "";
-
- const plural = units !== 1;
- return getFeatureName({
- feature: feature!,
- plural: plural,
- });
- };
-
- const getFreeFeatureString = (item: ProductItem) => {
- const feature = features.find((f: Feature) => f.id == item.feature_id);
-
- if (feature?.type === FeatureType.Boolean) {
- return `${feature.name}`;
- }
-
- const featureName = getName({
- featureId: item.feature_id!,
- units: item.included_usage,
- });
-
- if (item.included_usage == Infinite) {
- return `Unlimited ${featureName}`;
- }
-
- let entityFeatureName = "";
- if (item.entity_feature_id) {
- entityFeatureName = getName({
- featureId: item.entity_feature_id!,
- units: 1,
- });
- }
-
- let intervalStr = "";
- if (
- item.interval_count &&
- item.interval_count > 1 &&
- notNullish(item.interval)
- ) {
- intervalStr = ` ${item.interval_count} ${item.interval}s`;
- } else if (item.interval) {
- intervalStr = ` ${item.interval}`;
- }
-
- return (
-
- {item.included_usage ?? 0}
- {featureName}
- {item.entity_feature_id && (
- per {entityFeatureName}
- )}
- {notNullish(item.interval) && (
- per {intervalStr}
- )}
-
- );
- };
-
- const getPaidFeatureString = (item: ProductItem) => {
- let amountStr = "";
-
- if (item.price) {
- amountStr = formatAmount({
- currency: org?.default_currency || "USD",
- amount: item.price,
- });
- } else if (item.tiers && item.tiers.length == 1) {
- amountStr = formatAmount({
- currency: org?.default_currency || "USD",
- amount: item.tiers![0].amount,
- });
- } else {
- amountStr = `${formatAmount({
- currency: org?.default_currency || "USD",
- amount: item.tiers![0].amount,
- })} - ${formatAmount({
- currency: org?.default_currency || "USD",
- amount: item.tiers![item.tiers!.length - 1].amount,
- })}`;
- }
-
- const feature = features.find((f: Feature) => f.id == item.feature_id);
- const billUnitsFeatureName = getName({
- featureId: item.feature_id!,
- units: item.billing_units,
- });
-
- amountStr += ` per ${item.billing_units! > 1 ? item.billing_units : ""} ${
- billUnitsFeatureName
- }`;
-
- const intervalStr = formatIntervalText({
- billingInterval: item.interval as unknown as BillingInterval,
- intervalCount: item.interval_count ?? 1,
- });
-
- if (!intervalIsNone(item.interval)) {
- amountStr += ` ${intervalStr}`;
- }
-
- if (item.included_usage) {
- const includedUsageFeatureName = getName({
- featureId: item.feature_id!,
- units: item.included_usage,
- });
-
- return `${item.included_usage} ${includedUsageFeatureName} free, then ${amountStr}`;
- } else {
- return amountStr;
- }
- };
-
- const getFixedPriceString = (item: ProductItem) => {
- const currencyCode = org?.default_currency || "USD";
- const formattedAmount = formatAmount({
- currency: currencyCode,
- amount: item.price!,
- });
-
- const intervalStr = formatIntervalText({
- billingInterval: item.interval as unknown as BillingInterval,
- intervalCount: item.interval_count ?? 1,
- });
-
- if (!intervalIsNone(item.interval)) {
- return `${formattedAmount} ${intervalStr}`;
- }
-
- return `${formattedAmount}`;
- };
-
- const getAdminHoverTexts = (item: ProductItem) => {
- if (isFeatureItem(item)) {
- return [
- {
- key: "Entitlement ID",
- value: item.entitlement_id || "N/A",
- },
- ];
- }
-
- let texts = [
- {
- key: "Price ID",
- value: item.price_id || "N/A",
- },
- {
- key: "Stripe Price ID",
- value: item.price_config?.stripe_price_id || "N/A",
- },
- ...(item.price_config?.stripe_empty_price_id
- ? [
- {
- key: "Stripe Empty Price ID",
- value: item.price_config?.stripe_empty_price_id || "N/A",
- },
- ]
- : []),
- ];
-
- if (!isPriceItem(item)) {
- texts = texts.concat([
- {
- key: "Entitlement ID",
- value: item.entitlement_id || "N/A",
- },
- {
- key: "Stripe Product ID",
- value: item.price_config?.stripe_product_id || "N/A",
- },
- {
- key: "Stripe Meter ID",
- value: item.price_config?.stripe_meter_id || "N/A",
- },
- ]);
- }
-
- return texts;
- };
-
- const itemType = getItemType(item);
-
- return (
- onRowClick(item, index)}
- >
- {!isOnboarding && (
-
-
- {item.feature_id || ""}
-
-
- )}
-
-
- {itemType === ProductItemType.Feature
- ? getFreeFeatureString(item)
- : itemType === ProductItemType.Price
- ? getFixedPriceString(item)
- : getPaidFeatureString(item)}
-
-
-
-
- Feature
-
-
-
- Price
-
-
- {!isOnboarding && (
-
- {item.created_at
- ? formatUnixToDateTime(item.created_at).date
- : formatUnixToDateTime(Math.floor(Date.now())).date}
-
- )}
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/ProductItemTable.tsx b/vite/src/views/products/product/product-item/ProductItemTable.tsx
deleted file mode 100644
index 4dd2f69de..000000000
--- a/vite/src/views/products/product/product-item/ProductItemTable.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-import type { ProductItem } from "@autumn/shared";
-import { EllipsisVertical } from "lucide-react";
-import { useState } from "react";
-import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip";
-import { Button } from "@/components/ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { cn } from "@/lib/utils";
-import { CreateFreeTrial } from "../free-trial/CreateFreeTrial";
-import { useProductContext } from "../ProductContext";
-import { CreateProductItem } from "./CreateProductItem";
-import { EntitiesDropdownContent } from "./EntitiesDropdown";
-import { ProductItemRow } from "./ProductItemRow";
-import UpdateProductItem from "./UpdateProductItem";
-import { getFrontendItemConfig } from "./utils/getFrontendItemConfig";
-
-export const ProductItemTable = () => {
- const { features } = useFeaturesQuery();
-
- const { org } = useOrg();
- const { product, entityFeatureIds, isOnboarding } = useProductContext();
-
- const [selectedItem, setSelectedItem] = useState(null);
- const [selectedIndex, setSelectedIndex] = useState(null);
- const [open, setOpen] = useState(false);
- const [dropdownOpen, setDropdownOpen] = useState(false);
- const [freeTrialOpen, setFreeTrialOpen] = useState(false);
-
- const handleRowClick = (item: ProductItem, index: number) => {
- const frontendItem = getFrontendItemConfig(item);
-
- setSelectedItem(frontendItem);
- setSelectedIndex(index);
- setOpen(true);
- };
-
- // Group product items by entity_feature_id, and also include items where feature_id matches
- const groupedItems = entityFeatureIds.reduce(
- (acc: Record, entityFeatureId: string) => {
- acc[entityFeatureId] = product.items.filter(
- (item: ProductItem) => item.entity_feature_id === entityFeatureId,
- );
- return acc;
- },
- {} as Record,
- );
-
- return (
- <>
-
-
-
-
- Product Items
-
-
-
- {!isOnboarding && (
- <>
-
-
-
- }
- className={cn(
- "w-10 h-10 p-0 text-purple-600 hover:text-purple-700 hover:bg-purple-50",
- isOnboarding && "!h-full",
- isOnboarding && product.items.length === 0 && "hidden",
- )}
- />
-
-
-
-
- Add Entity
-
-
- Add an entity to group items by (eg, usage limits
- per users, compute instances, etc).
-
-
-
-
-
-
-
- {
- setDropdownOpen(false);
- setFreeTrialOpen(true);
- }}
- >
- Add Free Trial
-
-
-
-
- >
- )}
-
- {/*
*/}
-
-
-
-
- {/* Original product items mapping - excluding items that appear in grouped sections */}
- {product.items
- .filter(
- (item: ProductItem) =>
- !entityFeatureIds.some(
- (entityFeatureId: string) =>
- item.entity_feature_id === entityFeatureId,
- ),
- )
- .map((item: ProductItem, index: number) => (
-
- ))}
-
- {/* Grouped items with separators */}
- {entityFeatureIds.map((entityFeatureId: string) => (
-
- {/* Separator for each entityFeatureId */}
-
-
- {entityFeatureId}
-
-
-
- {/* Filtered items for this entityFeatureId */}
- {groupedItems[entityFeatureId]?.map(
- (item: ProductItem, index: number) => (
-
- ),
- )}
-
- {/* Show message if no items for this entityFeatureId */}
- {groupedItems[entityFeatureId]?.length === 0 && (
-
- Add the features this entity gets access to
-
- )}
-
- ))}
-
- {product.items.length === 0 && (
-
- {/*
- Product items determine what customers get access to and how
- they're billed. Start by adding one.
-
*/}
-
- Product items determine what customers get access to and how
- they're billed{" "}
-
- learn more:
-
-
-
-
- ↳ Features: {" "}
- features included with this product (eg, 100 credits per
- month)
-
-
- ↳ Prices: a fixed
- price to charge customers (eg, $10 per month)
-
-
- ↳{" "}
- Priced Features: {" "}
- features that have a price based on usage (eg, $1 per credit)
-
-
-
- )}
-
-
- >
- );
-};
diff --git a/vite/src/views/products/product/product-item/UpdateProductItem.tsx b/vite/src/views/products/product/product-item/UpdateProductItem.tsx
deleted file mode 100644
index 53652492c..000000000
--- a/vite/src/views/products/product/product-item/UpdateProductItem.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import type { FrontendProductItem, ProductItem } from "@autumn/shared";
-import { useEffect, useState } from "react";
-import CopyButton from "@/components/general/CopyButton";
-import {
- CustomDialogBody,
- CustomDialogContent,
-} from "@/components/general/modal-components/DialogContentWrapper";
-import { Dialog, DialogTitle } from "@/components/ui/dialog";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { notNullish } from "@/utils/genUtils";
-import { isPriceItem } from "@/utils/product/getItemType";
-import { validateProductItem } from "@/utils/product/product-item/validateProductItem";
-import { useProductContext } from "../ProductContext";
-import { ProductItemConfig } from "./ProductItemConfig";
-import { ProductItemContext } from "./ProductItemContext";
-import {
- AdvancedConfigSidebar,
- MainDialogBodyWrapper,
- ToggleAdvancedConfigButton,
-} from "./product-item-config/AdvancedConfigSidebar";
-import { ItemConfigFooter } from "./product-item-config/item-config-footer/ItemConfigFooter";
-
-export default function UpdateProductItem({
- selectedItem,
- selectedIndex,
- setSelectedItem,
- open,
- setOpen,
-}: {
- selectedItem: ProductItem | null;
- selectedIndex: number | null;
- setSelectedItem: (item: ProductItem | null) => void;
- open: boolean;
- setOpen: (open: boolean) => void;
-}) {
- const { features } = useFeaturesQuery();
- const { product, setProduct } = useProductContext();
- const [showCreateFeature, setShowCreateFeature] = useState(false);
-
- const [advancedOpen, setAdvancedOpen] = useState(false);
-
- useEffect(() => {
- if (!open) {
- setTimeout(() => {
- setAdvancedOpen(false);
- }, 300);
- }
- }, [open]);
-
- const handleUpdateProductItem = () => {
- const frontendItem = selectedItem as FrontendProductItem;
- const validatedItem = validateProductItem({
- item: frontendItem!,
- features,
- });
-
- if (!validatedItem) return null;
- if (notNullish(selectedIndex)) {
- const newProduct = { ...product };
- newProduct.items[selectedIndex!] = validatedItem;
- setProduct(newProduct);
- setOpen(false);
-
- return newProduct;
- }
- };
-
- const handleDeleteProductItem = () => {
- if (notNullish(selectedIndex)) {
- const newProduct = { ...product };
- newProduct.items.splice(selectedIndex!, 1);
- setProduct(newProduct);
- setOpen(false);
- return newProduct;
- }
- };
-
- return (
-
-
-
-
-
-
-
- Update Item
- {selectedItem?.feature_id && (
-
- {selectedItem.feature_id || ""}
-
- )}
-
-
-
-
-
-
-
-
-
-
- {/*
-
-
- Update Item
- {selectedItem?.feature_id && (
-
- {selectedItem.feature_id || ""}
-
- )}
-
-
-
-
- */}
-
-
- );
-}
diff --git a/vite/src/views/products/product/product-item/components/ConfigFixedPrice.tsx b/vite/src/views/products/product/product-item/components/ConfigFixedPrice.tsx
deleted file mode 100644
index 0a2296556..000000000
--- a/vite/src/views/products/product/product-item/components/ConfigFixedPrice.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import CreateFixedPrice from "../../prices/CreateFixedPrice";
-import { useProductItemContext } from "../ProductItemContext";
-
-export default function FixedPriceConfig({
- show,
- setShow,
-}: {
- show: any;
- setShow: (show: any) => void;
-}) {
- const { item, setItem } = useProductItemContext();
-
- return (
-
- );
-}
diff --git a/vite/src/views/products/product/product-item/components/ConfigWithFeature.tsx b/vite/src/views/products/product/product-item/components/ConfigWithFeature.tsx
deleted file mode 100644
index 52bef7ae9..000000000
--- a/vite/src/views/products/product/product-item/components/ConfigWithFeature.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { FeatureType } from "@autumn/shared";
-import { ArrowRight } from "lucide-react";
-import { toast } from "sonner";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { WarningBox } from "@/components/general/modal-components/WarningBox";
-import { Button } from "@/components/ui/button";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { getFeature } from "@/utils/product/entitlementUtils";
-import { isFeatureItem, isFeaturePriceItem } from "@/utils/product/getItemType";
-import { itemsHaveSameInterval } from "@/utils/product/productItemUtils";
-import { useProductContext } from "../../ProductContext";
-import { useProductItemContext } from "../ProductItemContext";
-import { FeatureConfig } from "../product-item-config/FeatureItemConfig";
-import { SelectItemFeature } from "./SelectItemFeature";
-
-export const ConfigWithFeature = () => {
- const { product, setProduct } = useProductContext();
- const { item, setOpen, warning } = useProductItemContext();
- const { features } = useFeaturesQuery();
-
- const isBooleanFeature =
- getFeature(item.feature_id, features)?.type === FeatureType.Boolean;
-
- const otherItemIndex = product.items.findIndex(
- (i: any) =>
- i.feature_id === item.feature_id &&
- isFeaturePriceItem(i) &&
- itemsHaveSameInterval({ item1: i, item2: item }),
- );
-
- const handleAddToExistingItem = () => {
- const newItems = [...product.items];
- const newIncludedUsage = parseFloat(item.included_usage);
- if (!item.included_usage || isNaN(newIncludedUsage)) {
- toast.error("You must set an included usage for this item");
- return;
- }
- newItems[otherItemIndex] = {
- ...newItems[otherItemIndex],
- included_usage: newIncludedUsage,
- };
-
- setProduct({ ...product, items: newItems });
- setOpen(false);
- };
-
- return (
-
-
- Feature
-
-
-
- {!isBooleanFeature &&
}
-
- {warning && (
-
-
-
- {/* You already have a usage-based price for this feature. If you're
- looking to make it an overage (eg. 100 free, then $0.5
- thereafter), you should add it to the existing item. */}
- {warning}
-
-
- {/*
- }
- onClick={handleAddToExistingItem}
- >
- Add to existing item
-
-
*/}
-
-
- )}
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/components/SelectItemFeature.tsx b/vite/src/views/products/product/product-item/components/SelectItemFeature.tsx
deleted file mode 100644
index f73572998..000000000
--- a/vite/src/views/products/product/product-item/components/SelectItemFeature.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import { type Feature, FeatureType, ProductItemType } from "@autumn/shared";
-import { PlusIcon, X } from "lucide-react";
-import { useEffect, useState } from "react";
-import { Button } from "@/components/ui/button";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { getItemType } from "@/utils/product/productItemUtils";
-import CreateFeatureSheet from "@/views/products/features/components/CreateFeatureSheet";
-import { FeatureTypeBadge } from "@/views/products/features/components/FeatureTypeBadge";
-import { useProductContext } from "../../ProductContext";
-import { useProductItemContext } from "../ProductItemContext";
-import { CreateItemStep } from "../utils/CreateItemStep";
-
-export const SelectItemFeature = () => {
- const { features } = useFeaturesQuery();
- const { item, setItem, isUpdate, stepState } = useProductItemContext();
- const [open, setOpen] = useState(false);
- const [sheetOpen, setSheetOpen] = useState(false);
- const itemType = getItemType(item);
-
- const handleFeatureCreated = (featureId: string) => {
- setItem({ ...item, feature_id: featureId });
- };
-
- return (
- <>
-
-
{
- setItem({ ...item, feature_id: value });
- }}
- disabled={isUpdate}
- >
-
-
-
-
- {features
- .filter((feature: Feature) => {
- if (feature.archived && feature.id !== item.feature_id)
- return false;
- if (itemType === ProductItemType.FeaturePrice) {
- return feature.type !== FeatureType.Boolean;
- }
- return true;
- })
- .map((feature: Feature) => (
-
-
- {feature.name}
-
-
-
- ))}
- {
- e.preventDefault();
- e.stopPropagation();
- setOpen(false);
- setSheetOpen(true);
- }}
- >
-
- Create new feature
-
-
-
- {!isUpdate && item.feature_id && (
-
{
- setItem({
- ...item,
- feature_id: null,
- included_usage: null,
- feature_type: null,
- tiers: null,
- price: null,
- // price: item.tiers?.[0]?.amount || 0,
- });
- // setShow({ ...show, feature: false });
- }}
- >
-
-
- )}
-
-
-
- >
- );
-};
diff --git a/vite/src/views/products/product/product-item/create-product-item/CreateItemDialogContent.tsx b/vite/src/views/products/product/product-item/create-product-item/CreateItemDialogContent.tsx
deleted file mode 100644
index 4244fa96b..000000000
--- a/vite/src/views/products/product/product-item/create-product-item/CreateItemDialogContent.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import type { CreateFeature as CreateFeatureType } from "@autumn/shared";
-import { useEffect, useState } from "react";
-import {
- CustomDialogBody,
- CustomDialogContent,
-} from "@/components/general/modal-components/DialogContentWrapper";
-import { DialogHeader, DialogTitle } from "@/components/ui/dialog";
-import { keyToTitle } from "@/utils/formatUtils/formatTextUtils";
-import { getItemType } from "@/utils/product/productItemUtils";
-import { CreateFeature } from "@/views/products/features/components/CreateFeature";
-import { ProductItemConfig } from "../ProductItemConfig";
-import { useProductItemContext } from "../ProductItemContext";
-import {
- AdvancedConfigSidebar,
- MainDialogBodyWrapper,
- ToggleAdvancedConfigButton,
-} from "../product-item-config/AdvancedConfigSidebar";
-import { ItemConfigFooter } from "../product-item-config/item-config-footer/ItemConfigFooter";
-import { CreateItemStep } from "../utils/CreateItemStep";
-
-export const CreateItemDialogContent = ({
- open,
- setOpen,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
-}) => {
- const { stepState, item, setItem } = useProductItemContext();
-
- const { stepVal, popStep, resetSteps, replaceStep, stepCount } = stepState;
-
- useEffect(() => {
- if (open) {
- resetSteps();
- }
- }, [open, resetSteps]);
-
- const handleFeatureCreated = async (feature: CreateFeatureType) => {
- // setFeatures([...features, feature]);
- setItem({ ...item, feature_id: feature.id });
- replaceStep(CreateItemStep.CreateItem);
- };
-
- const itemType = getItemType(item);
- const [advancedOpen, setAdvancedOpen] = useState(false);
-
- useEffect(() => {
- if (!open) {
- setTimeout(() => {
- setAdvancedOpen(false);
- }, 300);
- }
- }, [open]);
-
- // Advanced sidebar width - adjust this value to change sidebar size
- const ADVANCED_SIDEBAR_WIDTH = 320; // 320px = w-80 in Tailwind
- const showAdvancedButton =
- stepVal === CreateItemStep.CreateItem &&
- (!item.isPrice || item.isVariable === true);
-
- return (
-
- {stepVal === CreateItemStep.CreateFeature ? (
- 1 ? popStep : undefined}
- />
- ) : (
-
- {/* Main Dialog Content */}
-
-
-
-
- Add {keyToTitle(itemType)}
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- );
-};
-
-// {
-/*
-
-
- Feature
-
-
- Priced Feature
-
-
- Price
-
-
-
-
-
- */
-// }
diff --git a/vite/src/views/products/product/product-item/create-product-item/defaultItemConfigs.ts b/vite/src/views/products/product/product-item/create-product-item/defaultItemConfigs.ts
deleted file mode 100644
index 95d98bf42..000000000
--- a/vite/src/views/products/product/product-item/create-product-item/defaultItemConfigs.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import {
- type FrontendProductItem,
- Infinite,
- type ProductItem,
- ProductItemInterval,
-} from "@autumn/shared";
-
-export const defaultFeatureItem: ProductItem = {
- feature_id: null,
-
- included_usage: null,
-
- interval: ProductItemInterval.Month,
- interval_count: 1,
-
- // Price config
- price: null,
- tiers: null,
- billing_units: 1,
-
- // Others
- entity_feature_id: null,
- reset_usage_when_enabled: true,
-};
-
-export const defaultPriceItem: FrontendProductItem = {
- feature_id: null,
- included_usage: null,
-
- interval: ProductItemInterval.Month,
- interval_count: 1,
-
- // Price config
- price: 0,
- tiers: null,
- billing_units: 1,
-
- // Others
- entity_feature_id: null,
- reset_usage_when_enabled: true,
- isPrice: true,
- isVariable: false,
-};
-
-export const defaultPaidFeatureItem: FrontendProductItem = {
- feature_id: null,
- included_usage: null,
- interval: ProductItemInterval.Month,
- interval_count: 1,
-
- // Price config
- price: null,
- tiers: [
- {
- amount: 0,
- to: Infinite,
- },
- ],
- billing_units: 1,
-
- // Others
- entity_feature_id: null,
- reset_usage_when_enabled: true,
- isPrice: true,
- isVariable: true,
- usage_model: null,
-};
-
-export const emptyPriceItem: FrontendProductItem = {
- feature_id: null,
- included_usage: null,
-
- interval: ProductItemInterval.Month,
- interval_count: 1,
-
- // Price config
- price: 0,
- tiers: null,
- billing_units: 1,
-
- // Others
- entity_feature_id: null,
- reset_usage_when_enabled: true,
- usage_model: null,
-
- isPrice: true,
- isVariable: null,
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/AdvancedConfigSidebar.tsx b/vite/src/views/products/product/product-item/product-item-config/AdvancedConfigSidebar.tsx
deleted file mode 100644
index a2c711c04..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/AdvancedConfigSidebar.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { ChevronRight } from "lucide-react";
-import { Button } from "@/components/ui/button";
-import { AdvancedItemConfig } from "./advanced-config/AdvancedItemConfig";
-
-const ADVANCED_SIDEBAR_WIDTH = 340; // 320px = w-80 in Tailwind
-
-export const AdvancedConfigSidebar = ({
- advancedOpen,
-}: {
- advancedOpen: boolean;
-}) => {
- return (
-
- );
-};
-
-export const MainDialogBodyWrapper = ({
- children,
- advancedOpen,
-}: {
- children: React.ReactNode;
- advancedOpen: boolean;
-}) => {
- return (
-
- {children}
-
- );
-};
-
-export const ToggleAdvancedConfigButton = ({
- advancedOpen,
- setAdvancedOpen,
- showAdvancedButton,
-}: {
- advancedOpen: boolean;
- setAdvancedOpen: (open: boolean) => void;
- showAdvancedButton: boolean;
-}) => {
- return showAdvancedButton ? (
-
- {
- setAdvancedOpen(!advancedOpen);
- }}
- className="flex items-center gap-1 text-t3"
- size="sm"
- >
-
- {advancedOpen ? "Hide Advanced" : "Show Advanced"}
-
-
- ) : (
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/FeatureItemConfig.tsx b/vite/src/views/products/product/product-item/product-item-config/FeatureItemConfig.tsx
deleted file mode 100644
index b0fbdbb48..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/FeatureItemConfig.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from "react";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { isFeatureItem, isFeaturePriceItem } from "@/utils/product/getItemType";
-import { useProductItemContext } from "../ProductItemContext";
-import FeaturePrice from "./components/feature-price/FeaturePrice";
-import { SelectCycle } from "./components/feature-price/SelectBillingCycle";
-import { IncludedUsage } from "./components/IncludedUsage";
-import { SelectResetCycle } from "./components/SelectResetCycle";
-
-export const FeatureConfig = () => {
- const { features } = useFeaturesQuery();
- const { item, setItem } = useProductItemContext();
-
- if (!item?.feature_id) return null;
-
- const isFeaturePrice = isFeaturePriceItem(item);
- const isFeature = isFeatureItem(item);
-
- // const handleAddUsagePrice = () => {
- // const newIncludedUsage =
- // item.included_usage == Infinite ? 0 : item.included_usage;
-
- // let newInterval = item.interval;
- // if (
- // notNullish(item.interval) &&
- // !Object.values(BillingInterval).includes(item.interval)
- // ) {
- // newInterval = BillingInterval.Month;
- // }
-
- // setItem({
- // ...item,
- // included_usage: newIncludedUsage,
- // tiers: [{ to: Infinite, amount: 0 }],
- // interval: newInterval,
- // });
- // };
-
- // const price =
- // getFeatureUsageType({ item, features }) == FeatureUsageType.Continuous
- // ? "10"
- // : "1";
-
- // const feature = getFeature(item?.feature_id, features);
-
- return (
- <>
- {isFeature && (
-
-
-
-
- )}
-
- {isFeaturePrice && (
-
-
-
-
-
-
-
- )}
- >
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/PriceItemConfig.tsx b/vite/src/views/products/product/product-item/product-item-config/PriceItemConfig.tsx
deleted file mode 100644
index 1c42d805e..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/PriceItemConfig.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { UsageModel } from "@autumn/shared";
-import { CircleGauge, Cog } from "lucide-react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { SelectType } from "@/components/general/SelectType";
-import { nullish } from "@/utils/genUtils";
-import CreateFixedPrice from "../../prices/CreateFixedPrice";
-import { ConfigWithFeature } from "../components/ConfigWithFeature";
-import {
- defaultPaidFeatureItem,
- defaultPriceItem,
-} from "../create-product-item/defaultItemConfigs";
-import { useProductItemContext } from "../ProductItemContext";
-
-export const PriceItemConfig = () => {
- const { item, setItem, isUpdate } = useProductItemContext();
-
- return (
-
-
- {/*
Price Type */}
-
- }
- isSelected={item.isVariable === false}
- onClick={() => {
- if (item.isVariable !== false) {
- setItem({
- ...defaultPriceItem,
- tiers: null,
- isVariable: false,
- });
- }
- }}
- disabled={item.isVariable === true && isUpdate}
- />
- }
- isSelected={item.isVariable}
- onClick={() => {
- if (!item.isVariable) {
- setItem({ ...defaultPaidFeatureItem, isVariable: true });
- }
- }}
- disabled={item.isVariable === false && isUpdate}
- />
-
-
- {item.isVariable === true && (
- <>
- {/* {item.usage_model !== null && } */}
-
- {item.feature_id &&
}
- >
- )}
- {item.isVariable === false &&
}
-
- );
-};
-
-const SelectUsageModel = () => {
- const { item, setItem } = useProductItemContext();
-
- return (
-
-
Usage Model
-
- }
- isSelected={item.usage_model === UsageModel.PayPerUse}
- onClick={() => {
- if (item.usage_model !== UsageModel.PayPerUse) {
- setItem({ ...item, usage_model: UsageModel.PayPerUse });
- }
- }}
- disabled={nullish(item.interval)}
- />
- }
- isSelected={item.usage_model === UsageModel.Prepaid}
- onClick={() => {
- if (item.usage_model !== UsageModel.Prepaid) {
- setItem({ ...item, usage_model: UsageModel.Prepaid });
- }
- }}
- />
-
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/advanced-config/AdvancedItemConfig.tsx b/vite/src/views/products/product/product-item/product-item-config/advanced-config/AdvancedItemConfig.tsx
deleted file mode 100644
index 94f398438..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/advanced-config/AdvancedItemConfig.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import { FeatureUsageType } from "@autumn/shared";
-
-import { ToggleButton } from "@/components/general/ToggleButton";
-import { Input } from "@/components/ui/input";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { notNullish } from "@/utils/genUtils";
-import {
- getFeatureCreditSystem,
- getFeatureUsageType,
-} from "@/utils/product/entitlementUtils";
-import { shouldShowProrationConfig } from "@/utils/product/productItemUtils";
-import { useProductItemContext } from "../../ProductItemContext";
-import { OnDecreaseSelect } from "./proration-config/OnDecreaseSelect";
-import { OnIncreaseSelect } from "./proration-config/OnIncreaseSelect";
-import { RolloverConfigView } from "./RolloverConfig";
-
-export const AdvancedItemConfig = () => {
- const { features } = useFeaturesQuery();
- const { item, setItem } = useProductItemContext();
-
- const showProrationConfig = shouldShowProrationConfig({ item, features });
-
- const usageType = getFeatureUsageType({ item, features });
- const hasCreditSystem = getFeatureCreditSystem({ item, features });
- const showRolloverConfig =
- (hasCreditSystem || usageType === FeatureUsageType.Single) &&
- item.interval !== null &&
- item.included_usage > 0;
-
- return (
-
-
Advanced
-
-
-
{
- setItem({
- ...item,
- reset_usage_when_enabled: !item.reset_usage_when_enabled,
- });
- }}
- infoContent="A customer has used 20/100 credits on a free plan. Then they upgrade to a Pro plan with 500 credits. If this flag is enabled, they'll get 500 credits on upgrade. If false, they'll have 480."
- buttonText={
-
- Reset existing usage when product is enabled
-
- }
- className="text-t3 h-fit items-start gap-1"
- disabled={
- usageType === FeatureUsageType.Continuous ||
- notNullish(item.config?.rollover)
- }
- switchClassName="mt-[3px]"
- />
-
-
- {
- let usage_limit;
- if (item.usage_limit) {
- usage_limit = null;
- } else {
- usage_limit = Infinity;
- }
- setItem({
- ...item,
- usage_limit: usage_limit,
- });
- }}
- buttonText="Enable usage limits"
- className="text-t3 h-fit"
- />
-
- {item.usage_limit != null && (
- {
- setItem({
- ...item,
- usage_limit: parseInt(e.target.value),
- });
- }}
- placeholder="eg. 100"
- />
- )}
-
-
- {showProrationConfig && (
- <>
-
-
- >
- )}
- {/*
-
*/}
-
- {showRolloverConfig && (
-
- )}
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/advanced-config/RolloverConfig.tsx b/vite/src/views/products/product/product-item/product-item-config/advanced-config/RolloverConfig.tsx
deleted file mode 100644
index 4066aba5a..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/advanced-config/RolloverConfig.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import {
- type ProductItem,
- type RolloverConfig,
- RolloverExpiryDurationType,
-} from "@autumn/shared";
-import { ToggleButton } from "@/components/general/ToggleButton";
-import { ToggleDisplayButton } from "@/components/general/ToggleDisplayButton";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-
-export const RolloverConfigView = ({
- item,
- setItem,
- showRolloverConfig,
-}: {
- item: ProductItem;
- setItem: (item: any) => void;
- showRolloverConfig: boolean;
-}) => {
- const defaultRollover: RolloverConfig = {
- duration: RolloverExpiryDurationType.Month,
- length: 1,
- max: null,
- };
-
- const setRolloverConfigKey = (key: keyof RolloverConfig, value: any) => {
- setItem({
- ...item,
- config: {
- ...(item.config || {}),
- rollover: {
- ...(item.config?.rollover || {}),
- [key]: value,
- },
- },
- });
- };
-
- const setRolloverConfig = (rollover: RolloverConfig | null) => {
- setItem({
- ...item,
- config: {
- ...(item.config || {}),
- rollover: rollover,
- },
- });
- };
-
- const rollover = item.config?.rollover as RolloverConfig;
-
- return (
-
-
{
- if (item.config?.rollover != null) {
- setRolloverConfig(null);
- } else {
- setItem({
- ...item,
- reset_usage_when_enabled: true,
- config: {
- ...(item.config || {}),
- rollover: defaultRollover,
- },
- });
- }
- }}
- buttonText="Enable rollovers"
- infoContent="Rollovers carry unused credits to the next billing cycle. Set a maximum rollover amount and specify how many cycles before resetting."
- className="text-t3 h-fit"
- disabled={!showRolloverConfig}
- />
-
- {item.config?.rollover && showRolloverConfig && (
-
-
-
up to
-
{
- setRolloverConfigKey("max", e.target.value);
- }}
- />
-
{
- if (rollover.max === null) {
- setRolloverConfigKey("max", 0);
- } else {
- setRolloverConfigKey("max", null);
- }
- }}
- >
- ♾️
-
-
-
-
- {rollover.duration === RolloverExpiryDurationType.Month && (
- {
- setRolloverConfigKey("length", e.target.value);
- }}
- className="w-14"
- />
- )}
- {
- setRolloverConfigKey(
- "duration",
- value as RolloverExpiryDurationType,
- );
- }}
- >
-
-
-
-
- {Object.values(RolloverExpiryDurationType).map((duration) => (
-
- {duration}
-
- ))}
-
-
-
-
- )}
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/advanced-config/proration-config/OnDecreaseSelect.tsx b/vite/src/views/products/product/product-item/product-item-config/advanced-config/proration-config/OnDecreaseSelect.tsx
deleted file mode 100644
index 0cdc70c19..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/advanced-config/proration-config/OnDecreaseSelect.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { OnDecrease, UsageModel } from "@autumn/shared";
-import { nullish } from "@/utils/genUtils";
-import { useProductItemContext } from "../../../ProductItemContext";
-import { ProrationSelect } from "./ProrationSelect";
-
-const optionToText = ({
- option,
- usageModel,
-}: {
- option: OnDecrease;
- usageModel: UsageModel;
-}) => {
- switch (option) {
- case OnDecrease.Prorate:
- return "Prorate";
- case OnDecrease.None:
- if (usageModel == UsageModel.Prepaid) {
- return "No proration (balance will be kept till next cycle)";
- }
-
- return "No proration (usage will be kept till next cycle)";
- case OnDecrease.NoProrations:
- return "No proration";
- }
-};
-
-export const OnDecreaseSelect = () => {
- const { item, setItem } = useProductItemContext();
-
- const getOnDecreaseVal = () => {
- if (nullish(item.config?.on_decrease)) {
- return OnDecrease.Prorate;
- }
-
- if (item.config?.on_decrease == OnDecrease.NoProrations) {
- return OnDecrease.NoProrations;
- }
-
- if (
- item.config?.on_decrease == OnDecrease.ProrateImmediately ||
- item.config?.on_decrease == OnDecrease.ProrateNextCycle ||
- item.config?.on_decrease == OnDecrease.Prorate
- ) {
- return OnDecrease.Prorate;
- }
-
- return OnDecrease.None;
- };
-
- const text =
- item.usage_model == UsageModel.PayPerUse
- ? "On usage decrease"
- : "On quantity decrease";
-
- return (
-
-
{text}
-
-
{
- setItem({
- ...item,
- config: { ...item.config, on_decrease: value },
- });
- }}
- optionToText={(option: OnDecrease) =>
- optionToText({ option, usageModel: item.usage_model })
- }
- options={[OnDecrease.Prorate, OnDecrease.None]}
- />
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/advanced-config/proration-config/OnIncreaseSelect.tsx b/vite/src/views/products/product/product-item/product-item-config/advanced-config/proration-config/OnIncreaseSelect.tsx
deleted file mode 100644
index 0fa88d77b..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/advanced-config/proration-config/OnIncreaseSelect.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { OnIncrease, UsageModel } from "@autumn/shared";
-import { useProductItemContext } from "../../../ProductItemContext";
-import { ProrationSelect } from "./ProrationSelect";
-
-const optionToText = (option: OnIncrease) => {
- switch (option) {
- case OnIncrease.BillImmediately:
- return "Pay full amount immediately";
- case OnIncrease.ProrateImmediately:
- return "Pay for prorated amount immediately";
- case OnIncrease.ProrateNextCycle:
- return "Add prorated amount to next invoice";
- case OnIncrease.BillNextCycle:
- return "Pay for full amount next cycle";
- }
-};
-
-export const OnIncreaseSelect = () => {
- const { item, setItem } = useProductItemContext();
-
- const value = item.config?.on_increase;
-
- const text =
- item.usage_model == UsageModel.PayPerUse
- ? "On usage increase"
- : "On quantity increase";
-
- return (
-
-
{text}
-
- setItem({ ...item, config: { ...item.config, on_increase: value } })
- }
- optionToText={optionToText}
- options={Object.values(OnIncrease).filter((o) => {
- if (
- item.usage_model == UsageModel.Prepaid &&
- o == OnIncrease.BillImmediately
- )
- return false;
- return true;
- })}
- />
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/advanced-config/proration-config/ProrationSelect.tsx b/vite/src/views/products/product/product-item/product-item-config/advanced-config/proration-config/ProrationSelect.tsx
deleted file mode 100644
index 0f33fc25e..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/advanced-config/proration-config/ProrationSelect.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { OnDecrease, OnIncrease } from "@autumn/shared";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-
-export const ProrationSelect = ({
- value,
- setValue,
- optionToText,
- options,
-}: {
- value: OnDecrease | OnIncrease;
- setValue: (value: OnDecrease | OnIncrease) => void;
- optionToText: any;
- options: (OnDecrease | OnIncrease)[];
-}) => {
- return (
- setValue(value as OnDecrease | OnIncrease)}
- >
-
-
-
- {optionToText(value)}
-
-
-
-
- {options.map((value) => (
-
- {optionToText(value)}
-
- ))}
-
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/components/CusomiseIntervalPopover.tsx b/vite/src/views/products/product/product-item/product-item-config/components/CusomiseIntervalPopover.tsx
deleted file mode 100644
index e2191e0b6..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/components/CusomiseIntervalPopover.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Infinite } from "autumn-js";
-import { useRef, useState } from "react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { useProductItemContext } from "../../ProductItemContext";
-
-export const CustomiseIntervalPopover = () => {
- const [open, setOpen] = useState(false);
- const { item, setItem } = useProductItemContext();
- const [intervalCount, setIntervalCount] = useState(item.interval_count || 1);
- const triggerRef = useRef(null);
-
- const handleSave = () => {
- setItem({
- ...item,
- interval_count: parseInt(intervalCount || 1),
- });
- setOpen(false);
- };
-
- return (
-
-
-
- Customise Interval
-
-
- e.preventDefault()}
- onCloseAutoFocus={(e) => e.preventDefault()}
- // avoidCollisions={false}
- // sticky="always"
- >
-
- Interval Count
-
-
- setIntervalCount(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === "Enter") {
- handleSave();
- }
- if (e.key === "Escape") {
- setOpen(false);
- }
- }}
- />
-
- Save
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/components/IncludedUsage.tsx b/vite/src/views/products/product/product-item/product-item-config/components/IncludedUsage.tsx
deleted file mode 100644
index f15cdccd3..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/components/IncludedUsage.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import {
- BillingInterval,
- EntInterval,
- Infinite,
- UsageModel,
-} from "@autumn/shared";
-import { useState } from "react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip";
-import { ToggleDisplayButton } from "@/components/general/ToggleDisplayButton";
-import { Input } from "@/components/ui/input";
-import { isFeaturePriceItem } from "@/utils/product/getItemType";
-import { itemIsUnlimited } from "@/utils/product/productItemUtils";
-import { useProductItemContext } from "../../ProductItemContext";
-
-export const IncludedUsage = () => {
- const { item, setItem } = useProductItemContext();
- const isFeaturePrice = isFeaturePriceItem(item);
- const [open, setOpen] = useState(false);
- const [intervalCount, setIntervalCount] = useState(
- item.interval_count || 1,
- );
-
- const handleBillingIntervalSelected = (
- value: BillingInterval | EntInterval,
- ) => {
- let usageModel = item.usage_model;
- if (value === BillingInterval.OneOff) {
- usageModel = UsageModel.Prepaid;
- }
-
- setItem({
- ...item,
- interval:
- value === BillingInterval.OneOff || value === EntInterval.Lifetime
- ? null
- : value,
- usage_model: usageModel,
- });
- };
-
- const handleSaveCustomInterval = () => {
- setItem({
- ...item,
- interval_count: parseInt(intervalCount.toString()),
- });
- setOpen(false);
- };
-
- // Get current interval for display
- const currentInterval = item.interval ?? BillingInterval.OneOff;
-
- return (
-
-
-
- Included Usage
-
-
- How much of this feature can be used for free with this plan. If
- there is no price, it is a usage limit.
-
-
- Leave this blank if the feature is paid-only. Eg, 10 USD per seat.
-
-
-
-
- {
- const newItem = {
- ...item,
- included_usage: e.target.value,
- };
-
- setItem(newItem);
- }}
- />
- {
- if (itemIsUnlimited(item)) {
- setItem({
- ...item,
- included_usage: "",
- });
- } else {
- setItem({
- ...item,
- included_usage: Infinite,
- interval: null,
- });
- }
- }}
- >
- ♾️
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/components/SelectFeature.tsx b/vite/src/views/products/product/product-item/product-item-config/components/SelectFeature.tsx
deleted file mode 100644
index a3c3a1200..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/components/SelectFeature.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { type Feature, FeatureType } from "@autumn/shared";
-import { ArrowLeft, Plus } from "lucide-react";
-import {
- CustomDialogBody,
- CustomDialogFooter,
-} from "@/components/general/modal-components/DialogContentWrapper";
-import { Button } from "@/components/ui/button";
-import { DialogHeader } from "@/components/ui/dialog";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { isFeaturePriceItem } from "@/utils/product/getItemType";
-import { FeatureTypeBadge } from "@/views/products/features/components/FeatureTypeBadge";
-import { useProductItemContext } from "../../ProductItemContext";
-import { CreateItemStep } from "../../utils/CreateItemStep";
-
-export const SelectFeatureStep = ({
- popStep,
- pushStep,
-}: {
- popStep: () => void;
- pushStep: (step: CreateItemStep) => void;
-}) => {
- const { features } = useFeaturesQuery();
- const { item } = useProductItemContext();
-
- return (
- <>
-
- Select a feature
-
- {features
- .filter((feature: Feature) => {
- if (isFeaturePriceItem(item)) {
- return feature.type !== FeatureType.Boolean;
- }
- return true;
- })
- .map((feature: Feature, index: number) => (
-
{
- // setItem({ ...item, feature_id: feature.id! });
- }}
- >
- {feature.name}
-
-
- ))}
-
-
- }
- onClick={() => {
- pushStep(CreateItemStep.CreateFeature);
- }}
- >
- Create new feature
-
-
-
-
- {
- popStep();
- }}
- >
-
- Back
-
-
- >
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/components/SelectResetCycle.tsx b/vite/src/views/products/product/product-item/product-item-config/components/SelectResetCycle.tsx
deleted file mode 100644
index 4d25c611a..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/components/SelectResetCycle.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-import {
- BillingInterval,
- EntInterval,
- FeatureUsageType,
- Infinite,
- itemToEntInterval,
-} from "@autumn/shared";
-import { useState } from "react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { cn } from "@/lib/utils";
-import { getFeatureUsageType } from "@/utils/product/entitlementUtils";
-import { isFeaturePriceItem } from "@/utils/product/getItemType";
-import { useProductItemContext } from "../../ProductItemContext";
-import { CustomiseIntervalPopover } from "./CusomiseIntervalPopover";
-
-const getIntervalText = ({
- interval,
- intervalCount,
- billingInterval,
-}: {
- interval?: EntInterval;
- billingInterval?: BillingInterval;
- intervalCount?: number;
-}) => {
- const finalInterval = interval ?? billingInterval;
- if (
- finalInterval === EntInterval.Lifetime ||
- finalInterval === BillingInterval.OneOff
- ) {
- return "no reset";
- }
- if (intervalCount && intervalCount > 1) {
- return `per ${intervalCount} ${finalInterval}s`;
- }
- return finalInterval === BillingInterval.SemiAnnual
- ? "per half year"
- : `per ${finalInterval}`;
-};
-
-export const SelectResetCycle = () => {
- const { features } = useFeaturesQuery();
- const { item, setItem } = useProductItemContext();
-
- const handleChange = (value: EntInterval) => {
- setItem({
- ...item,
- interval: value === EntInterval.Lifetime ? null : (value as EntInterval),
- });
- };
-
- const isFeaturePrice = isFeaturePriceItem(item!);
- const usageType = getFeatureUsageType({ item: item!, features });
-
- if (usageType === FeatureUsageType.Continuous) {
- return null;
- }
-
- const interval = itemToEntInterval({ item: item! });
-
- return (
-
-
- Reset Interval
-
-
- How often usage counts reset for this feature. Choose "no reset" for
- items that don't expire.
-
-
-
-
- {
- handleChange(value as EntInterval);
- }}
- >
-
-
-
- {getIntervalText({
- interval,
- intervalCount: item?.interval_count ?? 1,
- })}
-
-
-
-
- {Object.values(EntInterval)
- .filter((i) => i !== EntInterval.Minute)
- .map((intervalOption) => {
- const isSelected = intervalOption === interval;
- return (
-
- );
- })}
-
-
-
-
-
- );
-};
-
-// const SelectIntervalCountPopover = () => {
-// const [open, setOpen] = useState(false);
-// const { item, setItem } = useProductItemContext();
-// const [intervalCount, setIntervalCount] = useState(item.interval_count || 1);
-
-// const handleSave = () => {
-// setItem({
-// ...item,
-// interval_count: parseInt(intervalCount || 1),
-// });
-// setOpen(false);
-// };
-
-// return (
-//
-//
-//
-// {/* */}
-// Customise Interval
-//
-//
-// e.preventDefault()}
-// onCloseAutoFocus={(e) => e.preventDefault()}
-// >
-//
-// Interval Count
-//
-//
-// setIntervalCount(e.target.value)}
-// onKeyDown={(e) => {
-// if (e.key === "Enter") {
-// handleSave();
-// }
-// if (e.key === "Escape") {
-// setOpen(false);
-// }
-// }}
-// />
-//
-// Save
-//
-//
-//
-//
-// );
-// };
-
-const SelectIntervalItem = ({
- interval,
- isSelected,
-}: {
- interval: EntInterval;
- isSelected: boolean;
-}) => {
- const { item } = useProductItemContext();
- const [hover, setHover] = useState(false);
-
- return (
- setHover(true)}
- onMouseLeave={() => setHover(false)}
- >
-
-
- {getIntervalText({ interval, intervalCount: item!.interval_count })}
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/BillingUnits.tsx b/vite/src/views/products/product/product-item/product-item-config/components/feature-price/BillingUnits.tsx
deleted file mode 100644
index 4d013b1a1..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/BillingUnits.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import { type Feature, getFeatureName } from "@autumn/shared";
-import { useRef, useState } from "react";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { cn } from "@/lib/utils";
-import { useProductItemContext } from "../../../ProductItemContext";
-
-export const BillingUnits = ({
- className,
- disabled,
-}: {
- className?: string;
- disabled: boolean;
-}) => {
- const { features } = useFeaturesQuery();
- const { item, setItem } = useProductItemContext();
- const [popoverOpen, setPopoverOpen] = useState(false);
- const triggerRef = useRef(null);
-
- const featureName = getFeatureName({
- feature: features.find((f: Feature) => f.id == item.feature_id),
- plural: item.billing_units > 1,
- });
-
- const [inputValue, setInputValue] = useState(item.billing_units);
-
- const handleEnterClick = () => {
- let num = Number(inputValue);
- if (isNaN(num) || num <= 0) {
- num = 1;
- }
- setItem({ ...item, billing_units: num });
- setPopoverOpen(false);
- };
-
- return (
-
- );
-
- // return (
- // <>
- // {editBillingUnits ? (
- // <>
- // per
- // setEditBillingUnits(false)}
- // >
- //
- // setItem({
- // ...item,
- // billing_units: e.target.value,
- // })
- // }
- // />
- //
- // {featureName ?? "units"}
- //
- //
- // >
- // ) : (
- //
- // setEditBillingUnits(true)}
- // >
- //
- // {item.billing_units == 1
- // ? `per ${featureName ?? "units"}`
- // : `per ${item.billing_units || ""} ${featureName ?? "units"}`}
- //
- //
- //
- // )}
- // >
- // );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/FeaturePrice.tsx b/vite/src/views/products/product/product-item/product-item-config/components/feature-price/FeaturePrice.tsx
deleted file mode 100644
index d6546be50..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/FeaturePrice.tsx
+++ /dev/null
@@ -1,206 +0,0 @@
-import {
- Infinite,
- type PriceTier,
- type ProductItem,
- TierInfinite,
-} from "@autumn/shared";
-import { Plus, X } from "lucide-react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { useOrg } from "@/hooks/common/useOrg";
-import { cn } from "@/lib/utils";
-import { useProductItemContext } from "../../../ProductItemContext";
-import { BillingUnits } from "./BillingUnits";
-import { UsageTierInput } from "./UsageTierInput";
-
-export default function FeaturePrice() {
- const { item, setItem } = useProductItemContext();
-
- const setUsageTier = (index: number, key: string, value: string | number) => {
- const newUsageTiers = [...item.tiers];
- newUsageTiers[index] = { ...newUsageTiers[index], [key]: value };
- if (key === "to" && newUsageTiers[index + 1]) {
- newUsageTiers[index + 1].from = value; // set value of next tier from to the value of the current tier to
- }
- setItem({ ...item, tiers: newUsageTiers });
- };
-
- const handleAddTier = () => {
- const newTiers = [...item.tiers];
-
- // Set current last tier
- newTiers[newTiers.length - 1].to =
- newTiers.length > 1 ? newTiers[newTiers.length - 2].to : 0;
-
- newTiers.push({
- to: TierInfinite,
- amount: 0,
- });
- setItem({ ...item, tiers: newTiers });
- };
-
- const handlePriceRemoved = () => {
- setItem({
- feature_id: item.feature_id,
- included_usage: item.included_usage || 0,
- interval: item.interval,
- entity_feature_id: item.entity_feature_id,
- reset_usage_when_enabled: item.reset_usage_when_enabled,
- config: item.config,
- });
- };
- const handleRemoveTier = (index: number) => {
- const newTiers = [...item.tiers];
-
- if (newTiers.length === 1) {
- handlePriceRemoved();
- return;
- }
-
- newTiers.splice(index, 1);
- newTiers[newTiers.length - 1].to = TierInfinite;
- setItem({ ...item, tiers: newTiers });
- };
-
- const hasTiers = item.tiers?.length > 1;
-
- return (
-
-
Pricing
-
- {hasTiers ? (
-
- ) : (
-
- )}
-
-
- );
-}
-
-const MultiTierPrice = ({
- handleAddTier,
- handleRemoveTier,
- setUsageTier,
-}: {
- handleAddTier: () => void;
- handleRemoveTier: (index: number) => void;
- setUsageTier: (index: number, key: string, value: string | number) => void;
-}) => {
- const {
- item,
- }: {
- item: ProductItem;
- } = useProductItemContext();
- return (
- <>
- {item.tiers?.map((tier: PriceTier, index: number) => (
-
-
-
-
- {/* {index == 0 && item.included_usage > 0 && (
- then
- )} */}
- null}
- type="from"
- />
-
-
to
-
- {
- setUsageTier(index, "to", e.target.value);
- }}
- type="to"
- />
-
-
-
-
- setUsageTier(index, "amount", e.target.value)}
- type="amount"
- />
-
-
0} />
-
-
-
handleAddTier()}
- dim={6}
- >
-
-
-
handleRemoveTier(index)}
- dim={6}
- >
-
-
-
-
- ))}
- >
- );
-};
-
-const SingleTierPrice = ({ handleAddTier }: { handleAddTier: () => void }) => {
- const { item, setItem } = useProductItemContext();
- const { org } = useOrg();
- const currency = org?.default_currency.toUpperCase() ?? "USD";
-
- return (
-
-
-
- setItem({
- ...item,
- tiers: [{ amount: e.target.value, to: Infinite }],
- })
- }
- endContent={
- {currency}
- }
- />
-
-
-
- Add Tiers
-
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/PrepaidToggle.tsx b/vite/src/views/products/product/product-item/product-item-config/components/feature-price/PrepaidToggle.tsx
deleted file mode 100644
index 70ce5ac6e..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/PrepaidToggle.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { UsageModel } from "autumn-js";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip";
-import { ToggleButton } from "@/components/general/ToggleButton";
-import { useProductItemContext } from "../../../ProductItemContext";
-
-export const PrepaidToggle = () => {
- const { item, setItem } = useProductItemContext();
-
- return (
-
-
{"\u00A0"}
-
- {
- setItem({
- ...item,
- usage_model:
- item.usage_model === UsageModel.Prepaid
- ? UsageModel.PayPerUse
- : UsageModel.Prepaid,
- });
- }}
- buttonText="Prepaid"
- className="text-xs gap-2 text-t1 p-1"
- />
-
- Prepaid features are paid for upfront, instead of at the end of the
- billing period. You can pass in a{" "}
-
- quantity
- {" "}
- field when purchasing the product.
-
-
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/SelectBillingCycle.tsx b/vite/src/views/products/product/product-item/product-item-config/components/feature-price/SelectBillingCycle.tsx
deleted file mode 100644
index 2f905d31d..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/SelectBillingCycle.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { BillingInterval, UsageModel } from "@autumn/shared";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { formatIntervalText } from "@/utils/formatUtils/formatTextUtils";
-import { useProductItemContext } from "../../../ProductItemContext";
-import { CustomiseIntervalPopover } from "../CusomiseIntervalPopover";
-
-export const SelectCycle = () => {
- const { item, setItem } = useProductItemContext();
-
- const handleBillingIntervalSelected = (value: BillingInterval) => {
- let usageModel = item.usage_model;
- if (value == BillingInterval.OneOff) {
- usageModel = UsageModel.Prepaid;
- }
-
- setItem({
- ...item,
- interval: value == BillingInterval.OneOff ? null : value,
- usage_model: usageModel,
- });
- };
-
- return (
-
-
- Billing Interval
-
-
- How often to bill for this feature. Usage will also be reset on this
- period.
-
-
-
-
-
-
-
-
-
-
- {Object.values(BillingInterval).map((interval) => (
-
- {formatIntervalText({
- billingInterval: interval,
- intervalCount: item.interval_count,
- isBillingInterval: true,
- })}
-
- ))}
-
-
-
-
-
-
- );
-};
-
-{
- /*
-
-
-
-
- {featureItem ? (
-
- How often usage counts reset for this feature. Choose "no reset"
- for items that don't expire.
-
- ) : (
-
- How often to bill for this feature. Usage will also be reset on
- this period.
-
- )}
-
- */
-}
diff --git a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/UsageTierInput.tsx b/vite/src/views/products/product/product-item/product-item-config/components/feature-price/UsageTierInput.tsx
deleted file mode 100644
index 1dbbead64..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/UsageTierInput.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Infinite } from "@autumn/shared";
-import { Input } from "@/components/ui/input";
-import { useOrg } from "@/hooks/common/useOrg";
-import { cn } from "@/lib/utils";
-
-export const UsageTierInput = ({
- value,
- onChange,
- type,
-}: {
- value: number | string;
- onChange: (e: any) => void;
- type: "from" | "to" | "amount";
-}) => {
- const { org } = useOrg();
-
- const defaultCurrency = org?.default_currency?.toUpperCase() ?? "USD";
-
- if ((type === "to" && value === Infinite) || type === "from") {
- return (
-
- );
- }
-
- return (
-
-
- {type === "amount" && (
-
- {defaultCurrency}
-
- )}
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddToEntityDropdown.tsx b/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddToEntityDropdown.tsx
deleted file mode 100644
index e49afa4ed..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddToEntityDropdown.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Button } from "@/components/ui/button";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { useProductContext } from "../../../ProductContext";
-import { useProductItemContext } from "../../ProductItemContext";
-
-export const AddToEntityDropdown = () => {
- const { product, entityFeatureIds } = useProductContext();
- const { item, handleCreateProductItem } = useProductItemContext();
-
- return (
- <>
-
-
-
- Add Item
-
-
-
-
- {
- // setItem({
- // ...item,
- // entity_feature_id: product.name,
- // });
- handleCreateProductItem(null);
- }}
- >
- {product.name} (Product)
-
- {entityFeatureIds.map((entityFeatureId: string) => (
- {
- // setItem({
- // ...item,
- // entity_feature_id: entityFeatureId,
- // });
- handleCreateProductItem(entityFeatureId);
- }}
- >
- {entityFeatureId} (Entity)
-
- ))}
-
-
-
- >
- );
-};
diff --git a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/ItemConfigFooter.tsx b/vite/src/views/products/product/product-item/product-item-config/item-config-footer/ItemConfigFooter.tsx
deleted file mode 100644
index dabe416d3..000000000
--- a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/ItemConfigFooter.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { ArrowLeftIcon, XIcon } from "lucide-react";
-import { CustomDialogFooter } from "@/components/general/modal-components/DialogContentWrapper";
-import { Button } from "@/components/ui/button";
-import { cn } from "@/lib/utils";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { isEmptyItem } from "@/utils/product/getItemType";
-import { handleAutoSave } from "@/views/onboarding2/model-pricing/model-pricing-utils/modelPricingUtils";
-import { useProductContext } from "../../../ProductContext";
-import { useProductItemContext } from "../../ProductItemContext";
-import { AddToEntityDropdown } from "./AddToEntityDropdown";
-
-export const ItemConfigFooter = ({
- handleBack,
-}: {
- handleBack?: () => void;
-}) => {
- const axiosInstance = useAxiosInstance();
- const {
- entityFeatureIds,
- product,
- autoSave,
- // This refetch is used in the onboarding flow
- refetch,
- } = useProductContext();
-
- const {
- item,
- handleCreateProductItem,
- handleUpdateProductItem,
- handleDeleteProductItem,
- } = useProductItemContext();
-
- const showEntityDropdown =
- item.feature_id &&
- !entityFeatureIds.includes(item.feature_id) &&
- entityFeatureIds.length > 0;
-
- const isEmpty = isEmptyItem(item);
-
- return (
-
- {handleBack ? (
- }
- >
- Back
-
- ) : (
-
- )}
-
-
- {handleUpdateProductItem && (
-
}
- onClick={async () => {
- const newProduct = await handleDeleteProductItem();
-
- if (autoSave && newProduct) {
- handleAutoSave({
- axiosInstance,
- productId: product.id,
- product,
- refetch,
- });
- }
- }}
- >
- Delete Item
-
- )}
- {handleUpdateProductItem && (
-
{
- const newProduct = await handleUpdateProductItem();
-
- if (autoSave && newProduct) {
- handleAutoSave({
- axiosInstance,
- productId: product.id,
- product,
- refetch,
- });
- }
- }}
- >
- Update Item
-
- )}
- {showEntityDropdown && handleCreateProductItem && (
-
- )}
- {!showEntityDropdown && handleCreateProductItem && (
-
{
- const newProduct = await handleCreateProductItem(null);
-
- if (autoSave && newProduct) {
- handleAutoSave({
- axiosInstance,
- productId: product.id,
- product: newProduct,
- refetch,
- });
- }
- }}
- disabled={isEmpty}
- >
- Add Item
-
- )}
-
-
- );
-};
diff --git a/vite/src/views/products/product/product-item/useSteps.tsx b/vite/src/views/products/product/product-item/useSteps.tsx
deleted file mode 100644
index 3e6e42082..000000000
--- a/vite/src/views/products/product/product-item/useSteps.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { useState } from "react";
-import type { CreateItemStep } from "./utils/CreateItemStep";
-
-export const useSteps = ({
- initialStep,
-}: {
- initialStep: T;
-}) => {
- const [stepVal, setStepVal] = useState(initialStep);
- const [stepStack, setStepStack] = useState([initialStep]);
-
- const popStep = () => {
- if (stepStack.length === 1) {
- return;
- }
-
- setStepStack((prev) => {
- const newStack = prev.slice(0, -1);
- const newStep = newStack[newStack.length - 1];
- setStepVal(newStep);
- return newStack;
- });
- };
-
- const pushStep = (step: T) => {
- setStepStack((prev) => [...prev, step]);
- setStepVal(step);
- };
-
- const resetSteps = () => {
- setStepStack([initialStep]);
- setStepVal(initialStep);
- };
-
- const replaceStep = (step: T) => {
- const curStack = stepStack;
- if (curStack.length <= 1) {
- setStepStack([step]);
- setStepVal(step);
- } else {
- setStepStack((prev) => [...prev.slice(0, -1), step]);
- setStepVal(step);
- }
- };
-
- return {
- stepVal,
- popStep,
- pushStep,
- resetSteps,
- replaceStep,
- previousStep: stepStack.length > 1 ? stepStack[stepStack.length - 2] : null,
- stepCount: stepStack.length,
- };
-};
diff --git a/vite/src/views/products/product/product-item/utils/CreateItemStep.tsx b/vite/src/views/products/product/product-item/utils/CreateItemStep.tsx
deleted file mode 100644
index 22acf5d0e..000000000
--- a/vite/src/views/products/product/product-item/utils/CreateItemStep.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-export enum CreateItemStep {
- SelectItemType = "select_item_type",
- CreateFeature = "create_feature",
- SelectFeature = "select_feature",
- CreateItem = "create_item",
-}
diff --git a/vite/src/views/products/product/product-item/utils/getFrontendItemConfig.tsx b/vite/src/views/products/product/product-item/utils/getFrontendItemConfig.tsx
deleted file mode 100644
index 03a65b4b3..000000000
--- a/vite/src/views/products/product/product-item/utils/getFrontendItemConfig.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { FrontendProductItem, ProductItem } from "@autumn/shared";
-import {
- isFeatureItem,
- isFeaturePriceItem,
- isPriceItem,
-} from "@/utils/product/getItemType";
-
-export const getFrontendItemConfig = (item: ProductItem) => {
- // 1. Convert included usage + tiers
-
- const finalItem: FrontendProductItem = {
- ...structuredClone(item),
- isPrice: false,
- };
-
- if (isFeatureItem(item)) return finalItem;
-
- if (isFeaturePriceItem(item)) {
- console.log("item", item);
- const includedUsage =
- typeof item.included_usage === "number" ? item.included_usage : 0;
-
- if (includedUsage > 0) {
- const finalTiers = (item.tiers || []).map((tier) => ({
- ...tier,
- to: typeof tier.to === "number" ? tier.to + includedUsage : tier.to,
- }));
-
- finalItem.tiers = [{ amount: 0, to: includedUsage }, ...finalTiers];
- }
-
- finalItem.included_usage = 0;
- finalItem.isVariable = true;
- } else if (isPriceItem(item)) {
- finalItem.isVariable = false;
- }
-
- finalItem.isPrice = true;
- return finalItem;
-};
diff --git a/vite/src/views/products/product/product-sidebar/ToggleDefaultProduct.tsx b/vite/src/views/products/product/product-sidebar/ToggleDefaultProduct.tsx
deleted file mode 100644
index 2b3dd9e49..000000000
--- a/vite/src/views/products/product/product-sidebar/ToggleDefaultProduct.tsx
+++ /dev/null
@@ -1,213 +0,0 @@
-import { isFreeProductV2 } from "@autumn/shared";
-import { useState } from "react";
-import { toast } from "sonner";
-import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip";
-import { ToggleButton } from "@/components/general/ToggleButton";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr, notNullish } from "@/utils/genUtils";
-import { useProductCountsQuery } from "../hooks/queries/useProductCountsQuery";
-import { useProductContext } from "../ProductContext";
-
-const ToggleProductDialog = ({
- open,
- setOpen,
- description,
- toggleKey,
- value,
- toggleProduct,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
- description: string;
- toggleKey: "is_default" | "is_add_on";
- value: boolean;
- toggleProduct: (value: boolean, optimisticUpdate?: boolean) => Promise;
-}) => {
- const { product } = useProductContext();
- const [loading, setLoading] = useState(false);
- const handleConfirm = async () => {
- setLoading(true);
- try {
- await toggleProduct(value, false);
- setOpen(false);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to update plan"));
- }
- setLoading(false);
- };
-
- const getTitle = () => {
- if (toggleKey === "is_default") {
- return value
- ? `Make ${product.name} a default plan`
- : `Remove default from ${product.name}`;
- } else {
- return value
- ? `Make ${product.name} an add-on`
- : `Remove ${product.name} as an add-on`;
- }
- };
- return (
-
-
-
- {getTitle()}
-
-
- {description}
-
-
-
- Confirm
-
-
-
-
- );
-};
-
-export const ToggleDefaultProduct = ({
- toggleKey,
-}: {
- toggleKey: "is_default" | "is_add_on";
-}) => {
- const axiosInstance = useAxiosInstance();
- const { product, setProduct, isCusProductView, groupDefaults } =
- useProductContext();
-
- const { counts } = useProductCountsQuery();
-
- const activeCount = counts?.active;
- const [open, setOpen] = useState(false);
- const [dialogDescription, setDialogDescription] = useState("");
- const [toggling, setToggling] = useState(false);
-
- const toggleProduct = async (value: boolean, optimisticUpdate = true) => {
- setToggling(true);
-
- if (toggling) {
- return;
- }
-
- try {
- if (optimisticUpdate) {
- setProduct({
- ...product,
- [toggleKey]: value,
- });
- }
-
- const data = {
- [toggleKey]: value,
- free_trial: toggleKey === "is_default" ? product.free_trial : undefined,
- };
-
- await ProductService.updateProduct(axiosInstance, product.id, data);
- // mutate();
- setOpen(false);
- toast.success("Successfully updated plan");
- } catch (error) {
- setProduct({
- ...product,
- [toggleKey]: !value,
- });
-
- toast.error(getBackendErr(error, "Failed to update plan"));
- } finally {
- setToggling(false);
- }
- };
-
- const handleToggle = async (value: boolean) => {
- if (toggling) return;
-
- const disableDefaultDescription = getDisableDefaultDescription(value);
- if (disableDefaultDescription) {
- setDialogDescription(disableDefaultDescription);
- setOpen(true);
- return;
- }
-
- if (activeCount > 0) {
- const activeCusStr = activeCount === 1 ? "customer" : "customers";
- // 1. If key is default
- if (toggleKey === "is_default") {
- if (value) {
- setDialogDescription(
- `You have ${activeCount} active ${activeCusStr} on this plan. Are you sure you want to make this plan default?`,
- );
- } else {
- setDialogDescription(
- `You have ${activeCount} active ${activeCusStr} on this plan. Are you sure you want to remove this plan as default?`,
- );
- }
- } else {
- if (value) {
- setDialogDescription(
- `You have ${activeCount} active ${activeCusStr} on this plan. Are you sure you want to make this plan an add-on?`,
- );
- } else {
- setDialogDescription(
- `You have ${activeCount} active ${activeCusStr} on this plan. Are you sure you want to remove this plan as an add-on?`,
- );
- }
- }
- setOpen(true);
- } else {
- await toggleProduct(value);
- }
- };
-
- const getDisableDefaultDescription = (value: boolean) => {
- // 1. Is default trial
- if (toggleKey !== "is_default") return;
-
- const isDefaultTrial =
- value && product.free_trial && !isFreeProductV2(product);
-
- if (isDefaultTrial && notNullish(groupDefaults?.defaultTrial)) {
- return `${groupDefaults.defaultTrial.name} is currently a default trial plan. Making ${product.name} a default trial will remove ${groupDefaults.defaultTrial.name} as a default trial plan.`;
- }
-
- if (value && notNullish(groupDefaults?.free)) {
- return `${groupDefaults.free.name} is currently a default plan. Making ${product.name} a default plan will remove ${groupDefaults.free.name} as a default plan.`;
- }
- };
-
- const isDisabled =
- (toggleKey === "is_add_on" && product.is_default) ||
- (toggleKey === "is_default" && product.is_add_on);
-
- return (
- <>
-
-
- >
- );
-};
diff --git a/vite/src/views/products/product/versioning/ConfirmMigrateDialog.tsx b/vite/src/views/products/product/versioning/ConfirmMigrateDialog.tsx
deleted file mode 100644
index 7b9a986bf..000000000
--- a/vite/src/views/products/product/versioning/ConfirmMigrateDialog.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { useProductContext } from "../ProductContext";
-
-export default function ConfirmNewVersionDialog({
- open,
- setOpen,
- startMigration,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
- startMigration: () => Promise;
-}) {
- const { product, version } = useProductContext();
- const [confirmText, setConfirmText] = useState("");
- const [isLoading, setIsLoading] = useState(false);
-
- const onClick = async () => {
- if (confirmText !== product.id) {
- toast.error("Confirmation text is incorrect");
- return;
- }
-
- setIsLoading(true);
- await startMigration();
- setIsLoading(false);
- setOpen(false);
- };
-
- return (
-
-
- {/* Confirm New Version */}
-
-
-
- Migrate customers?
-
-
- Note: This will migrate all customers on {product.name} (version{" "}
- {version}) to the latest version. Custom plans and cancelled plans
- will not be migrated.
-
-
- Type {product.id} to continue.
-
- setConfirmText(e.target.value)}
- type="text"
- placeholder={product.id}
- className="w-full text-black"
- />
-
-
-
-
- Start migration
-
-
-
-
- );
-}
diff --git a/vite/src/views/products/product/versioning/ConfirmNewVersionDialog.tsx b/vite/src/views/products/product/versioning/ConfirmNewVersionDialog.tsx
deleted file mode 100644
index 1899c04c4..000000000
--- a/vite/src/views/products/product/versioning/ConfirmNewVersionDialog.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useProductQuery } from "../hooks/useProductQuery";
-import { useProductContext } from "../ProductContext";
-import { updateProduct } from "../utils/updateProduct";
-
-export default function ConfirmNewVersionDialog({
- open,
- setOpen,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
-}) {
- const axiosInstance = useAxiosInstance();
- const { product } = useProductContext();
- const { refetch } = useProductQuery();
-
- const [confirmText, setConfirmText] = useState("");
- const [isLoading, setIsLoading] = useState(false);
-
- const onClick = async () => {
- if (confirmText !== product.id) {
- toast.error("Confirmation text is incorrect");
- return;
- }
-
- setIsLoading(true);
- await updateProduct({
- axiosInstance,
- product,
- onSuccess: async () => {
- await refetch();
- },
- });
- setIsLoading(false);
- setOpen(false);
- // toast.success("New version created successfully");
- };
-
- return (
-
-
-
-
- Create new version?
-
-
- After creating a new version, it will be{" "}
-
- active immediately for new customers
-
- . You can migrate existing customers to the new version
- after.
-
-
- Type {product.id} to continue.
-
- setConfirmText(e.target.value)}
- type="text"
- placeholder={product.id}
- className="w-full text-black"
- />
-
-
-
-
- Create new version
-
-
-
-
- );
-}
diff --git a/vite/src/views/products/product/versioning/CountAndMigrate.tsx b/vite/src/views/products/product/versioning/CountAndMigrate.tsx
deleted file mode 100644
index 4a488b70e..000000000
--- a/vite/src/views/products/product/versioning/CountAndMigrate.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import { type MigrationJob, MigrationJobStep } from "@autumn/shared";
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@radix-ui/react-tooltip";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import SmallSpinner from "@/components/general/SmallSpinner";
-import { Button } from "@/components/ui/button";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { isOneOffProduct } from "@/utils/product/priceUtils";
-import { useMigrationsQuery } from "../hooks/queries/useMigrationsQuery.tsx";
-import { useProductCountsQuery } from "../hooks/queries/useProductCountsQuery";
-import { useProductQuery } from "../hooks/useProductQuery";
-import ConfirmMigrateDialog from "./ConfirmMigrateDialog";
-
-export const CountAndMigrate = () => {
- const { product, numVersions } = useProductQuery();
- const { counts, refetch: refetchCounts } = useProductCountsQuery();
- const { migrations, refetch: refetchMigrations } = useMigrationsQuery();
-
- const axiosInstance = useAxiosInstance();
- const [loading, setLoading] = useState(false);
- const [confirmMigrateOpen, setConfirmMigrateOpen] = useState(false);
-
- const migrateCustomers = async () => {
- setLoading(true);
- try {
- const { data } = await axiosInstance.post("/v1/migrations", {
- from_product_id: product.id,
- from_version: product.version,
- to_product_id: product.id,
- to_version: numVersions,
- });
-
- await refetchMigrations();
-
- toast.success(`Migration started. ID: ${data.id}`);
- } catch (error) {
- toast.error(getBackendErr(error, "Something went wrong with migration"));
- }
- setLoading(false);
- };
-
- const onMigrateClicked = () => {
- setConfirmMigrateOpen(true);
- };
-
- useEffect(() => {
- if (migrations.length > 0) {
- // Run poll job on mutate
- const pollInterval = setInterval(() => {
- refetchCounts();
- refetchMigrations();
- }, 5000);
- return () => clearInterval(pollInterval);
- }
- }, [migrations]);
-
- if (!counts) {
- return <>>;
- }
-
- const renderCurrentMigration = () => {
- const migration: MigrationJob = migrations[0];
-
- const getCusDetails = migration.step_details[MigrationJobStep.GetCustomers];
- const migrateDetails =
- migration.step_details[MigrationJobStep.MigrateCustomers];
-
- return (
-
-
-
-
-
-
- Migration in progress
-
-
-
-
-
-
- Migration in progress
- {getCusDetails && (
-
-
- Total count: {getCusDetails?.total_customers}
-
-
- Canceled count: {" "}
- {getCusDetails?.canceled_customers}
-
-
- Custom count: {getCusDetails?.custom_customers}
-
-
- )}
- {migrateDetails && (
-
-
- Succeeded: {migrateDetails?.succeeded}
-
-
- Failed: {migrateDetails?.failed}
-
-
- )}
-
-
-
- );
- };
-
- const fromIsOneOff = isOneOffProduct(product.items);
- const migrateCount = counts?.active - counts?.canceled - counts?.custom;
- const version = product.version;
-
- const canMigrate =
- counts &&
- migrateCount > 0 &&
- !fromIsOneOff &&
- version &&
- version < numVersions;
-
- return (
- <>
-
- {!canMigrate ? null : migrations.length > 0 ? (
- renderCurrentMigration()
- ) : (
-
- {loading && }
- {`Migrate to v${numVersions}`}
-
- )}
- >
- );
-};
diff --git a/vite/src/views/products/products/components/CreatePlanDialog.tsx b/vite/src/views/products/products/components/CreatePlanDialog.tsx
deleted file mode 100644
index a641a0a44..000000000
--- a/vite/src/views/products/products/components/CreatePlanDialog.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { PlusCircleIcon } from "@phosphor-icons/react";
-import { useEffect, useRef, useState } from "react";
-import { useNavigate } from "react-router";
-import { toast } from "sonner";
-import { Button } from "@/components/v2/buttons/Button";
-import { IconButton } from "@/components/v2/buttons/IconButton";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/v2/dialogs/Dialog";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr, navigateTo } from "@/utils/genUtils";
-import { ProductConfig } from "../../ProductConfig";
-
-const defaultProduct = {
- name: "",
- id: "",
- group: "",
- is_add_on: false,
- is_default: false,
-};
-
-function CreatePlanDialog({
- onSuccess,
- size = "default",
- buttonClassName,
-}: {
- onSuccess?: (newProduct: ProductV2) => Promise;
- size?: "default" | "sm";
- buttonClassName?: string;
-}) {
- const [loading, setLoading] = useState(false);
- const [product, setProduct] = useState(defaultProduct);
- const [open, setOpen] = useState(false);
- const idManuallyChangedRef = useRef(false);
-
- const axiosInstance = useAxiosInstance();
- const navigate = useNavigate();
-
- const handleCreateClicked = async () => {
- const productName = product.name?.trim() || "";
-
- if (!/^[a-zA-Z0-9 _-]+$/.test(productName)) {
- toast.error(
- !productName
- ? "Plan name is required"
- : "Plan name can only contain alphanumeric characters, dashes (-), and underscores (_)",
- );
- return;
- }
-
- setLoading(true);
- try {
- const newProduct = await ProductService.createProduct(
- axiosInstance,
- product,
- );
-
- if (onSuccess) {
- await onSuccess(newProduct);
- } else {
- navigateTo(`/products/${newProduct.id}`, navigate);
- }
- setOpen(false);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to create plan"));
- }
- setLoading(false);
- };
-
- useEffect(() => {
- if (open) {
- setProduct(defaultProduct);
- idManuallyChangedRef.current = false;
- }
- }, [open]);
-
- return (
- <>
- }
- variant={"secondary"}
- size={size}
- className={buttonClassName}
- onClick={() => setOpen(true)}
- >
- Add Plan
-
-
-
- Create Plan
-
-
- setOpen(false)} variant="secondary">
- Cancel
-
-
- Create Plan
-
-
-
-
- >
- );
-}
-
-export default CreatePlanDialog;
diff --git a/vite/src/views/products/products/components/CreateProductDialog.tsx b/vite/src/views/products/products/components/CreateProductDialog.tsx
deleted file mode 100644
index 2d36bdac6..000000000
--- a/vite/src/views/products/products/components/CreateProductDialog.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { useEffect, useState } from "react";
-import { useNavigate } from "react-router";
-import { toast } from "sonner";
-import { WarningBox } from "@/components/general/modal-components/WarningBox";
-import { ToggleButton } from "@/components/general/ToggleButton";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr, navigateTo } from "@/utils/genUtils";
-import { ProductConfig } from "../../ProductConfig";
-
-const defaultProduct = {
- name: "",
- id: "",
- group: "",
- is_add_on: false,
- is_default: false,
-};
-
-function CreateProduct({
- onSuccess,
-}: {
- onSuccess?: (newProduct: ProductV2) => Promise;
-}) {
- const [loading, setLoading] = useState(false);
- const [product, setProduct] = useState(defaultProduct);
- const [open, setOpen] = useState(false);
-
- const { groupToDefaults } = useProductsQuery();
-
- const axiosInstance = useAxiosInstance();
- const navigate = useNavigate();
-
- const handleCreateClicked = async () => {
- const productName = product.name?.trim() || "";
-
- if (!/^[a-zA-Z0-9 _-]+$/.test(productName)) {
- toast.error(
- !productName
- ? "Plan name is required"
- : "Plan name can only contain alphanumeric characters, dashes (-), and underscores (_)",
- );
- return;
- }
-
- setLoading(true);
- try {
- const newProduct = await ProductService.createProduct(
- axiosInstance,
- product,
- );
-
- if (onSuccess) {
- await onSuccess(newProduct);
- } else {
- navigateTo(`/products/${newProduct.id}`, navigate);
- }
- setOpen(false);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to create plan"));
- }
- setLoading(false);
- };
-
- useEffect(() => {
- if (open) {
- setProduct(defaultProduct);
- }
- }, [open]);
-
- const groupDefault = groupToDefaults[product.group]?.free;
-
- return (
-
-
-
- Plan
-
-
-
- Create Plan
-
-
- {groupDefault && product.is_default && (
-
- Creating this product will disable default on {groupDefault.name}{" "}
- and enable it on this product.
-
- )}
-
-
-
-
-
- setProduct({
- ...product,
- is_default: !product?.is_default,
- })
- }
- />
-
- setProduct({ ...product, is_add_on: !product?.is_add_on })
- }
- />
-
-
- Create Plan
-
-
-
-
-
- );
-}
-
-export default CreateProduct;
diff --git a/vite/src/views/products/products/components/ProductsTable.tsx b/vite/src/views/products/products/components/ProductsTable.tsx
deleted file mode 100644
index bf9486361..000000000
--- a/vite/src/views/products/products/components/ProductsTable.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-import { useNavigate } from "react-router";
-import { AdminHover } from "@/components/general/AdminHover";
-import CopyButton from "@/components/general/CopyButton";
-import { Item, Row } from "@/components/general/TableGrid";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { cn } from "@/lib/utils";
-import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
-import { navigateTo } from "@/utils/genUtils";
-import { useProductsQueryState } from "../../hooks/useProductsQueryState";
-import { useProductsContext } from "../../ProductsContext";
-import { ProductCountsTooltip } from "../product-row-toolbar/ProductCountsTooltip";
-import { ProductRowToolbar } from "../product-row-toolbar/ProductRowToolbar";
-import { ProductTypeBadge } from "../product-row-toolbar/ProductTypeBadge";
-
-export const ProductsTable = ({
- onRowClick,
-}: {
- onRowClick?: (id: string) => void;
-}) => {
- const { onboarding } = useProductsContext();
- const { queryStates } = useProductsQueryState();
- const { products, counts } = useProductsQuery();
-
- const navigate = useNavigate();
-
- const hasAnyGroup = products?.some((product) =>
- Boolean(product?.group?.trim()),
- );
-
- const filteredProducts = products?.filter((product) =>
- queryStates.showArchivedProducts ? product.archived : !product.archived,
- );
-
- return (
- <>
- {filteredProducts && filteredProducts.length > 0 ? (
-
- - Name
- - Product ID
- {!onboarding && (
- <>
- - Active
- - Type
- {hasAnyGroup && - Group
}
- - Created At
- >
- )}
-
-
- ) : (
- !onboarding && (
-
- {queryStates.showArchivedProducts ? (
- You haven't archived any products yet.
- ) : (
- <>
-
- Each product defines features your customers get access to and
- how much they cost. Create separate products for any free
- plans, paid plans and any add-on or top up products ☝️
-
- >
- )}
-
- )
- )}
-
- {filteredProducts &&
- filteredProducts
- .reduce(
- (acc, product) => {
- const existingIndex = acc.findIndex((p) => p.id === product.id);
-
- if (existingIndex === -1) {
- acc.push(product);
- } else {
- const existing = acc[existingIndex];
-
- if (queryStates.showArchivedProducts) {
- // If showing archived, always keep the newest version
- if (product.version > existing.version) {
- acc[existingIndex] = product;
- }
- } else {
- // If not showing archived, prefer non-archived versions
- if (product.archived && !existing.archived) {
- // Keep existing non-archived version
- } else if (!product.archived && existing.archived) {
- // Replace archived with non-archived
- acc[existingIndex] = product;
- } else if (product.version > existing.version) {
- // Both have same archived status, keep newer version
- acc[existingIndex] = product;
- }
- }
- }
-
- return acc;
- },
- [] as typeof filteredProducts,
- )
- .map((product) => (
- {
- if (onRowClick) {
- onRowClick(product.id);
- } else {
- navigateTo(`/products/${product.id}`, navigate);
- }
- }}
- >
- -
-
- {product.name}
-
-
- -
-
- {product.id}
-
-
- {!onboarding && (
- <>
- -
-
-
- -
-
-
- {hasAnyGroup && (
- - {product.group}
- )}
- -
- {formatUnixToDateTime(product.created_at).date}
-
- >
- )}
- -
-
-
-
- ))}
- >
- );
-};
diff --git a/vite/src/views/products/products/components/UpdateProductDialog.tsx b/vite/src/views/products/products/components/UpdateProductDialog.tsx
deleted file mode 100644
index 7b90bb341..000000000
--- a/vite/src/views/products/products/components/UpdateProductDialog.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import { UpdateProductSchema } from "@autumn/shared";
-import type { AxiosError } from "axios";
-import { useRef, useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/v2/buttons/Button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/v2/dialogs/Dialog";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { getBackendErr } from "@/utils/genUtils";
-import { ProductConfig } from "../../ProductConfig";
-
-export const UpdateProductDialog = ({
- open,
- setOpen,
- selectedProduct,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
- selectedProduct: ProductV2;
-}) => {
- const { refetch } = useProductsQuery();
- const originalProduct = useRef(selectedProduct);
- const [product, setProduct] = useState(selectedProduct);
- const [loading, setLoading] = useState(false);
- const env = useEnv();
- const axiosInstance = useAxiosInstance({ env });
-
- const handleSave = async () => {
- setLoading(true);
- const originalProductId = originalProduct.current.id;
-
- try {
- await ProductService.updateProduct(axiosInstance, originalProductId, {
- ...UpdateProductSchema.parse(product),
- });
- await refetch();
- setOpen(false);
-
- toast.success(`Successfully updated plan ${product.id}`);
- } catch (error: unknown) {
- toast.error(getBackendErr(error as AxiosError, "Failed to update plan"));
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
- e.stopPropagation()}
- >
-
- Edit Plan
-
-
-
- setOpen(false)}>
- Cancel
-
-
- Save
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/products/components/archives/CopyDialog.tsx b/vite/src/views/products/products/components/archives/CopyDialog.tsx
deleted file mode 100644
index 879a9f5a1..000000000
--- a/vite/src/views/products/products/components/archives/CopyDialog.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { AppEnv, type ProductV2 } from "@autumn/shared";
-import React, { useState } from "react";
-import { toast } from "sonner";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Button } from "@/components/ui/button";
-import {
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { getBackendErr } from "@/utils/genUtils";
-
-export const CopyDialog = ({
- product,
- setModalOpen,
-}: {
- product: ProductV2;
- setModalOpen: (open: boolean) => void;
-}) => {
- const env = useEnv();
- const axiosInstance = useAxiosInstance({ env });
- const { refetch } = useProductsQuery();
-
- const [copyLoading, setCopyLoading] = useState(false);
- const [name, setName] = useState(product.name);
- const [id, setId] = useState(product.id);
- const [toEnv, setToEnv] = useState(
- env == AppEnv.Live ? AppEnv.Sandbox : AppEnv.Live,
- );
-
- const handleCopy = async () => {
- // 1. If env is the same and id is same, throw error
-
- if (env == toEnv && id == product.id) {
- toast.error("Product ID already exists");
- return;
- }
-
- setCopyLoading(true);
- try {
- await ProductService.copyProduct(axiosInstance, product.id, {
- id: id,
- name: name,
- env: toEnv,
- });
- await refetch();
-
- toast.success("Successfully copied product");
- setModalOpen(false);
- } catch (error) {
- console.log("Error copying product", error);
- toast.error(getBackendErr(error, "Failed to copy product"));
- }
- setCopyLoading(false);
- };
- return (
-
- e.stopPropagation()}>
- Copy Product
-
-
-
- Environment
- setToEnv(value as AppEnv)}
- >
-
-
-
-
- Live
- Sandbox
-
-
-
-
-
-
- Copy
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/products/components/archives/DeleteProductDialog.tsx b/vite/src/views/products/products/components/archives/DeleteProductDialog.tsx
deleted file mode 100644
index 2525f94c5..000000000
--- a/vite/src/views/products/products/components/archives/DeleteProductDialog.tsx
+++ /dev/null
@@ -1,273 +0,0 @@
-import { ProductCounts, type ProductV2 } from "@autumn/shared";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useGeneralQuery } from "@/hooks/queries/useGeneralQuery";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { useModelPricingContext } from "@/views/onboarding2/model-pricing/ModelPricingContext";
-
-export const DeleteProductDialog = ({
- product,
- dropdownOpen,
- open,
- setOpen,
-}: {
- product: ProductV2;
- dropdownOpen: boolean;
- open: boolean;
- setOpen: (open: boolean) => void;
-}) => {
- const [deleteLoading, setDeleteLoading] = useState(false);
- const [archiveLoading, setArchiveLoading] = useState(false);
- const axiosInstance = useAxiosInstance();
-
- const modelPricingContext = useModelPricingContext();
- const { refetch: refetchProducts } = useProductsQuery();
-
- const { data: productInfo, isLoading } = useGeneralQuery({
- url: `/products/${product.id}/info`,
- queryKey: ["productInfo", product.id],
- enabled: dropdownOpen,
- });
-
- const [deleteAllVersions, setDeleteAllVersions] = useState(false);
-
- const handleDelete = async () => {
- setDeleteLoading(true);
- try {
- await ProductService.deleteProduct(
- axiosInstance,
- product.id,
- deleteAllVersions,
- );
-
- if (modelPricingContext) {
- await modelPricingContext.refetch();
- } else {
- await refetchProducts();
- }
- setOpen(false);
- } catch (error) {
- console.error("Error deleting product:", error);
- toast.error(getBackendErr(error, "Error deleting product"));
- } finally {
- setDeleteLoading(false);
- }
- };
-
- const handleArchive = async () => {
- setArchiveLoading(true);
- const newArchivedState = !product.archived;
- try {
- if (!deleteAllVersions) {
- if (newArchivedState == true)
- await ProductService.updateProduct(axiosInstance, product.id, {
- archived: newArchivedState,
- });
- else {
- const updatePromises = [];
- for (let i = 1; i <= productInfo.numVersion; i++) {
- updatePromises.push(
- ProductService.updateProduct(
- axiosInstance,
- product.id,
- {
- archived: newArchivedState,
- version: i,
- },
- i,
- ),
- );
- }
- await Promise.all(updatePromises);
- }
- } else {
- const updatePromises = [];
- for (let i = 1; i <= productInfo.numVersion; i++) {
- updatePromises.push(
- ProductService.updateProduct(
- axiosInstance,
- product.id,
- {
- archived: newArchivedState,
- version: i,
- },
- i,
- ),
- );
- }
- await Promise.all(updatePromises);
- }
- await refetchProducts();
- toast.success(
- `Product ${product.name} ${newArchivedState ? "archived" : "unarchived"} successfully`,
- );
- setOpen(false);
- } catch (error) {
- toast.error(
- getBackendErr(
- error,
- `Error ${newArchivedState ? "archiving" : "unarchiving"} product`,
- ),
- );
- } finally {
- setArchiveLoading(false);
- }
- };
-
- const hasCusProductsAll = productInfo?.hasCusProducts;
- const hasCusProductsLatest = productInfo?.hasCusProductsLatest;
-
- const hasCusProducts = deleteAllVersions
- ? hasCusProductsAll
- : hasCusProductsLatest;
-
- const getDeleteMessage = () => {
- if (product.archived) {
- return `This product is currently archived and hidden from the UI. Would you like to unarchive it to make it visible again?\n
- Note: If there are multiple versions, this will unarchive all versions at once.`;
- }
-
- const isMultipleVersions = productInfo?.numVersion > 1;
- const versionText = deleteAllVersions ? "product" : "version";
- const productText = isMultipleVersions ? versionText : "product";
- // Deleting this ${productText} will remove it from their accounts. Are you sure you want to continue? You can also archive the product instead.
- const messageTemplates = {
- withCustomers: {
- single: (customerName: string, productText: string) =>
- `${customerName} is on this ${productText}. Are you sure you want to archive it?`,
- multiple: (
- customerName: string,
- otherCount: number,
- productText: string,
- ) =>
- `${customerName} and ${otherCount} other customer${otherCount > 1 ? "s" : ""} are on this ${productText}. Are you sure you want to archive this product?
-
- `,
- fallback: (productText: string) =>
- `There are customers on this ${productText}. Deleting this ${productText} will remove it from their accounts. Are you sure you want to continue? You can also archive the product instead.`,
- },
- withoutCustomers: (productText: string) =>
- `Are you sure you want to delete this ${productText}? This action cannot be undone.`,
- };
-
- const templates = messageTemplates;
-
- if (hasCusProducts) {
- if (productInfo?.customerName && productInfo?.totalCount) {
- const totalCount = parseInt(productInfo.totalCount);
-
- if (isNaN(totalCount) || totalCount <= 0) {
- return templates.withCustomers.fallback(productText);
- } else if (totalCount === 1) {
- return templates.withCustomers.single(
- productInfo.customerName,
- productText,
- );
- } else {
- const otherCount = totalCount - 1;
- return templates.withCustomers.multiple(
- productInfo.customerName,
- otherCount,
- productText,
- );
- }
- } else {
- return templates.withCustomers.fallback(productText);
- }
- } else {
- return templates.withoutCustomers(productText);
- }
- };
-
- if (!productInfo || isLoading) {
- return <>>;
- }
-
- return (
-
- e.stopPropagation()}>
-
-
- {product.archived ? "Unarchive" : "Delete"} {product.name}
-
-
-
- {productInfo.numVersion > 1 && !product.archived && (
- setDeleteAllVersions(value === "all")}
- >
-
-
-
-
- Delete latest version
- Archive product
-
-
- )}
-
-
-
- {getDeleteMessage()
- .split("\n")
- .map((line, index) => (
-
- {line}
- {index < getDeleteMessage().split("\n").length - 1 && }
-
- ))}
-
-
-
- {product.archived && (
-
- Unarchive
-
- )}
- {hasCusProducts && !product.archived && (
-
- Archive
-
- )}
-
- {!hasCusProducts && !product.archived && (
-
- Delete
-
- )}
-
-
-
- );
-};
diff --git a/vite/src/views/products/products/components/archives/DeleteProductDialog2.tsx b/vite/src/views/products/products/components/archives/DeleteProductDialog2.tsx
deleted file mode 100644
index 96c94efc5..000000000
--- a/vite/src/views/products/products/components/archives/DeleteProductDialog2.tsx
+++ /dev/null
@@ -1,252 +0,0 @@
-import type { ProductV2 } from "@autumn/shared";
-import type { AxiosError } from "axios";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/v2/buttons/Button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/v2/dialogs/Dialog";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/v2/selects/Select";
-import { useGeneralQuery } from "@/hooks/queries/useGeneralQuery";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-
-export const DeleteProductDialog = ({
- product,
- open,
- setOpen,
- onDeleteSuccess,
-}: {
- product: ProductV2;
- open: boolean;
- setOpen: (open: boolean) => void;
- onDeleteSuccess?: () => Promise;
-}) => {
- const axiosInstance = useAxiosInstance();
- const [loading, setLoading] = useState(false);
- const [deleteAllVersions, setDeleteAllVersions] = useState(false);
- const { refetch: refetchProducts } = useProductsQuery();
-
- const { data: productInfo, isLoading } = useGeneralQuery({
- url: `/products/${product.id}/info`,
- queryKey: ["productInfo", product.id],
- enabled: open,
- });
-
- const handleDelete = async () => {
- setLoading(true);
- try {
- await ProductService.deleteProduct(
- axiosInstance,
- product.id,
- deleteAllVersions,
- );
-
- await refetchProducts();
- setOpen(false);
-
- if (onDeleteSuccess) {
- await onDeleteSuccess();
- }
-
- toast.success("Product deleted successfully");
- } catch (error: unknown) {
- toast.error(getBackendErr(error as AxiosError, "Error deleting product"));
- } finally {
- setLoading(false);
- }
- };
-
- const handleArchive = async () => {
- setLoading(true);
- try {
- await ProductService.updateProduct(axiosInstance, product.id, {
- archived: true,
- });
- toast.success(`${product.name} archived successfully`);
- setOpen(false);
- if (onDeleteSuccess) {
- await onDeleteSuccess();
- }
- await refetchProducts();
- } catch (error: unknown) {
- toast.error(
- getBackendErr(error as AxiosError, "Error archiving product"),
- );
- } finally {
- setLoading(false);
- }
- };
-
- const handleUnarchive = async () => {
- setLoading(true);
- try {
- await ProductService.updateProduct(axiosInstance, product.id, {
- archived: false,
- });
- await refetchProducts();
- if (onDeleteSuccess) {
- await onDeleteSuccess();
- }
- toast.success(`${product.name} unarchived successfully`);
- setOpen(false);
- } catch (error: unknown) {
- toast.error(
- getBackendErr(error as AxiosError, "Error unarchiving product"),
- );
- } finally {
- setLoading(false);
- }
- };
-
- const hasCusProductsAll = productInfo?.hasCusProducts;
- const hasCusProductsLatest = productInfo?.hasCusProductsLatest;
-
- const hasCusProducts = deleteAllVersions
- ? hasCusProductsAll
- : hasCusProductsLatest;
-
- const getDeleteMessage = () => {
- if (product.archived) {
- return `Are you sure you want to unarchive ${product.name}? This will make it visible in your list of products.`;
- }
-
- const isMultipleVersions = productInfo?.numVersion > 1;
- const versionText = deleteAllVersions ? "product" : "version";
- const productText = isMultipleVersions ? versionText : "product";
-
- const messageTemplates = {
- withCustomers: {
- single: (customerName: string, productText: string) =>
- `${customerName} is on this ${productText}. Are you sure you want to archive it?`,
- multiple: (
- customerName: string,
- otherCount: number,
- productText: string,
- ) =>
- `${customerName} and ${otherCount} other customer${otherCount > 1 ? "s" : ""} are on this ${productText}. Are you sure you want to archive this product?`,
- fallback: (productText: string) =>
- `There are customers on this ${productText}. Deleting this ${productText} will remove it from their accounts. Are you sure you want to continue? You can also archive the product instead.`,
- },
- withoutCustomers: (productText: string) =>
- `Are you sure you want to delete this ${productText}? This action cannot be undone.`,
- };
-
- if (hasCusProducts) {
- if (productInfo?.customerName && productInfo?.totalCount) {
- const totalCount = Number.parseInt(productInfo.totalCount);
-
- if (Number.isNaN(totalCount) || totalCount <= 0) {
- return messageTemplates.withCustomers.fallback(productText);
- }
- if (totalCount === 1) {
- return messageTemplates.withCustomers.single(
- productInfo.customerName,
- productText,
- );
- }
- const otherCount = totalCount - 1;
- return messageTemplates.withCustomers.multiple(
- productInfo.customerName,
- otherCount,
- productText,
- );
- }
- return messageTemplates.withCustomers.fallback(productText);
- }
- return messageTemplates.withoutCustomers(productText);
- };
-
- if (!productInfo || isLoading) return null;
-
- return (
-
- e.stopPropagation()}>
-
-
- {product.archived
- ? "Unarchive"
- : hasCusProducts
- ? "Archive"
- : "Delete"}{" "}
- {product.name}
-
-
- {getDeleteMessage()
- .split("\n")
- .map((line, index) => (
-
- {line}
- {index < getDeleteMessage().split("\n").length - 1 && }
-
- ))}
-
-
-
- {productInfo.numVersion > 1 &&
- !product.archived &&
- !productInfo.hasCusProductsLatest && (
- setDeleteAllVersions(value === "all")}
- >
-
-
-
-
- Delete latest version
- Archive product
-
-
- )}
-
-
- setOpen(false)}>
- Cancel
-
- {product.archived && (
-
- Unarchive
-
- )}
- {hasCusProducts && !product.archived && (
-
- Archive
-
- )}
-
- {!hasCusProducts && !product.archived && (
-
- Delete
-
- )}
-
-
-
- );
-};
diff --git a/vite/src/views/products/products/components/archives/UpdateProduct.tsx b/vite/src/views/products/products/components/archives/UpdateProduct.tsx
deleted file mode 100644
index 00c1b6ebe..000000000
--- a/vite/src/views/products/products/components/archives/UpdateProduct.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { type ProductV2, UpdateProductSchema } from "@autumn/shared";
-import React, { useRef, useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { ProductService } from "@/services/products/ProductService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { getBackendErr } from "@/utils/genUtils";
-import { ProductConfig } from "./ProductConfig";
-
-export const UpdateProductDialog = ({
- selectedProduct,
- setModalOpen,
- setDropdownOpen,
-}: {
- selectedProduct: ProductV2;
- setModalOpen: (open: boolean) => void;
- setDropdownOpen: (open: boolean) => void;
-}) => {
- const { refetch } = useProductsQuery();
- const originalProduct = useRef(selectedProduct);
- const [product, setProduct] = useState(selectedProduct);
- const [saveLoading, setSaveLoading] = useState(false);
- const env = useEnv();
- const axiosInstance = useAxiosInstance({ env });
-
- const handleSave = async () => {
- setSaveLoading(true);
- const originalProductId = originalProduct.current.id;
-
- try {
- await ProductService.updateProduct(axiosInstance, originalProductId, {
- ...UpdateProductSchema.parse(product),
- });
- await refetch();
- setModalOpen(false);
-
- toast.success(`Successfully updated product ${product.id}`);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to update product"));
- }
- setSaveLoading(false);
- };
- return (
-
- e.stopPropagation()}>
- Edit Product
-
-
-
- Save
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/products/components/product-list/AddOnPlanCreateButton.tsx b/vite/src/views/products/products/components/product-list/AddOnPlanCreateButton.tsx
deleted file mode 100644
index b5c91b2f2..000000000
--- a/vite/src/views/products/products/components/product-list/AddOnPlanCreateButton.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useState } from "react";
-import { Button } from "@/components/v2/buttons/Button";
-import CreateProductSheet from "../CreateProductSheet";
-
-export function AddOnPlanCreateButton() {
- const [open, setOpen] = useState(false);
-
- return (
- <>
-
- setOpen(true)}>
- Create Add-on Plan
-
- >
- );
-}
diff --git a/vite/src/views/products/products/product-row-toolbar/ProductRowToolbar.tsx b/vite/src/views/products/products/product-row-toolbar/ProductRowToolbar.tsx
deleted file mode 100644
index 33ee1c48b..000000000
--- a/vite/src/views/products/products/product-row-toolbar/ProductRowToolbar.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import type { ProductCounts, ProductV2 } from "@autumn/shared";
-import { Archive, ArchiveRestore, Copy, Delete, Pen } from "lucide-react";
-import { useState } from "react";
-import { ToolbarButton } from "@/components/general/table-components/ToolbarButton";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { DeletePlanDialog } from "../../plan/components/DeletePlanDialog";
-import { CopyProductDialog } from "../components/CopyProductDialog";
-import { UpdateProductDialog } from "../components/UpdateProductDialog";
-
-export const ProductRowToolbar = ({
- className,
- isOnboarding = false,
- product,
- productCounts,
-}: {
- isOnboarding?: boolean;
- className?: string;
- product: ProductV2;
- productCounts: ProductCounts | undefined;
-}) => {
- const [dropdownOpen, setDropdownOpen] = useState(false);
- const [updateOpen, setUpdateOpen] = useState(false);
- const [copyOpen, setCopyOpen] = useState(false);
- const [deleteOpen, setDeleteOpen] = useState(false);
-
- const allCount = productCounts?.all || 0;
- let deleteText = allCount > 0 ? "Archive" : "Delete";
- let DeleteIcon = allCount > 0 ? Archive : Delete;
-
- if (product.archived) {
- deleteText = "Unarchive";
- DeleteIcon = ArchiveRestore;
- }
-
- return (
- <>
-
-
- {
- // pushPage({
- // navigate,
- // path: "/products",
- // queryParams: {
- // tab: "products",
- // },
- // preserveParams: true,
- // });
- // }}
- />
- {/* */}
-
-
-
-
-
-
- {!isOnboarding && (
- {
- e.stopPropagation();
- e.preventDefault();
- setDropdownOpen(false);
- setCopyOpen(true);
- }}
- >
-
- Copy
-
-
-
- )}
- {!isOnboarding && (
- {
- e.stopPropagation();
- e.preventDefault();
- setDropdownOpen(false);
- setUpdateOpen(true);
- }}
- >
-
-
- )}
- {
- e.stopPropagation();
- e.preventDefault();
- setDropdownOpen(false);
- setDeleteOpen(true);
- }}
- >
-
- {deleteText}
-
-
-
-
-
- >
- );
-};
diff --git a/vite/src/views/products/products/product-row-toolbar/ProductTypeBadge.tsx b/vite/src/views/products/products/product-row-toolbar/ProductTypeBadge.tsx
deleted file mode 100644
index e6a502ae2..000000000
--- a/vite/src/views/products/products/product-row-toolbar/ProductTypeBadge.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { FreeTrial, Product } from "@autumn/shared";
-import { Badge } from "@/components/ui/badge";
-import { cn } from "@/lib/utils";
-
-export const ProductTypeBadge = ({ product }: { product: any }) => {
- const badgeType =
- product.is_default &&
- product.free_trial &&
- !product.free_trial.card_required
- ? "default trial"
- : product.is_default
- ? "default"
- : product.is_add_on
- ? "add-on"
- : null;
-
- if (!badgeType) return null;
-
- return (
- // <>
- // {product.is_default ? (
- // Default
- // ) : product.is_add_on ? (
- // Add-On
- // ) : (
- // <>>
- // )}
- // >
-
- {badgeType}
-
- );
-};
diff --git a/vite/src/views/products/rewards/reward-config/FreeDurationSelect.tsx b/vite/src/views/products/rewards/reward-config/FreeDurationSelect.tsx
deleted file mode 100644
index dc4ef8cf5..000000000
--- a/vite/src/views/products/rewards/reward-config/FreeDurationSelect.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { CouponDurationType, type Reward } from "@autumn/shared";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { keyToTitle } from "@/utils/formatUtils/formatTextUtils";
-
-export const FreeDurationSelect = ({
- reward,
- setReward,
-}: {
- reward: Reward;
- setReward: (reward: Reward) => void;
-}) => {
- return (
-
-
Duration
-
- {
- {
- setReward({
- ...reward,
- // @ts-expect-error
- free_product_config: {
- ...(reward.free_product_config ?? {}),
- duration_value: Number(e.target.value),
- },
- });
- }}
- type="number"
- min={1}
- max={12}
- />
- }
-
- setReward({
- ...reward,
- // @ts-expect-error
- free_product_config: {
- ...(reward.free_product_config ?? {}),
- duration_type: value as CouponDurationType,
- },
- })
- }
- >
-
-
-
-
- {Object.values(CouponDurationType)
- .filter((x) => x !== CouponDurationType.Forever)
- .filter((x) => x !== CouponDurationType.OneOff)
- .map((type) => (
-
- {keyToTitle(type)}
-
- ))}
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/rewards/reward-config/archives/CreateReward.tsx b/vite/src/views/products/rewards/reward-config/archives/CreateReward.tsx
deleted file mode 100644
index 39637b1ec..000000000
--- a/vite/src/views/products/rewards/reward-config/archives/CreateReward.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
-import { RewardService } from "@/services/products/RewardService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { defaultReward } from "../utils/defaultRewardModels";
-import { RewardConfig } from "./RewardConfig";
-
-function CreateReward() {
- const axiosInstance = useAxiosInstance();
- const { refetch } = useRewardsQuery();
-
- const [isLoading, setIsLoading] = useState(false);
- const [open, setOpen] = useState(false);
-
- const [reward, setReward] = useState(defaultReward);
-
- useEffect(() => {
- if (open) {
- setReward(defaultReward);
- }
- }, [open]);
-
- const handleCreate = () => {
- setIsLoading(true);
- (async () => {
- if (!reward?.id && !reward?.name) {
- toast.error("ID and name are required");
- setIsLoading(false);
- return;
- }
-
- // Validate product selection for discount rewards
- if (reward.discount_config) {
- const { apply_to_all, price_ids } = reward.discount_config;
- if (!apply_to_all && (!price_ids || price_ids.length === 0)) {
- toast.error("Please select price(s) to apply this reward to");
- setIsLoading(false);
- return;
- }
- }
-
- try {
- await RewardService.createReward({
- axiosInstance,
- data: reward,
- });
-
- await refetch();
- setOpen(false);
- } catch (error) {
- console.log("Error:", error);
- toast.error(getBackendErr(error, "Failed to create coupon"));
- } finally {
- setIsLoading(false);
- }
- })();
- };
-
- return (
-
-
- Reward
-
-
-
- Create Reward
-
-
-
-
- Create
-
-
-
-
- );
-}
-
-export default CreateReward;
diff --git a/vite/src/views/products/rewards/reward-config/archives/DiscountConfig.tsx b/vite/src/views/products/rewards/reward-config/archives/DiscountConfig.tsx
deleted file mode 100644
index f2ac614ac..000000000
--- a/vite/src/views/products/rewards/reward-config/archives/DiscountConfig.tsx
+++ /dev/null
@@ -1,309 +0,0 @@
-import {
- CouponDurationType,
- type ProductItem,
- type Reward,
- RewardType,
-} from "@autumn/shared";
-import { Check, ChevronsUpDown, X } from "lucide-react";
-import { useState } from "react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { Button } from "@/components/ui/button";
-import {
- Command,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
-} from "@/components/ui/command";
-import { Input } from "@/components/ui/input";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { keyToTitle } from "@/utils/formatUtils/formatTextUtils";
-import { isFeatureItem } from "@/utils/product/getItemType";
-import { formatProductItemText } from "@/utils/product/product-item/formatProductItem";
-
-export const DiscountConfig = ({
- reward,
- setReward,
-}: {
- reward: Reward;
- setReward: (reward: Reward) => void;
-}) => {
- const { org } = useOrg();
-
- const config = reward.discount_config!;
- const setConfig = (key: any, value: any) => {
- setReward({
- ...reward,
- discount_config: { ...config, [key]: value },
- });
- };
-
- return (
-
-
-
-
Amount
-
- setConfig("discount_value", Number(e.target.value))
- }
- endContent={
-
- {reward.type === RewardType.PercentageDiscount
- ? "%"
- : org?.default_currency || "USD"}
-
- }
- />
-
-
-
Duration
-
- {config.duration_type === CouponDurationType.Months && (
- {
- setConfig("duration_value", Number(e.target.value));
- }}
- type="number"
- />
- )}
-
- setConfig("duration_type", value as CouponDurationType)
- }
- >
-
-
-
-
- {Object.values(CouponDurationType)
- .filter((type) => {
- if (
- reward.type === RewardType.FixedDiscount &&
- type === CouponDurationType.Forever &&
- config.duration_type !== CouponDurationType.Forever
- ) {
- return false;
- }
- if (
- reward.type === RewardType.InvoiceCredits &&
- type === CouponDurationType.OneOff
- ) {
- return false;
- }
- return true;
- })
- .map((type) => (
-
- {keyToTitle(type)}
-
- ))}
-
-
-
-
-
-
-
- {/*
Products
*/}
-
Products
-
-
-
-
- );
-};
-
-const ProductPriceSelector = ({
- reward,
- setReward,
-}: {
- reward: Reward;
- setReward: (reward: Reward) => void;
-}) => {
- const { org } = useOrg();
- const { products } = useProductsQuery();
- const { features } = useFeaturesQuery();
-
- const [open, setOpen] = useState(false);
-
- const config = reward.discount_config!;
- const setConfig = (key: any, value: any) => {
- setReward({
- ...reward,
- discount_config: { ...config, [key]: value },
- });
- };
-
- // Handle selection/deselection of a price
- const handlePriceToggle = (priceId: string) => {
- let newPriceIds = [...(config.price_ids || [])];
- if (config.price_ids?.includes(priceId)) {
- newPriceIds = config.price_ids?.filter((id) => id !== priceId) || [];
- } else {
- newPriceIds = [...(config.price_ids || []), priceId];
- }
- setConfig("price_ids", newPriceIds);
- };
-
- // Handle toggling "Apply to all products"
- const handleApplyToAllToggle = () => {
- const newApplyToAll = !config.apply_to_all;
- // If enabling "Apply to all", clear the price_ids
- if (newApplyToAll) {
- setReward({
- ...reward,
- discount_config: {
- ...config,
- apply_to_all: newApplyToAll,
- price_ids: [],
- },
- });
- } else {
- setConfig("apply_to_all", newApplyToAll);
- }
- };
-
- if (!products || products.length === 0) {
- return No products available
;
- }
-
- return (
-
-
-
- {config.apply_to_all ? (
- All Products
- ) : config.price_ids?.length === 0 ? (
- Select products or apply to all
- ) : (
- config.price_ids?.map((priceId) => {
- const item = products
- .find((p: any) =>
- p.items.find((i: any) => i.price_id === priceId),
- )
- ?.items.find((i: any) => i.price_id === priceId);
-
- const text = item
- ? formatProductItemText({
- item,
- org,
- features,
- })
- : "Unknown Price";
- return (
-
-
{text}
-
{
- e.stopPropagation();
- handlePriceToggle(priceId);
- }}
- className="bg-transparent hover:bg-transparent p-0 w-5 h-5"
- >
-
-
-
- );
- })
- )}
-
-
-
-
-
-
-
-
- No prices found.
-
-
- Apply to all products
- {config.apply_to_all && (
-
- )}
-
-
- {!config.apply_to_all &&
- products
- .filter((product: any) => {
- // Filter out products that have no non-feature items
- const nonFeatureItems = product.items?.filter(
- (item: ProductItem) => !isFeatureItem(item),
- );
- return nonFeatureItems && nonFeatureItems.length > 0;
- })
- .map((product: any) => {
- const nonFeatureItems = product.items.filter(
- (item: ProductItem) => !isFeatureItem(item),
- );
-
- return (
-
- {nonFeatureItems.map((item: any) => (
- handlePriceToggle(item.price_id)}
- className="cursor-pointer overflow-x-hidden"
- >
-
- {formatProductItemText({
- item,
- org,
- features,
- })}
-
-
- {config.price_ids?.includes(item.price_id) && (
-
- )}
-
- ))}
-
- );
- })}
-
-
-
-
-
- );
-};
diff --git a/vite/src/views/products/rewards/reward-config/archives/RewardConfig.tsx b/vite/src/views/products/rewards/reward-config/archives/RewardConfig.tsx
deleted file mode 100644
index 53298aaef..000000000
--- a/vite/src/views/products/rewards/reward-config/archives/RewardConfig.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import {
- type ProductV2,
- type Reward,
- RewardType,
- UsageModel,
-} from "@autumn/shared";
-import { useEffect, useState } from "react";
-import FieldLabel from "@/components/general/modal-components/FieldLabel";
-import { WarningBox } from "@/components/general/modal-components/WarningBox";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useOrg } from "@/hooks/common/useOrg";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import {
- formatAmount as formatCurrency,
- keyToTitle,
- slugify,
-} from "@/utils/formatUtils/formatTextUtils";
-import { notNullish } from "@/utils/genUtils";
-import { isFeaturePriceItem, isPriceItem } from "@/utils/product/getItemType";
-import { isFreeProduct, isOneOffProduct } from "@/utils/product/priceUtils";
-import { defaultDiscountConfig } from "../utils/defaultRewardModels";
-import { DiscountConfig } from "./DiscountConfig";
-
-export const RewardConfig = ({
- reward,
- setReward,
-}: {
- reward: Reward;
- setReward: (reward: Reward) => void;
-}) => {
- const [idChanged, setIdChanged] = useState(false);
- const { products } = useProductsQuery();
- const { org } = useOrg();
-
- useEffect(() => {
- if (!idChanged) {
- setReward({
- ...reward,
- id: slugify(reward.name || ""),
- });
- }
- }, [idChanged, reward, setReward]);
-
- return (
-
-
-
-
- Promotional Code
- 0 ? reward.promo_codes[0].code : ""
- }
- onChange={(e) =>
- setReward({
- ...reward,
- promo_codes: [{ code: e.target.value }],
- })
- }
- />
-
-
- Type
- {
- setReward({
- ...reward,
- type: value as RewardType,
- discount_config:
- value === RewardType.FreeProduct
- ? null
- : defaultDiscountConfig,
- });
- }}
- >
-
-
-
-
- {Object.values(RewardType).map((type) => (
-
- {keyToTitle(type)}
-
- ))}
-
-
-
-
- {reward.type === RewardType.FreeProduct ? (
-
-
- Product
-
-
- setReward({ ...reward, free_product_id: value })
- }
- >
- {(() => {
- const freeAddOns = products
- .filter((product: ProductV2) => product.is_add_on)
- .filter((product: ProductV2) => isFreeProduct(product.items));
-
- const empty = freeAddOns.length === 0;
- return (
- <>
-
-
-
-
- {freeAddOns.map((product: ProductV2) => (
-
- {product.name}
-
- ))}
-
- >
- );
- })()}
-
-
- {(() => {
- const selectedProduct = products.find(
- (p: ProductV2) => p.id === reward.free_product_id,
- );
-
- if (!selectedProduct) return null;
-
- const isPaidSelected = !isFreeProduct(selectedProduct.items);
- if (!isPaidSelected) return null;
-
- const isRecurringSelected = !isOneOffProduct(selectedProduct.items);
- const hasUsagePrices = selectedProduct.items.some(
- (x) =>
- isFeaturePriceItem(x) && x.usage_model === UsageModel.PayPerUse,
- );
-
- const priceItem = selectedProduct.items.find((x) => isPriceItem(x));
- const currency = org?.default_currency || "USD";
- const fixedAmountStr = priceItem?.price
- ? formatCurrency({ amount: priceItem.price, currency })
- : undefined;
-
- if (isRecurringSelected) {
- return (
-
-
- If the receiver is already on a product, they will receive a
- coupon equal to this product's fixed price amount (
- {fixedAmountStr}).{" "}
- {hasUsagePrices
- ? "Charges due to usage prices will not be included in the coupon."
- : ""}
-
-
- );
- }
-
- return;
- })()}
-
- ) : notNullish(reward.type) ? (
-
- ) : null}
-
- );
-};
diff --git a/vite/src/views/products/rewards/reward-config/archives/UpdateReward.tsx b/vite/src/views/products/rewards/reward-config/archives/UpdateReward.tsx
deleted file mode 100644
index 8421d5f14..000000000
--- a/vite/src/views/products/rewards/reward-config/archives/UpdateReward.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import type { ProductV2, Reward } from "@autumn/shared";
-import { analyzeRewardPrices } from "@autumn/shared";
-import { useState } from "react";
-import { toast } from "sonner";
-import { WarningBox } from "@/components/general/modal-components/WarningBox";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
-import { RewardService } from "@/services/products/RewardService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { useEnv } from "@/utils/envUtils";
-import { getBackendErr } from "@/utils/genUtils";
-import { RewardConfig } from "./RewardConfig";
-
-const checkRewardMigration = (
- reward: Reward,
- products: ProductV2[],
-): { willMigrateCount: number; willNotMigrateCount: number } => {
- // Extract all available price IDs from current products
- const availablePriceIds: string[] = [];
- for (const product of products) {
- if (product.items) {
- for (const item of product.items) {
- if (item.price_id) {
- availablePriceIds.push(item.price_id);
- }
- }
- }
- }
-
- // Use the shared utility to analyze the reward
- const analysis = analyzeRewardPrices({
- reward,
- availablePriceIds,
- });
-
- return {
- willMigrateCount: analysis.validPriceCount,
- willNotMigrateCount: analysis.invalidPriceCount,
- };
-};
-
-function UpdateReward({
- open,
- setOpen,
- selectedReward,
- setSelectedReward,
-}: {
- open: boolean;
- setOpen: (open: boolean) => void;
- selectedReward: Reward | null;
- setSelectedReward: (reward: Reward) => void;
-}) {
- const [updateLoading, setUpdateLoading] = useState(false);
- const { refetch } = useRewardsQuery();
- const { products } = useProductsQuery();
-
- const env = useEnv();
- const axiosInstance = useAxiosInstance({ env });
-
- if (!selectedReward) {
- setOpen(false);
- return;
- }
-
- const handleUpdate = async () => {
- setUpdateLoading(true);
- try {
- // Validate product selection for discount rewards
- if (selectedReward.discount_config) {
- const { apply_to_all, price_ids } = selectedReward.discount_config;
- if (!apply_to_all && (!price_ids || price_ids.length === 0)) {
- toast.error("Please select price(s) to apply this reward to");
- setUpdateLoading(false);
- return;
- }
- }
-
- // Check migration status and show warning if needed
- if (products) {
- const migrationResult = checkRewardMigration(selectedReward, products);
- if (migrationResult.willNotMigrateCount > 0) {
- toast.warning(
- `${migrationResult.willNotMigrateCount} price${migrationResult.willNotMigrateCount === 1 ? "" : "s"} won't be migrated to the latest product version.`,
- {
- duration: 5000,
- },
- );
- }
- }
-
- await RewardService.updateReward({
- axiosInstance,
- internalId: selectedReward.internal_id,
- data: selectedReward,
- });
- toast.success("Reward updated successfully");
- await refetch();
- setOpen(false);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to update coupon"));
- }
- setUpdateLoading(false);
- };
-
- return (
-
-
- Update Reward
-
- Existing customers with this coupon will not be affected
-
-
- {selectedReward && (
-
- )}
-
-
- handleUpdate()}
- variant="gradientPrimary"
- >
- Update
-
-
-
-
- );
-}
-
-export default UpdateReward;
diff --git a/vite/src/views/products/rewards/reward-programs/CreateRewardProgram.tsx b/vite/src/views/products/rewards/reward-programs/CreateRewardProgram.tsx
deleted file mode 100644
index 0b6918357..000000000
--- a/vite/src/views/products/rewards/reward-programs/CreateRewardProgram.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import type { CreateRewardProgram } from "@autumn/shared";
-import { RewardReceivedBy, RewardTriggerEvent } from "@autumn/shared";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr } from "@/utils/genUtils";
-import { RewardProgramConfig } from "./RewardProgramConfig";
-
-const defaultRewardProgram: CreateRewardProgram = {
- id: "",
- // trigger: {
- // type: RewardTriggerEvent.SignUp,
- // product_ids: [],
- // exclude_trial: false,
- // },
- when: RewardTriggerEvent.CustomerCreation,
- product_ids: [],
- exclude_trial: false,
- internal_reward_id: "",
- max_redemptions: 0,
- received_by: RewardReceivedBy.Referrer,
-};
-
-function CreateRewardProgramModal() {
- const { refetch } = useRewardsQuery();
- const axiosInstance = useAxiosInstance();
-
- const [isLoading, setIsLoading] = useState(false);
- const [open, setOpen] = useState(false);
-
- const [rewardProgram, setRewardProgram] = useState(defaultRewardProgram);
-
- useEffect(() => {
- if (open) {
- setRewardProgram(defaultRewardProgram);
- }
- }, [open]);
-
- const handleCreate = () => {
- setIsLoading(true);
- (async () => {
- try {
- await axiosInstance.post("/v1/reward_programs", rewardProgram);
-
- await refetch();
- setOpen(false);
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to create referral program"));
- } finally {
- setIsLoading(false);
- }
- })();
- };
-
- return (
-
-
- Referral Program
-
-
-
- Create Referral Program
-
-
-
-
- Create
-
-
-
-
- );
-}
-
-export default CreateRewardProgramModal;