wip
This commit is contained in:
@@ -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
173
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
146
vite/src/App.tsx
146
vite/src/App.tsx
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
29
vite/src/components/general/buttons/ClickableIcon.tsx
Normal file
29
vite/src/components/general/buttons/ClickableIcon.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
52
vite/src/components/v2/badge.tsx
Normal file
52
vite/src/components/v2/badge.tsx
Normal 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 };
|
||||
42
vite/src/components/v2/breadcrumb.tsx
Normal file
42
vite/src/components/v2/breadcrumb.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
0
vite/src/components/v2/button.tsx
Normal file
0
vite/src/components/v2/button.tsx
Normal file
41
vite/src/views/products/components/PlanTypeBadge.tsx
Normal file
41
vite/src/views/products/components/PlanTypeBadge.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
92
vite/src/views/products/plan/PlanEditorView.tsx
Normal file
92
vite/src/views/products/plan/PlanEditorView.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
28
vite/src/views/products/plan/components/Editor.tsx
Normal file
28
vite/src/views/products/plan/components/Editor.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
55
vite/src/views/products/plan/components/EditorTopSection.tsx
Normal file
55
vite/src/views/products/plan/components/EditorTopSection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
25
vite/src/views/products/plan/components/PlanCard.tsx
Normal file
25
vite/src/views/products/plan/components/PlanCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
115
vite/src/views/products/plan/components/RightPanel.tsx
Normal file
115
vite/src/views/products/plan/components/RightPanel.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user