This commit is contained in:
amianthus
2025-09-22 13:11:31 +00:00
parent f7599b1bfa
commit 5b7d58813e
16 changed files with 865 additions and 285 deletions

View File

@@ -21,11 +21,9 @@
"dev:wsl": "concurrently \"cd server && npm run dev\" \"cd vite && npm run dev\" \"cd shared && npm run dev\"",
"setup": "node setup.js",
"setupci": "node setupci",
"db:push": " bun -F @autumn/shared db:push",
"db:generate": "bun -F @autumn/shared db:generate",
"db:migrate": " bun -F @autumn/shared db:migrate",
"docker:up": "docker compose -f docker-compose.dev.yml up --build",
"docker:up:unix": "docker compose -f docker-compose.unix.yml up --build",
"docker:up:ci": "docker compose -f docker-compose.ci.yml up --build",
@@ -43,6 +41,7 @@
"tailwind-scrollbar-hide": "^4.0.0"
},
"devDependencies": {
"@biomejs/biome": "^2.2.4",
"@types/node": "^24.0.3",
"concurrently": "^9.1.2",
"dotenv": "^16.5.0",

173
pnpm-lock.yaml generated
View File

@@ -24,6 +24,9 @@ importers:
specifier: ^4.0.0
version: 4.0.0(tailwindcss@4.1.12)
devDependencies:
'@biomejs/biome':
specifier: ^2.2.4
version: 2.2.4
'@types/node':
specifier: ^24.0.3
version: 24.3.0
@@ -80,7 +83,7 @@ importers:
version: 1.9.0
'@opentelemetry/auto-instrumentations-node':
specifier: ^0.60.1
version: 0.60.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))
version: 0.60.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))
'@opentelemetry/exporter-trace-otlp-proto':
specifier: ^0.202.0
version: 0.202.0(@opentelemetry/api@1.9.0)
@@ -173,7 +176,7 @@ importers:
version: 16.6.1
drizzle-orm:
specifier: ^0.43.1
version: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.21(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)
version: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.22(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)
express:
specifier: ^4.21.1
version: 4.21.2
@@ -324,17 +327,17 @@ importers:
version: 0.31.4
drizzle-orm:
specifier: ^0.43.1
version: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.21(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)
version: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.22(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)
drizzle-zod:
specifier: ^0.8.2
version: 0.8.3(drizzle-orm@0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.21(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@3.25.76)
version: 0.8.3(drizzle-orm@0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.22(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@3.25.76)
zod:
specifier: ^3.25.23
version: 3.25.76
devDependencies:
'@types/bun':
specifier: latest
version: 1.2.21(@types/react@18.3.24)
version: 1.2.22(@types/react@18.3.24)
'@types/node':
specifier: ^24.0.3
version: 24.3.0
@@ -789,6 +792,59 @@ packages:
'@better-fetch/fetch@1.1.18':
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
'@biomejs/biome@2.2.4':
resolution: {integrity: sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==}
engines: {node: '>=14.21.3'}
hasBin: true
'@biomejs/cli-darwin-arm64@2.2.4':
resolution: {integrity: sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
'@biomejs/cli-darwin-x64@2.2.4':
resolution: {integrity: sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
'@biomejs/cli-linux-arm64-musl@2.2.4':
resolution: {integrity: sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-arm64@2.2.4':
resolution: {integrity: sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-x64-musl@2.2.4':
resolution: {integrity: sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-linux-x64@2.2.4':
resolution: {integrity: sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-win32-arm64@2.2.4':
resolution: {integrity: sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [win32]
'@biomejs/cli-win32-x64@2.2.4':
resolution: {integrity: sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [win32]
'@browserbasehq/sdk@2.6.0':
resolution: {integrity: sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==}
@@ -3775,8 +3831,8 @@ packages:
'@types/body-parser@1.19.6':
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
'@types/bun@1.2.21':
resolution: {integrity: sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A==}
'@types/bun@1.2.22':
resolution: {integrity: sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA==}
'@types/bunyan@1.8.11':
resolution: {integrity: sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==}
@@ -4372,8 +4428,8 @@ packages:
bullmq@5.58.4:
resolution: {integrity: sha512-1EE7pbEw2YNc64nVW+RYVvA6I1ijkhCacsZ/zWbjhDtN8sxZ+Wy3WQ/rUSMOOk/PGAGEQRTgngbTpYbvGYijHQ==}
bun-types@1.2.21:
resolution: {integrity: sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw==}
bun-types@1.2.22:
resolution: {integrity: sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA==}
peerDependencies:
'@types/react': ^19
@@ -7584,7 +7640,7 @@ snapshots:
'@babel/traverse': 7.28.3
'@babel/types': 7.28.2
convert-source-map: 2.0.0
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -7727,7 +7783,7 @@ snapshots:
'@babel/parser': 7.28.3
'@babel/template': 7.27.2
'@babel/types': 7.28.2
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -7750,6 +7806,41 @@ snapshots:
'@better-fetch/fetch@1.1.18': {}
'@biomejs/biome@2.2.4':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 2.2.4
'@biomejs/cli-darwin-x64': 2.2.4
'@biomejs/cli-linux-arm64': 2.2.4
'@biomejs/cli-linux-arm64-musl': 2.2.4
'@biomejs/cli-linux-x64': 2.2.4
'@biomejs/cli-linux-x64-musl': 2.2.4
'@biomejs/cli-win32-arm64': 2.2.4
'@biomejs/cli-win32-x64': 2.2.4
'@biomejs/cli-darwin-arm64@2.2.4':
optional: true
'@biomejs/cli-darwin-x64@2.2.4':
optional: true
'@biomejs/cli-linux-arm64-musl@2.2.4':
optional: true
'@biomejs/cli-linux-arm64@2.2.4':
optional: true
'@biomejs/cli-linux-x64-musl@2.2.4':
optional: true
'@biomejs/cli-linux-x64@2.2.4':
optional: true
'@biomejs/cli-win32-arm64@2.2.4':
optional: true
'@biomejs/cli-win32-x64@2.2.4':
optional: true
'@browserbasehq/sdk@2.6.0':
dependencies:
'@types/node': 18.19.123
@@ -8076,7 +8167,7 @@ snapshots:
'@eslint/config-array@0.21.0':
dependencies:
'@eslint/object-schema': 2.1.6
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -8090,7 +8181,7 @@ snapshots:
'@eslint/eslintrc@3.3.1':
dependencies:
ajv: 6.12.6
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
espree: 10.4.0
globals: 14.0.0
ignore: 5.3.2
@@ -8780,10 +8871,10 @@ snapshots:
- encoding
- supports-color
'@opentelemetry/auto-instrumentations-node@0.60.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))':
'@opentelemetry/auto-instrumentations-node@0.60.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation-amqplib': 0.49.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation-aws-lambda': 0.53.1(@opentelemetry/api@1.9.0)
@@ -10163,7 +10254,7 @@ snapshots:
'@puppeteer/browsers@2.10.8':
dependencies:
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
extract-zip: 2.0.1
progress: 2.0.3
proxy-agent: 6.5.0
@@ -11119,9 +11210,9 @@ snapshots:
'@types/connect': 3.4.38
'@types/node': 22.18.0
'@types/bun@1.2.21(@types/react@18.3.24)':
'@types/bun@1.2.22(@types/react@18.3.24)':
dependencies:
bun-types: 1.2.21(@types/react@18.3.24)
bun-types: 1.2.22(@types/react@18.3.24)
transitivePeerDependencies:
- '@types/react'
@@ -11381,7 +11472,7 @@ snapshots:
'@typescript-eslint/types': 8.41.0
'@typescript-eslint/typescript-estree': 8.41.0(typescript@5.7.3)
'@typescript-eslint/visitor-keys': 8.41.0
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
eslint: 9.34.0(jiti@2.5.1)
typescript: 5.7.3
transitivePeerDependencies:
@@ -11391,7 +11482,7 @@ snapshots:
dependencies:
'@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.7.3)
'@typescript-eslint/types': 8.41.0
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
typescript: 5.7.3
transitivePeerDependencies:
- supports-color
@@ -11410,7 +11501,7 @@ snapshots:
'@typescript-eslint/types': 8.41.0
'@typescript-eslint/typescript-estree': 8.41.0(typescript@5.7.3)
'@typescript-eslint/utils': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.7.3)
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
eslint: 9.34.0(jiti@2.5.1)
ts-api-utils: 2.1.0(typescript@5.7.3)
typescript: 5.7.3
@@ -11425,7 +11516,7 @@ snapshots:
'@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.7.3)
'@typescript-eslint/types': 8.41.0
'@typescript-eslint/visitor-keys': 8.41.0
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
@@ -11795,9 +11886,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
bun-types@1.2.21(@types/react@18.3.24):
bun-types@1.2.22(@types/react@18.3.24):
dependencies:
'@types/node': 24.3.0
'@types/node': 22.18.0
'@types/react': 18.3.24
bytes@3.1.2: {}
@@ -12224,18 +12315,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.21(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7):
drizzle-orm@0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.22(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7):
optionalDependencies:
'@opentelemetry/api': 1.9.0
'@types/pg': 8.15.5
bun-types: 1.2.21(@types/react@18.3.24)
bun-types: 1.2.22(@types/react@18.3.24)
kysely: 0.28.5
pg: 8.16.3
postgres: 3.4.7
drizzle-zod@0.8.3(drizzle-orm@0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.21(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@3.25.76):
drizzle-zod@0.8.3(drizzle-orm@0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.22(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@3.25.76):
dependencies:
drizzle-orm: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.21(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)
drizzle-orm: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(bun-types@1.2.22(@types/react@18.3.24))(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)
zod: 3.25.76
dunder-proto@1.0.1:
@@ -12310,7 +12401,7 @@ snapshots:
esbuild-register@3.6.0(esbuild@0.25.9):
dependencies:
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
esbuild: 0.25.9
transitivePeerDependencies:
- supports-color
@@ -12446,7 +12537,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -12542,7 +12633,7 @@ snapshots:
extract-zip@2.0.1:
dependencies:
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
@@ -12747,7 +12838,7 @@ snapshots:
dependencies:
basic-ftp: 5.0.5
data-uri-to-buffer: 6.0.2
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -12872,7 +12963,7 @@ snapshots:
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -12881,7 +12972,7 @@ snapshots:
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.4
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -12983,7 +13074,7 @@ snapshots:
dependencies:
'@ioredis/commands': 1.3.1
cluster-key-slot: 1.1.2
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
@@ -13666,7 +13757,7 @@ snapshots:
dependencies:
'@tootallnate/quickjs-emscripten': 0.23.0
agent-base: 7.1.4
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
get-uri: 6.0.5
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
@@ -13890,7 +13981,7 @@ snapshots:
proxy-agent@6.5.0:
dependencies:
agent-base: 7.1.4
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
lru-cache: 7.18.3
@@ -13915,7 +14006,7 @@ snapshots:
dependencies:
'@puppeteer/browsers': 2.10.8
chromium-bidi: 8.0.0(devtools-protocol@0.0.1475386)
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
devtools-protocol: 0.0.1475386
typed-query-selector: 2.12.0
ws: 8.18.3
@@ -14201,7 +14292,7 @@ snapshots:
require-in-the-middle@7.5.2:
dependencies:
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
module-details-from-path: 1.0.4
resolve: 1.22.10
transitivePeerDependencies:
@@ -14462,7 +14553,7 @@ snapshots:
socks-proxy-agent@8.0.5:
dependencies:
agent-base: 7.1.4
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
socks: 2.8.7
transitivePeerDependencies:
- supports-color
@@ -14582,7 +14673,7 @@ snapshots:
dependencies:
component-emitter: 1.3.1
cookiejar: 2.1.4
debug: 4.4.1(supports-color@5.5.0)
debug: 4.4.1(supports-color@8.1.1)
fast-safe-stringify: 2.1.1
form-data: 4.0.4
formidable: 3.5.4

View File

@@ -1,81 +1,81 @@
import OnboardingView2 from "./views/onboarding2/OnboardingView2";
import { AppEnv } from "@autumn/shared";
import { BrowserRouter, Route, Routes } from "react-router";
import { MainLayout } from "./app/layout";
import { AdminView } from "./views/admin/AdminView";
import { AcceptInvitation } from "./views/auth/AcceptInvitation";
import { PasswordSignIn } from "./views/auth/components/PasswordSignIn";
import { SignIn } from "./views/auth/SignIn";
import CliAuth from "./views/CliAuth";
import { Otp } from "./views/cli/Otp";
import CustomersPage from "./views/customers/CustomersPage";
import { AnalyticsView } from "./views/customers/customer/analytics/AnalyticsView";
import CustomerView from "./views/customers/customer/CustomerView";
import CustomerProductView from "./views/customers/customer/product/CustomerProductView";
import DevScreen from "./views/developer/DevView";
import ProductView from "./views/products/product/ProductView";
import ProductsView from "./views/products/ProductsView";
import CliAuth from "./views/CliAuth";
import { AppEnv } from "@autumn/shared";
import { Otp } from "./views/cli/Otp";
import { SignIn } from "./views/auth/SignIn";
import { AcceptInvitation } from "./views/auth/AcceptInvitation";
import { BrowserRouter, Routes, Route } from "react-router";
import { AdminView } from "./views/admin/AdminView";
import { PasswordSignIn } from "./views/auth/components/PasswordSignIn";
import { AnalyticsView } from "./views/customers/customer/analytics/AnalyticsView";
import { TerminalView } from "./views/TerminalView";
import { DefaultView } from "./views/DefaultView";
import { MainLayout } from "./app/layout";
import CustomersPage from "./views/customers/CustomersPage";
import DevScreen from "./views/developer/DevView";
import OnboardingView2 from "./views/onboarding2/OnboardingView2";
import ProductsView from "./views/products/ProductsView";
import PlanEditorView from "./views/products/plan/PlanEditorView";
import ProductView from "./views/products/product/ProductView";
import { TerminalView } from "./views/TerminalView";
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/sign-in" element={<SignIn />} />
<Route path="/pw-sign-in" element={<PasswordSignIn />} />
<Route path="/accept" element={<AcceptInvitation />} />
<Route element={<MainLayout />}>
<Route path="*" element={<DefaultView />} />
<Route path="/admin" element={<AdminView />} />
<Route path="/trmnl" element={<TerminalView />} />
<Route path="/onboarding" element={<OnboardingView2 />} />
<Route path="/sandbox/onboarding" element={<OnboardingView2 />} />
<Route path="/cli-auth" element={<CliAuth />} />
return (
<BrowserRouter>
<Routes>
<Route path="/sign-in" element={<SignIn />} />
<Route path="/pw-sign-in" element={<PasswordSignIn />} />
<Route path="/accept" element={<AcceptInvitation />} />
<Route element={<MainLayout />}>
<Route path="*" element={<DefaultView />} />
<Route path="/admin" element={<AdminView />} />
<Route path="/trmnl" element={<TerminalView />} />
<Route path="/onboarding" element={<OnboardingView2 />} />
<Route path="/sandbox/onboarding" element={<OnboardingView2 />} />
<Route path="/cli-auth" element={<CliAuth />} />
<Route
path="/products"
element={<ProductsView env={AppEnv.Live} />}
/>
<Route
path="/sandbox/products"
element={<ProductsView env={AppEnv.Sandbox} />}
/>
<Route path="/products/:product_id" element={<ProductView />} />
<Route
path="/sandbox/products/:product_id"
element={<ProductView />}
/>
<Route
path="/products"
element={<ProductsView env={AppEnv.Live} />}
/>
<Route
path="/sandbox/products"
element={<ProductsView env={AppEnv.Sandbox} />}
/>
<Route path="/products/:product_id" element={<PlanEditorView />} />
<Route
path="/sandbox/products/:product_id"
element={<ProductView />}
/>
<Route path="/customers" element={<CustomersPage />} />
<Route path="/sandbox/customers" element={<CustomersPage />} />
<Route path="/customers/:customer_id" element={<CustomerView />} />
<Route
path="/sandbox/customers/:customer_id"
element={<CustomerView />}
/>
<Route
path="/customers/:customer_id/:product_id"
element={<CustomerProductView />}
/>
<Route
path="/sandbox/customers/:customer_id/:product_id"
element={<CustomerProductView />}
/>
<Route path="/dev" element={<DevScreen />} />
<Route path="/sandbox/dev" element={<DevScreen />} />
<Route
path="/analytics"
element={<AnalyticsView env={AppEnv.Live} />}
/>
<Route
path="/sandbox/analytics"
element={<AnalyticsView env={AppEnv.Sandbox} />}
/>
<Route path="/dev/cli" element={<Otp />} />
</Route>
</Routes>
</BrowserRouter>
);
<Route path="/customers" element={<CustomersPage />} />
<Route path="/sandbox/customers" element={<CustomersPage />} />
<Route path="/customers/:customer_id" element={<CustomerView />} />
<Route
path="/sandbox/customers/:customer_id"
element={<CustomerView />}
/>
<Route
path="/customers/:customer_id/:product_id"
element={<CustomerProductView />}
/>
<Route
path="/sandbox/customers/:customer_id/:product_id"
element={<CustomerProductView />}
/>
<Route path="/dev" element={<DevScreen />} />
<Route path="/sandbox/dev" element={<DevScreen />} />
<Route
path="/analytics"
element={<AnalyticsView env={AppEnv.Live} />}
/>
<Route
path="/sandbox/analytics"
element={<AnalyticsView env={AppEnv.Sandbox} />}
/>
<Route path="/dev/cli" element={<Otp />} />
</Route>
</Routes>
</BrowserRouter>
);
}

View File

@@ -1,19 +1,33 @@
import { Button } from "../ui/button";
import { Copy } from "lucide-react";
import CopyButton from "./CopyButton";
export function CopyablePre({ text }: { text: string }) {
return (
<div className="relative w-full">
<pre className="text-sm bg-muted/50 p-4 rounded-lg overflow-auto border w-full h-full">
<CopyButton
text={text}
className="absolute top-2 right-2 z-10"
/>
<code className="text-sm block w-full break-words whitespace-pre-wrap">
{text}
</code>
</pre>
</div>
);
return (
<div className="relative w-full">
<pre className="text-sm bg-muted/50 p-4 rounded-lg overflow-auto border w-full h-full">
<CopyButton text={text} className="absolute top-2 right-2 z-10" />
<code className="text-sm block w-full break-words whitespace-pre-wrap">
{text}
</code>
</pre>
</div>
);
}
// A compact, inline version for use in places like badges or small UI elements.
export function CopyableSpan({ text, className = "" }: { text: string; className?: string }) {
return (
<span
className={
`inline-flex items-center rounded-xl bg-muted/50 px-4 py-1 text-md font-mono text-muted-foreground relative font-normal gap-1` +
(className ? ` ${className}` : "")
}
>
<span className="pr-2">{text}</span>
<CopyButton
text={text}
className="ml-1 p-0.5 rounded transition hover:bg-muted/80 w-6 h-6 z-[1] static"
copySize={20}
/>
</span>
);
}

View File

@@ -0,0 +1,29 @@
import { Slot } from "@radix-ui/react-slot";
import type { ButtonHTMLAttributes } from "react";
import { cn } from "@/lib/utils";
interface ClickableIconProps extends ButtonHTMLAttributes<HTMLButtonElement> {
icon: React.ReactElement;
asChild?: boolean;
}
export const ClickableIcon = ({
icon,
onClick,
asChild,
...props
}: ClickableIconProps) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
{...{
...props,
className: cn("hover:", props.className ?? ""),
onClick: onClick,
}}
>
{icon}
</Comp>
);
};

View File

@@ -0,0 +1,52 @@
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border border-zinc-200 px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-950 focus:ring-offset-2 dark:border-zinc-800 dark:focus:ring-zinc-300",
{
variants: {
variant: {
default:
"border-transparent bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/80 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/80",
secondary:
"border-transparent bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
destructive:
"border-transparent bg-red-500 text-zinc-50 shadow hover:bg-red-500/80 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/80",
outline: "text-zinc-950 dark:text-zinc-50",
purple: "bg-purple-50 text-purple-500 border-purple-200",
blue: "bg-blue-50 text-blue-500 border-blue-200",
green:
"bg-green-50 text-green-500 border-green-200 !hover:bg-green-100",
yellow: "bg-yellow-50 text-yellow-500 border-yellow-200",
red: "bg-red-50 text-red-500 border-red-200",
status: "px-1.5 py-0 text-white border-none",
},
},
defaultVariants: {
variant: "default",
},
},
);
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span";
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
}
export { Badge, badgeVariants };

View File

@@ -0,0 +1,42 @@
import { useNavigate } from "react-router";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbSeparator
} from "@/components/ui/breadcrumb";
import { useEnv } from "@/utils/envUtils";
import { navigateTo } from "@/utils/genUtils";
interface BreadcrumbItemType {
name: string;
href: string;
}
export default function V2Breadcrumb({
items,
}: {
items: BreadcrumbItemType[];
}) {
const env = useEnv();
const navigate = useNavigate();
return (
<Breadcrumb className="text-t3 pt-6 pl-4 flex justify-center">
<BreadcrumbList className="text-t3 text-xs w-full">
{items.map((item, index) => (
<>
<BreadcrumbItem
key={item.name}
onClick={() => navigateTo(item.href, navigate, env)}
className="cursor-pointer"
>
{item.name}
</BreadcrumbItem>
{index < items.length - 1 && <BreadcrumbSeparator />}
</>
))}
</BreadcrumbList>
</Breadcrumb>
);
}

View File

View File

@@ -0,0 +1,41 @@
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 (
// <>
// {product.is_default ? (
// <Badge variant="outline">Default</Badge>
// ) : product.is_add_on ? (
// <Badge variant="outline">Add-On</Badge>
// ) : (
// <></>
// )}
// </>
<Badge
className={cn(
"bg-transparent border border-t1 text-t1 rounded-md px-2 pointer-events-none",
badgeType === "default" &&
"bg-stone-200 text-stone-700 border-stone-700",
badgeType === "add-on" && "bg-zinc-100 text-zinc-500 border-zinc-400",
badgeType === "default trial" &&
"bg-blue-100 text-blue-500 border-blue-400",
)}
>
{badgeType}
</Badge>
);
};

View File

@@ -0,0 +1,92 @@
import { useState } from "react";
import { useParams } from "react-router";
import V2Breadcrumb from "@/components/v2/breadcrumb";
import ErrorScreen from "@/views/general/ErrorScreen";
import LoadingScreen from "@/views/general/LoadingScreen";
import { UpdateProductButton } from "../product/components/UpdateProductButton";
import { useProductChangedAlert } from "../product/hooks/useProductChangedAlert";
import { useProductData } from "../product/hooks/useProductData";
import { useProductQuery } from "../product/hooks/useProductQuery";
import { ProductContext } from "../product/ProductContext";
import ProductSidebar from "../product/ProductSidebar";
import ConfirmNewVersionDialog from "../product/versioning/ConfirmNewVersionDialog";
import { ManagePlan } from "./components/Editor";
export default function PlanEditorView() {
const { plan_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 <LoadingScreen />;
if (error) {
return (
<ErrorScreen returnUrl="/products">
{error ? error.message : `Plan ${plan_id} not found`}
</ErrorScreen>
);
}
if (!product) return;
return (
<ProductContext.Provider
value={{
setShowNewVersionDialog,
product,
setProduct,
actionState,
entityFeatureIds,
setEntityFeatureIds,
hasChanges,
}}
>
<ConfirmNewVersionDialog
open={showNewVersionDialog}
setOpen={setShowNewVersionDialog}
/>
<div className="flex w-full">
<div className="flex flex-col gap-4 w-full">
<V2Breadcrumb
items={[
{
name: "Plans",
href: "/products",
},
{
name: "Plan Editor",
href: `/products`,
},
]}
/>
<div className="flex">
<div className="flex-1 w-full min-w-sm">
<ManagePlan />
</div>
</div>
<div className="flex justify-end gap-2 p-10 w-full lg:hidden">
<div className="w-fit">
<UpdateProductButton />
</div>
</div>
</div>
<div className="hidden max-w-md w-1/3 shrink-1 lg:block lg:min-w-xs sticky top-0">
<ProductSidebar />
</div>
</div>
{modal}
</ProductContext.Provider>
);
}

View File

@@ -0,0 +1,28 @@
import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
import { useProductContext } from "@/views/products/product/ProductContext";
import EditorTopSection from "./EditorTopSection";
import PlanCard from "./PlanCard";
export const ManagePlan = ({
hideAdminHover = false,
}: {
hideAdminHover?: boolean;
}) => {
const { customer } = useCusQuery();
const { product, entityId } = useProductContext();
return (
<div className="flex flex-col gap-4 ">
<EditorTopSection
product={product}
hideAdminHover={hideAdminHover}
customer={customer}
entityId={entityId}
/>
<div className="flex flex-col gap-10 h-full bg-[#EEEEEE] min-h-screen items-center justify-center">
<PlanCard product={product} />
</div>
</div>
);
};

View File

@@ -0,0 +1,55 @@
import type { FullCustomer } from "@autumn/shared";
import { AdminHover } from "@/components/general/AdminHover";
import { Badge } from "@/components/ui/badge";
import { SelectEntity } from "@/views/customers/customer/components/customer-header/SelectEntity";
import { PlanTypeBadge } from "../../components/PlanTypeBadge";
export default function EditorTopSection({
product,
hideAdminHover,
customer,
entityId,
}: {
product: any;
hideAdminHover: boolean;
customer: FullCustomer;
entityId: string;
}) {
return (
<div className="flex pl-4 pr-10 flex-col gap-2">
<div className="col-span-2 flex">
<div className="flex flex-row items-baseline justify-start gap-2 w-full whitespace-nowrap">
<AdminHover
texts={[
{
key: "internal_product_id",
value: product.internal_id!,
},
{
key: "stripe_id",
value: product.stripe_id || "N/A",
},
{
key: "customer_product_id",
value: product.cusProductId || "N/A",
},
]}
hide={hideAdminHover}
>
<span className="text-lg font-medium w-fit whitespace-nowrap">
{product.name}
</span>
</AdminHover>
<span className="text-sm text-t3">v{product.version}</span>
</div>
</div>
<div className="flex flex-row gap-2">
<Badge variant="secondary">Add-on</Badge>
<PlanTypeBadge product={product} />
</div>
{customer && (
<SelectEntity entityId={entityId} entities={customer?.entities} />
)}
</div>
);
}

View File

@@ -0,0 +1,25 @@
import type { ProductV2 } from "@autumn/shared";
import { PencilIcon, Trash2 } from "lucide-react";
import { CopyableSpan } from "@/components/general/CopyablePre";
import { Card, CardHeader } from "@/components/ui/card";
export default function PlanCard({ product }: { product: ProductV2 }) {
return (
<Card className="w-[95%] mx-auto">
<CardHeader>
<div className="flex flex-row items-center justify-between gap-2 w-full whitespace-nowrap">
<div className="flex flex-row items-baseline gap-2">
<span className="text-lg font-medium w-fit whitespace-nowrap">
{product.name}
</span>
<CopyableSpan text={product.id} />
</div>
<div className="flex flex-row items-center gap-2">
<PencilIcon />
<Trash2/>
</div>
</div>
</CardHeader>
</Card>
);
}

View File

@@ -0,0 +1,115 @@
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 "../../product/components/UpdateProductButton";
import { CreateFreeTrial } from "../../product/free-trial/CreateFreeTrial";
import { FreeTrialView } from "../../product/free-trial/FreeTrialView";
import { useProductContext } from "../../product/ProductContext";
import { ProductProps } from "../../product/ProductProps";
import { ProductVersions } from "../../product/ProductVersions";
import { EntitiesSidebar } from "../../product/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 (
<div className="flex-col gap-4 h-full border-l py-6">
<div className="flex items-center gap-2 justify-start px-4">
{isCusProductView && <CustomerProductBadge />}
</div>
<Accordion
type="multiple"
className="w-full flex flex-col"
value={accordionValues}
onValueChange={setAccordionValues}
>
<div className="flex w-full border-b mt-[2px] p-4">
<SideAccordion title="Properties" value="properties">
<ProductProps />
</SideAccordion>
</div>
<div className="flex w-full border-b p-4">
<SideAccordion title="Versions" value="versions">
<ProductVersions />
</SideAccordion>
</div>
<div className="flex w-full border-b p-4 relative">
<SideAccordion
title="Entities"
value="entities"
isOpen={accordionValues.includes("entities")}
onToggle={handleAccordionToggle}
onClick={() => setEntitiesOpen(!entitiesOpen)}
buttonIcon={<Plus size={14} />}
>
<EntitiesSidebar open={entitiesOpen} setOpen={setEntitiesOpen} />
</SideAccordion>
</div>
<CreateFreeTrial
open={freeTrialModalOpen}
setOpen={setFreeTrialModalOpen}
/>
<div className="flex w-full border-b p-4">
<SideAccordion
title="Free Trial"
value="free-trial"
onClick={
product.free_trial
? handleDeleteFreeTrial
: handleFreeTrialModalOpen
}
buttonIcon={
product.free_trial ? <Minus size={14} /> : <Plus size={14} />
}
disabledReason={
isOneOffProduct(product.items, product.is_add_on)
? "Can't add a free trial to an a one time product"
: undefined
}
>
<div>
{product.free_trial ? (
<FreeTrialView product={product} />
) : (
<span className="text-t3">
Add a free trial to this product.
</span>
)}
</div>
</SideAccordion>
</div>
<div className="flex gap-2 px-4 py-6 w-full">
{isCusProductView ? <AttachButton /> : <UpdateProductButton />}
</div>
</Accordion>
</div>
);
}

View File

@@ -1,56 +1,54 @@
import { AdminHover } from "@/components/general/AdminHover";
import { useProductContext } from "./ProductContext";
import { useNavigate } from "react-router";
import { useEnv } from "@/utils/envUtils";
import { ProductItemTable } from "./product-item/ProductItemTable";
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 = false,
}: {
hideAdminHover?: boolean;
hideAdminHover?: boolean;
}) => {
const { customer } = useCusQuery();
const { product, entityId } = useProductContext();
const { customer } = useCusQuery();
const { product, entityId } = useProductContext();
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between pl-10 pr-10">
<div className="col-span-2 flex">
<div className="flex flex-col gap-1 justify-center w-full whitespace-nowrap">
<AdminHover
texts={[
{
key: "internal_product_id",
value: product.internal_id!,
},
{
key: "stripe_id",
value: product.stripe_id || "N/A",
},
{
key: "customer_product_id",
value: product.cusProductId || "N/A",
},
]}
hide={hideAdminHover}
>
<h2 className="text-lg font-medium w-fit whitespace-nowrap">
{product.name}
</h2>
</AdminHover>
</div>
</div>
{/* <EntityHeader entity={entity} /> */}
{customer && (
<SelectEntity entityId={entityId} entities={customer?.entities} />
)}
</div>
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between pl-10 pr-10">
<div className="col-span-2 flex">
<div className="flex flex-col gap-1 justify-center w-full whitespace-nowrap">
<AdminHover
texts={[
{
key: "internal_product_id",
value: product.internal_id!,
},
{
key: "stripe_id",
value: product.stripe_id || "N/A",
},
{
key: "customer_product_id",
value: product.cusProductId || "N/A",
},
]}
hide={hideAdminHover}
>
<h2 className="text-lg font-medium w-fit whitespace-nowrap">
{product.name}
</h2>
</AdminHover>
</div>
</div>
{/* <EntityHeader entity={entity} /> */}
{customer && (
<SelectEntity entityId={entityId} entities={customer?.entities} />
)}
</div>
<div className="flex flex-col gap-10">
<ProductItemTable />
</div>
</div>
);
<div className="flex flex-col gap-10">
<ProductItemTable />
</div>
</div>
);
};

View File

@@ -1,140 +1,139 @@
"use client";
import { useState } from "react";
import { useParams } from "react-router";
import ErrorScreen from "@/views/general/ErrorScreen";
import LoadingScreen from "@/views/general/LoadingScreen";
import ProductSidebar from "./ProductSidebar";
import ProductViewBreadcrumbs from "./components/ProductViewBreadcrumbs";
import { useState } from "react";
import { ProductContext } from "./ProductContext";
import { useParams } from "react-router";
import { ManageProduct } from "./ManageProduct";
import { UpdateProductButton } from "./components/UpdateProductButton";
import { useProductChangedAlert } from "./hooks/useProductChangedAlert";
import { useProductData } from "./hooks/useProductData";
import { UpdateProductButton } from "./components/UpdateProductButton";
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_id } = useParams();
const { product: originalProduct, isLoading, error } = useProductQuery();
const { product: originalProduct, isLoading, error } = useProductQuery();
const {
product,
setProduct,
hasChanges,
entityFeatureIds,
setEntityFeatureIds,
actionState,
} = useProductData({ originalProduct });
const {
product,
setProduct,
hasChanges,
entityFeatureIds,
setEntityFeatureIds,
actionState,
} = useProductData({ originalProduct });
const { modal } = useProductChangedAlert({ hasChanges });
const [showNewVersionDialog, setShowNewVersionDialog] = useState(false);
const { modal } = useProductChangedAlert({ hasChanges });
const [showNewVersionDialog, setShowNewVersionDialog] = useState(false);
if (isLoading) return <LoadingScreen />;
if (error) {
return (
<ErrorScreen returnUrl="/products">
{error ? error.message : `Product ${product_id} not found`}
</ErrorScreen>
);
}
if (isLoading) return <LoadingScreen />;
if (error) {
return (
<ErrorScreen returnUrl="/products">
{error ? error.message : `Product ${product_id} not found`}
</ErrorScreen>
);
}
if (!product) return <></>;
if (!product) return;
// const updateProduct = async () => {
// try {
// await ProductService.updateProduct(axiosInstance, product.id, {
// ...UpdateProductSchema.parse(product),
// items: product.items,
// free_trial: product.free_trial,
// });
// const updateProduct = async () => {
// try {
// await ProductService.updateProduct(axiosInstance, product.id, {
// ...UpdateProductSchema.parse(product),
// items: product.items,
// free_trial: product.free_trial,
// });
// if (isNewProduct) {
// toast.success("Product created successfully");
// } else {
// toast.success("Product updated successfully");
// }
// if (isNewProduct) {
// toast.success("Product created successfully");
// } else {
// toast.success("Product updated successfully");
// }
// await refetch();
// await mutateCount();
// } catch (error) {
// toast.error(getBackendErr(error, "Failed to update product"));
// }
// };
// await refetch();
// await mutateCount();
// } catch (error) {
// toast.error(getBackendErr(error, "Failed to update product"));
// }
// };
// const updateProductClicked = async () => {
// if (!counts) {
// toast.error("Something went wrong, please try again...");
// return;
// }
// const updateProductClicked = async () => {
// if (!counts) {
// toast.error("Something went wrong, please try again...");
// return;
// }
// if (version && version < data?.numVersions) {
// toast.error("You can only update the latest version of a product");
// return;
// }
// if (version && version < data?.numVersions) {
// toast.error("You can only update the latest version of a product");
// return;
// }
// if (counts?.all > 0) {
// setShowNewVersionDialog(true);
// return;
// }
// if (counts?.all > 0) {
// setShowNewVersionDialog(true);
// return;
// }
// await updateProduct();
// };
// await updateProduct();
// };
return (
<ProductContext.Provider
value={{
// ...data,
// features,
// setFeatures,
return (
<ProductContext.Provider
value={{
// ...data,
// features,
// setFeatures,
// For versioning?
// counts,
// version,
// For versioning?
// counts,
// version,
// mutate,
// env,
// mutate,
// env,
setShowNewVersionDialog,
product,
setProduct,
setShowNewVersionDialog,
product,
setProduct,
// mutateCount,
actionState,
// handleCreateProduct: updateProductClicked,
entityFeatureIds,
setEntityFeatureIds,
hasChanges,
}}
>
<ConfirmNewVersionDialog
open={showNewVersionDialog}
setOpen={setShowNewVersionDialog}
/>
<div className="flex w-full">
<div className="flex flex-col gap-4 w-full">
<ProductViewBreadcrumbs />
// mutateCount,
actionState,
// handleCreateProduct: updateProductClicked,
entityFeatureIds,
setEntityFeatureIds,
hasChanges,
}}
>
<ConfirmNewVersionDialog
open={showNewVersionDialog}
setOpen={setShowNewVersionDialog}
/>
<div className="flex w-full">
<div className="flex flex-col gap-4 w-full">
<ProductViewBreadcrumbs />
<div className="flex">
<div className="flex-1 w-full min-w-sm">
<ManageProduct />
</div>
</div>
<div className="flex justify-end gap-2 p-10 w-full lg:hidden">
<div className="w-fit">
<UpdateProductButton />
</div>
</div>
</div>
<div className="hidden max-w-md w-1/3 shrink-1 lg:block lg:min-w-xs sticky top-0">
<ProductSidebar />
</div>
</div>
{modal}
</ProductContext.Provider>
);
<div className="flex">
<div className="flex-1 w-full min-w-sm">
<ManageProduct />
</div>
</div>
<div className="flex justify-end gap-2 p-10 w-full lg:hidden">
<div className="w-fit">
<UpdateProductButton />
</div>
</div>
</div>
<div className="hidden max-w-md w-1/3 shrink-1 lg:block lg:min-w-xs sticky top-0">
<ProductSidebar />
</div>
</div>
{modal}
</ProductContext.Provider>
);
}
export default ProductView;