feat(deps): bump zod 3→4 + @hookform/resolvers 3→5
Resolved 65 type errors across the codebase via these v4 migration
patterns:
- `ZodError.errors` renamed to `ZodError.issues` (4 call sites in auth
routes + central error handler).
- `z.record(value)` now requires explicit key type: `z.record(z.string(),
value)`. Updated 7 sites across templates / forms / saved-views /
website-inquiries.
- `.refine(check, msgFn)` second-arg shape changed — now requires an
`{ error: (issue) => ... }` object form. Updated
`mergeFieldsSchema` in document-templates validator.
- `.transform(...).default(...)` chains: v4 enforces default value type
matches transform OUTPUT. Reordered to `.default(...).transform(...)`
in list-query / company-memberships handlers.
- `z.coerce.*()` INPUT type widened to `unknown` in v4. Service signatures
using `z.input<typeof schema>` (kept for caller flexibility around
defaults) now re-parse via `schema.parse(data)` to recover the
post-coercion shape Drizzle needs. Done in berth-reservations service.
Invoice service narrows `lineItems` locally with a typed cast since
re-parsing would double-validate.
- `.optional().transform(...)` no longer propagates the optional marker
through v4's new ZodPipe. Moved `.optional()` to the END of chain in
`optionalDesiredDimSchema` (interests) and documents list query
(folderId, signatureOnly).
- ZodIssue subtype shapes simplified: `received` removed from
invalid_type, `type` renamed to `origin` on too_small. Test fixtures
updated.
- @hookform/resolvers v5 splits Resolver into 3-generic form (Input,
Context, Output). useForm calls in 6 forms (client, yacht, berth,
interest, expense, invoices-new-page) now pass explicit generics:
`useForm<z.input<typeof schema>, unknown, z.infer<typeof schema>>`.
Verified: tsc clean (0 errors), vitest 1293/1293 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,7 @@
|
|||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@pdfme/common": "^6.1.2",
|
"@pdfme/common": "^6.1.2",
|
||||||
"@pdfme/generator": "^6.1.2",
|
"@pdfme/generator": "^6.1.2",
|
||||||
"@pdfme/schemas": "^6.1.2",
|
"@pdfme/schemas": "^6.1.2",
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
"tesseract.js": "^7.0.0",
|
"tesseract.js": "^7.0.0",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"web-vitals": "^5.2.0",
|
"web-vitals": "^5.2.0",
|
||||||
"zod": "^3.25.76",
|
"zod": "^4.4.3",
|
||||||
"zustand": "^5.0.13"
|
"zustand": "^5.0.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -120,6 +120,7 @@
|
|||||||
"autoprefixer": "^10.5.0",
|
"autoprefixer": "^10.5.0",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"drizzle-kit": "^0.31.10",
|
"drizzle-kit": "^0.31.10",
|
||||||
|
"drizzle-zod": "^0.8.3",
|
||||||
"esbuild": "^0.28.0",
|
"esbuild": "^0.28.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
"eslint-config-next": "15.5.18",
|
"eslint-config-next": "15.5.18",
|
||||||
|
|||||||
82
pnpm-lock.yaml
generated
82
pnpm-lock.yaml
generated
@@ -23,8 +23,8 @@ importers:
|
|||||||
specifier: ^3.2.2
|
specifier: ^3.2.2
|
||||||
version: 3.2.2(react@19.2.6)
|
version: 3.2.2(react@19.2.6)
|
||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^3.10.0
|
specifier: ^5.2.2
|
||||||
version: 3.10.0(react-hook-form@7.75.0(react@19.2.6))
|
version: 5.2.2(react-hook-form@7.75.0(react@19.2.6))
|
||||||
'@pdfme/common':
|
'@pdfme/common':
|
||||||
specifier: ^6.1.2
|
specifier: ^6.1.2
|
||||||
version: 6.1.2
|
version: 6.1.2
|
||||||
@@ -171,7 +171,7 @@ importers:
|
|||||||
version: 8.0.7
|
version: 8.0.7
|
||||||
openai:
|
openai:
|
||||||
specifier: ^6.37.0
|
specifier: ^6.37.0
|
||||||
version: 6.37.0(ws@8.18.3)(zod@3.25.76)
|
version: 6.37.0(ws@8.18.3)(zod@4.4.3)
|
||||||
pdf-lib:
|
pdf-lib:
|
||||||
specifier: ^1.17.1
|
specifier: ^1.17.1
|
||||||
version: 1.17.1
|
version: 1.17.1
|
||||||
@@ -233,8 +233,8 @@ importers:
|
|||||||
specifier: ^5.2.0
|
specifier: ^5.2.0
|
||||||
version: 5.2.0
|
version: 5.2.0
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.25.76
|
specifier: ^4.4.3
|
||||||
version: 3.25.76
|
version: 4.4.3
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^5.0.13
|
specifier: ^5.0.13
|
||||||
version: 5.0.13(@types/react@19.2.14)(immer@11.1.7)(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6))
|
version: 5.0.13(@types/react@19.2.14)(immer@11.1.7)(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6))
|
||||||
@@ -284,6 +284,9 @@ importers:
|
|||||||
drizzle-kit:
|
drizzle-kit:
|
||||||
specifier: ^0.31.10
|
specifier: ^0.31.10
|
||||||
version: 0.31.10
|
version: 0.31.10
|
||||||
|
drizzle-zod:
|
||||||
|
specifier: ^0.8.3
|
||||||
|
version: 0.8.3(drizzle-orm@0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9))(zod@4.4.3)
|
||||||
esbuild:
|
esbuild:
|
||||||
specifier: '>=0.25.0'
|
specifier: '>=0.25.0'
|
||||||
version: 0.28.0
|
version: 0.28.0
|
||||||
@@ -776,10 +779,10 @@ packages:
|
|||||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||||
react-dom: ^16.8.0 || ^17 || ^18 || ^19
|
react-dom: ^16.8.0 || ^17 || ^18 || ^19
|
||||||
|
|
||||||
'@hookform/resolvers@3.10.0':
|
'@hookform/resolvers@5.2.2':
|
||||||
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
|
resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react-hook-form: ^7.0.0
|
react-hook-form: ^7.55.0
|
||||||
|
|
||||||
'@humanfs/core@0.19.2':
|
'@humanfs/core@0.19.2':
|
||||||
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
||||||
@@ -2986,6 +2989,12 @@ packages:
|
|||||||
sqlite3:
|
sqlite3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
drizzle-zod@0.8.3:
|
||||||
|
resolution: {integrity: sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==}
|
||||||
|
peerDependencies:
|
||||||
|
drizzle-orm: '>=0.36.0'
|
||||||
|
zod: ^3.25.0 || ^4.0.0
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -5375,9 +5384,6 @@ packages:
|
|||||||
zlibjs@0.3.1:
|
zlibjs@0.3.1:
|
||||||
resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==}
|
resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==}
|
||||||
|
|
||||||
zod@3.25.76:
|
|
||||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
|
||||||
|
|
||||||
zod@4.4.3:
|
zod@4.4.3:
|
||||||
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
|
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
|
||||||
|
|
||||||
@@ -5463,7 +5469,7 @@ snapshots:
|
|||||||
|
|
||||||
'@bcoe/v8-coverage@1.0.2': {}
|
'@bcoe/v8-coverage@1.0.2': {}
|
||||||
|
|
||||||
'@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)':
|
'@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/utils': 0.4.0
|
'@better-auth/utils': 0.4.0
|
||||||
'@better-fetch/fetch': 1.1.21
|
'@better-fetch/fetch': 1.1.21
|
||||||
@@ -5475,40 +5481,40 @@ snapshots:
|
|||||||
nanostores: 1.3.0
|
nanostores: 1.3.0
|
||||||
zod: 4.4.3
|
zod: 4.4.3
|
||||||
|
|
||||||
'@better-auth/drizzle-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9))':
|
'@better-auth/drizzle-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
||||||
'@better-auth/utils': 0.4.0
|
'@better-auth/utils': 0.4.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
drizzle-orm: 0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9)
|
drizzle-orm: 0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9)
|
||||||
|
|
||||||
'@better-auth/kysely-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(kysely@0.28.17)':
|
'@better-auth/kysely-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(kysely@0.28.17)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
||||||
'@better-auth/utils': 0.4.0
|
'@better-auth/utils': 0.4.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
kysely: 0.28.17
|
kysely: 0.28.17
|
||||||
|
|
||||||
'@better-auth/memory-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)':
|
'@better-auth/memory-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
||||||
'@better-auth/utils': 0.4.0
|
'@better-auth/utils': 0.4.0
|
||||||
|
|
||||||
'@better-auth/mongo-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(mongodb@7.1.0(socks@2.8.8))':
|
'@better-auth/mongo-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(mongodb@7.1.0(socks@2.8.8))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
||||||
'@better-auth/utils': 0.4.0
|
'@better-auth/utils': 0.4.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
mongodb: 7.1.0(socks@2.8.8)
|
mongodb: 7.1.0(socks@2.8.8)
|
||||||
|
|
||||||
'@better-auth/prisma-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)':
|
'@better-auth/prisma-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
||||||
'@better-auth/utils': 0.4.0
|
'@better-auth/utils': 0.4.0
|
||||||
|
|
||||||
'@better-auth/telemetry@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)':
|
'@better-auth/telemetry@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
||||||
'@better-auth/utils': 0.4.0
|
'@better-auth/utils': 0.4.0
|
||||||
'@better-fetch/fetch': 1.1.21
|
'@better-fetch/fetch': 1.1.21
|
||||||
|
|
||||||
@@ -5813,8 +5819,9 @@ snapshots:
|
|||||||
- '@types/react'
|
- '@types/react'
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@hookform/resolvers@3.10.0(react-hook-form@7.75.0(react@19.2.6))':
|
'@hookform/resolvers@5.2.2(react-hook-form@7.75.0(react@19.2.6))':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@standard-schema/utils': 0.3.0
|
||||||
react-hook-form: 7.75.0(react@19.2.6)
|
react-hook-form: 7.75.0(react@19.2.6)
|
||||||
|
|
||||||
'@humanfs/core@0.19.2':
|
'@humanfs/core@0.19.2':
|
||||||
@@ -7388,13 +7395,13 @@ snapshots:
|
|||||||
|
|
||||||
better-auth@1.6.10(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9))(mongodb@7.1.0(socks@2.8.8))(next@15.5.18(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.6):
|
better-auth@1.6.10(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9))(mongodb@7.1.0(socks@2.8.8))(next@15.5.18(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
|
||||||
'@better-auth/drizzle-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9))
|
'@better-auth/drizzle-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9))
|
||||||
'@better-auth/kysely-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(kysely@0.28.17)
|
'@better-auth/kysely-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(kysely@0.28.17)
|
||||||
'@better-auth/memory-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)
|
'@better-auth/memory-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)
|
||||||
'@better-auth/mongo-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(mongodb@7.1.0(socks@2.8.8))
|
'@better-auth/mongo-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(mongodb@7.1.0(socks@2.8.8))
|
||||||
'@better-auth/prisma-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)
|
'@better-auth/prisma-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)
|
||||||
'@better-auth/telemetry': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)
|
'@better-auth/telemetry': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)
|
||||||
'@better-auth/utils': 0.4.0
|
'@better-auth/utils': 0.4.0
|
||||||
'@better-fetch/fetch': 1.1.21
|
'@better-fetch/fetch': 1.1.21
|
||||||
'@noble/ciphers': 2.2.0
|
'@noble/ciphers': 2.2.0
|
||||||
@@ -7814,6 +7821,11 @@ snapshots:
|
|||||||
kysely: 0.28.17
|
kysely: 0.28.17
|
||||||
postgres: 3.4.9
|
postgres: 3.4.9
|
||||||
|
|
||||||
|
drizzle-zod@0.8.3(drizzle-orm@0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9))(zod@4.4.3):
|
||||||
|
dependencies:
|
||||||
|
drizzle-orm: 0.45.2(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9)
|
||||||
|
zod: 4.4.3
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
@@ -9174,10 +9186,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-function: 5.0.1
|
mimic-function: 5.0.1
|
||||||
|
|
||||||
openai@6.37.0(ws@8.18.3)(zod@3.25.76):
|
openai@6.37.0(ws@8.18.3)(zod@4.4.3):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ws: 8.18.3
|
ws: 8.18.3
|
||||||
zod: 3.25.76
|
zod: 4.4.3
|
||||||
|
|
||||||
opencollective-postinstall@2.0.3: {}
|
opencollective-postinstall@2.0.3: {}
|
||||||
|
|
||||||
@@ -10521,8 +10533,6 @@ snapshots:
|
|||||||
|
|
||||||
zlibjs@0.3.1: {}
|
zlibjs@0.3.1: {}
|
||||||
|
|
||||||
zod@3.25.76: {}
|
|
||||||
|
|
||||||
zod@4.4.3: {}
|
zod@4.4.3: {}
|
||||||
|
|
||||||
zustand@5.0.13(@types/react@19.2.14)(immer@11.1.7)(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6)):
|
zustand@5.0.13(@types/react@19.2.14)(immer@11.1.7)(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6)):
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { CurrencySelect } from '@/components/shared/currency-select';
|
|||||||
import { InvoiceLineItems } from '@/components/invoices/invoice-line-items';
|
import { InvoiceLineItems } from '@/components/invoices/invoice-line-items';
|
||||||
import { apiFetch } from '@/lib/api/client';
|
import { apiFetch } from '@/lib/api/client';
|
||||||
import { formatCurrency } from '@/lib/utils/currency';
|
import { formatCurrency } from '@/lib/utils/currency';
|
||||||
|
import type { z } from 'zod';
|
||||||
import { createInvoiceSchema, type CreateInvoiceInput } from '@/lib/validators/invoices';
|
import { createInvoiceSchema, type CreateInvoiceInput } from '@/lib/validators/invoices';
|
||||||
|
|
||||||
const PAYMENT_TERMS = [
|
const PAYMENT_TERMS = [
|
||||||
@@ -76,7 +77,7 @@ export default function NewInvoicePage() {
|
|||||||
enabled: !!prefilledInterestId,
|
enabled: !!prefilledInterestId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const methods = useForm<CreateInvoiceInput>({
|
const methods = useForm<z.input<typeof createInvoiceSchema>, unknown, CreateInvoiceInput>({
|
||||||
resolver: zodResolver(createInvoiceSchema),
|
resolver: zodResolver(createInvoiceSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
paymentTerms: 'net30',
|
paymentTerms: 'net30',
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
|||||||
|
|
||||||
const parsed = bodySchema.safeParse(body);
|
const parsed = bodySchema.safeParse(body);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
throw new ValidationError(parsed.error.errors[0]?.message ?? 'Invalid input');
|
throw new ValidationError(parsed.error.issues[0]?.message ?? 'Invalid input');
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await consumeCrmInvite({
|
const result = await consumeCrmInvite({
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
|||||||
|
|
||||||
const parsed = bodySchema.safeParse(body);
|
const parsed = bodySchema.safeParse(body);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
throw new ValidationError(parsed.error.errors[0]?.message ?? 'Invalid input');
|
throw new ValidationError(parsed.error.issues[0]?.message ?? 'Invalid input');
|
||||||
}
|
}
|
||||||
|
|
||||||
await activateAccount(parsed.data.token, parsed.data.password);
|
await activateAccount(parsed.data.token, parsed.data.password);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
|||||||
|
|
||||||
const parsed = bodySchema.safeParse(body);
|
const parsed = bodySchema.safeParse(body);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
throw new ValidationError(parsed.error.errors[0]?.message ?? 'Invalid input');
|
throw new ValidationError(parsed.error.issues[0]?.message ?? 'Invalid input');
|
||||||
}
|
}
|
||||||
|
|
||||||
await resetPassword(parsed.data.token, parsed.data.password);
|
await resetPassword(parsed.data.token, parsed.data.password);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { checkRateLimit, rateLimiters } from '@/lib/rate-limit';
|
|||||||
const SubmissionSchema = z.object({
|
const SubmissionSchema = z.object({
|
||||||
submission_id: z.string().uuid(),
|
submission_id: z.string().uuid(),
|
||||||
kind: z.enum(['berth_inquiry', 'residence_inquiry', 'contact_form']),
|
kind: z.enum(['berth_inquiry', 'residence_inquiry', 'contact_form']),
|
||||||
payload: z.record(z.unknown()),
|
payload: z.record(z.string(), z.unknown()),
|
||||||
legacy_nocodb_id: z.string().optional(),
|
legacy_nocodb_id: z.string().optional(),
|
||||||
/** Defaults to port-nimara since that's currently the only port with a
|
/** Defaults to port-nimara since that's currently the only port with a
|
||||||
* public marketing site. Future ports can override per-submission. */
|
* public marketing site. Future ports can override per-submission. */
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import { addMembershipSchema } from '@/lib/validators/company-memberships';
|
|||||||
const listQuerySchema = z.object({
|
const listQuerySchema = z.object({
|
||||||
activeOnly: z
|
activeOnly: z
|
||||||
.enum(['true', 'false'])
|
.enum(['true', 'false'])
|
||||||
.transform((v) => v === 'true')
|
.default('true')
|
||||||
.default('true'),
|
.transform((v) => v === 'true'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const listHandler: RouteHandler = async (req, ctx, params) => {
|
export const listHandler: RouteHandler = async (req, ctx, params) => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { CurrencyInput } from '@/components/shared/currency-input';
|
|||||||
import { CurrencySelect } from '@/components/shared/currency-select';
|
import { CurrencySelect } from '@/components/shared/currency-select';
|
||||||
import { apiFetch } from '@/lib/api/client';
|
import { apiFetch } from '@/lib/api/client';
|
||||||
import { toastError } from '@/lib/api/toast-error';
|
import { toastError } from '@/lib/api/toast-error';
|
||||||
|
import type { z } from 'zod';
|
||||||
import { updateBerthSchema, type UpdateBerthInput } from '@/lib/validators/berths';
|
import { updateBerthSchema, type UpdateBerthInput } from '@/lib/validators/berths';
|
||||||
import {
|
import {
|
||||||
BERTH_AREAS,
|
BERTH_AREAS,
|
||||||
@@ -120,7 +121,7 @@ export function BerthForm({ berth, open, onOpenChange }: BerthFormProps) {
|
|||||||
setValue,
|
setValue,
|
||||||
watch,
|
watch,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
} = useForm<UpdateBerthInput>({
|
} = useForm<z.input<typeof updateBerthSchema>, unknown, UpdateBerthInput>({
|
||||||
resolver: zodResolver(updateBerthSchema),
|
resolver: zodResolver(updateBerthSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
area: berth.area ?? undefined,
|
area: berth.area ?? undefined,
|
||||||
@@ -403,7 +404,7 @@ export function BerthForm({ berth, open, onOpenChange }: BerthFormProps) {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Price</Label>
|
<Label>Price</Label>
|
||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
value={watch('price') ?? ''}
|
value={(watch('price') as number | null | undefined) ?? ''}
|
||||||
currency={watch('priceCurrency') ?? 'USD'}
|
currency={watch('priceCurrency') ?? 'USD'}
|
||||||
onChange={(v) => setValue('price', v ?? undefined, { shouldDirty: true })}
|
onChange={(v) => setValue('price', v ?? undefined, { shouldDirty: true })}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { TimezoneCombobox } from '@/components/shared/timezone-combobox';
|
|||||||
import { PhoneInput } from '@/components/shared/phone-input';
|
import { PhoneInput } from '@/components/shared/phone-input';
|
||||||
import { DedupSuggestionPanel } from '@/components/clients/dedup-suggestion-panel';
|
import { DedupSuggestionPanel } from '@/components/clients/dedup-suggestion-panel';
|
||||||
import { apiFetch } from '@/lib/api/client';
|
import { apiFetch } from '@/lib/api/client';
|
||||||
|
import type { z } from 'zod';
|
||||||
import { createClientSchema, type CreateClientInput } from '@/lib/validators/clients';
|
import { createClientSchema, type CreateClientInput } from '@/lib/validators/clients';
|
||||||
import { SOURCES } from '@/lib/constants';
|
import { SOURCES } from '@/lib/constants';
|
||||||
import type { CountryCode } from '@/lib/i18n/countries';
|
import type { CountryCode } from '@/lib/i18n/countries';
|
||||||
@@ -74,7 +75,7 @@ export function ClientForm({ open, onOpenChange, client, onUseExistingClient }:
|
|||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = useForm<CreateClientInput>({
|
} = useForm<z.input<typeof createClientSchema>, unknown, CreateClientInput>({
|
||||||
resolver: zodResolver(createClientSchema),
|
resolver: zodResolver(createClientSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
fullName: '',
|
fullName: '',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { CurrencyInput } from '@/components/shared/currency-input';
|
|||||||
import { CurrencySelect } from '@/components/shared/currency-select';
|
import { CurrencySelect } from '@/components/shared/currency-select';
|
||||||
import { TripLabelCombobox } from '@/components/expenses/trip-label-combobox';
|
import { TripLabelCombobox } from '@/components/expenses/trip-label-combobox';
|
||||||
import { apiFetch } from '@/lib/api/client';
|
import { apiFetch } from '@/lib/api/client';
|
||||||
|
import type { z } from 'zod';
|
||||||
import { createExpenseSchema, type CreateExpenseInput } from '@/lib/validators/expenses';
|
import { createExpenseSchema, type CreateExpenseInput } from '@/lib/validators/expenses';
|
||||||
import { EXPENSE_CATEGORIES, PAYMENT_METHODS } from '@/lib/constants';
|
import { EXPENSE_CATEGORIES, PAYMENT_METHODS } from '@/lib/constants';
|
||||||
import type { ExpenseRow } from './expense-columns';
|
import type { ExpenseRow } from './expense-columns';
|
||||||
@@ -55,7 +56,7 @@ export function ExpenseFormDialog({ open, onOpenChange, expense }: ExpenseFormDi
|
|||||||
reset,
|
reset,
|
||||||
watch,
|
watch,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = useForm<CreateExpenseInput>({
|
} = useForm<z.input<typeof createExpenseSchema>, unknown, CreateExpenseInput>({
|
||||||
resolver: zodResolver(createExpenseSchema),
|
resolver: zodResolver(createExpenseSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
@@ -211,7 +212,7 @@ export function ExpenseFormDialog({ open, onOpenChange, expense }: ExpenseFormDi
|
|||||||
<Label htmlFor="amount">Amount *</Label>
|
<Label htmlFor="amount">Amount *</Label>
|
||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
id="amount"
|
id="amount"
|
||||||
value={watch('amount') ?? ''}
|
value={(watch('amount') as number | null | undefined) ?? ''}
|
||||||
currency={watch('currency') ?? 'USD'}
|
currency={watch('currency') ?? 'USD'}
|
||||||
onChange={(v) =>
|
onChange={(v) =>
|
||||||
setValue('amount', v ?? Number.NaN, { shouldDirty: true, shouldValidate: true })
|
setValue('amount', v ?? Number.NaN, { shouldDirty: true, shouldValidate: true })
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import { YachtForm } from '@/components/yachts/yacht-form';
|
|||||||
import { YachtPicker } from '@/components/yachts/yacht-picker';
|
import { YachtPicker } from '@/components/yachts/yacht-picker';
|
||||||
import { apiFetch } from '@/lib/api/client';
|
import { apiFetch } from '@/lib/api/client';
|
||||||
import { useEntityOptions } from '@/hooks/use-entity-options';
|
import { useEntityOptions } from '@/hooks/use-entity-options';
|
||||||
|
import type { z } from 'zod';
|
||||||
import { createInterestSchema, type CreateInterestInput } from '@/lib/validators/interests';
|
import { createInterestSchema, type CreateInterestInput } from '@/lib/validators/interests';
|
||||||
import { PIPELINE_STAGES, STAGE_LABELS, LEAD_CATEGORIES, SOURCES } from '@/lib/constants';
|
import { PIPELINE_STAGES, STAGE_LABELS, LEAD_CATEGORIES, SOURCES } from '@/lib/constants';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
@@ -96,7 +97,7 @@ export function InterestForm({ open, onOpenChange, defaultClientId, interest }:
|
|||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
formState: { errors, isSubmitting, isDirty },
|
formState: { errors, isSubmitting, isDirty },
|
||||||
} = useForm<CreateInterestInput>({
|
} = useForm<z.input<typeof createInterestSchema>, unknown, CreateInterestInput>({
|
||||||
resolver: zodResolver(createInterestSchema),
|
resolver: zodResolver(createInterestSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
clientId: '',
|
clientId: '',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { CountryCombobox } from '@/components/shared/country-combobox';
|
|||||||
import { OwnerPicker, type OwnerRef } from '@/components/shared/owner-picker';
|
import { OwnerPicker, type OwnerRef } from '@/components/shared/owner-picker';
|
||||||
import { TagPicker } from '@/components/shared/tag-picker';
|
import { TagPicker } from '@/components/shared/tag-picker';
|
||||||
import { apiFetch } from '@/lib/api/client';
|
import { apiFetch } from '@/lib/api/client';
|
||||||
|
import type { z } from 'zod';
|
||||||
import { createYachtSchema, type CreateYachtInput } from '@/lib/validators/yachts';
|
import { createYachtSchema, type CreateYachtInput } from '@/lib/validators/yachts';
|
||||||
|
|
||||||
interface YachtFormProps {
|
interface YachtFormProps {
|
||||||
@@ -73,7 +74,7 @@ export function YachtForm({ open, onOpenChange, yacht, initialOwner }: YachtForm
|
|||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = useForm<CreateYachtInput>({
|
} = useForm<z.input<typeof createYachtSchema>, unknown, CreateYachtInput>({
|
||||||
resolver: zodResolver(createYachtSchema),
|
resolver: zodResolver(createYachtSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: '',
|
name: '',
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export const baseListQuerySchema = z.object({
|
|||||||
search: z.string().optional(),
|
search: z.string().optional(),
|
||||||
includeArchived: z
|
includeArchived: z
|
||||||
.enum(['true', 'false'])
|
.enum(['true', 'false'])
|
||||||
.transform((v) => v === 'true')
|
.default('false')
|
||||||
.default('false'),
|
.transform((v) => v === 'true'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type BaseListQuery = z.infer<typeof baseListQuerySchema>;
|
export type BaseListQuery = z.infer<typeof baseListQuerySchema>;
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ export function errorResponse(error: unknown): NextResponse {
|
|||||||
const body: Record<string, unknown> = {
|
const body: Record<string, unknown> = {
|
||||||
error: 'Validation failed',
|
error: 'Validation failed',
|
||||||
code: 'VALIDATION_ERROR',
|
code: 'VALIDATION_ERROR',
|
||||||
details: error.errors.map((e) => ({
|
details: error.issues.map((e) => ({
|
||||||
field: e.path.join('.'),
|
field: e.path.join('.'),
|
||||||
message: e.message,
|
message: e.message,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -11,14 +11,17 @@ import { createAuditLog, type AuditMeta } from '@/lib/audit';
|
|||||||
import { ConflictError, NotFoundError, ValidationError } from '@/lib/errors';
|
import { ConflictError, NotFoundError, ValidationError } from '@/lib/errors';
|
||||||
import { emitToRoom } from '@/lib/socket/server';
|
import { emitToRoom } from '@/lib/socket/server';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
import { createPendingSchema } from '@/lib/validators/reservations';
|
||||||
import type {
|
import type {
|
||||||
createPendingSchema,
|
|
||||||
ActivateInput,
|
ActivateInput,
|
||||||
EndReservationInput,
|
EndReservationInput,
|
||||||
CancelInput,
|
CancelInput,
|
||||||
ListReservationsInput,
|
ListReservationsInput,
|
||||||
} from '@/lib/validators/reservations';
|
} from '@/lib/validators/reservations';
|
||||||
|
|
||||||
|
// Use z.input so callers (including tests) can omit fields with
|
||||||
|
// `.default()` like `tenureType`. The service re-parses below to get
|
||||||
|
// the post-coercion shape Drizzle expects (Date, defaulted tenureType).
|
||||||
type CreatePendingInput = z.input<typeof createPendingSchema>;
|
type CreatePendingInput = z.input<typeof createPendingSchema>;
|
||||||
|
|
||||||
export type { BerthReservation };
|
export type { BerthReservation };
|
||||||
@@ -110,18 +113,23 @@ export async function createPending(
|
|||||||
data.clientId,
|
data.clientId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Re-parse to apply coercions/defaults locally — Drizzle's .values()
|
||||||
|
// wants the post-coercion shape (Date, defaulted enum), and v4's
|
||||||
|
// z.input is too loose to satisfy that.
|
||||||
|
const parsed = createPendingSchema.parse(data);
|
||||||
|
|
||||||
const [reservation] = await db
|
const [reservation] = await db
|
||||||
.insert(berthReservations)
|
.insert(berthReservations)
|
||||||
.values({
|
.values({
|
||||||
portId,
|
portId,
|
||||||
berthId: data.berthId,
|
berthId: parsed.berthId,
|
||||||
clientId: data.clientId,
|
clientId: parsed.clientId,
|
||||||
yachtId: data.yachtId,
|
yachtId: parsed.yachtId,
|
||||||
interestId: data.interestId ?? null,
|
interestId: parsed.interestId ?? null,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
startDate: data.startDate,
|
startDate: parsed.startDate,
|
||||||
tenureType: data.tenureType ?? 'permanent',
|
tenureType: parsed.tenureType,
|
||||||
notes: data.notes ?? null,
|
notes: parsed.notes ?? null,
|
||||||
createdBy: meta.userId,
|
createdBy: meta.userId,
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|||||||
@@ -248,8 +248,12 @@ export async function createInvoice(portId: string, data: CreateInvoiceInput, me
|
|||||||
|
|
||||||
const invoiceNumber = await generateInvoiceNumber(portId, tx);
|
const invoiceNumber = await generateInvoiceNumber(portId, tx);
|
||||||
|
|
||||||
// Calculate subtotal from line items
|
// Calculate subtotal from line items. The `z.coerce.number()` in
|
||||||
const lineItemsData = data.lineItems ?? [];
|
// the schema makes the parsed value a number at runtime — narrow
|
||||||
|
// the post-parse shape locally so v4's stricter input typing
|
||||||
|
// (unknown for coerced fields) doesn't leak into arithmetic.
|
||||||
|
type ParsedLineItem = { quantity: number; unitPrice: number; description: string };
|
||||||
|
const lineItemsData = (data.lineItems ?? []) as ParsedLineItem[];
|
||||||
const subtotal = lineItemsData.reduce((sum, li) => sum + (li.quantity ?? 1) * li.unitPrice, 0);
|
const subtotal = lineItemsData.reduce((sum, li) => sum + (li.quantity ?? 1) * li.unitPrice, 0);
|
||||||
|
|
||||||
// BR-042: net10 discount - read from systemSettings
|
// BR-042: net10 discount - read from systemSettings
|
||||||
@@ -429,9 +433,12 @@ export async function updateInvoice(
|
|||||||
}
|
}
|
||||||
if (data.kind !== undefined) updateData.kind = data.kind;
|
if (data.kind !== undefined) updateData.kind = data.kind;
|
||||||
|
|
||||||
// Recalculate totals if line items changed
|
// Recalculate totals if line items changed (see createInvoice for
|
||||||
|
// the ParsedLineItem narrowing rationale — same coerced-number
|
||||||
|
// story applies on the update path).
|
||||||
if (data.lineItems !== undefined) {
|
if (data.lineItems !== undefined) {
|
||||||
const lineItemsData = data.lineItems;
|
type ParsedLineItem = { quantity: number; unitPrice: number; description: string };
|
||||||
|
const lineItemsData = data.lineItems as ParsedLineItem[];
|
||||||
const subtotal = lineItemsData.reduce(
|
const subtotal = lineItemsData.reduce(
|
||||||
(sum, li) => sum + (li.quantity ?? 1) * li.unitPrice,
|
(sum, li) => sum + (li.quantity ?? 1) * li.unitPrice,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ const mergeFieldsSchema = z
|
|||||||
.array(z.string())
|
.array(z.string())
|
||||||
.optional()
|
.optional()
|
||||||
.default([])
|
.default([])
|
||||||
.refine(
|
.refine((tokens) => tokens.every(isAcceptableMergeToken), {
|
||||||
(tokens) => tokens.every(isAcceptableMergeToken),
|
error: (issue) => {
|
||||||
(tokens) => {
|
const tokens = issue.input as string[] | undefined;
|
||||||
const unknown = tokens?.filter((t) => !isAcceptableMergeToken(t)) ?? [];
|
const unknown = tokens?.filter((t) => !isAcceptableMergeToken(t)) ?? [];
|
||||||
return { message: `Unknown merge tokens: ${unknown.join(', ')}` };
|
return `Unknown merge tokens: ${unknown.join(', ')}`;
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
export const templateFormats = ['html', 'pdf_form', 'pdf_overlay', 'documenso_render'] as const;
|
export const templateFormats = ['html', 'pdf_form', 'pdf_overlay', 'documenso_render'] as const;
|
||||||
|
|
||||||
@@ -104,18 +104,18 @@ export const tiptapDocumentTypes = [
|
|||||||
export const createAdminTemplateSchema = z.object({
|
export const createAdminTemplateSchema = z.object({
|
||||||
name: z.string().min(1).max(200),
|
name: z.string().min(1).max(200),
|
||||||
type: z.enum(tiptapDocumentTypes),
|
type: z.enum(tiptapDocumentTypes),
|
||||||
content: z.record(z.unknown()), // TipTap JSON document
|
content: z.record(z.string(), z.unknown()), // TipTap JSON document
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateAdminTemplateSchema = z.object({
|
export const updateAdminTemplateSchema = z.object({
|
||||||
name: z.string().min(1).max(200).optional(),
|
name: z.string().min(1).max(200).optional(),
|
||||||
content: z.record(z.unknown()).optional(),
|
content: z.record(z.string(), z.unknown()).optional(),
|
||||||
isActive: z.boolean().optional(),
|
isActive: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const previewAdminTemplateSchema = z.object({
|
export const previewAdminTemplateSchema = z.object({
|
||||||
content: z.record(z.unknown()),
|
content: z.record(z.string(), z.unknown()),
|
||||||
sampleData: z.record(z.string()).optional(),
|
sampleData: z.record(z.string(), z.string()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rollbackAdminTemplateSchema = z.object({
|
export const rollbackAdminTemplateSchema = z.object({
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ export const listDocumentsSchema = baseListQuerySchema
|
|||||||
folderId: z
|
folderId: z
|
||||||
.string()
|
.string()
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional()
|
.transform((v) => (v === '' ? null : v))
|
||||||
.transform((v) => (v === '' ? null : v)),
|
.optional(),
|
||||||
includeDescendants: z.coerce.boolean().optional(),
|
includeDescendants: z.coerce.boolean().optional(),
|
||||||
status: z.string().optional(),
|
status: z.string().optional(),
|
||||||
/** Hub tab filter - applies tab-specific status / signer-membership constraints. */
|
/** Hub tab filter - applies tab-specific status / signer-membership constraints. */
|
||||||
@@ -100,8 +100,8 @@ export const listDocumentsSchema = baseListQuerySchema
|
|||||||
/** When true, only docs intended for signing (default true on hub). */
|
/** When true, only docs intended for signing (default true on hub). */
|
||||||
signatureOnly: z
|
signatureOnly: z
|
||||||
.enum(['true', 'false'])
|
.enum(['true', 'false'])
|
||||||
.optional()
|
.transform((v) => v === 'true')
|
||||||
.transform((v) => (v === undefined ? undefined : v === 'true')),
|
.optional(),
|
||||||
sentSince: z.string().datetime().optional(),
|
sentSince: z.string().datetime().optional(),
|
||||||
sentUntil: z.string().datetime().optional(),
|
sentUntil: z.string().datetime().optional(),
|
||||||
/** Entity-aggregated projection params — mutually exclusive with folderId. */
|
/** Entity-aggregated projection params — mutually exclusive with folderId. */
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const createFormTemplateSchema = z.object({
|
|||||||
name: z.string().min(1).max(200),
|
name: z.string().min(1).max(200),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
fields: z.array(formFieldSchema).min(1, 'At least one field is required'),
|
fields: z.array(formFieldSchema).min(1, 'At least one field is required'),
|
||||||
branding: z.record(z.unknown()).optional(),
|
branding: z.record(z.string(), z.unknown()).optional(),
|
||||||
isActive: z.boolean().optional().default(true),
|
isActive: z.boolean().optional().default(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,15 +16,19 @@ import {
|
|||||||
* empty strings collapse to `undefined` so a blank form field doesn't
|
* empty strings collapse to `undefined` so a blank form field doesn't
|
||||||
* round-trip "" → numeric error on the API.
|
* round-trip "" → numeric error on the API.
|
||||||
*/
|
*/
|
||||||
|
// In Zod 4, the optional() marker must live at the *outside* of the
|
||||||
|
// chain to propagate the field's optional-ness into the parent z.object.
|
||||||
|
// In v3 the same pattern worked with optional() in the middle, but v4's
|
||||||
|
// new ZodPipe (transform) doesn't forward optional through the pipe.
|
||||||
const optionalDesiredDimSchema = z
|
const optionalDesiredDimSchema = z
|
||||||
.union([z.string(), z.number()])
|
.union([z.string(), z.number()])
|
||||||
.optional()
|
|
||||||
.transform((v) => {
|
.transform((v) => {
|
||||||
if (v === undefined || v === null || v === '') return undefined;
|
if (v === '') return undefined;
|
||||||
const n = typeof v === 'number' ? v : parseFloat(v);
|
const n = typeof v === 'number' ? v : parseFloat(v);
|
||||||
if (!Number.isFinite(n) || n <= 0) return undefined;
|
if (!Number.isFinite(n) || n <= 0) return undefined;
|
||||||
return String(Math.round(n * 100) / 100);
|
return String(Math.round(n * 100) / 100);
|
||||||
});
|
})
|
||||||
|
.optional();
|
||||||
|
|
||||||
const desiredUnitSchema = z.enum(['ft', 'm']).optional();
|
const desiredUnitSchema = z.enum(['ft', 'm']).optional();
|
||||||
|
|
||||||
|
|||||||
@@ -81,9 +81,13 @@ export const listInvoicesSchema = baseListQuerySchema.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// `z.input` keeps fields with `.default()` (paymentTerms, currency, kind)
|
// `z.input` keeps fields with `.default()` (paymentTerms, currency, kind)
|
||||||
// optional from the caller's perspective. The schema parser still fills in
|
// optional from the caller's perspective. The route layer runs the
|
||||||
// the defaults, so the service body can rely on them being present at runtime.
|
// schema through `parseBody`, so the service body can rely on those
|
||||||
|
// defaults being present at runtime — narrow with a local cast where
|
||||||
|
// the post-parse shape matters (e.g. coerced `unitPrice` is `number`).
|
||||||
export type CreateInvoiceInput = z.input<typeof createInvoiceSchema>;
|
export type CreateInvoiceInput = z.input<typeof createInvoiceSchema>;
|
||||||
export type UpdateInvoiceInput = z.input<typeof updateInvoiceSchema>;
|
export type UpdateInvoiceInput = z.input<typeof updateInvoiceSchema>;
|
||||||
|
export type CreateInvoiceParsed = z.infer<typeof createInvoiceSchema>;
|
||||||
|
export type UpdateInvoiceParsed = z.infer<typeof updateInvoiceSchema>;
|
||||||
export type RecordPaymentInput = z.infer<typeof recordPaymentSchema>;
|
export type RecordPaymentInput = z.infer<typeof recordPaymentSchema>;
|
||||||
export type ListInvoicesInput = z.infer<typeof listInvoicesSchema>;
|
export type ListInvoicesInput = z.infer<typeof listInvoicesSchema>;
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { z } from 'zod';
|
|||||||
export const createSavedViewSchema = z.object({
|
export const createSavedViewSchema = z.object({
|
||||||
entityType: z.string().min(1),
|
entityType: z.string().min(1),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
filters: z.record(z.unknown()).default({}),
|
filters: z.record(z.string(), z.unknown()).default({}),
|
||||||
sortConfig: z
|
sortConfig: z
|
||||||
.object({
|
.object({
|
||||||
field: z.string(),
|
field: z.string(),
|
||||||
direction: z.enum(['asc', 'desc']),
|
direction: z.enum(['asc', 'desc']),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
columnConfig: z.record(z.unknown()).optional(),
|
columnConfig: z.record(z.string(), z.unknown()).optional(),
|
||||||
isShared: z.boolean().optional().default(false),
|
isShared: z.boolean().optional().default(false),
|
||||||
isDefault: z.boolean().optional().default(false),
|
isDefault: z.boolean().optional().default(false),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ describe('Error response security — ZodError', () => {
|
|||||||
{
|
{
|
||||||
code: ZodIssueCode.invalid_type,
|
code: ZodIssueCode.invalid_type,
|
||||||
expected: 'string',
|
expected: 'string',
|
||||||
received: 'number',
|
input: 42,
|
||||||
path: ['name'],
|
path: ['name'],
|
||||||
message: 'Expected string, received number',
|
message: 'Expected string, received number',
|
||||||
},
|
},
|
||||||
@@ -196,8 +196,9 @@ describe('Error response security — ZodError', () => {
|
|||||||
{
|
{
|
||||||
code: ZodIssueCode.too_small,
|
code: ZodIssueCode.too_small,
|
||||||
minimum: 1,
|
minimum: 1,
|
||||||
type: 'string',
|
origin: 'string',
|
||||||
inclusive: true,
|
inclusive: true,
|
||||||
|
input: '',
|
||||||
path: ['fullName'],
|
path: ['fullName'],
|
||||||
message: 'String must contain at least 1 character(s)',
|
message: 'String must contain at least 1 character(s)',
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user