@businessdash/sdk 0.9.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +899 -0
- package/dist/adapters-revalidate.d.ts +44 -0
- package/dist/adapters-revalidate.d.ts.map +1 -0
- package/dist/adapters-revalidate.js +68 -0
- package/dist/adapters-revalidate.js.map +1 -0
- package/dist/alpha.d.ts +21 -0
- package/dist/alpha.d.ts.map +1 -0
- package/dist/alpha.js +21 -0
- package/dist/alpha.js.map +1 -0
- package/dist/analytics-core.d.ts +34 -0
- package/dist/analytics-core.d.ts.map +1 -0
- package/dist/analytics-core.js +138 -0
- package/dist/analytics-core.js.map +1 -0
- package/dist/analytics-server.d.ts +54 -0
- package/dist/analytics-server.d.ts.map +1 -0
- package/dist/analytics-server.js +78 -0
- package/dist/analytics-server.js.map +1 -0
- package/dist/attribution-logo.d.ts +2 -0
- package/dist/attribution-logo.d.ts.map +1 -0
- package/dist/attribution-logo.js +5 -0
- package/dist/attribution-logo.js.map +1 -0
- package/dist/auth-handler.d.ts +92 -0
- package/dist/auth-handler.d.ts.map +1 -0
- package/dist/auth-handler.js +277 -0
- package/dist/auth-handler.js.map +1 -0
- package/dist/biab-forms.css +484 -0
- package/dist/cli.d.ts +46 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1824 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +1681 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +2845 -0
- package/dist/client.js.map +1 -0
- package/dist/collections.d.ts +13 -0
- package/dist/collections.d.ts.map +1 -0
- package/dist/collections.js +13 -0
- package/dist/collections.js.map +1 -0
- package/dist/contracts.d.ts +14609 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +2428 -0
- package/dist/contracts.js.map +1 -0
- package/dist/data-model-schema.d.ts +962 -0
- package/dist/data-model-schema.d.ts.map +1 -0
- package/dist/data-model-schema.js +2295 -0
- package/dist/data-model-schema.js.map +1 -0
- package/dist/distribution.d.ts +38 -0
- package/dist/distribution.d.ts.map +1 -0
- package/dist/distribution.js +61 -0
- package/dist/distribution.js.map +1 -0
- package/dist/element.d.ts +121 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +276 -0
- package/dist/element.js.map +1 -0
- package/dist/enums.d.ts +25 -0
- package/dist/enums.d.ts.map +1 -0
- package/dist/enums.js +38 -0
- package/dist/enums.js.map +1 -0
- package/dist/errors.d.ts +195 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +188 -0
- package/dist/errors.js.map +1 -0
- package/dist/forms-core/autocomplete.d.ts +37 -0
- package/dist/forms-core/autocomplete.d.ts.map +1 -0
- package/dist/forms-core/autocomplete.js +82 -0
- package/dist/forms-core/autocomplete.js.map +1 -0
- package/dist/forms-core/business-function.d.ts +61 -0
- package/dist/forms-core/business-function.d.ts.map +1 -0
- package/dist/forms-core/business-function.js +149 -0
- package/dist/forms-core/business-function.js.map +1 -0
- package/dist/forms-core/calculation.d.ts +72 -0
- package/dist/forms-core/calculation.d.ts.map +1 -0
- package/dist/forms-core/calculation.js +103 -0
- package/dist/forms-core/calculation.js.map +1 -0
- package/dist/forms-core/controller.d.ts +249 -0
- package/dist/forms-core/controller.d.ts.map +1 -0
- package/dist/forms-core/controller.js +590 -0
- package/dist/forms-core/controller.js.map +1 -0
- package/dist/forms-core/elements.d.ts +44 -0
- package/dist/forms-core/elements.d.ts.map +1 -0
- package/dist/forms-core/elements.js +68 -0
- package/dist/forms-core/elements.js.map +1 -0
- package/dist/forms-core/email-domains.d.ts +10 -0
- package/dist/forms-core/email-domains.d.ts.map +1 -0
- package/dist/forms-core/email-domains.js +69 -0
- package/dist/forms-core/email-domains.js.map +1 -0
- package/dist/forms-core/file-rules.d.ts +51 -0
- package/dist/forms-core/file-rules.d.ts.map +1 -0
- package/dist/forms-core/file-rules.js +105 -0
- package/dist/forms-core/file-rules.js.map +1 -0
- package/dist/forms-core/formula.d.ts +114 -0
- package/dist/forms-core/formula.d.ts.map +1 -0
- package/dist/forms-core/formula.js +1170 -0
- package/dist/forms-core/formula.js.map +1 -0
- package/dist/forms-core/index.d.ts +24 -0
- package/dist/forms-core/index.d.ts.map +1 -0
- package/dist/forms-core/index.js +28 -0
- package/dist/forms-core/index.js.map +1 -0
- package/dist/forms-core/units.d.ts +49 -0
- package/dist/forms-core/units.d.ts.map +1 -0
- package/dist/forms-core/units.js +255 -0
- package/dist/forms-core/units.js.map +1 -0
- package/dist/forms-core/us-states.d.ts +14 -0
- package/dist/forms-core/us-states.d.ts.map +1 -0
- package/dist/forms-core/us-states.js +66 -0
- package/dist/forms-core/us-states.js.map +1 -0
- package/dist/forms-core/validation.d.ts +266 -0
- package/dist/forms-core/validation.d.ts.map +1 -0
- package/dist/forms-core/validation.js +850 -0
- package/dist/forms-core/validation.js.map +1 -0
- package/dist/forms.d.ts +19 -0
- package/dist/forms.d.ts.map +1 -0
- package/dist/forms.js +29 -0
- package/dist/forms.js.map +1 -0
- package/dist/gallery-types.d.ts +32 -0
- package/dist/gallery-types.d.ts.map +1 -0
- package/dist/gallery-types.js +2 -0
- package/dist/gallery-types.js.map +1 -0
- package/dist/graphql.d.ts +97 -0
- package/dist/graphql.d.ts.map +1 -0
- package/dist/graphql.js +212 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.cjs +19448 -0
- package/dist/index.d.cts +19 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/marketing-schema.d.ts +216 -0
- package/dist/marketing-schema.d.ts.map +1 -0
- package/dist/marketing-schema.js +200 -0
- package/dist/marketing-schema.js.map +1 -0
- package/dist/mcp.d.ts +42 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +87 -0
- package/dist/mcp.js.map +1 -0
- package/dist/native.d.ts +45 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +43 -0
- package/dist/native.js.map +1 -0
- package/dist/next-revalidate.d.ts +43 -0
- package/dist/next-revalidate.d.ts.map +1 -0
- package/dist/next-revalidate.js +84 -0
- package/dist/next-revalidate.js.map +1 -0
- package/dist/parallel-pages.d.ts +102 -0
- package/dist/parallel-pages.d.ts.map +1 -0
- package/dist/parallel-pages.js +108 -0
- package/dist/parallel-pages.js.map +1 -0
- package/dist/payment-icons.d.ts +66 -0
- package/dist/payment-icons.d.ts.map +1 -0
- package/dist/payment-icons.js +160 -0
- package/dist/payment-icons.js.map +1 -0
- package/dist/proxy.d.ts +67 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +97 -0
- package/dist/proxy.js.map +1 -0
- package/dist/react-analytics.d.ts +39 -0
- package/dist/react-analytics.d.ts.map +1 -0
- package/dist/react-analytics.js +59 -0
- package/dist/react-analytics.js.map +1 -0
- package/dist/react-attribution.d.ts +99 -0
- package/dist/react-attribution.d.ts.map +1 -0
- package/dist/react-attribution.js +52 -0
- package/dist/react-attribution.js.map +1 -0
- package/dist/react-marketing.d.ts +181 -0
- package/dist/react-marketing.d.ts.map +1 -0
- package/dist/react-marketing.js +215 -0
- package/dist/react-marketing.js.map +1 -0
- package/dist/react.d.ts +727 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +2418 -0
- package/dist/react.js.map +1 -0
- package/dist/reserved-paths.d.ts +24 -0
- package/dist/reserved-paths.d.ts.map +1 -0
- package/dist/reserved-paths.js +60 -0
- package/dist/reserved-paths.js.map +1 -0
- package/dist/revalidate-verify.d.ts +30 -0
- package/dist/revalidate-verify.d.ts.map +1 -0
- package/dist/revalidate-verify.js +74 -0
- package/dist/revalidate-verify.js.map +1 -0
- package/dist/sdk.d.ts +678 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +801 -0
- package/dist/sdk.js.map +1 -0
- package/dist/seo.d.ts +218 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +309 -0
- package/dist/seo.js.map +1 -0
- package/dist/socials.d.ts +47 -0
- package/dist/socials.d.ts.map +1 -0
- package/dist/socials.js +172 -0
- package/dist/socials.js.map +1 -0
- package/dist/static.d.ts +14 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +14 -0
- package/dist/static.js.map +1 -0
- package/dist/url-pattern.d.ts +47 -0
- package/dist/url-pattern.d.ts.map +1 -0
- package/dist/url-pattern.js +124 -0
- package/dist/url-pattern.js.map +1 -0
- package/dist/vanilla.d.ts +77 -0
- package/dist/vanilla.d.ts.map +1 -0
- package/dist/vanilla.js +903 -0
- package/dist/vanilla.js.map +1 -0
- package/openapi.json +13779 -0
- package/package.json +204 -0
- package/src/adapters-revalidate.ts +88 -0
- package/src/alpha.ts +89 -0
- package/src/analytics-core.ts +175 -0
- package/src/analytics-server.ts +99 -0
- package/src/angular/biab-form.component.ts +1443 -0
- package/src/angular/index.ts +26 -0
- package/src/attribution-logo.ts +5 -0
- package/src/auth-handler.ts +352 -0
- package/src/cli.ts +2517 -0
- package/src/client.ts +3578 -0
- package/src/collections.ts +101 -0
- package/src/contracts.ts +3390 -0
- package/src/data-model-schema.ts +4042 -0
- package/src/distribution.ts +73 -0
- package/src/element.ts +321 -0
- package/src/enums.ts +65 -0
- package/src/errors.ts +338 -0
- package/src/forms-core/autocomplete.ts +97 -0
- package/src/forms-core/business-function.ts +209 -0
- package/src/forms-core/calculation.ts +167 -0
- package/src/forms-core/controller.ts +922 -0
- package/src/forms-core/elements.ts +86 -0
- package/src/forms-core/email-domains.ts +68 -0
- package/src/forms-core/file-rules.ts +123 -0
- package/src/forms-core/formula.ts +1339 -0
- package/src/forms-core/index.ts +122 -0
- package/src/forms-core/units.ts +308 -0
- package/src/forms-core/us-states.ts +68 -0
- package/src/forms-core/validation.ts +1037 -0
- package/src/forms.ts +63 -0
- package/src/gallery-types.ts +31 -0
- package/src/graphql.ts +295 -0
- package/src/index.ts +505 -0
- package/src/marketing-schema.ts +395 -0
- package/src/mcp.ts +108 -0
- package/src/native.ts +73 -0
- package/src/next-revalidate.ts +124 -0
- package/src/parallel-pages.ts +192 -0
- package/src/payment-icons.ts +205 -0
- package/src/proxy.ts +173 -0
- package/src/qwik.tsx +1234 -0
- package/src/react-analytics.tsx +76 -0
- package/src/react-attribution.tsx +192 -0
- package/src/react-marketing.tsx +338 -0
- package/src/react.tsx +5495 -0
- package/src/reserved-paths.ts +65 -0
- package/src/revalidate-verify.ts +98 -0
- package/src/sdk.ts +1671 -0
- package/src/seo.ts +488 -0
- package/src/socials.ts +230 -0
- package/src/solid.tsx +1466 -0
- package/src/static.ts +21 -0
- package/src/styles/biab-forms.css +484 -0
- package/src/svelte/BiabForm.svelte +1069 -0
- package/src/svelte/context.ts +57 -0
- package/src/svelte/index.ts +79 -0
- package/src/url-pattern.ts +153 -0
- package/src/vanilla.ts +1290 -0
- package/src/vue/BiabForm.vue +1538 -0
- package/src/vue/index.ts +119 -0
package/src/contracts.ts
ADDED
|
@@ -0,0 +1,3390 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A string field whose values the SERVER owns and may extend.
|
|
5
|
+
*
|
|
6
|
+
* `T` gives callers the known members — autocomplete, and a `switch` that
|
|
7
|
+
* reads as a real enum — while `string & {}` keeps any other string
|
|
8
|
+
* assignable. (The `& {}` is load-bearing: a bare `T | string` collapses to
|
|
9
|
+
* `string` and the literals vanish from tooling.)
|
|
10
|
+
*/
|
|
11
|
+
export type OpenEnum<T extends string> = T | (string & {});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Declare a server-owned enum on a RESPONSE field.
|
|
15
|
+
*
|
|
16
|
+
* ## Why this is not `z.enum([...])`
|
|
17
|
+
*
|
|
18
|
+
* Response schemas here are parsed at runtime — `client.ts` ends every request
|
|
19
|
+
* with `options.responseSchema.parse(body)`, and `.parse` **throws**. So a
|
|
20
|
+
* `z.enum` on a response is a promise that the set never grows. It does grow:
|
|
21
|
+
* these mirror pg enums, and a migration can add a member and ship long before
|
|
22
|
+
* a consumer upgrades their pinned `@businessdash/sdk`. The old value keeps
|
|
23
|
+
* working; the new one takes down every call that returns a row carrying it.
|
|
24
|
+
*
|
|
25
|
+
* So runtime stays permissive (`z.string()` — a string is still a string) and
|
|
26
|
+
* the precision goes into the TYPE, where being wrong costs a stale hint
|
|
27
|
+
* instead of an outage. The cast is sound rather than a convenience: every
|
|
28
|
+
* `string` genuinely is an `OpenEnum<T>`.
|
|
29
|
+
*
|
|
30
|
+
* The known members aren't lost — they ride along in `.describe()`, so they
|
|
31
|
+
* reach `openapi.json` and the clients generated from it.
|
|
32
|
+
*
|
|
33
|
+
* Use `z.enum` freely on REQUEST fields: rejecting a bad value the caller
|
|
34
|
+
* just typed is the entire point, and the blast radius is one call.
|
|
35
|
+
*/
|
|
36
|
+
export function openEnum<const T extends readonly [string, ...string[]]>(
|
|
37
|
+
...values: T
|
|
38
|
+
): z.ZodType<OpenEnum<T[number]>> {
|
|
39
|
+
return z
|
|
40
|
+
.string()
|
|
41
|
+
.describe(
|
|
42
|
+
`Known values: ${values.join(" | ")}. The server may add more — treat unrecognised values as forward-compatible rather than invalid.`,
|
|
43
|
+
) as z.ZodType<OpenEnum<T[number]>>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const siteIdSchema = z.string().uuid();
|
|
47
|
+
export const collectionIdSchema = z.string().uuid();
|
|
48
|
+
export const rowIdSchema = z.string().uuid();
|
|
49
|
+
export const fieldIdSchema = z.string().uuid();
|
|
50
|
+
export const apiKeyIdSchema = z.string().uuid();
|
|
51
|
+
export const timestampSchema = z.string().min(1);
|
|
52
|
+
|
|
53
|
+
export const collectionSlugSchema = z
|
|
54
|
+
.string()
|
|
55
|
+
.min(1)
|
|
56
|
+
.max(80)
|
|
57
|
+
.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/);
|
|
58
|
+
|
|
59
|
+
export const collectionFieldNameSchema = z
|
|
60
|
+
.string()
|
|
61
|
+
.min(1)
|
|
62
|
+
.max(80)
|
|
63
|
+
.regex(/^[a-zA-Z][a-zA-Z0-9_]*$/);
|
|
64
|
+
|
|
65
|
+
export const collectionFieldTypeValues = [
|
|
66
|
+
"string",
|
|
67
|
+
"number",
|
|
68
|
+
"boolean",
|
|
69
|
+
"json",
|
|
70
|
+
"date",
|
|
71
|
+
"richtext",
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
export const collectionFieldTypeSchema = z.enum(collectionFieldTypeValues);
|
|
75
|
+
|
|
76
|
+
export const jsonValueSchema: z.ZodType<
|
|
77
|
+
string | number | boolean | null | { [key: string]: unknown } | unknown[]
|
|
78
|
+
> = z.lazy(() =>
|
|
79
|
+
z.union([
|
|
80
|
+
z.string(),
|
|
81
|
+
z.number(),
|
|
82
|
+
z.boolean(),
|
|
83
|
+
z.null(),
|
|
84
|
+
z.record(z.unknown()),
|
|
85
|
+
z.array(z.unknown()),
|
|
86
|
+
]),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export const collectionFieldInputSchema = z.object({
|
|
90
|
+
name: collectionFieldNameSchema,
|
|
91
|
+
label: z.string().max(120).optional(),
|
|
92
|
+
type: collectionFieldTypeSchema,
|
|
93
|
+
required: z.boolean().optional(),
|
|
94
|
+
searchable: z.boolean().optional(),
|
|
95
|
+
queryable: z.boolean().optional(),
|
|
96
|
+
validation: z.record(z.unknown()).optional(),
|
|
97
|
+
displayConfig: z.record(z.unknown()).optional(),
|
|
98
|
+
position: z.number().int().min(0).optional(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export const collectionFieldSchema = z.object({
|
|
102
|
+
id: fieldIdSchema,
|
|
103
|
+
tableId: collectionIdSchema,
|
|
104
|
+
siteId: siteIdSchema,
|
|
105
|
+
name: collectionFieldNameSchema,
|
|
106
|
+
label: z.string().max(120).nullable().optional(),
|
|
107
|
+
type: collectionFieldTypeSchema,
|
|
108
|
+
required: z.boolean(),
|
|
109
|
+
searchable: z.boolean(),
|
|
110
|
+
queryable: z.boolean(),
|
|
111
|
+
validation: z.record(z.unknown()).nullable().optional(),
|
|
112
|
+
displayConfig: z.record(z.unknown()).nullable().optional(),
|
|
113
|
+
position: z.number().int(),
|
|
114
|
+
createdAt: timestampSchema,
|
|
115
|
+
updatedAt: timestampSchema,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export const collectionSchema = z.object({
|
|
119
|
+
id: collectionIdSchema,
|
|
120
|
+
siteId: siteIdSchema,
|
|
121
|
+
name: z.string().min(1).max(120),
|
|
122
|
+
slug: collectionSlugSchema,
|
|
123
|
+
description: z.string().max(500).nullable().optional(),
|
|
124
|
+
createdByUserId: z.string().uuid().nullable().optional(),
|
|
125
|
+
createdAt: timestampSchema,
|
|
126
|
+
updatedAt: timestampSchema,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export const createCollectionInputSchema = z.object({
|
|
130
|
+
name: z.string().min(1).max(120),
|
|
131
|
+
slug: collectionSlugSchema.optional(),
|
|
132
|
+
description: z.string().max(500).optional(),
|
|
133
|
+
fields: z.array(collectionFieldInputSchema).max(64).optional(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const createCollectionResponseSchema = z.object({
|
|
137
|
+
collection: collectionSchema,
|
|
138
|
+
fields: z.array(collectionFieldSchema),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export const listCollectionsResponseSchema = z.object({
|
|
142
|
+
collections: z.array(collectionSchema),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const getCollectionResponseSchema = z.object({
|
|
146
|
+
collection: collectionSchema,
|
|
147
|
+
fields: z.array(collectionFieldSchema),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
export const rowDataSchema = z.record(z.unknown());
|
|
151
|
+
|
|
152
|
+
export const collectionRowSchema = z.object({
|
|
153
|
+
id: rowIdSchema,
|
|
154
|
+
tableId: collectionIdSchema,
|
|
155
|
+
siteId: siteIdSchema,
|
|
156
|
+
data: rowDataSchema,
|
|
157
|
+
createdByUserId: z.string().uuid().nullable().optional(),
|
|
158
|
+
updatedByUserId: z.string().uuid().nullable().optional(),
|
|
159
|
+
createdAt: timestampSchema,
|
|
160
|
+
updatedAt: timestampSchema,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export const listRowsInputSchema = z.object({
|
|
164
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export const listRowsResponseSchema = z.object({
|
|
168
|
+
rows: z.array(collectionRowSchema),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export const upsertRowInputSchema = z.object({
|
|
172
|
+
rowId: rowIdSchema.optional(),
|
|
173
|
+
data: rowDataSchema,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
export const upsertRowResponseSchema = z.object({
|
|
177
|
+
row: collectionRowSchema,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
export const queryOperatorValues = ["equals", "contains"] as const;
|
|
181
|
+
export const queryOperatorSchema = z.enum(queryOperatorValues);
|
|
182
|
+
|
|
183
|
+
export const queryFilterSchema = z.object({
|
|
184
|
+
fieldName: collectionFieldNameSchema,
|
|
185
|
+
value: z.union([z.string().min(1), z.number(), z.boolean()]),
|
|
186
|
+
operator: queryOperatorSchema.optional(),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
export const queryRowsInputSchema = z.object({
|
|
190
|
+
filters: z.array(queryFilterSchema).min(1).max(10),
|
|
191
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
export const queryRowsResponseSchema = z.object({
|
|
195
|
+
rows: z.array(collectionRowSchema),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
export const siteAssetInputSchema = z.object({
|
|
199
|
+
filename: z.string().min(1),
|
|
200
|
+
url: z.string().url(),
|
|
201
|
+
mimeType: z.string().optional(),
|
|
202
|
+
sizeBytes: z.number().int().nonnegative().optional(),
|
|
203
|
+
altText: z.string().optional(),
|
|
204
|
+
folder: z.string().optional(),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export const siteAssetSchema = z.object({
|
|
208
|
+
id: z.number().int(),
|
|
209
|
+
siteId: siteIdSchema,
|
|
210
|
+
filename: z.string(),
|
|
211
|
+
url: z.string().url(),
|
|
212
|
+
mimeType: z.string().nullable().optional(),
|
|
213
|
+
sizeBytes: z.number().int().nullable().optional(),
|
|
214
|
+
altText: z.string().nullable().optional(),
|
|
215
|
+
folder: z.string().nullable().optional(),
|
|
216
|
+
createdAt: timestampSchema,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
export const uploadSiteAssetResponseSchema = z.object({
|
|
220
|
+
asset: siteAssetSchema,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
export const followerSchema = z.object({
|
|
224
|
+
id: z.string().uuid(),
|
|
225
|
+
email: z.string().email(),
|
|
226
|
+
name: z.string().nullable().optional(),
|
|
227
|
+
source: z.string().nullable().optional(),
|
|
228
|
+
isActive: z.boolean(),
|
|
229
|
+
joinedAt: timestampSchema,
|
|
230
|
+
unsubscribedAt: timestampSchema.nullable().optional(),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
export const followerJoinInputSchema = z.object({
|
|
234
|
+
email: z.string().email(),
|
|
235
|
+
name: z.string().min(1).max(200).optional(),
|
|
236
|
+
source: z.string().min(1).max(80).optional(),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
export const followerJoinResponseSchema = z.object({
|
|
240
|
+
follower: followerSchema,
|
|
241
|
+
created: z.boolean(),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
export const followerMeInputSchema = z.object({
|
|
245
|
+
email: z.string().email(),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
export const followerMeResponseSchema = z.object({
|
|
249
|
+
follower: followerSchema.nullable(),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
export const followerEditInputSchema = z.object({
|
|
253
|
+
email: z.string().email(),
|
|
254
|
+
patch: z.object({
|
|
255
|
+
name: z.string().min(1).max(200).optional(),
|
|
256
|
+
source: z.string().min(1).max(80).optional(),
|
|
257
|
+
}),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
export const followerEditResponseSchema = z.object({
|
|
261
|
+
follower: followerSchema,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
export const followerLeaveInputSchema = z.object({
|
|
265
|
+
email: z.string().email(),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
export const followerLeaveResponseSchema = z.object({
|
|
269
|
+
follower: followerSchema.nullable(),
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
export const dashboardSessionResponseSchema = z.object({
|
|
273
|
+
url: z.string().url(),
|
|
274
|
+
token: z.string().min(1),
|
|
275
|
+
expiresAt: timestampSchema,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// ── Storefront / Cart / Checkout / Blog embed sessions ─────────────────────
|
|
279
|
+
|
|
280
|
+
const baseEmbedSessionResponse = z.object({
|
|
281
|
+
url: z.string().url(),
|
|
282
|
+
token: z.string().min(1),
|
|
283
|
+
expiresAt: timestampSchema,
|
|
284
|
+
orgSlug: z.string().min(1),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
export const storefrontSessionInputSchema = z
|
|
288
|
+
.object({
|
|
289
|
+
productId: z.string().uuid().optional(),
|
|
290
|
+
})
|
|
291
|
+
.optional();
|
|
292
|
+
export type StorefrontSessionInput = z.infer<
|
|
293
|
+
typeof storefrontSessionInputSchema
|
|
294
|
+
>;
|
|
295
|
+
|
|
296
|
+
export const storefrontSessionResponseSchema = baseEmbedSessionResponse;
|
|
297
|
+
export type StorefrontSessionResponse = z.infer<
|
|
298
|
+
typeof storefrontSessionResponseSchema
|
|
299
|
+
>;
|
|
300
|
+
|
|
301
|
+
export const cartSessionResponseSchema = baseEmbedSessionResponse;
|
|
302
|
+
export type CartSessionResponse = z.infer<typeof cartSessionResponseSchema>;
|
|
303
|
+
|
|
304
|
+
export const checkoutLineSchema = z.object({
|
|
305
|
+
productId: z.string().uuid(),
|
|
306
|
+
variantId: z.string().uuid(),
|
|
307
|
+
combinationKey: z.string().optional(),
|
|
308
|
+
quantity: z.number().int().positive(),
|
|
309
|
+
});
|
|
310
|
+
export type CheckoutLine = z.infer<typeof checkoutLineSchema>;
|
|
311
|
+
|
|
312
|
+
export const checkoutSessionInputSchema = z.object({
|
|
313
|
+
lines: z.array(checkoutLineSchema).min(1),
|
|
314
|
+
customerEmail: z.string().email().optional(),
|
|
315
|
+
successUrl: z.string().url().optional(),
|
|
316
|
+
cancelUrl: z.string().url().optional(),
|
|
317
|
+
});
|
|
318
|
+
export type CheckoutSessionInput = z.infer<typeof checkoutSessionInputSchema>;
|
|
319
|
+
|
|
320
|
+
export const checkoutSessionResponseSchema = z.object({
|
|
321
|
+
url: z.string().url(),
|
|
322
|
+
stripeUrl: z.string().url(),
|
|
323
|
+
sessionId: z.string().min(1),
|
|
324
|
+
totalAmountCents: z.number().int(),
|
|
325
|
+
currency: z.string().min(1),
|
|
326
|
+
token: z.string().min(1),
|
|
327
|
+
expiresAt: timestampSchema,
|
|
328
|
+
});
|
|
329
|
+
export type CheckoutSessionResponse = z.infer<
|
|
330
|
+
typeof checkoutSessionResponseSchema
|
|
331
|
+
>;
|
|
332
|
+
|
|
333
|
+
// ── Programmatic checkout-from-cart (Batch II, 0.9.0) ────────────────
|
|
334
|
+
|
|
335
|
+
export const checkoutStartInputSchema = z
|
|
336
|
+
.object({
|
|
337
|
+
customerEmail: z.string().email().optional(),
|
|
338
|
+
successUrl: z.string().url().optional(),
|
|
339
|
+
cancelUrl: z.string().url().optional(),
|
|
340
|
+
})
|
|
341
|
+
.optional();
|
|
342
|
+
export type CheckoutStartInput = z.infer<typeof checkoutStartInputSchema>;
|
|
343
|
+
|
|
344
|
+
export const checkoutStartResponseSchema = z.object({
|
|
345
|
+
sessionId: z.string().min(1),
|
|
346
|
+
stripeUrl: z.string().url(),
|
|
347
|
+
totalAmountCents: z.number().int(),
|
|
348
|
+
currency: z.string().min(1),
|
|
349
|
+
});
|
|
350
|
+
export type CheckoutStartResponse = z.infer<typeof checkoutStartResponseSchema>;
|
|
351
|
+
|
|
352
|
+
export const checkoutStatusResponseSchema = z.object({
|
|
353
|
+
sessionId: z.string().min(1),
|
|
354
|
+
paymentStatus: z.enum(["no_payment_required", "paid", "unpaid"]),
|
|
355
|
+
amountTotalCents: z.number().int().nullable(),
|
|
356
|
+
currency: z.string().nullable(),
|
|
357
|
+
customerEmail: z.string().nullable(),
|
|
358
|
+
customerName: z.string().nullable(),
|
|
359
|
+
});
|
|
360
|
+
export type CheckoutStatusResponse = z.infer<
|
|
361
|
+
typeof checkoutStatusResponseSchema
|
|
362
|
+
>;
|
|
363
|
+
|
|
364
|
+
export const blogSessionInputSchema = z
|
|
365
|
+
.object({
|
|
366
|
+
postSlug: z.string().min(1).max(200).optional(),
|
|
367
|
+
})
|
|
368
|
+
.optional();
|
|
369
|
+
export type BlogSessionInput = z.infer<typeof blogSessionInputSchema>;
|
|
370
|
+
|
|
371
|
+
export const blogSessionResponseSchema = baseEmbedSessionResponse;
|
|
372
|
+
export type BlogSessionResponse = z.infer<typeof blogSessionResponseSchema>;
|
|
373
|
+
|
|
374
|
+
// ── Chatbot ────────────────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
export const chatbotSessionResponseSchema = baseEmbedSessionResponse;
|
|
377
|
+
export type ChatbotSessionResponse = z.infer<
|
|
378
|
+
typeof chatbotSessionResponseSchema
|
|
379
|
+
>;
|
|
380
|
+
|
|
381
|
+
/** How proactively the bot should suggest "Talk to a person" — server-driven. */
|
|
382
|
+
export const chatbotHandoffThresholdSchema = z.enum([
|
|
383
|
+
"never",
|
|
384
|
+
"low",
|
|
385
|
+
"medium",
|
|
386
|
+
"high",
|
|
387
|
+
"always",
|
|
388
|
+
]);
|
|
389
|
+
export type ChatbotHandoffThreshold = z.infer<
|
|
390
|
+
typeof chatbotHandoffThresholdSchema
|
|
391
|
+
>;
|
|
392
|
+
|
|
393
|
+
export const chatbotConfigSchema = z.object({
|
|
394
|
+
systemPrompt: z.string(),
|
|
395
|
+
knowledgeBase: z.string(),
|
|
396
|
+
model: z.string().min(1),
|
|
397
|
+
temperature: z.number().min(0).max(2),
|
|
398
|
+
lastUpdated: timestampSchema.nullable(),
|
|
399
|
+
usingDefaults: z.boolean(),
|
|
400
|
+
/**
|
|
401
|
+
* Live-handoff dial. The host bakes the matching guidance into the
|
|
402
|
+
* system prompt; SDK consumers can still branch on it to render their
|
|
403
|
+
* own UI hints.
|
|
404
|
+
*/
|
|
405
|
+
handoffThreshold: chatbotHandoffThresholdSchema.default("medium"),
|
|
406
|
+
/**
|
|
407
|
+
* `true` when the org's subscription plan unlocks live front-desk
|
|
408
|
+
* handoff (Growth+ on the host's pricing). Use this to render a
|
|
409
|
+
* "Talk to a person" button or hide it when the org is on a tier that
|
|
410
|
+
* is bot-only.
|
|
411
|
+
*/
|
|
412
|
+
frontDeskEnabled: z.boolean().default(false),
|
|
413
|
+
/**
|
|
414
|
+
* `true` while the org's visitor file-upload "receive window" is open. Use it
|
|
415
|
+
* to show/hide an upload affordance. Uploads also require the
|
|
416
|
+
* `chat.file_uploads` entitlement (enforced on the upload route).
|
|
417
|
+
*/
|
|
418
|
+
fileUploadsEnabled: z.boolean().default(false),
|
|
419
|
+
/**
|
|
420
|
+
* Whether/how the chat should ask the visitor for an email: `"request"` is a
|
|
421
|
+
* skippable ask, `"require"` blocks the first message until one is provided.
|
|
422
|
+
*/
|
|
423
|
+
emailCaptureMode: z.enum(["off", "request", "require"]).default("off"),
|
|
424
|
+
});
|
|
425
|
+
export type ChatbotConfig = z.infer<typeof chatbotConfigSchema>;
|
|
426
|
+
|
|
427
|
+
/** Response from the chat file-upload presign endpoint. */
|
|
428
|
+
export const chatbotUploadUrlResponseSchema = z.object({
|
|
429
|
+
uploadUrl: z.string(),
|
|
430
|
+
mediaId: z.string(),
|
|
431
|
+
publicUrl: z.string(),
|
|
432
|
+
storageId: z.string(),
|
|
433
|
+
uploadExpiresAt: z.string(),
|
|
434
|
+
});
|
|
435
|
+
export type ChatbotUploadUrlResponse = z.infer<
|
|
436
|
+
typeof chatbotUploadUrlResponseSchema
|
|
437
|
+
>;
|
|
438
|
+
|
|
439
|
+
export const chatbotConfigUpdateInputSchema = z.object({
|
|
440
|
+
systemPrompt: z.string().min(1).max(10_000).optional(),
|
|
441
|
+
knowledgeBase: z.string().max(50_000).optional(),
|
|
442
|
+
model: z.string().min(1).max(120).optional(),
|
|
443
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
444
|
+
});
|
|
445
|
+
export type ChatbotConfigUpdateInput = z.infer<
|
|
446
|
+
typeof chatbotConfigUpdateInputSchema
|
|
447
|
+
>;
|
|
448
|
+
|
|
449
|
+
export const chatbotMessageSchema = z.object({
|
|
450
|
+
role: z.enum(["user", "assistant", "system"]),
|
|
451
|
+
content: z.string().max(16_000),
|
|
452
|
+
});
|
|
453
|
+
export type ChatbotMessage = z.infer<typeof chatbotMessageSchema>;
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Per-call provider override (BYO key). When set, the turn runs against the
|
|
457
|
+
* SDK consumer's own LLM account — their `apiKey`, their billing — instead
|
|
458
|
+
* of the BIAB platform's configured provider, and the platform's monthly
|
|
459
|
+
* token budget is bypassed for that call.
|
|
460
|
+
*
|
|
461
|
+
* **Server-only.** Never send this from a browser: the key would be visible
|
|
462
|
+
* in DevTools. Hold it in your server env (e.g. `process.env.OPENAI_API_KEY`)
|
|
463
|
+
* and pass it on the server-side `client.chatbot.chat()` call. The
|
|
464
|
+
* `useChatbot` browser hook deliberately does not surface this field.
|
|
465
|
+
*/
|
|
466
|
+
export const chatbotByoConfigSchema = z.object({
|
|
467
|
+
provider: z.enum(["openai", "anthropic", "grok"]),
|
|
468
|
+
apiKey: z.string().min(1).max(500),
|
|
469
|
+
model: z.string().min(1).max(120).optional(),
|
|
470
|
+
});
|
|
471
|
+
export type ChatbotByoConfig = z.infer<typeof chatbotByoConfigSchema>;
|
|
472
|
+
|
|
473
|
+
export const chatbotChatInputSchema = z.object({
|
|
474
|
+
messages: z.array(chatbotMessageSchema).min(1).max(40),
|
|
475
|
+
/** See {@link chatbotByoConfigSchema}. Server-side use only. */
|
|
476
|
+
byo: chatbotByoConfigSchema.optional(),
|
|
477
|
+
});
|
|
478
|
+
export type ChatbotChatInput = z.infer<typeof chatbotChatInputSchema>;
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Side-effect the assistant requested mid-turn. The SDK consumer renders
|
|
482
|
+
* each action — currently only `show_form`, which the consumer resolves by
|
|
483
|
+
* fetching the form definition and rendering inline in the chat thread.
|
|
484
|
+
*/
|
|
485
|
+
export const chatbotUiActionSchema = z.discriminatedUnion("kind", [
|
|
486
|
+
z.object({
|
|
487
|
+
kind: z.literal("show_form"),
|
|
488
|
+
formSlug: z.string().min(1).max(120),
|
|
489
|
+
}),
|
|
490
|
+
]);
|
|
491
|
+
export type ChatbotUiAction = z.infer<typeof chatbotUiActionSchema>;
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Live front-desk availability for the org. Mirrors what the public-site
|
|
495
|
+
* widget surfaces in its presence strip; SDK consumers can render a
|
|
496
|
+
* "Talk to a person" button conditional on `staffAvailable`, and a
|
|
497
|
+
* wait-time hint from `estimateLabel`.
|
|
498
|
+
*/
|
|
499
|
+
export const chatbotAvailabilitySchema = z.object({
|
|
500
|
+
frontDeskEnabled: z.boolean(),
|
|
501
|
+
staffAvailable: z.boolean(),
|
|
502
|
+
offline: z.boolean(),
|
|
503
|
+
estimateLabel: z.string().nullable(),
|
|
504
|
+
onlineHandlers: z.array(
|
|
505
|
+
z.object({
|
|
506
|
+
userId: z.string(),
|
|
507
|
+
name: z.string(),
|
|
508
|
+
avatarUrl: z.string().nullable(),
|
|
509
|
+
}),
|
|
510
|
+
),
|
|
511
|
+
});
|
|
512
|
+
export type ChatbotAvailability = z.infer<typeof chatbotAvailabilitySchema>;
|
|
513
|
+
|
|
514
|
+
export const chatbotChatResponseSchema = z.object({
|
|
515
|
+
text: z.string(),
|
|
516
|
+
model: z.string(),
|
|
517
|
+
usingDefaults: z.boolean(),
|
|
518
|
+
uiActions: z.array(chatbotUiActionSchema).default([]),
|
|
519
|
+
availability: chatbotAvailabilitySchema.optional(),
|
|
520
|
+
/** True when the turn ran against a BYO provider key (consumer-paid). */
|
|
521
|
+
byo: z.boolean().default(false),
|
|
522
|
+
});
|
|
523
|
+
export type ChatbotChatResponse = z.infer<typeof chatbotChatResponseSchema>;
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Minimal field shape the SDK needs to render an inline form. Mirrors the
|
|
527
|
+
* host's `FormField` type but kept loose (only the keys the renderer
|
|
528
|
+
* actually reads) so additive server-side changes don't break old SDK
|
|
529
|
+
* builds.
|
|
530
|
+
*/
|
|
531
|
+
export const chatbotFormFieldSchema = z
|
|
532
|
+
.object({
|
|
533
|
+
id: z.string(),
|
|
534
|
+
label: z.string(),
|
|
535
|
+
type: z.string(),
|
|
536
|
+
required: z.boolean().default(false),
|
|
537
|
+
placeholder: z.string().optional(),
|
|
538
|
+
helpText: z.string().optional(),
|
|
539
|
+
options: z.array(z.string()).optional(),
|
|
540
|
+
})
|
|
541
|
+
.passthrough();
|
|
542
|
+
export type ChatbotFormField = z.infer<typeof chatbotFormFieldSchema>;
|
|
543
|
+
|
|
544
|
+
export const chatbotFormDefinitionSchema = z.object({
|
|
545
|
+
id: z.string(),
|
|
546
|
+
slug: z.string(),
|
|
547
|
+
name: z.string(),
|
|
548
|
+
description: z.string(),
|
|
549
|
+
fields: z.array(chatbotFormFieldSchema),
|
|
550
|
+
addsInquiry: z.boolean(),
|
|
551
|
+
addsLead: z.boolean(),
|
|
552
|
+
});
|
|
553
|
+
export type ChatbotFormDefinition = z.infer<typeof chatbotFormDefinitionSchema>;
|
|
554
|
+
|
|
555
|
+
export const chatbotFormSubmitInputSchema = z.object({
|
|
556
|
+
data: z.record(z.unknown()),
|
|
557
|
+
submitterEmail: z.string().email().optional(),
|
|
558
|
+
submitterName: z.string().optional(),
|
|
559
|
+
priorChat: z
|
|
560
|
+
.array(
|
|
561
|
+
z.object({
|
|
562
|
+
role: z.string().min(1).max(40),
|
|
563
|
+
content: z.string().min(1).max(16_000),
|
|
564
|
+
createdAt: z.number().optional(),
|
|
565
|
+
}),
|
|
566
|
+
)
|
|
567
|
+
.max(200)
|
|
568
|
+
.optional(),
|
|
569
|
+
});
|
|
570
|
+
export type ChatbotFormSubmitInput = z.infer<
|
|
571
|
+
typeof chatbotFormSubmitInputSchema
|
|
572
|
+
>;
|
|
573
|
+
|
|
574
|
+
export const chatbotFormSubmitResponseSchema = z.object({
|
|
575
|
+
success: z.literal(true),
|
|
576
|
+
submissionId: z.string(),
|
|
577
|
+
slug: z.string(),
|
|
578
|
+
});
|
|
579
|
+
export type ChatbotFormSubmitResponse = z.infer<
|
|
580
|
+
typeof chatbotFormSubmitResponseSchema
|
|
581
|
+
>;
|
|
582
|
+
|
|
583
|
+
// ── Front Desk programmatic surface (Batch JJ, 0.9.0) ────────────────
|
|
584
|
+
|
|
585
|
+
export const chatbotPersistedSessionInputSchema = z.object({
|
|
586
|
+
visitorToken: z.string().min(8).max(200),
|
|
587
|
+
visitorName: z.string().max(200).optional(),
|
|
588
|
+
visitorEmail: z.string().email().optional(),
|
|
589
|
+
});
|
|
590
|
+
export type ChatbotPersistedSessionInput = z.infer<
|
|
591
|
+
typeof chatbotPersistedSessionInputSchema
|
|
592
|
+
>;
|
|
593
|
+
|
|
594
|
+
export const chatbotPersistedSessionResponseSchema = z.object({
|
|
595
|
+
sessionId: z.string().uuid(),
|
|
596
|
+
/** Mirrors the `chat_session_status` pg enum. */
|
|
597
|
+
status: openEnum("bot", "awaiting_human", "assigned", "closed"),
|
|
598
|
+
startedAt: timestampSchema,
|
|
599
|
+
visitorToken: z.string(),
|
|
600
|
+
});
|
|
601
|
+
export type ChatbotPersistedSessionResponse = z.infer<
|
|
602
|
+
typeof chatbotPersistedSessionResponseSchema
|
|
603
|
+
>;
|
|
604
|
+
|
|
605
|
+
export const chatbotPersistedMessageSchema = z.object({
|
|
606
|
+
id: z.string(),
|
|
607
|
+
/** Mirrors the `chat_message_role` pg enum. */
|
|
608
|
+
role: openEnum("visitor", "assistant_bot", "staff", "system", "staff_note"),
|
|
609
|
+
content: z.string(),
|
|
610
|
+
createdAt: timestampSchema,
|
|
611
|
+
authorUserId: z.string().nullable(),
|
|
612
|
+
});
|
|
613
|
+
export type ChatbotPersistedMessage = z.infer<
|
|
614
|
+
typeof chatbotPersistedMessageSchema
|
|
615
|
+
>;
|
|
616
|
+
|
|
617
|
+
export const chatbotSessionStatusSchema = z.object({
|
|
618
|
+
sessionId: z.string(),
|
|
619
|
+
/** Mirrors the `chat_session_status` pg enum. */
|
|
620
|
+
status: openEnum("bot", "awaiting_human", "assigned", "closed"),
|
|
621
|
+
assignedUserId: z.string().nullable(),
|
|
622
|
+
placeInLine: z.number().int().nullable(),
|
|
623
|
+
stalled: z.boolean(),
|
|
624
|
+
});
|
|
625
|
+
export type ChatbotSessionStatus = z.infer<typeof chatbotSessionStatusSchema>;
|
|
626
|
+
|
|
627
|
+
export const chatbotMessagesResponseSchema = z.object({
|
|
628
|
+
session: chatbotSessionStatusSchema,
|
|
629
|
+
messages: z.array(chatbotPersistedMessageSchema),
|
|
630
|
+
});
|
|
631
|
+
export type ChatbotMessagesResponse = z.infer<
|
|
632
|
+
typeof chatbotMessagesResponseSchema
|
|
633
|
+
>;
|
|
634
|
+
|
|
635
|
+
export const chatbotAppendMessageInputSchema = z.object({
|
|
636
|
+
sessionId: z.string().uuid(),
|
|
637
|
+
visitorToken: z.string().min(8).max(200),
|
|
638
|
+
content: z.string().min(1).max(16_000),
|
|
639
|
+
role: z.enum(["visitor", "assistant_bot"]).default("visitor"),
|
|
640
|
+
});
|
|
641
|
+
export type ChatbotAppendMessageInput = z.infer<
|
|
642
|
+
typeof chatbotAppendMessageInputSchema
|
|
643
|
+
>;
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Plan-gate body shape. Identical across every gated endpoint —
|
|
647
|
+
* lift this to model upgrade-card props in one place.
|
|
648
|
+
*/
|
|
649
|
+
export const planGatedUnavailableSchema = z.object({
|
|
650
|
+
available: z.literal(false),
|
|
651
|
+
reason: z.literal("plan_required"),
|
|
652
|
+
requiredFeature: z.string(),
|
|
653
|
+
requiredPlan: z.string(),
|
|
654
|
+
planDisplayName: z.string(),
|
|
655
|
+
upgradeUrl: z.string(),
|
|
656
|
+
});
|
|
657
|
+
export type PlanGatedUnavailable = z.infer<typeof planGatedUnavailableSchema>;
|
|
658
|
+
|
|
659
|
+
export const chatbotRequestHumanInputSchema = z.object({
|
|
660
|
+
sessionId: z.string().uuid(),
|
|
661
|
+
visitorToken: z.string().min(8).max(200),
|
|
662
|
+
});
|
|
663
|
+
export type ChatbotRequestHumanInput = z.infer<
|
|
664
|
+
typeof chatbotRequestHumanInputSchema
|
|
665
|
+
>;
|
|
666
|
+
|
|
667
|
+
export const chatbotRequestHumanAvailableSchema = z.object({
|
|
668
|
+
available: z.literal(true),
|
|
669
|
+
status: z.string(),
|
|
670
|
+
requestedAt: z.string().nullable(),
|
|
671
|
+
placeInLine: z.number().int().nullable(),
|
|
672
|
+
estimateLabel: z.string().nullable(),
|
|
673
|
+
staffAvailable: z.boolean(),
|
|
674
|
+
onlineHandlers: z.array(
|
|
675
|
+
z.object({
|
|
676
|
+
userId: z.string(),
|
|
677
|
+
name: z.string(),
|
|
678
|
+
avatarUrl: z.string().nullable(),
|
|
679
|
+
}),
|
|
680
|
+
),
|
|
681
|
+
});
|
|
682
|
+
export type ChatbotRequestHumanAvailable = z.infer<
|
|
683
|
+
typeof chatbotRequestHumanAvailableSchema
|
|
684
|
+
>;
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Discriminated union — every Front Desk write returns one of these.
|
|
688
|
+
* Branch on `.available` to render the upgrade CTA vs the queue UI.
|
|
689
|
+
*/
|
|
690
|
+
export const chatbotRequestHumanResponseSchema = z.union([
|
|
691
|
+
chatbotRequestHumanAvailableSchema,
|
|
692
|
+
planGatedUnavailableSchema,
|
|
693
|
+
]);
|
|
694
|
+
export type ChatbotRequestHumanResponse = z.infer<
|
|
695
|
+
typeof chatbotRequestHumanResponseSchema
|
|
696
|
+
>;
|
|
697
|
+
|
|
698
|
+
export const executeActionInputSchema = z.object({
|
|
699
|
+
payload: z.record(z.unknown()).default({}),
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
export const executeActionResponseSchema = z.object({
|
|
703
|
+
actionName: z.string().min(1),
|
|
704
|
+
result: z.unknown(),
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
export const siteMarketingPageKeySchema = z
|
|
708
|
+
.string()
|
|
709
|
+
.min(1)
|
|
710
|
+
.max(120)
|
|
711
|
+
.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/);
|
|
712
|
+
|
|
713
|
+
export const siteMarketingPageSchema = z.object({
|
|
714
|
+
siteId: siteIdSchema,
|
|
715
|
+
pageKey: siteMarketingPageKeySchema,
|
|
716
|
+
payload: jsonValueSchema,
|
|
717
|
+
updatedAt: timestampSchema,
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
export const getSiteMarketingPageResponseSchema = z.object({
|
|
721
|
+
page: siteMarketingPageSchema.nullable(),
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Lightweight directory entry — `pageKey` + `updatedAt` only. Pair with
|
|
726
|
+
* a per-page `get(pageKey)` (or React `useMarketingPage(pageKey)`) to
|
|
727
|
+
* load the payload on demand. Splitting list from content keeps a
|
|
728
|
+
* many-page site from paying the full-payload cost just to render a
|
|
729
|
+
* navigation index.
|
|
730
|
+
*/
|
|
731
|
+
export const siteMarketingPageSummarySchema = z.object({
|
|
732
|
+
siteId: siteIdSchema,
|
|
733
|
+
pageKey: siteMarketingPageKeySchema,
|
|
734
|
+
updatedAt: timestampSchema,
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
export const listSiteMarketingPagesResponseSchema = z.object({
|
|
738
|
+
pages: z.array(siteMarketingPageSummarySchema),
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
export const authIntrospectResponseSchema = z.object({
|
|
742
|
+
apiKeyId: apiKeyIdSchema.optional(),
|
|
743
|
+
orgId: z.string().uuid(),
|
|
744
|
+
userId: z.string().uuid().nullable().optional(),
|
|
745
|
+
siteIds: z.array(siteIdSchema).optional(),
|
|
746
|
+
scopes: z.array(z.string()).default([]),
|
|
747
|
+
expiresAt: timestampSchema.nullable().optional(),
|
|
748
|
+
label: z.string().optional(),
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
export const emailTemplateRendererValues = ["native-email-builder"] as const;
|
|
752
|
+
export const emailTemplateRendererSchema = z.enum(emailTemplateRendererValues);
|
|
753
|
+
|
|
754
|
+
export const emailTemplateCategoryValues = [
|
|
755
|
+
"system",
|
|
756
|
+
"marketing",
|
|
757
|
+
"transactional",
|
|
758
|
+
] as const;
|
|
759
|
+
export const emailTemplateCategorySchema = z.enum(emailTemplateCategoryValues);
|
|
760
|
+
|
|
761
|
+
export const emailTemplateVariableTypeValues = [
|
|
762
|
+
"string",
|
|
763
|
+
"number",
|
|
764
|
+
"url",
|
|
765
|
+
"currency",
|
|
766
|
+
"date",
|
|
767
|
+
] as const;
|
|
768
|
+
export const emailTemplateVariableTypeSchema = z.enum(
|
|
769
|
+
emailTemplateVariableTypeValues,
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
export const emailTemplateVariableSchema = z.object({
|
|
773
|
+
key: z.string().min(1),
|
|
774
|
+
label: z.string().min(1),
|
|
775
|
+
type: emailTemplateVariableTypeSchema,
|
|
776
|
+
required: z.boolean(),
|
|
777
|
+
defaultValue: z.unknown().optional(),
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
export const emailTemplateSummarySchema = z.object({
|
|
781
|
+
id: z.number().int(),
|
|
782
|
+
name: z.string().min(1),
|
|
783
|
+
slug: z.string().min(1),
|
|
784
|
+
description: z.string().nullable().optional(),
|
|
785
|
+
ownerId: z.string().min(1),
|
|
786
|
+
organizationId: z.string().nullable().optional(),
|
|
787
|
+
renderer: emailTemplateRendererSchema,
|
|
788
|
+
category: emailTemplateCategorySchema,
|
|
789
|
+
isActive: z.boolean(),
|
|
790
|
+
isArchived: z.boolean(),
|
|
791
|
+
createdAt: timestampSchema,
|
|
792
|
+
updatedAt: timestampSchema,
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
export const emailTemplateVersionSchema = z.object({
|
|
796
|
+
id: z.number().int(),
|
|
797
|
+
templateId: z.number().int(),
|
|
798
|
+
version: z.number().int(),
|
|
799
|
+
subject: z.string().min(1),
|
|
800
|
+
renderer: emailTemplateRendererSchema,
|
|
801
|
+
unlayerDesign: z.unknown().optional(),
|
|
802
|
+
builderDocument: z.unknown().optional(),
|
|
803
|
+
html: z.string().nullable().optional(),
|
|
804
|
+
reactComponent: z.string().nullable().optional(),
|
|
805
|
+
reactPropsSchema: z.unknown().optional(),
|
|
806
|
+
variables: z.array(emailTemplateVariableSchema),
|
|
807
|
+
createdBy: z.string().min(1),
|
|
808
|
+
createdAt: timestampSchema,
|
|
809
|
+
isPublished: z.boolean(),
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
export const listEmailTemplatesInputSchema = z.object({
|
|
813
|
+
category: emailTemplateCategorySchema.optional(),
|
|
814
|
+
activeOnly: z.boolean().optional(),
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
export const listEmailTemplatesResponseSchema = z.object({
|
|
818
|
+
templates: z.array(emailTemplateSummarySchema),
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
export const getEmailTemplateResponseSchema = z.object({
|
|
822
|
+
template: emailTemplateSummarySchema,
|
|
823
|
+
versions: z.array(emailTemplateVersionSchema),
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
export const createEmailTemplateInputSchema = z.object({
|
|
827
|
+
name: z.string().min(1),
|
|
828
|
+
slug: z.string().min(1),
|
|
829
|
+
description: z.string().optional(),
|
|
830
|
+
category: emailTemplateCategorySchema,
|
|
831
|
+
isActive: z.boolean().optional(),
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
export const createEmailTemplateResponseSchema = z.object({
|
|
835
|
+
template: emailTemplateSummarySchema,
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
export const createEmailTemplateVersionInputSchema = z.object({
|
|
839
|
+
subject: z.string().min(1),
|
|
840
|
+
builderDocument: z.unknown().optional(),
|
|
841
|
+
html: z.string().optional(),
|
|
842
|
+
reactComponent: z.string().optional(),
|
|
843
|
+
reactPropsSchema: z.unknown().optional(),
|
|
844
|
+
variables: z.array(emailTemplateVariableSchema).optional(),
|
|
845
|
+
isPublished: z.boolean().optional(),
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
export const createEmailTemplateVersionResponseSchema = z.object({
|
|
849
|
+
version: emailTemplateVersionSchema,
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
export type CollectionFieldType = z.infer<typeof collectionFieldTypeSchema>;
|
|
853
|
+
export type CollectionFieldInput = z.infer<typeof collectionFieldInputSchema>;
|
|
854
|
+
export type CollectionField = z.infer<typeof collectionFieldSchema>;
|
|
855
|
+
export type Collection = z.infer<typeof collectionSchema>;
|
|
856
|
+
export type CreateCollectionInput = z.infer<typeof createCollectionInputSchema>;
|
|
857
|
+
export type CreateCollectionResponse = z.infer<
|
|
858
|
+
typeof createCollectionResponseSchema
|
|
859
|
+
>;
|
|
860
|
+
export type ListCollectionsResponse = z.infer<
|
|
861
|
+
typeof listCollectionsResponseSchema
|
|
862
|
+
>;
|
|
863
|
+
export type GetCollectionResponse = z.infer<typeof getCollectionResponseSchema>;
|
|
864
|
+
export type CollectionRow = z.infer<typeof collectionRowSchema>;
|
|
865
|
+
export type ListRowsInput = z.infer<typeof listRowsInputSchema>;
|
|
866
|
+
export type ListRowsResponse = z.infer<typeof listRowsResponseSchema>;
|
|
867
|
+
export type UpsertRowInput = z.infer<typeof upsertRowInputSchema>;
|
|
868
|
+
export type UpsertRowResponse = z.infer<typeof upsertRowResponseSchema>;
|
|
869
|
+
export type QueryFilter = z.infer<typeof queryFilterSchema>;
|
|
870
|
+
export type QueryRowsInput = z.infer<typeof queryRowsInputSchema>;
|
|
871
|
+
export type QueryRowsResponse = z.infer<typeof queryRowsResponseSchema>;
|
|
872
|
+
export type SiteAssetInput = z.infer<typeof siteAssetInputSchema>;
|
|
873
|
+
export type SiteAsset = z.infer<typeof siteAssetSchema>;
|
|
874
|
+
export type UploadSiteAssetResponse = z.infer<
|
|
875
|
+
typeof uploadSiteAssetResponseSchema
|
|
876
|
+
>;
|
|
877
|
+
export type ExecuteActionInput = z.infer<typeof executeActionInputSchema>;
|
|
878
|
+
export type ExecuteActionResponse = z.infer<typeof executeActionResponseSchema>;
|
|
879
|
+
export type AuthIntrospectResponse = z.infer<
|
|
880
|
+
typeof authIntrospectResponseSchema
|
|
881
|
+
>;
|
|
882
|
+
export type EmailTemplateRenderer = z.infer<typeof emailTemplateRendererSchema>;
|
|
883
|
+
export type EmailTemplateCategory = z.infer<typeof emailTemplateCategorySchema>;
|
|
884
|
+
export type EmailTemplateVariable = z.infer<typeof emailTemplateVariableSchema>;
|
|
885
|
+
export type EmailTemplateSummary = z.infer<typeof emailTemplateSummarySchema>;
|
|
886
|
+
export type EmailTemplateVersion = z.infer<typeof emailTemplateVersionSchema>;
|
|
887
|
+
export type ListEmailTemplatesInput = z.infer<
|
|
888
|
+
typeof listEmailTemplatesInputSchema
|
|
889
|
+
>;
|
|
890
|
+
export type ListEmailTemplatesResponse = z.infer<
|
|
891
|
+
typeof listEmailTemplatesResponseSchema
|
|
892
|
+
>;
|
|
893
|
+
export type GetEmailTemplateResponse = z.infer<
|
|
894
|
+
typeof getEmailTemplateResponseSchema
|
|
895
|
+
>;
|
|
896
|
+
export type CreateEmailTemplateInput = z.infer<
|
|
897
|
+
typeof createEmailTemplateInputSchema
|
|
898
|
+
>;
|
|
899
|
+
export type CreateEmailTemplateResponse = z.infer<
|
|
900
|
+
typeof createEmailTemplateResponseSchema
|
|
901
|
+
>;
|
|
902
|
+
export type CreateEmailTemplateVersionInput = z.infer<
|
|
903
|
+
typeof createEmailTemplateVersionInputSchema
|
|
904
|
+
>;
|
|
905
|
+
export type CreateEmailTemplateVersionResponse = z.infer<
|
|
906
|
+
typeof createEmailTemplateVersionResponseSchema
|
|
907
|
+
>;
|
|
908
|
+
export type Follower = z.infer<typeof followerSchema>;
|
|
909
|
+
export type FollowerJoinInput = z.infer<typeof followerJoinInputSchema>;
|
|
910
|
+
export type FollowerJoinResponse = z.infer<typeof followerJoinResponseSchema>;
|
|
911
|
+
export type FollowerMeInput = z.infer<typeof followerMeInputSchema>;
|
|
912
|
+
export type FollowerMeResponse = z.infer<typeof followerMeResponseSchema>;
|
|
913
|
+
export type FollowerEditInput = z.infer<typeof followerEditInputSchema>;
|
|
914
|
+
export type FollowerEditResponse = z.infer<typeof followerEditResponseSchema>;
|
|
915
|
+
export type FollowerLeaveInput = z.infer<typeof followerLeaveInputSchema>;
|
|
916
|
+
export type FollowerLeaveResponse = z.infer<typeof followerLeaveResponseSchema>;
|
|
917
|
+
export type DashboardSessionResponse = z.infer<
|
|
918
|
+
typeof dashboardSessionResponseSchema
|
|
919
|
+
>;
|
|
920
|
+
export type SiteMarketingPage = z.infer<typeof siteMarketingPageSchema>;
|
|
921
|
+
export type GetSiteMarketingPageResponse = z.infer<
|
|
922
|
+
typeof getSiteMarketingPageResponseSchema
|
|
923
|
+
>;
|
|
924
|
+
export type SiteMarketingPageSummary = z.infer<
|
|
925
|
+
typeof siteMarketingPageSummarySchema
|
|
926
|
+
>;
|
|
927
|
+
export type ListSiteMarketingPagesResponse = z.infer<
|
|
928
|
+
typeof listSiteMarketingPagesResponseSchema
|
|
929
|
+
>;
|
|
930
|
+
|
|
931
|
+
// ── Customer Portal ──────────────────────────────────────────────────────────
|
|
932
|
+
//
|
|
933
|
+
// The customer portal is host- or session-scoped to one organization. The
|
|
934
|
+
// contracts mirror `(public)/portal/*` routes on the platform and the
|
|
935
|
+
// `customerPortal` tRPC router. Hosts: a tenant subdomain like
|
|
936
|
+
// `app.<slug>.<platform>` resolves to one org; in absence of a slug the
|
|
937
|
+
// caller's session organization id is used.
|
|
938
|
+
|
|
939
|
+
export const customerPortalOrgSchema = z.object({
|
|
940
|
+
id: z.string().uuid(),
|
|
941
|
+
name: z.string().nullable(),
|
|
942
|
+
/** @deprecated Internal auth-provider org id — use `id` (the BIAB org id).
|
|
943
|
+
* Kept for backwards compatibility; will be removed in a future release. */
|
|
944
|
+
workosOrganizationId: z.string().nullable().optional(),
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
export const customerPortalUserSchema = z.object({
|
|
948
|
+
id: z.string().uuid(),
|
|
949
|
+
email: z.string().nullable(),
|
|
950
|
+
displayName: z.string().nullable(),
|
|
951
|
+
avatarUrl: z.string().nullable(),
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
export const customerPortalContextResponseSchema = z.object({
|
|
955
|
+
organization: customerPortalOrgSchema,
|
|
956
|
+
user: customerPortalUserSchema,
|
|
957
|
+
isCustomer: z.boolean(),
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
export const customerPortalProfileSchema = z
|
|
961
|
+
.object({
|
|
962
|
+
externalCustomerId: z.string().nullable(),
|
|
963
|
+
profile: z.record(z.unknown()).nullable(),
|
|
964
|
+
lastSeenAt: timestampSchema.nullable(),
|
|
965
|
+
joinedAt: timestampSchema.nullable(),
|
|
966
|
+
})
|
|
967
|
+
.nullable();
|
|
968
|
+
|
|
969
|
+
export const customerPortalUpdateProfileInputSchema = z.object({
|
|
970
|
+
displayName: z.string().min(1).max(120).optional(),
|
|
971
|
+
profile: z.record(z.unknown()).optional(),
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
export const customerPortalUpdateProfileResponseSchema = z.object({
|
|
975
|
+
ok: z.literal(true),
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
export const customerPortalOtherOrgSchema = z.object({
|
|
979
|
+
orgId: z.string().uuid(),
|
|
980
|
+
name: z.string().nullable(),
|
|
981
|
+
/** @deprecated Internal auth-provider org id — use `orgId` (the BIAB org id).
|
|
982
|
+
* Kept for backwards compatibility; will be removed in a future release. */
|
|
983
|
+
workosOrganizationId: z.string().nullable().optional(),
|
|
984
|
+
});
|
|
985
|
+
export const customerPortalOtherOrgsResponseSchema = z.array(
|
|
986
|
+
customerPortalOtherOrgSchema,
|
|
987
|
+
);
|
|
988
|
+
|
|
989
|
+
export type CustomerPortalOrg = z.infer<typeof customerPortalOrgSchema>;
|
|
990
|
+
export type CustomerPortalUser = z.infer<typeof customerPortalUserSchema>;
|
|
991
|
+
export type CustomerPortalContextResponse = z.infer<
|
|
992
|
+
typeof customerPortalContextResponseSchema
|
|
993
|
+
>;
|
|
994
|
+
export type CustomerPortalProfile = z.infer<typeof customerPortalProfileSchema>;
|
|
995
|
+
export type CustomerPortalUpdateProfileInput = z.infer<
|
|
996
|
+
typeof customerPortalUpdateProfileInputSchema
|
|
997
|
+
>;
|
|
998
|
+
export type CustomerPortalUpdateProfileResponse = z.infer<
|
|
999
|
+
typeof customerPortalUpdateProfileResponseSchema
|
|
1000
|
+
>;
|
|
1001
|
+
export type CustomerPortalOtherOrg = z.infer<
|
|
1002
|
+
typeof customerPortalOtherOrgSchema
|
|
1003
|
+
>;
|
|
1004
|
+
export type CustomerPortalOtherOrgsResponse = z.infer<
|
|
1005
|
+
typeof customerPortalOtherOrgsResponseSchema
|
|
1006
|
+
>;
|
|
1007
|
+
|
|
1008
|
+
// ── Customer notification preferences (per-customer, per-channel) ─────
|
|
1009
|
+
//
|
|
1010
|
+
// The matrix shape is `{ [categoryId]: { email, sms, push, in_app } }`.
|
|
1011
|
+
// The server returns a fully-defaulted matrix plus the category metadata
|
|
1012
|
+
// so SDK consumers can render the UI without hardcoding either the
|
|
1013
|
+
// categories or the channels list — both grow over time.
|
|
1014
|
+
|
|
1015
|
+
export const notificationChannelSchema = z.enum([
|
|
1016
|
+
"email",
|
|
1017
|
+
"sms",
|
|
1018
|
+
"push",
|
|
1019
|
+
"in_app",
|
|
1020
|
+
]);
|
|
1021
|
+
export type NotificationChannel = z.infer<typeof notificationChannelSchema>;
|
|
1022
|
+
|
|
1023
|
+
export const notificationChannelMapSchema = z.object({
|
|
1024
|
+
email: z.boolean(),
|
|
1025
|
+
sms: z.boolean(),
|
|
1026
|
+
push: z.boolean(),
|
|
1027
|
+
in_app: z.boolean(),
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
export const notificationCategoryDefSchema = z.object({
|
|
1031
|
+
id: z.string(),
|
|
1032
|
+
label: z.string(),
|
|
1033
|
+
description: z.string(),
|
|
1034
|
+
defaults: notificationChannelMapSchema,
|
|
1035
|
+
required: z.record(notificationChannelSchema, z.literal(true)).optional(),
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
export const notificationPreferencesResponseSchema = z.object({
|
|
1039
|
+
preferences: z.record(notificationChannelMapSchema),
|
|
1040
|
+
categories: z.array(notificationCategoryDefSchema),
|
|
1041
|
+
channels: z.array(notificationChannelSchema),
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
export const notificationPreferencesUpdateInputSchema = z.object({
|
|
1045
|
+
/**
|
|
1046
|
+
* Sparse — only the categories / channels you want to flip need to
|
|
1047
|
+
* be present. Omitted keys keep their persisted (or default) value.
|
|
1048
|
+
*/
|
|
1049
|
+
preferences: z.record(
|
|
1050
|
+
z.object({
|
|
1051
|
+
email: z.boolean().optional(),
|
|
1052
|
+
sms: z.boolean().optional(),
|
|
1053
|
+
push: z.boolean().optional(),
|
|
1054
|
+
in_app: z.boolean().optional(),
|
|
1055
|
+
}),
|
|
1056
|
+
),
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
export type NotificationChannelMap = z.infer<
|
|
1060
|
+
typeof notificationChannelMapSchema
|
|
1061
|
+
>;
|
|
1062
|
+
export type NotificationCategoryDef = z.infer<
|
|
1063
|
+
typeof notificationCategoryDefSchema
|
|
1064
|
+
>;
|
|
1065
|
+
export type NotificationPreferencesResponse = z.infer<
|
|
1066
|
+
typeof notificationPreferencesResponseSchema
|
|
1067
|
+
>;
|
|
1068
|
+
export type NotificationPreferencesUpdateInput = z.infer<
|
|
1069
|
+
typeof notificationPreferencesUpdateInputSchema
|
|
1070
|
+
>;
|
|
1071
|
+
|
|
1072
|
+
// ── Customer-portal "work" bundle (jobs / quotes / invoices / contracts / payments) ─────
|
|
1073
|
+
|
|
1074
|
+
export const customerJobSummarySchema = z.object({
|
|
1075
|
+
id: z.string(),
|
|
1076
|
+
name: z.string().nullable(),
|
|
1077
|
+
status: z.string(),
|
|
1078
|
+
startDate: z.string().nullable(),
|
|
1079
|
+
dueDate: z.string().nullable(),
|
|
1080
|
+
statusCompletedAt: z.string().nullable(),
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
export const customerQuoteSummarySchema = z.object({
|
|
1084
|
+
id: z.string(),
|
|
1085
|
+
quoteNumber: z.string(),
|
|
1086
|
+
/** Mirrors the `quote_status` pg enum. */
|
|
1087
|
+
status: openEnum(
|
|
1088
|
+
"draft",
|
|
1089
|
+
"sent",
|
|
1090
|
+
"accepted",
|
|
1091
|
+
"rejected",
|
|
1092
|
+
"converted",
|
|
1093
|
+
"expired",
|
|
1094
|
+
),
|
|
1095
|
+
totalAmount: z.number(),
|
|
1096
|
+
validUntil: z.string(),
|
|
1097
|
+
publicAcceptUrl: z.string().nullable(),
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
export const customerInvoiceSummarySchema = z.object({
|
|
1101
|
+
id: z.string(),
|
|
1102
|
+
invoiceNumber: z.string(),
|
|
1103
|
+
/** Mirrors the `invoice_status` pg enum. */
|
|
1104
|
+
status: openEnum(
|
|
1105
|
+
"draft",
|
|
1106
|
+
"sent",
|
|
1107
|
+
"partial_paid",
|
|
1108
|
+
"paid",
|
|
1109
|
+
"overdue",
|
|
1110
|
+
"cancelled",
|
|
1111
|
+
),
|
|
1112
|
+
/** Mirrors the `invoice_job_status` pg enum. */
|
|
1113
|
+
jobStatus: openEnum("pending", "in_progress", "complete").nullable(),
|
|
1114
|
+
totalAmount: z.number(),
|
|
1115
|
+
paidAmount: z.number(),
|
|
1116
|
+
balanceDue: z.number(),
|
|
1117
|
+
dueDate: z.string(),
|
|
1118
|
+
issueDate: z.string(),
|
|
1119
|
+
paidAt: z.string().nullable(),
|
|
1120
|
+
publicPayUrl: z.string().nullable(),
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
export const customerContractSummarySchema = z.object({
|
|
1124
|
+
id: z.string(),
|
|
1125
|
+
title: z.string(),
|
|
1126
|
+
/** Mirrors the `contract_status` pg enum. */
|
|
1127
|
+
status: openEnum("draft", "sent", "signed", "expired", "cancelled"),
|
|
1128
|
+
sentAt: z.string().nullable(),
|
|
1129
|
+
signedAt: z.string().nullable(),
|
|
1130
|
+
signingUrl: z.string().nullable(),
|
|
1131
|
+
signedDocumentUrl: z.string().nullable(),
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
export const customerPaymentSummarySchema = z.object({
|
|
1135
|
+
id: z.string(),
|
|
1136
|
+
invoiceId: z.string(),
|
|
1137
|
+
amount: z.number(),
|
|
1138
|
+
/** Mirrors the `payment_method` pg enum. */
|
|
1139
|
+
method: openEnum("card", "bank_transfer", "cash", "check", "other"),
|
|
1140
|
+
date: z.string(),
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
export const customerWorkBundleSchema = z
|
|
1144
|
+
.object({
|
|
1145
|
+
/** True when the customer's email matches no CRM contact in this org. */
|
|
1146
|
+
unlinked: z.boolean(),
|
|
1147
|
+
jobs: z.array(customerJobSummarySchema),
|
|
1148
|
+
quotes: z.array(customerQuoteSummarySchema),
|
|
1149
|
+
invoices: z.array(customerInvoiceSummarySchema),
|
|
1150
|
+
contracts: z.array(customerContractSummarySchema),
|
|
1151
|
+
payments: z.array(customerPaymentSummarySchema),
|
|
1152
|
+
summary: z.object({
|
|
1153
|
+
openJobCount: z.number(),
|
|
1154
|
+
pendingQuoteCount: z.number(),
|
|
1155
|
+
unpaidInvoiceCount: z.number(),
|
|
1156
|
+
unpaidBalance: z.number(),
|
|
1157
|
+
awaitingSignatureCount: z.number(),
|
|
1158
|
+
}),
|
|
1159
|
+
})
|
|
1160
|
+
.passthrough();
|
|
1161
|
+
|
|
1162
|
+
export type CustomerJobSummary = z.infer<typeof customerJobSummarySchema>;
|
|
1163
|
+
export type CustomerQuoteSummary = z.infer<typeof customerQuoteSummarySchema>;
|
|
1164
|
+
export type CustomerInvoiceSummary = z.infer<
|
|
1165
|
+
typeof customerInvoiceSummarySchema
|
|
1166
|
+
>;
|
|
1167
|
+
export type CustomerContractSummary = z.infer<
|
|
1168
|
+
typeof customerContractSummarySchema
|
|
1169
|
+
>;
|
|
1170
|
+
export type CustomerPaymentSummary = z.infer<
|
|
1171
|
+
typeof customerPaymentSummarySchema
|
|
1172
|
+
>;
|
|
1173
|
+
export type CustomerWorkBundle = z.infer<typeof customerWorkBundleSchema>;
|
|
1174
|
+
|
|
1175
|
+
// ── Customer-portal per-item detail responses ─────────────────────────
|
|
1176
|
+
|
|
1177
|
+
export const customerJobDetailSchema = customerJobSummarySchema.passthrough();
|
|
1178
|
+
|
|
1179
|
+
export const customerQuoteDetailSchema = z
|
|
1180
|
+
.object({
|
|
1181
|
+
id: z.string(),
|
|
1182
|
+
quoteNumber: z.string(),
|
|
1183
|
+
/** Mirrors the `quote_status` pg enum. */
|
|
1184
|
+
status: openEnum(
|
|
1185
|
+
"draft",
|
|
1186
|
+
"sent",
|
|
1187
|
+
"accepted",
|
|
1188
|
+
"rejected",
|
|
1189
|
+
"converted",
|
|
1190
|
+
"expired",
|
|
1191
|
+
),
|
|
1192
|
+
items: z.array(z.unknown()),
|
|
1193
|
+
subtotalAmount: z.number(),
|
|
1194
|
+
discountAmount: z.number(),
|
|
1195
|
+
taxRate: z.number(),
|
|
1196
|
+
taxAmount: z.number(),
|
|
1197
|
+
totalAmount: z.number(),
|
|
1198
|
+
validUntil: z.string(),
|
|
1199
|
+
publicAcceptUrl: z.string().nullable(),
|
|
1200
|
+
notes: z.string().nullable(),
|
|
1201
|
+
})
|
|
1202
|
+
.passthrough();
|
|
1203
|
+
|
|
1204
|
+
/** One entry in the customer's cross-surface payment history — invoice
|
|
1205
|
+
* payments, store-order charges, and refunds (negative amount), newest
|
|
1206
|
+
* first. `reference` is the confirmation # (Stripe PaymentIntent id for
|
|
1207
|
+
* card, provider refund id, or a manual reference). */
|
|
1208
|
+
export const customerPaymentHistoryEntrySchema = z.object({
|
|
1209
|
+
id: z.string(),
|
|
1210
|
+
kind: z.enum(["invoice_payment", "order_charge", "order_refund"]),
|
|
1211
|
+
/** Dollars. Negative for refunds. */
|
|
1212
|
+
amount: z.number(),
|
|
1213
|
+
/** Integer cents for exact math. Negative for refunds. */
|
|
1214
|
+
amountCents: z.number(),
|
|
1215
|
+
/** Mirrors the `payment_method` pg enum. */
|
|
1216
|
+
method: openEnum(
|
|
1217
|
+
"card",
|
|
1218
|
+
"bank_transfer",
|
|
1219
|
+
"cash",
|
|
1220
|
+
"check",
|
|
1221
|
+
"other",
|
|
1222
|
+
).nullable(),
|
|
1223
|
+
status: z.string(),
|
|
1224
|
+
reference: z.string().nullable(),
|
|
1225
|
+
occurredAt: z.string(),
|
|
1226
|
+
invoiceId: z.string().optional(),
|
|
1227
|
+
invoiceNumber: z.string().optional(),
|
|
1228
|
+
orderId: z.string().optional(),
|
|
1229
|
+
orderNumber: z.string().optional(),
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
export const customerPaymentHistoryResponseSchema = z.object({
|
|
1233
|
+
items: z.array(customerPaymentHistoryEntrySchema),
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
/** A single recorded payment/refund on an invoice. */
|
|
1237
|
+
export const customerInvoicePaymentSchema = z.object({
|
|
1238
|
+
amount: z.number(),
|
|
1239
|
+
/** Mirrors the `payment_method` pg enum. */
|
|
1240
|
+
method: openEnum("card", "bank_transfer", "cash", "check", "other"),
|
|
1241
|
+
date: z.string(),
|
|
1242
|
+
/** Stripe PaymentIntent id (card) or manual reference (check #, etc.). */
|
|
1243
|
+
reference: z.string().nullable(),
|
|
1244
|
+
/**
|
|
1245
|
+
* These three are what the platform writes today, but unlike the other
|
|
1246
|
+
* statuses in this file there is no enum behind them — `payments.status` is
|
|
1247
|
+
* plain `text` with a `"completed"` default, so the column accepts anything.
|
|
1248
|
+
* `openEnum` is the honest shape for that: helpful to callers, and it can't
|
|
1249
|
+
* reject a row some other writer put there.
|
|
1250
|
+
*/
|
|
1251
|
+
status: openEnum("completed", "refunded", "failed"),
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
export const customerInvoiceDetailSchema = z
|
|
1255
|
+
.object({
|
|
1256
|
+
id: z.string(),
|
|
1257
|
+
invoiceNumber: z.string(),
|
|
1258
|
+
/** Mirrors the `invoice_status` pg enum. */
|
|
1259
|
+
status: openEnum(
|
|
1260
|
+
"draft",
|
|
1261
|
+
"sent",
|
|
1262
|
+
"partial_paid",
|
|
1263
|
+
"paid",
|
|
1264
|
+
"overdue",
|
|
1265
|
+
"cancelled",
|
|
1266
|
+
),
|
|
1267
|
+
/** Mirrors the `invoice_job_status` pg enum. */
|
|
1268
|
+
jobStatus: openEnum("pending", "in_progress", "complete").nullable(),
|
|
1269
|
+
totalAmount: z.number(),
|
|
1270
|
+
paidAmount: z.number(),
|
|
1271
|
+
balanceDue: z.number(),
|
|
1272
|
+
dueDate: z.string(),
|
|
1273
|
+
issueDate: z.string(),
|
|
1274
|
+
paidAt: z.string().nullable(),
|
|
1275
|
+
paymentSchedule: z.unknown().nullable(),
|
|
1276
|
+
notes: z.string().nullable(),
|
|
1277
|
+
terms: z.string().nullable(),
|
|
1278
|
+
publicPayUrl: z.string().nullable(),
|
|
1279
|
+
// Optional so the SDK stays compatible with older BIAB deployments
|
|
1280
|
+
// that don't return these yet.
|
|
1281
|
+
/** Recorded payments/refunds, oldest first. */
|
|
1282
|
+
payments: z.array(customerInvoicePaymentSchema).optional(),
|
|
1283
|
+
/** Most-recent completed payment reference — the confirmation number. */
|
|
1284
|
+
confirmationNumber: z.string().nullable().optional(),
|
|
1285
|
+
/** Host-less path to the downloadable invoice PDF. */
|
|
1286
|
+
pdfPath: z.string().nullable().optional(),
|
|
1287
|
+
})
|
|
1288
|
+
.passthrough();
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Response of `downloadInvoicePdf` — the invoice rendered as a PDF, base64
|
|
1292
|
+
* encoded (the SDK decodes it to bytes for you).
|
|
1293
|
+
*/
|
|
1294
|
+
export const customerInvoicePdfSchema = z.object({
|
|
1295
|
+
invoiceNumber: z.string(),
|
|
1296
|
+
filename: z.string(),
|
|
1297
|
+
contentType: z.string(),
|
|
1298
|
+
base64: z.string(),
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
export const customerContractDetailSchema = z
|
|
1302
|
+
.object({
|
|
1303
|
+
id: z.string(),
|
|
1304
|
+
title: z.string(),
|
|
1305
|
+
/** Mirrors the `contract_status` pg enum. */
|
|
1306
|
+
status: openEnum("draft", "sent", "signed", "expired", "cancelled"),
|
|
1307
|
+
contractNumber: z.string().nullable(),
|
|
1308
|
+
sentAt: z.string().nullable(),
|
|
1309
|
+
signedAt: z.string().nullable(),
|
|
1310
|
+
signedDocumentUrl: z.string().nullable(),
|
|
1311
|
+
signingUrl: z.string().nullable(),
|
|
1312
|
+
pdfUrl: z.string().nullable(),
|
|
1313
|
+
})
|
|
1314
|
+
.passthrough();
|
|
1315
|
+
|
|
1316
|
+
export const customerQuoteActionResponseSchema = z.object({
|
|
1317
|
+
ok: z.literal(true),
|
|
1318
|
+
status: z.string(),
|
|
1319
|
+
dealId: z.string().nullable().optional(),
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1322
|
+
/** Optional storefront return pages for the hosted Stripe Checkout. */
|
|
1323
|
+
export const customerInvoicePayInputSchema = z.object({
|
|
1324
|
+
successUrl: z.string().url().max(2000).optional(),
|
|
1325
|
+
cancelUrl: z.string().url().max(2000).optional(),
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
/** Response of `payInvoice` — a Stripe-hosted Checkout URL to redirect to. */
|
|
1329
|
+
export const customerInvoicePayResponseSchema = z.object({
|
|
1330
|
+
invoiceId: z.string(),
|
|
1331
|
+
status: z.string(),
|
|
1332
|
+
checkoutUrl: z.string().url(),
|
|
1333
|
+
checkoutSessionId: z.string(),
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
/** Response of `signContract` — a ready, absolute hosted signing URL. */
|
|
1337
|
+
export const customerContractSignResponseSchema = z.object({
|
|
1338
|
+
contractId: z.string(),
|
|
1339
|
+
status: z.string(),
|
|
1340
|
+
signingUrl: z.string().url(),
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
export type CustomerInvoicePayInput = z.infer<
|
|
1344
|
+
typeof customerInvoicePayInputSchema
|
|
1345
|
+
>;
|
|
1346
|
+
export type CustomerInvoicePayResponse = z.infer<
|
|
1347
|
+
typeof customerInvoicePayResponseSchema
|
|
1348
|
+
>;
|
|
1349
|
+
export type CustomerContractSignResponse = z.infer<
|
|
1350
|
+
typeof customerContractSignResponseSchema
|
|
1351
|
+
>;
|
|
1352
|
+
|
|
1353
|
+
// ── Customer-portal: referrals (the customer as an affiliate) ──────────
|
|
1354
|
+
// Read-only self surface. Counters are real (denormalized on the affiliate
|
|
1355
|
+
// row); they read zero / empty until the org's referral attribution +
|
|
1356
|
+
// payout pipeline records conversions for this customer.
|
|
1357
|
+
|
|
1358
|
+
/** Roll-up of the customer's referral activity, summed across programs. */
|
|
1359
|
+
export const customerReferralStatsSchema = z.object({
|
|
1360
|
+
/** True when the customer holds at least one affiliate record. */
|
|
1361
|
+
enrolled: z.boolean(),
|
|
1362
|
+
programCount: z.number(),
|
|
1363
|
+
claimsCount: z.number(),
|
|
1364
|
+
conversionsCount: z.number(),
|
|
1365
|
+
revenueCents: z.number(),
|
|
1366
|
+
earningsCents: z.number(),
|
|
1367
|
+
nextPayoutCents: z.number(),
|
|
1368
|
+
nextPayoutAt: z.string().nullable(),
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
export const customerReferralConversionSchema = z.object({
|
|
1372
|
+
id: z.string(),
|
|
1373
|
+
programId: z.string(),
|
|
1374
|
+
amountCents: z.number(),
|
|
1375
|
+
commissionCents: z.number(),
|
|
1376
|
+
currency: z.string(),
|
|
1377
|
+
/** Mirrors the `referral_conversion_status` pg enum. */
|
|
1378
|
+
status: openEnum("pending", "confirmed", "reversed"),
|
|
1379
|
+
createdAt: z.string(),
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
export const customerReferralHistoryResponseSchema = z.object({
|
|
1383
|
+
items: z.array(customerReferralConversionSchema),
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
export const customerReferralPayoutSchema = z.object({
|
|
1387
|
+
id: z.string(),
|
|
1388
|
+
programId: z.string(),
|
|
1389
|
+
periodStart: z.string(),
|
|
1390
|
+
periodEnd: z.string(),
|
|
1391
|
+
amountCents: z.number(),
|
|
1392
|
+
feeCents: z.number(),
|
|
1393
|
+
totalCents: z.number(),
|
|
1394
|
+
currency: z.string(),
|
|
1395
|
+
/** Mirrors the `referral_payout_status` pg enum. */
|
|
1396
|
+
status: openEnum("upcoming", "pending", "paid", "failed"),
|
|
1397
|
+
paidAt: z.string().nullable(),
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
export const customerReferralPayoutsResponseSchema = z.object({
|
|
1401
|
+
items: z.array(customerReferralPayoutSchema),
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
export type CustomerReferralStats = z.infer<typeof customerReferralStatsSchema>;
|
|
1405
|
+
export type CustomerReferralConversion = z.infer<
|
|
1406
|
+
typeof customerReferralConversionSchema
|
|
1407
|
+
>;
|
|
1408
|
+
export type CustomerReferralHistoryResponse = z.infer<
|
|
1409
|
+
typeof customerReferralHistoryResponseSchema
|
|
1410
|
+
>;
|
|
1411
|
+
export type CustomerReferralPayout = z.infer<
|
|
1412
|
+
typeof customerReferralPayoutSchema
|
|
1413
|
+
>;
|
|
1414
|
+
export type CustomerReferralPayoutsResponse = z.infer<
|
|
1415
|
+
typeof customerReferralPayoutsResponseSchema
|
|
1416
|
+
>;
|
|
1417
|
+
|
|
1418
|
+
// ── Customer-portal: orders + shipping status tracker ──────────────────
|
|
1419
|
+
|
|
1420
|
+
/** One order in the customer's list, with a shipment roll-up for the chip. */
|
|
1421
|
+
export const customerOrderSummarySchema = z.object({
|
|
1422
|
+
id: z.string(),
|
|
1423
|
+
orderNumber: z.string(),
|
|
1424
|
+
/** Mirrors the `order_status` pg enum. */
|
|
1425
|
+
status: openEnum(
|
|
1426
|
+
"pending",
|
|
1427
|
+
"confirmed",
|
|
1428
|
+
"processing",
|
|
1429
|
+
"packed",
|
|
1430
|
+
"shipped",
|
|
1431
|
+
"delivered",
|
|
1432
|
+
"cancelled",
|
|
1433
|
+
"refunded",
|
|
1434
|
+
),
|
|
1435
|
+
/** Mirrors the `order_payment_status` pg enum. */
|
|
1436
|
+
paymentStatus: openEnum("unpaid", "paid", "partial", "refunded"),
|
|
1437
|
+
totalCents: z.number(),
|
|
1438
|
+
createdAt: z.string(),
|
|
1439
|
+
shipmentCount: z.number(),
|
|
1440
|
+
/** Rolled up across the order's shipments. Mirrors `order_shipment_status`. */
|
|
1441
|
+
overallStatus: openEnum(
|
|
1442
|
+
"pending",
|
|
1443
|
+
"label_created",
|
|
1444
|
+
"in_transit",
|
|
1445
|
+
"delivered",
|
|
1446
|
+
"returned",
|
|
1447
|
+
),
|
|
1448
|
+
anyInTransit: z.boolean(),
|
|
1449
|
+
allDelivered: z.boolean(),
|
|
1450
|
+
latestEstimatedDelivery: z.string().nullable(),
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
export const customerOrderListResponseSchema = z.object({
|
|
1454
|
+
items: z.array(customerOrderSummarySchema),
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
export const customerOrderItemSchema = z.object({
|
|
1458
|
+
id: z.string(),
|
|
1459
|
+
productName: z.string(),
|
|
1460
|
+
variantName: z.string().nullable(),
|
|
1461
|
+
sku: z.string().nullable(),
|
|
1462
|
+
quantity: z.number(),
|
|
1463
|
+
unitPriceCents: z.number(),
|
|
1464
|
+
totalCents: z.number(),
|
|
1465
|
+
requiresShipping: z.boolean(),
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
export const customerShipmentSchema = z.object({
|
|
1469
|
+
id: z.string(),
|
|
1470
|
+
carrier: z.string().nullable(),
|
|
1471
|
+
trackingNumber: z.string().nullable(),
|
|
1472
|
+
trackingUrl: z.string().nullable(),
|
|
1473
|
+
serviceName: z.string().nullable(),
|
|
1474
|
+
/** Mirrors the `order_shipment_status` pg enum. */
|
|
1475
|
+
status: openEnum(
|
|
1476
|
+
"pending",
|
|
1477
|
+
"label_created",
|
|
1478
|
+
"in_transit",
|
|
1479
|
+
"delivered",
|
|
1480
|
+
"returned",
|
|
1481
|
+
),
|
|
1482
|
+
estimatedDelivery: z.string().nullable(),
|
|
1483
|
+
shippedAt: z.string().nullable(),
|
|
1484
|
+
deliveredAt: z.string().nullable(),
|
|
1485
|
+
isReturn: z.boolean(),
|
|
1486
|
+
/** Append-only carrier-event timeline (one entry per status transition). */
|
|
1487
|
+
statusHistory: z.array(z.unknown()),
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
/** A return/refund request the customer opened on this order (P10b). */
|
|
1491
|
+
export const customerReturnRequestSchema = z.object({
|
|
1492
|
+
id: z.string(),
|
|
1493
|
+
/**
|
|
1494
|
+
* Mirrors `order_return_request_status`. Note the spelling: this enum uses
|
|
1495
|
+
* `cancelled` (two Ls), while `booking_status` uses `canceled` (one). They
|
|
1496
|
+
* are separate enums and each is spelled as the database spells it.
|
|
1497
|
+
*/
|
|
1498
|
+
status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
|
|
1499
|
+
reason: z.string().nullable(),
|
|
1500
|
+
items: z
|
|
1501
|
+
.array(
|
|
1502
|
+
z.object({
|
|
1503
|
+
orderItemId: z.string(),
|
|
1504
|
+
productName: z.string(),
|
|
1505
|
+
quantity: z.number(),
|
|
1506
|
+
}),
|
|
1507
|
+
)
|
|
1508
|
+
.nullable(),
|
|
1509
|
+
customerNote: z.string().nullable(),
|
|
1510
|
+
resolutionNote: z.string().nullable(),
|
|
1511
|
+
createdAt: z.string(),
|
|
1512
|
+
resolvedAt: z.string().nullable(),
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
export const customerOrderDetailSchema = z.object({
|
|
1516
|
+
id: z.string(),
|
|
1517
|
+
orderNumber: z.string(),
|
|
1518
|
+
/** Mirrors the `order_status` pg enum. */
|
|
1519
|
+
status: openEnum(
|
|
1520
|
+
"pending",
|
|
1521
|
+
"confirmed",
|
|
1522
|
+
"processing",
|
|
1523
|
+
"packed",
|
|
1524
|
+
"shipped",
|
|
1525
|
+
"delivered",
|
|
1526
|
+
"cancelled",
|
|
1527
|
+
"refunded",
|
|
1528
|
+
),
|
|
1529
|
+
/** Mirrors the `order_payment_status` pg enum. */
|
|
1530
|
+
paymentStatus: openEnum("unpaid", "paid", "partial", "refunded"),
|
|
1531
|
+
subtotalCents: z.number(),
|
|
1532
|
+
shippingCents: z.number(),
|
|
1533
|
+
taxCents: z.number(),
|
|
1534
|
+
discountCents: z.number(),
|
|
1535
|
+
totalCents: z.number(),
|
|
1536
|
+
createdAt: z.string(),
|
|
1537
|
+
items: z.array(customerOrderItemSchema),
|
|
1538
|
+
shipments: z.array(customerShipmentSchema),
|
|
1539
|
+
// Optional for back-compat with older BIAB deployments.
|
|
1540
|
+
returnRequests: z.array(customerReturnRequestSchema).optional(),
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
/** Input for `submitReturnRequest`. Omit `items` for a whole-order request. */
|
|
1544
|
+
export const customerReturnRequestInputSchema = z.object({
|
|
1545
|
+
reason: z.string().max(120).optional(),
|
|
1546
|
+
customerNote: z.string().max(2000).optional(),
|
|
1547
|
+
items: z
|
|
1548
|
+
.array(
|
|
1549
|
+
z.object({
|
|
1550
|
+
orderItemId: z.string().uuid(),
|
|
1551
|
+
quantity: z.number().int().positive(),
|
|
1552
|
+
}),
|
|
1553
|
+
)
|
|
1554
|
+
.optional(),
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
/** Response of `submitReturnRequest` — the newly opened request. */
|
|
1558
|
+
export const customerReturnRequestCreatedSchema = z.object({
|
|
1559
|
+
id: z.string().nullable(),
|
|
1560
|
+
/** Mirrors the `order_return_request_status` pg enum. */
|
|
1561
|
+
status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
|
|
1562
|
+
createdAt: z.string().nullable(),
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
/** One downloadable digital deliverable for a paid order (P10d). */
|
|
1566
|
+
export const customerDownloadSchema = z.object({
|
|
1567
|
+
productName: z.string(),
|
|
1568
|
+
fileName: z.string().nullable(),
|
|
1569
|
+
/** Short-lived presigned URL (BIAB-hosted) or the org's external link. */
|
|
1570
|
+
url: z.string(),
|
|
1571
|
+
/** Seconds until `url` expires; null for a non-expiring external link. */
|
|
1572
|
+
expiresInSeconds: z.number().nullable(),
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
/** Response of `getDownloads` — the order's entitlement-checked downloads. */
|
|
1576
|
+
export const customerDownloadsResponseSchema = z.object({
|
|
1577
|
+
items: z.array(customerDownloadSchema),
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
/** `trackShipment` — stored shipment + a best-effort live carrier status. */
|
|
1581
|
+
export const customerShipmentTrackingSchema = z.object({
|
|
1582
|
+
id: z.string(),
|
|
1583
|
+
orderId: z.string().nullable(),
|
|
1584
|
+
carrier: z.string().nullable(),
|
|
1585
|
+
trackingNumber: z.string().nullable(),
|
|
1586
|
+
trackingUrl: z.string().nullable(),
|
|
1587
|
+
serviceName: z.string().nullable(),
|
|
1588
|
+
/** Mirrors the `order_shipment_status` pg enum. */
|
|
1589
|
+
status: openEnum(
|
|
1590
|
+
"pending",
|
|
1591
|
+
"label_created",
|
|
1592
|
+
"in_transit",
|
|
1593
|
+
"delivered",
|
|
1594
|
+
"returned",
|
|
1595
|
+
),
|
|
1596
|
+
estimatedDelivery: z.string().nullable(),
|
|
1597
|
+
shippedAt: z.string().nullable(),
|
|
1598
|
+
deliveredAt: z.string().nullable(),
|
|
1599
|
+
statusHistory: z.array(z.unknown()),
|
|
1600
|
+
/** Live Shippo tracking_status when polled; null otherwise. */
|
|
1601
|
+
liveStatus: z.unknown().nullable(),
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
export type CustomerOrderSummary = z.infer<typeof customerOrderSummarySchema>;
|
|
1605
|
+
export type CustomerOrderListResponse = z.infer<
|
|
1606
|
+
typeof customerOrderListResponseSchema
|
|
1607
|
+
>;
|
|
1608
|
+
export type CustomerOrderItem = z.infer<typeof customerOrderItemSchema>;
|
|
1609
|
+
export type CustomerShipment = z.infer<typeof customerShipmentSchema>;
|
|
1610
|
+
export type CustomerOrderDetail = z.infer<typeof customerOrderDetailSchema>;
|
|
1611
|
+
export type CustomerReturnRequest = z.infer<typeof customerReturnRequestSchema>;
|
|
1612
|
+
export type CustomerReturnRequestInput = z.infer<
|
|
1613
|
+
typeof customerReturnRequestInputSchema
|
|
1614
|
+
>;
|
|
1615
|
+
export type CustomerReturnRequestCreated = z.infer<
|
|
1616
|
+
typeof customerReturnRequestCreatedSchema
|
|
1617
|
+
>;
|
|
1618
|
+
export type CustomerDownload = z.infer<typeof customerDownloadSchema>;
|
|
1619
|
+
export type CustomerDownloadsResponse = z.infer<
|
|
1620
|
+
typeof customerDownloadsResponseSchema
|
|
1621
|
+
>;
|
|
1622
|
+
export type CustomerShipmentTracking = z.infer<
|
|
1623
|
+
typeof customerShipmentTrackingSchema
|
|
1624
|
+
>;
|
|
1625
|
+
|
|
1626
|
+
// ── Storefront branding ────────────────────────────────────────────────
|
|
1627
|
+
|
|
1628
|
+
/** Org storefront branding — logo + favicon (site media) + org name/icon. */
|
|
1629
|
+
export const siteBrandingSchema = z.object({
|
|
1630
|
+
siteId: z.string(),
|
|
1631
|
+
siteName: z.string().nullable(),
|
|
1632
|
+
logoUrl: z.string().nullable(),
|
|
1633
|
+
faviconUrl: z.string().nullable(),
|
|
1634
|
+
orgName: z.string().nullable(),
|
|
1635
|
+
orgIcon: z.string().nullable(),
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1638
|
+
export type SiteBranding = z.infer<typeof siteBrandingSchema>;
|
|
1639
|
+
|
|
1640
|
+
export type CustomerJobDetail = z.infer<typeof customerJobDetailSchema>;
|
|
1641
|
+
export type CustomerQuoteDetail = z.infer<typeof customerQuoteDetailSchema>;
|
|
1642
|
+
export type CustomerInvoiceDetail = z.infer<typeof customerInvoiceDetailSchema>;
|
|
1643
|
+
export type CustomerInvoicePayment = z.infer<
|
|
1644
|
+
typeof customerInvoicePaymentSchema
|
|
1645
|
+
>;
|
|
1646
|
+
export type CustomerPaymentHistoryEntry = z.infer<
|
|
1647
|
+
typeof customerPaymentHistoryEntrySchema
|
|
1648
|
+
>;
|
|
1649
|
+
export type CustomerPaymentHistoryResponse = z.infer<
|
|
1650
|
+
typeof customerPaymentHistoryResponseSchema
|
|
1651
|
+
>;
|
|
1652
|
+
export type CustomerInvoicePdf = z.infer<typeof customerInvoicePdfSchema>;
|
|
1653
|
+
export type CustomerContractDetail = z.infer<
|
|
1654
|
+
typeof customerContractDetailSchema
|
|
1655
|
+
>;
|
|
1656
|
+
export type CustomerQuoteActionResponse = z.infer<
|
|
1657
|
+
typeof customerQuoteActionResponseSchema
|
|
1658
|
+
>;
|
|
1659
|
+
|
|
1660
|
+
// ── Customer-portal: first-party reviews ──────────────────────────────
|
|
1661
|
+
|
|
1662
|
+
export const customerReviewSummarySchema = z.object({
|
|
1663
|
+
id: z.string(),
|
|
1664
|
+
rating: z.number().int(),
|
|
1665
|
+
title: z.string().nullable(),
|
|
1666
|
+
body: z.string(),
|
|
1667
|
+
/** Mirrors the `first_party_review_status` pg enum. */
|
|
1668
|
+
status: openEnum("pending", "published", "hidden"),
|
|
1669
|
+
jobId: z.string().nullable(),
|
|
1670
|
+
submittedAt: z.string(),
|
|
1671
|
+
publishedAt: z.string().nullable(),
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
export const customerReviewListResponseSchema = z.object({
|
|
1675
|
+
items: z.array(customerReviewSummarySchema),
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
export const customerReviewSubmitInputSchema = z.object({
|
|
1679
|
+
rating: z.number().int().min(1).max(5),
|
|
1680
|
+
title: z.string().max(200).optional(),
|
|
1681
|
+
body: z.string().min(1).max(5000),
|
|
1682
|
+
jobId: z.string().uuid().optional(),
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1685
|
+
export const customerReviewSubmitResponseSchema = z
|
|
1686
|
+
.object({
|
|
1687
|
+
id: z.string().optional(),
|
|
1688
|
+
status: z.string().optional(),
|
|
1689
|
+
submittedAt: z.string().nullable().optional(),
|
|
1690
|
+
})
|
|
1691
|
+
.passthrough();
|
|
1692
|
+
|
|
1693
|
+
export type CustomerReviewSummary = z.infer<typeof customerReviewSummarySchema>;
|
|
1694
|
+
export type CustomerReviewListResponse = z.infer<
|
|
1695
|
+
typeof customerReviewListResponseSchema
|
|
1696
|
+
>;
|
|
1697
|
+
export type CustomerReviewSubmitInput = z.infer<
|
|
1698
|
+
typeof customerReviewSubmitInputSchema
|
|
1699
|
+
>;
|
|
1700
|
+
export type CustomerReviewSubmitResponse = z.infer<
|
|
1701
|
+
typeof customerReviewSubmitResponseSchema
|
|
1702
|
+
>;
|
|
1703
|
+
|
|
1704
|
+
// ── Customer-portal: per-job comments ─────────────────────────────────
|
|
1705
|
+
|
|
1706
|
+
export const customerJobCommentSchema = z.object({
|
|
1707
|
+
id: z.string(),
|
|
1708
|
+
authorKind: z.enum(["customer", "staff", "system"]),
|
|
1709
|
+
authorName: z.string().nullable(),
|
|
1710
|
+
body: z.string(),
|
|
1711
|
+
createdAt: z.string(),
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
export const customerJobCommentsListResponseSchema = z.object({
|
|
1715
|
+
items: z.array(customerJobCommentSchema),
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
export const customerJobCommentPostInputSchema = z.object({
|
|
1719
|
+
body: z.string().min(1).max(5000),
|
|
1720
|
+
});
|
|
1721
|
+
|
|
1722
|
+
export type CustomerJobComment = z.infer<typeof customerJobCommentSchema>;
|
|
1723
|
+
export type CustomerJobCommentsListResponse = z.infer<
|
|
1724
|
+
typeof customerJobCommentsListResponseSchema
|
|
1725
|
+
>;
|
|
1726
|
+
export type CustomerJobCommentPostInput = z.infer<
|
|
1727
|
+
typeof customerJobCommentPostInputSchema
|
|
1728
|
+
>;
|
|
1729
|
+
|
|
1730
|
+
// ── Customer-portal: contact-the-company (front-desk session start) ───
|
|
1731
|
+
|
|
1732
|
+
export const customerContactCompanyInputSchema = z.object({
|
|
1733
|
+
message: z.string().min(1).max(8000),
|
|
1734
|
+
subject: z.string().max(200).optional(),
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
export const customerContactCompanyResponseSchema = z.object({
|
|
1738
|
+
sessionId: z.string(),
|
|
1739
|
+
visitorToken: z.string(),
|
|
1740
|
+
status: z.string(),
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
export type CustomerContactCompanyInput = z.infer<
|
|
1744
|
+
typeof customerContactCompanyInputSchema
|
|
1745
|
+
>;
|
|
1746
|
+
export type CustomerContactCompanyResponse = z.infer<
|
|
1747
|
+
typeof customerContactCompanyResponseSchema
|
|
1748
|
+
>;
|
|
1749
|
+
|
|
1750
|
+
// ── Customer-portal: staff ETA on a job ────────────────────────────────
|
|
1751
|
+
|
|
1752
|
+
export const customerJobEtaAssignmentSchema = z.object({
|
|
1753
|
+
displayName: z.string(),
|
|
1754
|
+
role: z.string().nullable(),
|
|
1755
|
+
dispatchStatus: z.string(),
|
|
1756
|
+
lastKnownLocation: z
|
|
1757
|
+
.object({
|
|
1758
|
+
lat: z.number(),
|
|
1759
|
+
lng: z.number(),
|
|
1760
|
+
capturedAt: z.string(),
|
|
1761
|
+
})
|
|
1762
|
+
.nullable(),
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1765
|
+
export const customerJobEtaResponseSchema = z.object({
|
|
1766
|
+
assignments: z.array(customerJobEtaAssignmentSchema),
|
|
1767
|
+
eta: z
|
|
1768
|
+
.object({
|
|
1769
|
+
seconds: z.number().nullable(),
|
|
1770
|
+
distanceMeters: z.number().nullable(),
|
|
1771
|
+
snapshotAt: z.string(),
|
|
1772
|
+
source: z.literal("dispatch_offer_snapshot"),
|
|
1773
|
+
})
|
|
1774
|
+
.nullable(),
|
|
1775
|
+
liveEtaAvailable: z.boolean(),
|
|
1776
|
+
});
|
|
1777
|
+
|
|
1778
|
+
export type CustomerJobEtaAssignment = z.infer<
|
|
1779
|
+
typeof customerJobEtaAssignmentSchema
|
|
1780
|
+
>;
|
|
1781
|
+
export type CustomerJobEtaResponse = z.infer<
|
|
1782
|
+
typeof customerJobEtaResponseSchema
|
|
1783
|
+
>;
|
|
1784
|
+
|
|
1785
|
+
// ── Customer-portal: job activity feed (events + chat interleaved) ────
|
|
1786
|
+
|
|
1787
|
+
export const customerJobActivityEntrySchema = z.discriminatedUnion("kind", [
|
|
1788
|
+
z
|
|
1789
|
+
.object({
|
|
1790
|
+
kind: z.literal("event"),
|
|
1791
|
+
id: z.string(),
|
|
1792
|
+
at: z.string(),
|
|
1793
|
+
event: z.object({
|
|
1794
|
+
eventKind: z.string(),
|
|
1795
|
+
title: z.string(),
|
|
1796
|
+
body: z.string().nullable(),
|
|
1797
|
+
actorName: z.string().nullable(),
|
|
1798
|
+
metadata: z.record(z.string(), z.unknown()).nullable(),
|
|
1799
|
+
}),
|
|
1800
|
+
})
|
|
1801
|
+
.passthrough(),
|
|
1802
|
+
z
|
|
1803
|
+
.object({
|
|
1804
|
+
kind: z.literal("chat"),
|
|
1805
|
+
id: z.string(),
|
|
1806
|
+
at: z.string(),
|
|
1807
|
+
chat: z.object({
|
|
1808
|
+
role: z.string(),
|
|
1809
|
+
content: z.string(),
|
|
1810
|
+
sessionId: z.string(),
|
|
1811
|
+
}),
|
|
1812
|
+
})
|
|
1813
|
+
.passthrough(),
|
|
1814
|
+
]);
|
|
1815
|
+
|
|
1816
|
+
export const customerJobActivityResponseSchema = z.object({
|
|
1817
|
+
items: z.array(customerJobActivityEntrySchema),
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
export type CustomerJobActivityEntry = z.infer<
|
|
1821
|
+
typeof customerJobActivityEntrySchema
|
|
1822
|
+
>;
|
|
1823
|
+
export type CustomerJobActivityResponse = z.infer<
|
|
1824
|
+
typeof customerJobActivityResponseSchema
|
|
1825
|
+
>;
|
|
1826
|
+
|
|
1827
|
+
// ── Blog programmatic surface (0.9.0) ─────────────────────────────────
|
|
1828
|
+
|
|
1829
|
+
export const blogPublicPostSchema = z
|
|
1830
|
+
.object({
|
|
1831
|
+
id: z.string(),
|
|
1832
|
+
orgId: z.string().nullable(),
|
|
1833
|
+
title: z.string(),
|
|
1834
|
+
slug: z.string(),
|
|
1835
|
+
content: z.string(),
|
|
1836
|
+
excerpt: z.string().nullable(),
|
|
1837
|
+
imageUrl: z.string().nullable(),
|
|
1838
|
+
imageUrlDark: z.string().nullable(),
|
|
1839
|
+
muxPlaybackId: z.string().nullable(),
|
|
1840
|
+
muxStatus: z.string().nullable(),
|
|
1841
|
+
accessLevel: z.enum(["public", "followers", "paid"]),
|
|
1842
|
+
downloadAccessLevel: z.enum(["public", "followers", "paid"]),
|
|
1843
|
+
publishedAt: z.string().nullable(),
|
|
1844
|
+
isPinned: z.boolean(),
|
|
1845
|
+
authorName: z.string().nullable(),
|
|
1846
|
+
authorAvatarUrl: z.string().nullable(),
|
|
1847
|
+
tags: z.array(z.string()),
|
|
1848
|
+
likes: z.number(),
|
|
1849
|
+
commentsCount: z.number(),
|
|
1850
|
+
viewCount: z.number(),
|
|
1851
|
+
createdAt: z.string(),
|
|
1852
|
+
updatedAt: z.string(),
|
|
1853
|
+
ctaLabel: z.string().nullable(),
|
|
1854
|
+
ctaHref: z.string().nullable(),
|
|
1855
|
+
ctaDescription: z.string().nullable(),
|
|
1856
|
+
})
|
|
1857
|
+
.passthrough();
|
|
1858
|
+
|
|
1859
|
+
export const blogListPostsResponseSchema = z.object({
|
|
1860
|
+
items: z.array(blogPublicPostSchema),
|
|
1861
|
+
nextCursor: z.number().nullable(),
|
|
1862
|
+
});
|
|
1863
|
+
|
|
1864
|
+
export const blogPostBySlugResponseSchema = z.object({
|
|
1865
|
+
post: blogPublicPostSchema,
|
|
1866
|
+
access: z.enum(["granted", "paywall"]),
|
|
1867
|
+
});
|
|
1868
|
+
|
|
1869
|
+
export const blogCategorySchema = z.object({
|
|
1870
|
+
id: z.string(),
|
|
1871
|
+
name: z.string(),
|
|
1872
|
+
slug: z.string(),
|
|
1873
|
+
description: z.string().nullable(),
|
|
1874
|
+
});
|
|
1875
|
+
export const blogCategoryListResponseSchema = z.object({
|
|
1876
|
+
items: z.array(blogCategorySchema),
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
export const blogTagListResponseSchema = z.object({
|
|
1880
|
+
items: z.array(z.string()),
|
|
1881
|
+
});
|
|
1882
|
+
|
|
1883
|
+
export const blogPublicCommentSchema = z.object({
|
|
1884
|
+
id: z.string(),
|
|
1885
|
+
postId: z.string(),
|
|
1886
|
+
parentId: z.string().nullable(),
|
|
1887
|
+
content: z.string(),
|
|
1888
|
+
authorName: z.string().nullable(),
|
|
1889
|
+
authorAvatarUrl: z.string().nullable(),
|
|
1890
|
+
createdAt: z.string(),
|
|
1891
|
+
});
|
|
1892
|
+
export const blogCommentListResponseSchema = z.object({
|
|
1893
|
+
items: z.array(blogPublicCommentSchema),
|
|
1894
|
+
nextCursor: z.number().nullable(),
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1897
|
+
export const blogCommentPostInputSchema = z.object({
|
|
1898
|
+
content: z.string().min(1).max(5000),
|
|
1899
|
+
parentId: z.string().uuid().optional(),
|
|
1900
|
+
});
|
|
1901
|
+
export const blogCommentPostResponseSchema = z.object({
|
|
1902
|
+
comment: z
|
|
1903
|
+
.object({
|
|
1904
|
+
id: z.string(),
|
|
1905
|
+
postId: z.string(),
|
|
1906
|
+
content: z.string(),
|
|
1907
|
+
isApproved: z.boolean(),
|
|
1908
|
+
/** Mirrors the `blog_comment_screening_status` pg enum. */
|
|
1909
|
+
screeningStatus: openEnum(
|
|
1910
|
+
"ok",
|
|
1911
|
+
"flagged",
|
|
1912
|
+
"blocked",
|
|
1913
|
+
"reviewed_ok",
|
|
1914
|
+
"reviewed_blocked",
|
|
1915
|
+
),
|
|
1916
|
+
createdAt: z.string(),
|
|
1917
|
+
})
|
|
1918
|
+
.passthrough(),
|
|
1919
|
+
moderation: z.object({
|
|
1920
|
+
status: z.string(),
|
|
1921
|
+
isApproved: z.boolean(),
|
|
1922
|
+
}),
|
|
1923
|
+
});
|
|
1924
|
+
|
|
1925
|
+
/** Like state + count returned by GET/POST `blog/posts/:slug/like`. */
|
|
1926
|
+
export const blogLikeResponseSchema = z.object({
|
|
1927
|
+
liked: z.boolean(),
|
|
1928
|
+
likesCount: z.number(),
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
export type BlogPublicPost = z.infer<typeof blogPublicPostSchema>;
|
|
1932
|
+
export type BlogListPostsResponse = z.infer<typeof blogListPostsResponseSchema>;
|
|
1933
|
+
export type BlogPostBySlugResponse = z.infer<
|
|
1934
|
+
typeof blogPostBySlugResponseSchema
|
|
1935
|
+
>;
|
|
1936
|
+
export type BlogCategory = z.infer<typeof blogCategorySchema>;
|
|
1937
|
+
export type BlogCategoryListResponse = z.infer<
|
|
1938
|
+
typeof blogCategoryListResponseSchema
|
|
1939
|
+
>;
|
|
1940
|
+
export type BlogTagListResponse = z.infer<typeof blogTagListResponseSchema>;
|
|
1941
|
+
export type BlogPublicComment = z.infer<typeof blogPublicCommentSchema>;
|
|
1942
|
+
export type BlogCommentListResponse = z.infer<
|
|
1943
|
+
typeof blogCommentListResponseSchema
|
|
1944
|
+
>;
|
|
1945
|
+
export type BlogCommentPostInput = z.infer<typeof blogCommentPostInputSchema>;
|
|
1946
|
+
export type BlogCommentPostResponse = z.infer<
|
|
1947
|
+
typeof blogCommentPostResponseSchema
|
|
1948
|
+
>;
|
|
1949
|
+
export type BlogLikeResponse = z.infer<typeof blogLikeResponseSchema>;
|
|
1950
|
+
|
|
1951
|
+
// ── Storefront programmatic surface (0.9.0) ───────────────────────────
|
|
1952
|
+
|
|
1953
|
+
/**
|
|
1954
|
+
* Loose-shape storefront product row. Server-side schema has many
|
|
1955
|
+
* columns; we keep this `.passthrough()` so consumers don't break
|
|
1956
|
+
* when new columns ship and so cross-variant fields (deferred to
|
|
1957
|
+
* 0.10) flow through untyped today.
|
|
1958
|
+
*/
|
|
1959
|
+
/** Resolved category attached to a product row/card (mirrors a storefront category). */
|
|
1960
|
+
export const storefrontProductCategorySchema = z.object({
|
|
1961
|
+
id: z.string(),
|
|
1962
|
+
name: z.string(),
|
|
1963
|
+
description: z.string().nullable(),
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
export const storefrontProductRowSchema = z
|
|
1967
|
+
.object({
|
|
1968
|
+
id: z.string(),
|
|
1969
|
+
orgId: z.string(),
|
|
1970
|
+
name: z.string(),
|
|
1971
|
+
isLive: z.boolean(),
|
|
1972
|
+
categoryId: z.string().nullable().optional(),
|
|
1973
|
+
/** Resolved category (name/description), when the product is categorized. */
|
|
1974
|
+
category: storefrontProductCategorySchema.nullable().optional(),
|
|
1975
|
+
images: z.array(z.string()).nullable().optional(),
|
|
1976
|
+
createdAt: z.string(),
|
|
1977
|
+
})
|
|
1978
|
+
.passthrough();
|
|
1979
|
+
|
|
1980
|
+
export const storefrontListProductsResponseSchema = z.object({
|
|
1981
|
+
items: z.array(storefrontProductRowSchema),
|
|
1982
|
+
nextCursor: z.number().nullable(),
|
|
1983
|
+
});
|
|
1984
|
+
|
|
1985
|
+
export const storefrontProductDetailSchema = storefrontProductRowSchema.and(
|
|
1986
|
+
z.object({
|
|
1987
|
+
variants: z.array(z.record(z.string(), z.unknown())),
|
|
1988
|
+
productImages: z.array(z.record(z.string(), z.unknown())),
|
|
1989
|
+
variantImages: z.array(z.record(z.string(), z.unknown())),
|
|
1990
|
+
// Cross-variant option groups (Size/Color/…) — the combinatorial picker.
|
|
1991
|
+
crossVariantTitles: z.array(z.record(z.string(), z.unknown())),
|
|
1992
|
+
crossVariants: z.array(z.record(z.string(), z.unknown())),
|
|
1993
|
+
// Raw specifications jsonb (shape is org-defined) — rides on the product
|
|
1994
|
+
// row; surfaced here for typing.
|
|
1995
|
+
specifications: z.unknown().optional(),
|
|
1996
|
+
}),
|
|
1997
|
+
);
|
|
1998
|
+
|
|
1999
|
+
/** A single approved product review. Lenient — server columns evolve. */
|
|
2000
|
+
export const storefrontReviewSchema = z
|
|
2001
|
+
.object({
|
|
2002
|
+
id: z.string(),
|
|
2003
|
+
productId: z.string(),
|
|
2004
|
+
rating: z.number(),
|
|
2005
|
+
title: z.string().nullable().optional(),
|
|
2006
|
+
body: z.string().nullable().optional(),
|
|
2007
|
+
content: z.string().nullable().optional(),
|
|
2008
|
+
reviewerName: z.string().nullable().optional(),
|
|
2009
|
+
createdAt: z.string().nullable().optional(),
|
|
2010
|
+
})
|
|
2011
|
+
.passthrough();
|
|
2012
|
+
|
|
2013
|
+
export const storefrontProductReviewsResponseSchema = z.object({
|
|
2014
|
+
items: z.array(storefrontReviewSchema),
|
|
2015
|
+
nextCursor: z.number().nullable(),
|
|
2016
|
+
avgRating: z.number(),
|
|
2017
|
+
totalCount: z.number(),
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
/** A "you may also like" recommendation. */
|
|
2021
|
+
export const storefrontRelatedProductSchema = z
|
|
2022
|
+
.object({
|
|
2023
|
+
id: z.string(),
|
|
2024
|
+
name: z.string(),
|
|
2025
|
+
description: z.string().nullable(),
|
|
2026
|
+
categoryId: z.string().nullable(),
|
|
2027
|
+
coverImageUrl: z.string().nullable(),
|
|
2028
|
+
minPriceDollars: z.number().nullable(),
|
|
2029
|
+
reasons: z.array(z.string()).optional(),
|
|
2030
|
+
})
|
|
2031
|
+
.passthrough();
|
|
2032
|
+
|
|
2033
|
+
export const storefrontRelatedProductsResponseSchema = z.object({
|
|
2034
|
+
items: z.array(storefrontRelatedProductSchema),
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
/** A companion/exclusive cross-sell addon for the "complete your X" rail. */
|
|
2038
|
+
export const storefrontAddonSchema = z
|
|
2039
|
+
.object({
|
|
2040
|
+
id: z.string(),
|
|
2041
|
+
addonProductId: z.string(),
|
|
2042
|
+
addonName: z.string(),
|
|
2043
|
+
addonDescription: z.string().nullable(),
|
|
2044
|
+
kind: z.string(),
|
|
2045
|
+
groupLabel: z.string().nullable(),
|
|
2046
|
+
priceCents: z.number().nullable(),
|
|
2047
|
+
originalPriceCents: z.number().nullable(),
|
|
2048
|
+
imageUrl: z.string().nullable(),
|
|
2049
|
+
variants: z.array(z.record(z.string(), z.unknown())),
|
|
2050
|
+
crossVariantTitles: z.array(z.record(z.string(), z.unknown())),
|
|
2051
|
+
})
|
|
2052
|
+
.passthrough();
|
|
2053
|
+
|
|
2054
|
+
export const storefrontAddonsResponseSchema = z.object({
|
|
2055
|
+
items: z.array(storefrontAddonSchema),
|
|
2056
|
+
});
|
|
2057
|
+
|
|
2058
|
+
export type StorefrontAddon = z.infer<typeof storefrontAddonSchema>;
|
|
2059
|
+
export type StorefrontAddonsResponse = z.infer<
|
|
2060
|
+
typeof storefrontAddonsResponseSchema
|
|
2061
|
+
>;
|
|
2062
|
+
|
|
2063
|
+
export type StorefrontReview = z.infer<typeof storefrontReviewSchema>;
|
|
2064
|
+
export type StorefrontProductReviewsResponse = z.infer<
|
|
2065
|
+
typeof storefrontProductReviewsResponseSchema
|
|
2066
|
+
>;
|
|
2067
|
+
export type StorefrontRelatedProduct = z.infer<
|
|
2068
|
+
typeof storefrontRelatedProductSchema
|
|
2069
|
+
>;
|
|
2070
|
+
export type StorefrontRelatedProductsResponse = z.infer<
|
|
2071
|
+
typeof storefrontRelatedProductsResponseSchema
|
|
2072
|
+
>;
|
|
2073
|
+
|
|
2074
|
+
export type StorefrontProductRow = z.infer<typeof storefrontProductRowSchema>;
|
|
2075
|
+
export type StorefrontListProductsResponse = z.infer<
|
|
2076
|
+
typeof storefrontListProductsResponseSchema
|
|
2077
|
+
>;
|
|
2078
|
+
export type StorefrontProductDetail = z.infer<
|
|
2079
|
+
typeof storefrontProductDetailSchema
|
|
2080
|
+
>;
|
|
2081
|
+
|
|
2082
|
+
/**
|
|
2083
|
+
* Storefront sort options for the listing grid (mirrors the platform shop demo).
|
|
2084
|
+
*/
|
|
2085
|
+
export const storefrontSortValues = [
|
|
2086
|
+
"featured",
|
|
2087
|
+
"newest",
|
|
2088
|
+
"price-asc",
|
|
2089
|
+
"price-desc",
|
|
2090
|
+
"rating-desc",
|
|
2091
|
+
] as const;
|
|
2092
|
+
export type StorefrontSort = (typeof storefrontSortValues)[number];
|
|
2093
|
+
|
|
2094
|
+
/** Enriched grid card — price, ratings, badges, cover image — for the
|
|
2095
|
+
* filterable listing page (`listProductsWithMeta`). */
|
|
2096
|
+
export const storefrontProductCardSchema = z.object({
|
|
2097
|
+
id: z.string(),
|
|
2098
|
+
name: z.string(),
|
|
2099
|
+
description: z.string().nullable(),
|
|
2100
|
+
categoryId: z.string().nullable(),
|
|
2101
|
+
/** Resolved category (name/description), when the product is categorized. */
|
|
2102
|
+
category: storefrontProductCategorySchema.nullable().optional(),
|
|
2103
|
+
coverImage: z.string().nullable(),
|
|
2104
|
+
cheapestPriceCents: z.number().nullable(),
|
|
2105
|
+
comparePriceCents: z.number().nullable(),
|
|
2106
|
+
isOnSale: z.boolean(),
|
|
2107
|
+
isLowStock: z.boolean(),
|
|
2108
|
+
hasMultipleVariants: z.boolean(),
|
|
2109
|
+
avgRating: z.number(),
|
|
2110
|
+
reviewCount: z.number(),
|
|
2111
|
+
isBestSeller: z.boolean(),
|
|
2112
|
+
isNew: z.boolean(),
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
export const storefrontProductsWithMetaResponseSchema = z.object({
|
|
2116
|
+
items: z.array(storefrontProductCardSchema),
|
|
2117
|
+
categoryCounts: z.array(
|
|
2118
|
+
z.object({ categoryId: z.string(), count: z.number() }),
|
|
2119
|
+
),
|
|
2120
|
+
priceRange: z.object({ minDollars: z.number(), maxDollars: z.number() }),
|
|
2121
|
+
});
|
|
2122
|
+
|
|
2123
|
+
export const storefrontCategorySchema = z.object({
|
|
2124
|
+
id: z.string(),
|
|
2125
|
+
name: z.string(),
|
|
2126
|
+
description: z.string().nullable(),
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
export const storefrontCategoriesResponseSchema = z.object({
|
|
2130
|
+
items: z.array(storefrontCategorySchema),
|
|
2131
|
+
});
|
|
2132
|
+
|
|
2133
|
+
export type StorefrontProductCard = z.infer<typeof storefrontProductCardSchema>;
|
|
2134
|
+
export type StorefrontProductsWithMetaResponse = z.infer<
|
|
2135
|
+
typeof storefrontProductsWithMetaResponseSchema
|
|
2136
|
+
>;
|
|
2137
|
+
export type StorefrontCategory = z.infer<typeof storefrontCategorySchema>;
|
|
2138
|
+
export type StorefrontCategoriesResponse = z.infer<
|
|
2139
|
+
typeof storefrontCategoriesResponseSchema
|
|
2140
|
+
>;
|
|
2141
|
+
|
|
2142
|
+
// ── Cart programmatic surface (0.9.0) ─────────────────────────────────
|
|
2143
|
+
|
|
2144
|
+
export const cartItemSnapshotSchema = z.object({
|
|
2145
|
+
id: z.string(),
|
|
2146
|
+
productId: z.string(),
|
|
2147
|
+
variantId: z.string().nullable(),
|
|
2148
|
+
quantity: z.number().int(),
|
|
2149
|
+
options: z.record(z.string(), z.unknown()).nullable(),
|
|
2150
|
+
addedAt: z.string(),
|
|
2151
|
+
updatedAt: z.string(),
|
|
2152
|
+
productName: z.string().nullable(),
|
|
2153
|
+
productImage: z.string().nullable(),
|
|
2154
|
+
variantTitle: z.string().nullable(),
|
|
2155
|
+
unitPrice: z.number(),
|
|
2156
|
+
currency: z.string(),
|
|
2157
|
+
subtotal: z.number(),
|
|
2158
|
+
});
|
|
2159
|
+
|
|
2160
|
+
export const cartSnapshotSchema = z.object({
|
|
2161
|
+
id: z.string(),
|
|
2162
|
+
orgId: z.string(),
|
|
2163
|
+
visitorToken: z.string().nullable(),
|
|
2164
|
+
customerUserId: z.string().nullable(),
|
|
2165
|
+
couponCode: z.string().nullable(),
|
|
2166
|
+
currency: z.string(),
|
|
2167
|
+
items: z.array(cartItemSnapshotSchema),
|
|
2168
|
+
subtotal: z.number(),
|
|
2169
|
+
itemCount: z.number(),
|
|
2170
|
+
lastActivityAt: z.string(),
|
|
2171
|
+
createdAt: z.string(),
|
|
2172
|
+
updatedAt: z.string(),
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
export const cartAddItemInputSchema = z.object({
|
|
2176
|
+
productId: z.string().uuid(),
|
|
2177
|
+
variantId: z.string().uuid().optional(),
|
|
2178
|
+
quantity: z.number().int().min(1).max(999).default(1),
|
|
2179
|
+
options: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
2180
|
+
});
|
|
2181
|
+
|
|
2182
|
+
export const cartUpdateItemInputSchema = z.object({
|
|
2183
|
+
quantity: z.number().int().min(0).max(999),
|
|
2184
|
+
});
|
|
2185
|
+
|
|
2186
|
+
export const cartCouponInputSchema = z.object({
|
|
2187
|
+
code: z.string().min(1).max(80),
|
|
2188
|
+
});
|
|
2189
|
+
|
|
2190
|
+
export type CartItemSnapshot = z.infer<typeof cartItemSnapshotSchema>;
|
|
2191
|
+
export type CartSnapshot = z.infer<typeof cartSnapshotSchema>;
|
|
2192
|
+
export type CartAddItemInput = z.infer<typeof cartAddItemInputSchema>;
|
|
2193
|
+
export type CartUpdateItemInput = z.infer<typeof cartUpdateItemInputSchema>;
|
|
2194
|
+
export type CartCouponInput = z.infer<typeof cartCouponInputSchema>;
|
|
2195
|
+
|
|
2196
|
+
// ── Shipping (Batch shipping, 0.9.0) ──────────────────────────────────
|
|
2197
|
+
|
|
2198
|
+
export const shippingDeliveryAddressSchema = z.object({
|
|
2199
|
+
name: z.string().min(1).max(120),
|
|
2200
|
+
street1: z.string().min(1).max(200),
|
|
2201
|
+
street2: z.string().max(200).optional(),
|
|
2202
|
+
city: z.string().min(1).max(120),
|
|
2203
|
+
state: z.string().min(1).max(80),
|
|
2204
|
+
postalCode: z.string().min(1).max(20),
|
|
2205
|
+
country: z.string().length(2),
|
|
2206
|
+
phone: z.string().max(40).optional(),
|
|
2207
|
+
email: z.string().email().optional(),
|
|
2208
|
+
});
|
|
2209
|
+
export type ShippingDeliveryAddress = z.infer<
|
|
2210
|
+
typeof shippingDeliveryAddressSchema
|
|
2211
|
+
>;
|
|
2212
|
+
|
|
2213
|
+
export const shippingRatesInputSchema = z.object({
|
|
2214
|
+
deliveryAddress: shippingDeliveryAddressSchema,
|
|
2215
|
+
});
|
|
2216
|
+
export type ShippingRatesInput = z.infer<typeof shippingRatesInputSchema>;
|
|
2217
|
+
|
|
2218
|
+
export const shippingOptionBreakdownSchema = z.object({
|
|
2219
|
+
originId: z.string().nullable(),
|
|
2220
|
+
shippoShipmentId: z.string(),
|
|
2221
|
+
shippoRateId: z.string(),
|
|
2222
|
+
amountCents: z.number().int().nonnegative(),
|
|
2223
|
+
parcelSummary: z.string(),
|
|
2224
|
+
});
|
|
2225
|
+
|
|
2226
|
+
export const shippingOptionSchema = z.object({
|
|
2227
|
+
id: z.string(),
|
|
2228
|
+
displayName: z.string(),
|
|
2229
|
+
carrierName: z.string(),
|
|
2230
|
+
serviceToken: z.string(),
|
|
2231
|
+
totalCents: z.number().int().nonnegative(),
|
|
2232
|
+
currency: z.string().min(3),
|
|
2233
|
+
estimatedDays: z.number().int().nullable(),
|
|
2234
|
+
durationTerms: z.string().nullable(),
|
|
2235
|
+
breakdown: z.array(shippingOptionBreakdownSchema),
|
|
2236
|
+
});
|
|
2237
|
+
export type ShippingOption = z.infer<typeof shippingOptionSchema>;
|
|
2238
|
+
|
|
2239
|
+
export const shippingRatesResponseSchema = z.object({
|
|
2240
|
+
options: z.array(shippingOptionSchema),
|
|
2241
|
+
requiresShipping: z.boolean(),
|
|
2242
|
+
});
|
|
2243
|
+
export type ShippingRatesResponse = z.infer<typeof shippingRatesResponseSchema>;
|
|
2244
|
+
|
|
2245
|
+
export const shippingTrackResponseSchema = z.object({
|
|
2246
|
+
shipmentId: z.string(),
|
|
2247
|
+
orderId: z.string(),
|
|
2248
|
+
carrier: z.string().nullable(),
|
|
2249
|
+
trackingNumber: z.string().nullable(),
|
|
2250
|
+
trackingUrl: z.string().nullable(),
|
|
2251
|
+
labelUrl: z.string().nullable(),
|
|
2252
|
+
/** Mirrors the `order_shipment_status` pg enum. */
|
|
2253
|
+
status: openEnum(
|
|
2254
|
+
"pending",
|
|
2255
|
+
"label_created",
|
|
2256
|
+
"in_transit",
|
|
2257
|
+
"delivered",
|
|
2258
|
+
"returned",
|
|
2259
|
+
),
|
|
2260
|
+
estimatedDelivery: z.string().nullable(),
|
|
2261
|
+
shippedAt: z.string().nullable(),
|
|
2262
|
+
deliveredAt: z.string().nullable(),
|
|
2263
|
+
serviceName: z.string().nullable(),
|
|
2264
|
+
rateAmountCents: z.number().int().nullable(),
|
|
2265
|
+
currency: z.string().nullable(),
|
|
2266
|
+
statusHistory: z.unknown(),
|
|
2267
|
+
liveStatus: z
|
|
2268
|
+
.object({
|
|
2269
|
+
status: z.string(),
|
|
2270
|
+
status_details: z.string().optional(),
|
|
2271
|
+
status_date: z.string().optional(),
|
|
2272
|
+
location: z
|
|
2273
|
+
.object({
|
|
2274
|
+
city: z.string().optional(),
|
|
2275
|
+
state: z.string().optional(),
|
|
2276
|
+
zip: z.string().optional(),
|
|
2277
|
+
country: z.string().optional(),
|
|
2278
|
+
})
|
|
2279
|
+
.nullable()
|
|
2280
|
+
.optional(),
|
|
2281
|
+
})
|
|
2282
|
+
.nullable(),
|
|
2283
|
+
});
|
|
2284
|
+
export type ShippingTrackResponse = z.infer<typeof shippingTrackResponseSchema>;
|
|
2285
|
+
|
|
2286
|
+
/**
|
|
2287
|
+
* The org's storefront-facing shipping config. Sanitized: no API keys/secrets,
|
|
2288
|
+
* and only a coarse city/state/country "ships from" (never the precise origin
|
|
2289
|
+
* street).
|
|
2290
|
+
*/
|
|
2291
|
+
export const shippingSettingsResponseSchema = z.object({
|
|
2292
|
+
enabled: z.boolean(),
|
|
2293
|
+
shipsFrom: z
|
|
2294
|
+
.object({
|
|
2295
|
+
city: z.string().nullable(),
|
|
2296
|
+
state: z.string().nullable(),
|
|
2297
|
+
country: z.string().nullable(),
|
|
2298
|
+
})
|
|
2299
|
+
.nullable(),
|
|
2300
|
+
/** ISO-3166-1 alpha-2 list. null = ships anywhere; [] = ships nowhere. */
|
|
2301
|
+
allowedDestinationCountries: z.array(z.string()).nullable(),
|
|
2302
|
+
/** DDU = customer pays duties, DDP = seller pays, FCA = free carrier. */
|
|
2303
|
+
defaultIncoterm: openEnum("DDU", "DDP", "FCA"),
|
|
2304
|
+
preferredCarriers: z.array(z.string()).nullable(),
|
|
2305
|
+
});
|
|
2306
|
+
export type ShippingSettingsResponse = z.infer<
|
|
2307
|
+
typeof shippingSettingsResponseSchema
|
|
2308
|
+
>;
|
|
2309
|
+
|
|
2310
|
+
// ── Customer notification preferences (Batch shipping, 0.9.0) ─────────
|
|
2311
|
+
// Distinct from the BIAB-user notification prefs in this file —
|
|
2312
|
+
// these are per-customer-per-org preferences exposed via the SDK so
|
|
2313
|
+
// the *consumer's* site can render a preferences UI for its signed-
|
|
2314
|
+
// in shoppers (in-app / email / SMS / outbound webhook).
|
|
2315
|
+
|
|
2316
|
+
export const customerNotificationEventMapSchema = z.record(
|
|
2317
|
+
z.string(),
|
|
2318
|
+
z.boolean(),
|
|
2319
|
+
);
|
|
2320
|
+
|
|
2321
|
+
export const customerNotificationPreferencesResponseSchema = z.object({
|
|
2322
|
+
exists: z.boolean(),
|
|
2323
|
+
inAppEvents: customerNotificationEventMapSchema,
|
|
2324
|
+
emailEvents: customerNotificationEventMapSchema,
|
|
2325
|
+
smsEvents: customerNotificationEventMapSchema,
|
|
2326
|
+
webhookEvents: customerNotificationEventMapSchema,
|
|
2327
|
+
emailAddress: z.string().nullable(),
|
|
2328
|
+
phoneNumber: z.string().nullable(),
|
|
2329
|
+
webhookUrl: z.string().nullable(),
|
|
2330
|
+
webhookHasSigningSecret: z.boolean(),
|
|
2331
|
+
emailVerified: z.boolean(),
|
|
2332
|
+
phoneVerified: z.boolean(),
|
|
2333
|
+
});
|
|
2334
|
+
export type CustomerNotificationPreferencesResponse = z.infer<
|
|
2335
|
+
typeof customerNotificationPreferencesResponseSchema
|
|
2336
|
+
>;
|
|
2337
|
+
|
|
2338
|
+
export const customerNotificationPreferencesUpdateInputSchema = z.object({
|
|
2339
|
+
inAppEvents: customerNotificationEventMapSchema.optional(),
|
|
2340
|
+
emailEvents: customerNotificationEventMapSchema.optional(),
|
|
2341
|
+
smsEvents: customerNotificationEventMapSchema.optional(),
|
|
2342
|
+
webhookEvents: customerNotificationEventMapSchema.optional(),
|
|
2343
|
+
emailAddress: z.string().email().nullable().optional(),
|
|
2344
|
+
phoneNumber: z.string().min(7).max(40).nullable().optional(),
|
|
2345
|
+
webhookUrl: z.string().url().nullable().optional(),
|
|
2346
|
+
webhookSigningSecret: z.string().min(16).max(128).nullable().optional(),
|
|
2347
|
+
});
|
|
2348
|
+
export type CustomerNotificationPreferencesUpdateInput = z.infer<
|
|
2349
|
+
typeof customerNotificationPreferencesUpdateInputSchema
|
|
2350
|
+
>;
|
|
2351
|
+
|
|
2352
|
+
export const customerNotificationPreferencesUpdateResponseSchema = z.object({
|
|
2353
|
+
ok: z.literal(true),
|
|
2354
|
+
});
|
|
2355
|
+
export type CustomerNotificationPreferencesUpdateResponse = z.infer<
|
|
2356
|
+
typeof customerNotificationPreferencesUpdateResponseSchema
|
|
2357
|
+
>;
|
|
2358
|
+
|
|
2359
|
+
// ── Customer destination verification (Batch shipping, 0.9.0) ─────────
|
|
2360
|
+
|
|
2361
|
+
export const startVerificationInputSchema = z.object({
|
|
2362
|
+
kind: z.enum(["email", "phone"]),
|
|
2363
|
+
destination: z.string().min(3).max(200),
|
|
2364
|
+
});
|
|
2365
|
+
export type StartVerificationInput = z.infer<
|
|
2366
|
+
typeof startVerificationInputSchema
|
|
2367
|
+
>;
|
|
2368
|
+
|
|
2369
|
+
export const startVerificationResponseSchema = z.object({
|
|
2370
|
+
ok: z.literal(true),
|
|
2371
|
+
message: z.string(),
|
|
2372
|
+
flow: z.enum(["link", "otp"]),
|
|
2373
|
+
});
|
|
2374
|
+
export type StartVerificationResponse = z.infer<
|
|
2375
|
+
typeof startVerificationResponseSchema
|
|
2376
|
+
>;
|
|
2377
|
+
|
|
2378
|
+
export const confirmVerificationInputSchema = z.object({
|
|
2379
|
+
token: z.string().min(4).max(128),
|
|
2380
|
+
});
|
|
2381
|
+
export type ConfirmVerificationInput = z.infer<
|
|
2382
|
+
typeof confirmVerificationInputSchema
|
|
2383
|
+
>;
|
|
2384
|
+
|
|
2385
|
+
export const confirmVerificationResponseSchema = z.object({
|
|
2386
|
+
ok: z.literal(true),
|
|
2387
|
+
verifiedKind: z.enum(["email", "phone"]),
|
|
2388
|
+
destination: z.string(),
|
|
2389
|
+
});
|
|
2390
|
+
export type ConfirmVerificationResponse = z.infer<
|
|
2391
|
+
typeof confirmVerificationResponseSchema
|
|
2392
|
+
>;
|
|
2393
|
+
|
|
2394
|
+
// ── Address search + verify (0.9.0) ───────────────────────────────────
|
|
2395
|
+
// Field shape mirrors Resources/addressParser/src/types/address.ts so
|
|
2396
|
+
// the working reference React components copy in cleanly.
|
|
2397
|
+
|
|
2398
|
+
export const addressSearchInputSchema = z.object({
|
|
2399
|
+
query: z.string().min(1).max(200),
|
|
2400
|
+
});
|
|
2401
|
+
export type AddressSearchInput = z.infer<typeof addressSearchInputSchema>;
|
|
2402
|
+
|
|
2403
|
+
export const addressPlacePredictionSchema = z.object({
|
|
2404
|
+
place_id: z.string(),
|
|
2405
|
+
structured_formatting: z.object({
|
|
2406
|
+
main_text: z.string(),
|
|
2407
|
+
secondary_text: z.string(),
|
|
2408
|
+
}),
|
|
2409
|
+
});
|
|
2410
|
+
export type AddressPlacePrediction = z.infer<
|
|
2411
|
+
typeof addressPlacePredictionSchema
|
|
2412
|
+
>;
|
|
2413
|
+
|
|
2414
|
+
export const addressSearchResponseSchema = z.union([
|
|
2415
|
+
z.object({
|
|
2416
|
+
enabled: z.literal(true),
|
|
2417
|
+
predictions: z.array(addressPlacePredictionSchema),
|
|
2418
|
+
}),
|
|
2419
|
+
z.object({
|
|
2420
|
+
enabled: z.literal(false),
|
|
2421
|
+
reason: z.string(),
|
|
2422
|
+
predictions: z.array(addressPlacePredictionSchema),
|
|
2423
|
+
}),
|
|
2424
|
+
]);
|
|
2425
|
+
export type AddressSearchResponse = z.infer<typeof addressSearchResponseSchema>;
|
|
2426
|
+
|
|
2427
|
+
/**
|
|
2428
|
+
* Structured address as Google returns it. Pipe through
|
|
2429
|
+
* `googleAddressToFormValues` (exported from the SDK helpers) to
|
|
2430
|
+
* fill a form whose fields match the reference `AddressFormValues`
|
|
2431
|
+
* shape.
|
|
2432
|
+
*/
|
|
2433
|
+
export const googleAddressSchema = z.object({
|
|
2434
|
+
street_number: z.string(),
|
|
2435
|
+
route: z.string(),
|
|
2436
|
+
subpremise: z.string().optional(),
|
|
2437
|
+
locality: z.string(),
|
|
2438
|
+
administrative_area_level_1: z.string(),
|
|
2439
|
+
postal_code: z.string(),
|
|
2440
|
+
country: z.string(),
|
|
2441
|
+
});
|
|
2442
|
+
export type GoogleAddress = z.infer<typeof googleAddressSchema>;
|
|
2443
|
+
|
|
2444
|
+
export const addressDetailsResponseSchema = z.union([
|
|
2445
|
+
z.object({ enabled: z.literal(true), address: googleAddressSchema }),
|
|
2446
|
+
z.object({
|
|
2447
|
+
enabled: z.literal(false),
|
|
2448
|
+
reason: z.string(),
|
|
2449
|
+
address: z.null(),
|
|
2450
|
+
}),
|
|
2451
|
+
]);
|
|
2452
|
+
export type AddressDetailsResponse = z.infer<
|
|
2453
|
+
typeof addressDetailsResponseSchema
|
|
2454
|
+
>;
|
|
2455
|
+
|
|
2456
|
+
export const addressFormValuesSchema = z.object({
|
|
2457
|
+
address_ln1: z.string().min(1).max(200),
|
|
2458
|
+
address_ln2: z.string().max(200).optional(),
|
|
2459
|
+
city: z.string().min(1).max(120),
|
|
2460
|
+
state: z.string().min(1).max(80),
|
|
2461
|
+
zip: z.string().min(1).max(20),
|
|
2462
|
+
country: z.string().min(2).max(80),
|
|
2463
|
+
});
|
|
2464
|
+
export type AddressFormValues = z.infer<typeof addressFormValuesSchema>;
|
|
2465
|
+
|
|
2466
|
+
export const addressVerifyResponseSchema = z.object({
|
|
2467
|
+
isVerified: z.boolean(),
|
|
2468
|
+
googleAddress: z.string().nullable(),
|
|
2469
|
+
});
|
|
2470
|
+
export type AddressVerifyResponse = z.infer<typeof addressVerifyResponseSchema>;
|
|
2471
|
+
|
|
2472
|
+
// ── Subscriptions (consumer-facing) ───────────────────────────────────
|
|
2473
|
+
|
|
2474
|
+
export const subscriptionOfferingSchema = z.object({
|
|
2475
|
+
id: z.string().uuid(),
|
|
2476
|
+
name: z.string(),
|
|
2477
|
+
description: z.string().nullable(),
|
|
2478
|
+
imageUrl: z.string().nullable(),
|
|
2479
|
+
amountCents: z.number().int(),
|
|
2480
|
+
interval: z.enum(["day", "week", "month", "year"]),
|
|
2481
|
+
createdAt: z.string(),
|
|
2482
|
+
updatedAt: z.string(),
|
|
2483
|
+
});
|
|
2484
|
+
export type SubscriptionOffering = z.infer<typeof subscriptionOfferingSchema>;
|
|
2485
|
+
|
|
2486
|
+
export const subscriptionOfferingsListResponseSchema = z.object({
|
|
2487
|
+
items: z.array(subscriptionOfferingSchema),
|
|
2488
|
+
});
|
|
2489
|
+
export type SubscriptionOfferingsListResponse = z.infer<
|
|
2490
|
+
typeof subscriptionOfferingsListResponseSchema
|
|
2491
|
+
>;
|
|
2492
|
+
|
|
2493
|
+
export const subscriptionCheckoutInputSchema = z
|
|
2494
|
+
.object({
|
|
2495
|
+
customerEmail: z.string().email().optional(),
|
|
2496
|
+
successUrl: z.string().url().optional(),
|
|
2497
|
+
cancelUrl: z.string().url().optional(),
|
|
2498
|
+
})
|
|
2499
|
+
.optional();
|
|
2500
|
+
export type SubscriptionCheckoutInput = z.infer<
|
|
2501
|
+
typeof subscriptionCheckoutInputSchema
|
|
2502
|
+
>;
|
|
2503
|
+
|
|
2504
|
+
export const subscriptionCheckoutResponseSchema = z.object({
|
|
2505
|
+
sessionId: z.string(),
|
|
2506
|
+
stripeUrl: z.string().url(),
|
|
2507
|
+
amountCents: z.number().int(),
|
|
2508
|
+
currency: z.string(),
|
|
2509
|
+
/** Mirrors the `subscription_interval` pg enum. */
|
|
2510
|
+
interval: openEnum("day", "week", "month", "year"),
|
|
2511
|
+
});
|
|
2512
|
+
export type SubscriptionCheckoutResponse = z.infer<
|
|
2513
|
+
typeof subscriptionCheckoutResponseSchema
|
|
2514
|
+
>;
|
|
2515
|
+
|
|
2516
|
+
// ── Coupons (Batch LL, 0.9.0) ─────────────────────────────────────────
|
|
2517
|
+
|
|
2518
|
+
export const couponValidateInputSchema = z.object({
|
|
2519
|
+
code: z.string().min(1).max(80),
|
|
2520
|
+
});
|
|
2521
|
+
export type CouponValidateInput = z.infer<typeof couponValidateInputSchema>;
|
|
2522
|
+
|
|
2523
|
+
export const couponValidateValidSchema = z.object({
|
|
2524
|
+
valid: z.literal(true),
|
|
2525
|
+
code: z.string(),
|
|
2526
|
+
discountType: z.enum(["percent", "amount"]),
|
|
2527
|
+
percentOff: z.number().nullable(),
|
|
2528
|
+
amountOffCents: z.number().int().nullable(),
|
|
2529
|
+
currency: z.string().nullable(),
|
|
2530
|
+
duration: z.enum(["once", "forever", "repeating"]),
|
|
2531
|
+
durationInMonths: z.number().int().nullable(),
|
|
2532
|
+
expiresAt: z.string().nullable(),
|
|
2533
|
+
});
|
|
2534
|
+
export type CouponValidateValid = z.infer<typeof couponValidateValidSchema>;
|
|
2535
|
+
|
|
2536
|
+
export const couponValidateInvalidSchema = z.object({
|
|
2537
|
+
valid: z.literal(false),
|
|
2538
|
+
reason: z.enum([
|
|
2539
|
+
"unknown_code",
|
|
2540
|
+
"expired",
|
|
2541
|
+
"inactive",
|
|
2542
|
+
"no_stripe_account",
|
|
2543
|
+
"lookup_failed",
|
|
2544
|
+
]),
|
|
2545
|
+
message: z.string(),
|
|
2546
|
+
});
|
|
2547
|
+
export type CouponValidateInvalid = z.infer<typeof couponValidateInvalidSchema>;
|
|
2548
|
+
|
|
2549
|
+
export const couponValidateResponseSchema = z.union([
|
|
2550
|
+
couponValidateValidSchema,
|
|
2551
|
+
couponValidateInvalidSchema,
|
|
2552
|
+
]);
|
|
2553
|
+
export type CouponValidateResponse = z.infer<
|
|
2554
|
+
typeof couponValidateResponseSchema
|
|
2555
|
+
>;
|
|
2556
|
+
|
|
2557
|
+
// ── Tenant auth (per-org hosted sign-in / sign-up / sign-out) ─────────
|
|
2558
|
+
|
|
2559
|
+
export const tenantAuthIntentSchema = z.enum(["sign-in", "sign-up"]);
|
|
2560
|
+
|
|
2561
|
+
export const tenantAuthStartInputSchema = z.object({
|
|
2562
|
+
intent: tenantAuthIntentSchema,
|
|
2563
|
+
/** Where the tenant's site should send the user after auth completes. */
|
|
2564
|
+
returnTo: z.string().url().optional(),
|
|
2565
|
+
/** Pre-fills the email field on the platform-hosted page. */
|
|
2566
|
+
loginHint: z.string().email().optional(),
|
|
2567
|
+
/**
|
|
2568
|
+
* The tenant's installed callback URL — must match what was registered with
|
|
2569
|
+
* the platform. Required because the tenant lives on its own domain.
|
|
2570
|
+
*/
|
|
2571
|
+
redirectUri: z.string().url(),
|
|
2572
|
+
});
|
|
2573
|
+
|
|
2574
|
+
export const tenantAuthStartResponseSchema = z.object({
|
|
2575
|
+
url: z.string().url(),
|
|
2576
|
+
});
|
|
2577
|
+
|
|
2578
|
+
export const tenantAuthExchangeInputSchema = z.object({
|
|
2579
|
+
code: z.string().min(1),
|
|
2580
|
+
/** Sealed OAuth `state` returned on the callback URL — contains the PKCE verifier for the hosted auth flow. */
|
|
2581
|
+
state: z.string().min(1),
|
|
2582
|
+
/** The same redirectUri used when starting; the platform validates this matches. */
|
|
2583
|
+
redirectUri: z.string().url(),
|
|
2584
|
+
});
|
|
2585
|
+
|
|
2586
|
+
export const tenantAuthUserSchema = z.object({
|
|
2587
|
+
id: z.string(),
|
|
2588
|
+
email: z.string().email().nullable(),
|
|
2589
|
+
firstName: z.string().nullable(),
|
|
2590
|
+
lastName: z.string().nullable(),
|
|
2591
|
+
});
|
|
2592
|
+
|
|
2593
|
+
export const tenantAuthSessionSchema = z.object({
|
|
2594
|
+
user: tenantAuthUserSchema,
|
|
2595
|
+
organizationId: z.string(),
|
|
2596
|
+
role: z.string().nullable(),
|
|
2597
|
+
/** Opaque token the tenant cookie should store; pass back as Bearer for `/me`. */
|
|
2598
|
+
sessionToken: z.string(),
|
|
2599
|
+
expiresAt: timestampSchema,
|
|
2600
|
+
});
|
|
2601
|
+
|
|
2602
|
+
export const tenantAuthExchangeResponseSchema = tenantAuthSessionSchema;
|
|
2603
|
+
|
|
2604
|
+
export const tenantAuthMeResponseSchema = z
|
|
2605
|
+
.object({
|
|
2606
|
+
user: tenantAuthUserSchema,
|
|
2607
|
+
organizationId: z.string(),
|
|
2608
|
+
role: z.string().nullable(),
|
|
2609
|
+
})
|
|
2610
|
+
.nullable();
|
|
2611
|
+
|
|
2612
|
+
export const tenantAuthSignOutResponseSchema = z.object({
|
|
2613
|
+
ok: z.literal(true),
|
|
2614
|
+
});
|
|
2615
|
+
|
|
2616
|
+
/**
|
|
2617
|
+
* Programmatic password-reset request. Always succeeds from the caller's
|
|
2618
|
+
* perspective — the server returns `ok: true` regardless of whether an
|
|
2619
|
+
* account matched, to avoid leaking which emails are registered. The
|
|
2620
|
+
* actual reset link is mailed out-of-band when a match exists.
|
|
2621
|
+
*/
|
|
2622
|
+
export const tenantAuthPasswordResetInputSchema = z.object({
|
|
2623
|
+
email: z.string().email().max(320),
|
|
2624
|
+
/**
|
|
2625
|
+
* Optional override for the redirect after the user sets a new password.
|
|
2626
|
+
* Falls back to the org's default sign-in landing page server-side.
|
|
2627
|
+
*/
|
|
2628
|
+
returnTo: z.string().url().max(2048).optional(),
|
|
2629
|
+
});
|
|
2630
|
+
|
|
2631
|
+
export const tenantAuthPasswordResetResponseSchema = z.object({
|
|
2632
|
+
ok: z.literal(true),
|
|
2633
|
+
});
|
|
2634
|
+
|
|
2635
|
+
export type TenantAuthIntent = z.infer<typeof tenantAuthIntentSchema>;
|
|
2636
|
+
export type TenantAuthStartInput = z.infer<typeof tenantAuthStartInputSchema>;
|
|
2637
|
+
export type TenantAuthStartResponse = z.infer<
|
|
2638
|
+
typeof tenantAuthStartResponseSchema
|
|
2639
|
+
>;
|
|
2640
|
+
export type TenantAuthExchangeInput = z.infer<
|
|
2641
|
+
typeof tenantAuthExchangeInputSchema
|
|
2642
|
+
>;
|
|
2643
|
+
export type TenantAuthExchangeResponse = z.infer<
|
|
2644
|
+
typeof tenantAuthExchangeResponseSchema
|
|
2645
|
+
>;
|
|
2646
|
+
export type TenantAuthSession = z.infer<typeof tenantAuthSessionSchema>;
|
|
2647
|
+
export type TenantAuthUser = z.infer<typeof tenantAuthUserSchema>;
|
|
2648
|
+
export type TenantAuthMeResponse = z.infer<typeof tenantAuthMeResponseSchema>;
|
|
2649
|
+
export type TenantAuthSignOutResponse = z.infer<
|
|
2650
|
+
typeof tenantAuthSignOutResponseSchema
|
|
2651
|
+
>;
|
|
2652
|
+
export type TenantAuthPasswordResetInput = z.infer<
|
|
2653
|
+
typeof tenantAuthPasswordResetInputSchema
|
|
2654
|
+
>;
|
|
2655
|
+
export type TenantAuthPasswordResetResponse = z.infer<
|
|
2656
|
+
typeof tenantAuthPasswordResetResponseSchema
|
|
2657
|
+
>;
|
|
2658
|
+
|
|
2659
|
+
// ── Schema-driven marketing flow (Phase 2 — V7 dashboard) ────────────────
|
|
2660
|
+
//
|
|
2661
|
+
// The bundle endpoint is the live-site read path. Each section is tagged
|
|
2662
|
+
// `{ ok: true, data, source } | { ok: false, error }` so one malformed
|
|
2663
|
+
// row degrades to empty in the consumer's renderer instead of crashing
|
|
2664
|
+
// the page. `source` records whether the value came from the requested
|
|
2665
|
+
// locale, fell back to EN, or was deep-merged from both.
|
|
2666
|
+
|
|
2667
|
+
export const siteMarketingSectionEntrySchema = z.discriminatedUnion("ok", [
|
|
2668
|
+
z.object({
|
|
2669
|
+
ok: z.literal(true),
|
|
2670
|
+
sectionKey: z.string(),
|
|
2671
|
+
data: z.unknown(),
|
|
2672
|
+
source: z.enum(["locale", "en-fallback", "merged"]),
|
|
2673
|
+
updatedAt: timestampSchema,
|
|
2674
|
+
}),
|
|
2675
|
+
z.object({
|
|
2676
|
+
ok: z.literal(false),
|
|
2677
|
+
sectionKey: z.string(),
|
|
2678
|
+
error: z.string(),
|
|
2679
|
+
}),
|
|
2680
|
+
]);
|
|
2681
|
+
|
|
2682
|
+
export const siteMarketingSeoDataSchema = z.object({
|
|
2683
|
+
seoTitle: z.string().nullable(),
|
|
2684
|
+
seoDescription: z.string().nullable(),
|
|
2685
|
+
canonicalUrl: z.string().nullable(),
|
|
2686
|
+
noIndex: z.boolean(),
|
|
2687
|
+
ogTitle: z.string().nullable(),
|
|
2688
|
+
ogDescription: z.string().nullable(),
|
|
2689
|
+
ogImageUrl: z.string().nullable(),
|
|
2690
|
+
twitterCard: z.enum(["summary", "summary_large_image", "app", "player"]),
|
|
2691
|
+
keywords: z.array(z.string()),
|
|
2692
|
+
jsonldNodes: z.array(z.unknown()),
|
|
2693
|
+
hreflang: z.record(z.string()),
|
|
2694
|
+
});
|
|
2695
|
+
|
|
2696
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
2697
|
+
// Bundle: brand / company / gallery / reviews surfaces
|
|
2698
|
+
//
|
|
2699
|
+
// Added in SDK 0.8.4. Prior versions zod-stripped these keys at the
|
|
2700
|
+
// response boundary even though the BIAB server already shipped them
|
|
2701
|
+
// (the underlying `.passthrough()` in this file is on a chatbot
|
|
2702
|
+
// schema, NOT on the bundle response). Consumers that need the
|
|
2703
|
+
// stripped data on older SDK versions can raw-fetch
|
|
2704
|
+
// /sites/{id}/marketing/bundle directly — same auth, same caching —
|
|
2705
|
+
// until they bump.
|
|
2706
|
+
//
|
|
2707
|
+
// The new keys are all optional + null-able to match the
|
|
2708
|
+
// server's opt-in posture: company.profile is null when the org
|
|
2709
|
+
// hasn't filled out /settings/company-profile; gallery is null when
|
|
2710
|
+
// no public-flagged media exists; reviews is null when the org
|
|
2711
|
+
// hasn't set their Google Place ID. Consumers branch on null and
|
|
2712
|
+
// either render an empty section or hide it.
|
|
2713
|
+
|
|
2714
|
+
const brandTokensSchema = z.object({
|
|
2715
|
+
logoUrl: z.string().nullable(),
|
|
2716
|
+
faviconUrl: z.string().nullable(),
|
|
2717
|
+
});
|
|
2718
|
+
|
|
2719
|
+
export const companyAddressSchema = z
|
|
2720
|
+
.object({
|
|
2721
|
+
addressLine1: z.string().nullable().optional(),
|
|
2722
|
+
addressLine2: z.string().nullable().optional(),
|
|
2723
|
+
city: z.string().nullable().optional(),
|
|
2724
|
+
state: z.string().nullable().optional(),
|
|
2725
|
+
zip: z.string().nullable().optional(),
|
|
2726
|
+
country: z.string().nullable().optional(),
|
|
2727
|
+
})
|
|
2728
|
+
.nullable();
|
|
2729
|
+
|
|
2730
|
+
export const companyLocationSchema = z.object({
|
|
2731
|
+
id: z.string(),
|
|
2732
|
+
name: z.string(),
|
|
2733
|
+
slug: z.string(),
|
|
2734
|
+
isPrimary: z.boolean(),
|
|
2735
|
+
isPublic: z.boolean(),
|
|
2736
|
+
type: z
|
|
2737
|
+
.enum([
|
|
2738
|
+
"office",
|
|
2739
|
+
"store",
|
|
2740
|
+
"showroom",
|
|
2741
|
+
"warehouse",
|
|
2742
|
+
"service_center",
|
|
2743
|
+
"other",
|
|
2744
|
+
])
|
|
2745
|
+
.optional(),
|
|
2746
|
+
businessType: z.string().nullable().optional(),
|
|
2747
|
+
address: companyAddressSchema.unwrap(),
|
|
2748
|
+
phone: z.string().nullable().optional(),
|
|
2749
|
+
webPhone: z.string().nullable().optional(),
|
|
2750
|
+
supportEmail: z.string().nullable().optional(),
|
|
2751
|
+
website: z.string().nullable().optional(),
|
|
2752
|
+
googlePlaceId: z.string().nullable().optional(),
|
|
2753
|
+
latitude: z.number().nullable().optional(),
|
|
2754
|
+
longitude: z.number().nullable().optional(),
|
|
2755
|
+
timezone: z.string().nullable().optional(),
|
|
2756
|
+
operationHours: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
2757
|
+
serviceAreas: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
2758
|
+
});
|
|
2759
|
+
|
|
2760
|
+
/**
|
|
2761
|
+
* Loose-typed CompanyProfileRow. The server-side shape is fully
|
|
2762
|
+
* typed; we keep this looser at the SDK boundary so adding a column
|
|
2763
|
+
* on the BIAB side doesn't break older SDK consumers.
|
|
2764
|
+
*/
|
|
2765
|
+
const companyProfileRowSchema = z
|
|
2766
|
+
.object({
|
|
2767
|
+
id: z.string(),
|
|
2768
|
+
orgId: z.string(),
|
|
2769
|
+
website: z.string().nullable(),
|
|
2770
|
+
phone: z.string().nullable(),
|
|
2771
|
+
webPhone: z.string().nullable(),
|
|
2772
|
+
supportEmail: z.string().nullable(),
|
|
2773
|
+
aboutShort: z.string().nullable(),
|
|
2774
|
+
aboutLongHtml: z.string().nullable(),
|
|
2775
|
+
shareImageUrl: z.string().nullable(),
|
|
2776
|
+
googlePlaceId: z.string().nullable().optional(),
|
|
2777
|
+
address: companyAddressSchema,
|
|
2778
|
+
locations: z.array(companyLocationSchema).nullable().optional(),
|
|
2779
|
+
operationHours: z.record(z.string(), z.unknown()).nullable(),
|
|
2780
|
+
serviceAreas: z.record(z.string(), z.unknown()).nullable(),
|
|
2781
|
+
paymentOptions: z.record(z.string(), z.unknown()).nullable(),
|
|
2782
|
+
warranties: z.record(z.string(), z.unknown()).nullable(),
|
|
2783
|
+
socials: z.record(z.string(), z.unknown()).nullable(),
|
|
2784
|
+
})
|
|
2785
|
+
.passthrough()
|
|
2786
|
+
.nullable();
|
|
2787
|
+
|
|
2788
|
+
const bundleCompanySchema = z.object({
|
|
2789
|
+
profile: companyProfileRowSchema,
|
|
2790
|
+
jsonLd: z.record(z.string(), z.unknown()),
|
|
2791
|
+
locationJsonLd: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
2792
|
+
shareMetadata: z.object({
|
|
2793
|
+
title: z.string(),
|
|
2794
|
+
description: z.string().nullable(),
|
|
2795
|
+
imageUrl: z.string().nullable(),
|
|
2796
|
+
siteName: z.string(),
|
|
2797
|
+
}),
|
|
2798
|
+
});
|
|
2799
|
+
|
|
2800
|
+
const bundleGalleryItemSchema = z.object({
|
|
2801
|
+
id: z.string(),
|
|
2802
|
+
type: z.enum(["image", "video"]),
|
|
2803
|
+
src: z.string(),
|
|
2804
|
+
width: z.number().nullable(),
|
|
2805
|
+
height: z.number().nullable(),
|
|
2806
|
+
blurDataURL: z.string().nullable(),
|
|
2807
|
+
alt: z.string().nullable(),
|
|
2808
|
+
title: z.string().nullable(),
|
|
2809
|
+
category: z.string().nullable(),
|
|
2810
|
+
jobId: z.string().nullable(),
|
|
2811
|
+
jobName: z.string().nullable(),
|
|
2812
|
+
takenAt: z.string().nullable(),
|
|
2813
|
+
location: z.string().nullable(),
|
|
2814
|
+
system: z.string().nullable(),
|
|
2815
|
+
year: z.number().nullable(),
|
|
2816
|
+
aboutHtml: z.string().nullable(),
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
const bundleGallerySchema = z
|
|
2820
|
+
.object({
|
|
2821
|
+
items: z.array(bundleGalleryItemSchema),
|
|
2822
|
+
totalCount: z.number(),
|
|
2823
|
+
})
|
|
2824
|
+
.nullable();
|
|
2825
|
+
|
|
2826
|
+
const bundleReviewSchema = z.object({
|
|
2827
|
+
reviewee: z.string(),
|
|
2828
|
+
rating: z.number(),
|
|
2829
|
+
image: z.string().nullable(),
|
|
2830
|
+
description: z.string(),
|
|
2831
|
+
date: z.string(),
|
|
2832
|
+
// Reviewer-attached gallery photos (URLs). Optional for back-compat with
|
|
2833
|
+
// bundles served by pre-photo servers; empty/absent when the review has none.
|
|
2834
|
+
photos: z.array(z.string()).optional(),
|
|
2835
|
+
// Widened from the 0.9.x `"Google"` literal: reviews now come from the
|
|
2836
|
+
// org's unified review store (Google + Yelp + manual + CSV), so the
|
|
2837
|
+
// platform can be any of those. Consumers should render this as a label.
|
|
2838
|
+
platform: z.string(),
|
|
2839
|
+
});
|
|
2840
|
+
|
|
2841
|
+
/** Per-rating histogram bucket (e.g. `{ rating: 5, count: 182 }`). */
|
|
2842
|
+
const bundleReviewCountSchema = z.object({
|
|
2843
|
+
rating: z.number(),
|
|
2844
|
+
count: z.number(),
|
|
2845
|
+
});
|
|
2846
|
+
|
|
2847
|
+
const bundleReviewsSchema = z
|
|
2848
|
+
.object({
|
|
2849
|
+
enabled: z.literal(true),
|
|
2850
|
+
// `org_external_reviews` is the canonical store; `google_places` is the
|
|
2851
|
+
// legacy Place-ID cache, kept as a fallback. Widened from the 0.9.x
|
|
2852
|
+
// `"google_places"` literal.
|
|
2853
|
+
source: z.enum(["org_external_reviews", "google_places"]),
|
|
2854
|
+
// Null when the payload didn't originate from a Place-ID source.
|
|
2855
|
+
placeId: z.string().nullable(),
|
|
2856
|
+
// Average + count are computed over ALL of the org's reviews, not just
|
|
2857
|
+
// the `items` page below.
|
|
2858
|
+
rating: z.number().nullable(),
|
|
2859
|
+
totalCount: z.number().nullable(),
|
|
2860
|
+
// Per-rating distribution over ALL reviews (newest 5→1), so the consumer
|
|
2861
|
+
// renders the histogram without holding every review client-side.
|
|
2862
|
+
// Optional for back-compat with bundles served by pre-0.9.4 servers.
|
|
2863
|
+
counts: z.array(bundleReviewCountSchema).optional(),
|
|
2864
|
+
// Only the first `reviewsLimit` reviews (newest-first). The rest are
|
|
2865
|
+
// fetched lazily via `client.reviews.list({ offset })`.
|
|
2866
|
+
items: z.array(bundleReviewSchema),
|
|
2867
|
+
// items.length — paired with `totalCount` so the consumer knows whether
|
|
2868
|
+
// more pages exist. Optional for back-compat.
|
|
2869
|
+
returnedCount: z.number().optional(),
|
|
2870
|
+
cachedAt: z.string(),
|
|
2871
|
+
})
|
|
2872
|
+
.nullable();
|
|
2873
|
+
|
|
2874
|
+
// ── Reviews wall: paginated public reviews ────────────────────────────
|
|
2875
|
+
// Backs `client.reviews.list({ limit, offset, source })` → the key-scoped
|
|
2876
|
+
// GET /reviews route. The marketing bundle ships the first page + the
|
|
2877
|
+
// aggregate; this endpoint fetches deeper pages on demand (View All / scroll).
|
|
2878
|
+
|
|
2879
|
+
export const reviewWallItemSchema = z.object({
|
|
2880
|
+
id: z.string(),
|
|
2881
|
+
source: z.enum(["google", "yelp", "housecall_pro", "other"]),
|
|
2882
|
+
rating: z.number(),
|
|
2883
|
+
text: z.string(),
|
|
2884
|
+
reviewUrl: z.string().nullable(),
|
|
2885
|
+
reviewerName: z.string(),
|
|
2886
|
+
reviewerImageUrl: z.string().nullable(),
|
|
2887
|
+
/** Reviewer-attached gallery photos (URLs); optional for back-compat,
|
|
2888
|
+
* empty/absent when the review has no media. */
|
|
2889
|
+
photos: z.array(z.string()).optional(),
|
|
2890
|
+
/** ISO 8601 timestamp of the original review. */
|
|
2891
|
+
timeCreated: z.string(),
|
|
2892
|
+
verified: z.boolean(),
|
|
2893
|
+
});
|
|
2894
|
+
|
|
2895
|
+
export const reviewWallListResponseSchema = z.object({
|
|
2896
|
+
items: z.array(reviewWallItemSchema),
|
|
2897
|
+
/** Total reviews matching the (optional) source filter — for "N reviews". */
|
|
2898
|
+
totalCount: z.number(),
|
|
2899
|
+
/** Offset to pass back for the next page, or null when the list is exhausted. */
|
|
2900
|
+
nextOffset: z.number().nullable(),
|
|
2901
|
+
});
|
|
2902
|
+
|
|
2903
|
+
export type ReviewWallItem = z.infer<typeof reviewWallItemSchema>;
|
|
2904
|
+
export type ReviewWallListResponse = z.infer<
|
|
2905
|
+
typeof reviewWallListResponseSchema
|
|
2906
|
+
>;
|
|
2907
|
+
|
|
2908
|
+
/**
|
|
2909
|
+
* Org-owned marketing-page banner (announcement strip / marquee).
|
|
2910
|
+
* Mirrors the platform-banner shape so consumer storefronts can
|
|
2911
|
+
* render a near-identical UI to BIAB's own marketing banner.
|
|
2912
|
+
* `null` when the org hasn't enabled one or has no visible messages.
|
|
2913
|
+
*/
|
|
2914
|
+
const bundleBannerMessageSchema = z
|
|
2915
|
+
.object({
|
|
2916
|
+
id: z.string(),
|
|
2917
|
+
enabled: z.boolean(),
|
|
2918
|
+
text: z.string(),
|
|
2919
|
+
marqueeLines: z.array(z.string()).optional(),
|
|
2920
|
+
linkUrl: z.string(),
|
|
2921
|
+
buttonText: z.string(),
|
|
2922
|
+
openInNewTab: z.boolean(),
|
|
2923
|
+
linkStyle: z.enum(["link", "button"]).optional(),
|
|
2924
|
+
urgent: z.boolean().optional(),
|
|
2925
|
+
appearance: z.enum(["always", "once", "until_dismiss", "every"]),
|
|
2926
|
+
everyIntervalHours: z.number().optional(),
|
|
2927
|
+
scheduleActive: z.boolean().optional(),
|
|
2928
|
+
displayFromUtc: z.string().nullable().optional(),
|
|
2929
|
+
displayUntilUtc: z.string().nullable().optional(),
|
|
2930
|
+
})
|
|
2931
|
+
.passthrough();
|
|
2932
|
+
|
|
2933
|
+
const bundleBannerSchema = z
|
|
2934
|
+
.object({
|
|
2935
|
+
enabled: z.boolean(),
|
|
2936
|
+
hiddenOnHome: z.boolean(),
|
|
2937
|
+
messages: z.array(bundleBannerMessageSchema),
|
|
2938
|
+
})
|
|
2939
|
+
.passthrough()
|
|
2940
|
+
.nullable();
|
|
2941
|
+
|
|
2942
|
+
export type BundleBannerMessage = z.infer<typeof bundleBannerMessageSchema>;
|
|
2943
|
+
export type BundleBanner = z.infer<typeof bundleBannerSchema>;
|
|
2944
|
+
|
|
2945
|
+
/**
|
|
2946
|
+
* Org Updates feed — cached Google Business Profile "Posts" (the Updates tab):
|
|
2947
|
+
* a blurb + image(s) + deep link, optionally with an offer/event window. Lets a
|
|
2948
|
+
* consumer storefront render a "News"/"Updates" feed from the bundle without a
|
|
2949
|
+
* second API call. `null` when the org has none.
|
|
2950
|
+
*/
|
|
2951
|
+
const bundleUpdateItemSchema = z
|
|
2952
|
+
.object({
|
|
2953
|
+
id: z.string(),
|
|
2954
|
+
kind: z.string().nullable(),
|
|
2955
|
+
title: z.string().nullable(),
|
|
2956
|
+
body: z.string(),
|
|
2957
|
+
link: z.string().nullable(),
|
|
2958
|
+
imageUrl: z.string().nullable(),
|
|
2959
|
+
images: z.array(z.string()),
|
|
2960
|
+
postedAt: z.string().nullable(),
|
|
2961
|
+
startsAt: z.string().nullable(),
|
|
2962
|
+
endsAt: z.string().nullable(),
|
|
2963
|
+
})
|
|
2964
|
+
.passthrough();
|
|
2965
|
+
|
|
2966
|
+
const bundleUpdatesSchema = z
|
|
2967
|
+
.object({
|
|
2968
|
+
items: z.array(bundleUpdateItemSchema),
|
|
2969
|
+
})
|
|
2970
|
+
.passthrough()
|
|
2971
|
+
.nullable();
|
|
2972
|
+
|
|
2973
|
+
export type BundleUpdateItem = z.infer<typeof bundleUpdateItemSchema>;
|
|
2974
|
+
export type BundleUpdates = z.infer<typeof bundleUpdatesSchema>;
|
|
2975
|
+
|
|
2976
|
+
export const getMarketingPageBundleResponseSchema = z
|
|
2977
|
+
.object({
|
|
2978
|
+
pageKey: z.string(),
|
|
2979
|
+
locale: z.string(),
|
|
2980
|
+
sections: z.record(z.string(), siteMarketingSectionEntrySchema),
|
|
2981
|
+
availableLocales: z.array(z.string()),
|
|
2982
|
+
seo: siteMarketingSeoDataSchema,
|
|
2983
|
+
// New in 0.8.4:
|
|
2984
|
+
brand: brandTokensSchema.optional(),
|
|
2985
|
+
company: bundleCompanySchema.optional(),
|
|
2986
|
+
gallery: bundleGallerySchema.optional(),
|
|
2987
|
+
reviews: bundleReviewsSchema.optional(),
|
|
2988
|
+
// New in 0.8.9:
|
|
2989
|
+
banner: bundleBannerSchema.optional(),
|
|
2990
|
+
// New in 0.9.6:
|
|
2991
|
+
updates: bundleUpdatesSchema.optional(),
|
|
2992
|
+
})
|
|
2993
|
+
// `.passthrough()` at the top level so any future BIAB-side
|
|
2994
|
+
// addition (e.g. bundle.events, bundle.faq, etc.) flows through
|
|
2995
|
+
// to consumers without an SDK release — the lesson from the
|
|
2996
|
+
// brand/company/gallery/reviews oversight on this version.
|
|
2997
|
+
.passthrough();
|
|
2998
|
+
|
|
2999
|
+
export const marketingSchemaSlotRowSchema = z.object({
|
|
3000
|
+
slot: z.enum(["draft", "published"]),
|
|
3001
|
+
schema: z.record(z.string(), z.unknown()),
|
|
3002
|
+
checksum: z.string().length(64),
|
|
3003
|
+
version: z.number().int().positive(),
|
|
3004
|
+
updatedAt: timestampSchema,
|
|
3005
|
+
});
|
|
3006
|
+
|
|
3007
|
+
export const getPublishedMarketingSchemaResponseSchema = z.object({
|
|
3008
|
+
schema: marketingSchemaSlotRowSchema.nullable(),
|
|
3009
|
+
});
|
|
3010
|
+
|
|
3011
|
+
export const upsertDraftMarketingSchemaInputSchema = z.object({
|
|
3012
|
+
schema: z.record(z.string(), z.unknown()),
|
|
3013
|
+
checksum: z.string().length(64),
|
|
3014
|
+
note: z.string().max(280).nullable().optional(),
|
|
3015
|
+
});
|
|
3016
|
+
|
|
3017
|
+
export const upsertDraftMarketingSchemaResponseSchema = z.object({
|
|
3018
|
+
ok: z.literal(true),
|
|
3019
|
+
version: z.number().int().positive(),
|
|
3020
|
+
checksum: z.string().length(64),
|
|
3021
|
+
noop: z.boolean(),
|
|
3022
|
+
});
|
|
3023
|
+
|
|
3024
|
+
export const getMarketingLocalesResponseSchema = z.object({
|
|
3025
|
+
locales: z.array(z.string()),
|
|
3026
|
+
brandTokens: z.record(z.string()),
|
|
3027
|
+
});
|
|
3028
|
+
|
|
3029
|
+
/**
|
|
3030
|
+
* Bulk section upsert — sent by `biab-dev sync-content`.
|
|
3031
|
+
*
|
|
3032
|
+
* One request carries up to 256 items (server-enforced). The CLI batches
|
|
3033
|
+
* larger imports automatically. Each item is a single
|
|
3034
|
+
* `(pageKey, locale, sectionKey)` value and goes through the same
|
|
3035
|
+
* validation pipeline as a dashboard save, so a partial failure on one
|
|
3036
|
+
* row reports `ok: false, error: "..."` without rolling back the rest.
|
|
3037
|
+
*/
|
|
3038
|
+
export const publishMarketingSectionsItemSchema = z.object({
|
|
3039
|
+
pageKey: z.string().min(1).max(120),
|
|
3040
|
+
locale: z.string().min(1).max(20),
|
|
3041
|
+
sectionKey: z.string().min(1).max(120),
|
|
3042
|
+
payload: z.unknown(),
|
|
3043
|
+
});
|
|
3044
|
+
|
|
3045
|
+
export const publishMarketingSectionsInputSchema = z.object({
|
|
3046
|
+
items: z.array(publishMarketingSectionsItemSchema).min(1).max(256),
|
|
3047
|
+
validate: z.boolean().default(true).optional(),
|
|
3048
|
+
note: z.string().max(280).nullable().optional(),
|
|
3049
|
+
});
|
|
3050
|
+
|
|
3051
|
+
export const publishMarketingSectionsResultItemSchema = z.discriminatedUnion(
|
|
3052
|
+
"ok",
|
|
3053
|
+
[
|
|
3054
|
+
z.object({
|
|
3055
|
+
ok: z.literal(true),
|
|
3056
|
+
pageKey: z.string(),
|
|
3057
|
+
locale: z.string(),
|
|
3058
|
+
sectionKey: z.string(),
|
|
3059
|
+
version: z.number().int().positive(),
|
|
3060
|
+
}),
|
|
3061
|
+
z.object({
|
|
3062
|
+
ok: z.literal(false),
|
|
3063
|
+
pageKey: z.string(),
|
|
3064
|
+
locale: z.string(),
|
|
3065
|
+
sectionKey: z.string(),
|
|
3066
|
+
error: z.string(),
|
|
3067
|
+
}),
|
|
3068
|
+
],
|
|
3069
|
+
);
|
|
3070
|
+
|
|
3071
|
+
export const publishMarketingSectionsResponseSchema = z.object({
|
|
3072
|
+
results: z.array(publishMarketingSectionsResultItemSchema),
|
|
3073
|
+
});
|
|
3074
|
+
|
|
3075
|
+
export type SiteMarketingSectionEntry = z.infer<
|
|
3076
|
+
typeof siteMarketingSectionEntrySchema
|
|
3077
|
+
>;
|
|
3078
|
+
export type SiteMarketingSeoData = z.infer<typeof siteMarketingSeoDataSchema>;
|
|
3079
|
+
export type BusinessAddress = NonNullable<z.infer<typeof companyAddressSchema>>;
|
|
3080
|
+
export type BusinessLocation = z.infer<typeof companyLocationSchema>;
|
|
3081
|
+
export type GetMarketingPageBundleResponse = z.infer<
|
|
3082
|
+
typeof getMarketingPageBundleResponseSchema
|
|
3083
|
+
>;
|
|
3084
|
+
export type MarketingSchemaSlotRow = z.infer<
|
|
3085
|
+
typeof marketingSchemaSlotRowSchema
|
|
3086
|
+
>;
|
|
3087
|
+
export type GetPublishedMarketingSchemaResponse = z.infer<
|
|
3088
|
+
typeof getPublishedMarketingSchemaResponseSchema
|
|
3089
|
+
>;
|
|
3090
|
+
export type UpsertDraftMarketingSchemaInput = z.infer<
|
|
3091
|
+
typeof upsertDraftMarketingSchemaInputSchema
|
|
3092
|
+
>;
|
|
3093
|
+
export type UpsertDraftMarketingSchemaResponse = z.infer<
|
|
3094
|
+
typeof upsertDraftMarketingSchemaResponseSchema
|
|
3095
|
+
>;
|
|
3096
|
+
export type GetMarketingLocalesResponse = z.infer<
|
|
3097
|
+
typeof getMarketingLocalesResponseSchema
|
|
3098
|
+
>;
|
|
3099
|
+
export type PublishMarketingSectionsItem = z.infer<
|
|
3100
|
+
typeof publishMarketingSectionsItemSchema
|
|
3101
|
+
>;
|
|
3102
|
+
export type PublishMarketingSectionsInput = z.infer<
|
|
3103
|
+
typeof publishMarketingSectionsInputSchema
|
|
3104
|
+
>;
|
|
3105
|
+
export type PublishMarketingSectionsResultItem = z.infer<
|
|
3106
|
+
typeof publishMarketingSectionsResultItemSchema
|
|
3107
|
+
>;
|
|
3108
|
+
export type PublishMarketingSectionsResponse = z.infer<
|
|
3109
|
+
typeof publishMarketingSectionsResponseSchema
|
|
3110
|
+
>;
|
|
3111
|
+
|
|
3112
|
+
// ── Data model: reading records ──────────────────────────────────────
|
|
3113
|
+
//
|
|
3114
|
+
// The read half of the custom data model. Until these existed the model was
|
|
3115
|
+
// write-only from outside: `sync-data-model` pushed a schema and `sync-records`
|
|
3116
|
+
// seeded rows, and nothing could read either back.
|
|
3117
|
+
//
|
|
3118
|
+
// Note this is NOT the `collections:*` family above. Those read `site_data_rows`
|
|
3119
|
+
// (Site Data / the site builder's tables). Custom-object rows live in
|
|
3120
|
+
// `org_object_records` and are reached only through here.
|
|
3121
|
+
|
|
3122
|
+
/** One edge out of a record. Relations are links, not embedded rows. */
|
|
3123
|
+
export const recordRelationLinkSchema = z.object({
|
|
3124
|
+
/** The linked row's `id`. Read it with a follow-up `listRecords` call. */
|
|
3125
|
+
recordId: z.string().uuid(),
|
|
3126
|
+
/** `universalIdentifier` of the object the linked row belongs to. */
|
|
3127
|
+
object: z.string(),
|
|
3128
|
+
});
|
|
3129
|
+
export type RecordRelationLink = z.infer<typeof recordRelationLinkSchema>;
|
|
3130
|
+
|
|
3131
|
+
export const dataModelRecordSchema = z.object({
|
|
3132
|
+
id: z.string().uuid(),
|
|
3133
|
+
/**
|
|
3134
|
+
* The identity a seed file gave this row, or null when a human created it
|
|
3135
|
+
* in the dashboard. Null means "not reproducible from your config".
|
|
3136
|
+
*/
|
|
3137
|
+
universalIdentifier: z.string().uuid().nullable(),
|
|
3138
|
+
/** Scalar values, keyed by field name. */
|
|
3139
|
+
fields: z.record(z.string(), z.unknown()),
|
|
3140
|
+
/**
|
|
3141
|
+
* Keyed by field name. EVERY declared relation field is present — `[]` when
|
|
3142
|
+
* it has no links — so an empty relation is never mistaken for a missing one.
|
|
3143
|
+
*/
|
|
3144
|
+
relations: z.record(z.string(), z.array(recordRelationLinkSchema)),
|
|
3145
|
+
createdAt: timestampSchema,
|
|
3146
|
+
updatedAt: timestampSchema,
|
|
3147
|
+
});
|
|
3148
|
+
export type DataModelRecord = z.infer<typeof dataModelRecordSchema>;
|
|
3149
|
+
|
|
3150
|
+
export const listDataModelRecordsInputSchema = z.object({
|
|
3151
|
+
/**
|
|
3152
|
+
* The object's `universalIdentifier` — the same value your config declares,
|
|
3153
|
+
* and the same one `sync-records` writes against. Not its name, which can
|
|
3154
|
+
* change without the identity changing.
|
|
3155
|
+
*/
|
|
3156
|
+
object: z.string().min(1),
|
|
3157
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
3158
|
+
/** Pass back the previous page's `nextCursor`. Opaque; don't construct one. */
|
|
3159
|
+
cursor: z.string().optional(),
|
|
3160
|
+
});
|
|
3161
|
+
export type ListDataModelRecordsInput = z.infer<
|
|
3162
|
+
typeof listDataModelRecordsInputSchema
|
|
3163
|
+
>;
|
|
3164
|
+
|
|
3165
|
+
export const listDataModelRecordsAvailableSchema = z.object({
|
|
3166
|
+
available: z.literal(true),
|
|
3167
|
+
records: z.array(dataModelRecordSchema),
|
|
3168
|
+
/** Null on the last page. */
|
|
3169
|
+
nextCursor: z.string().nullable(),
|
|
3170
|
+
});
|
|
3171
|
+
export type ListDataModelRecordsAvailable = z.infer<
|
|
3172
|
+
typeof listDataModelRecordsAvailableSchema
|
|
3173
|
+
>;
|
|
3174
|
+
|
|
3175
|
+
/**
|
|
3176
|
+
* Discriminated union — branch on `.available`. Custom objects are an à-la-carte
|
|
3177
|
+
* surface (`crm.custom_objects`), so an org without it gets the upgrade shape
|
|
3178
|
+
* rather than an exception.
|
|
3179
|
+
*/
|
|
3180
|
+
export const listDataModelRecordsResponseSchema = z.union([
|
|
3181
|
+
listDataModelRecordsAvailableSchema,
|
|
3182
|
+
planGatedUnavailableSchema,
|
|
3183
|
+
]);
|
|
3184
|
+
export type ListDataModelRecordsResponse = z.infer<
|
|
3185
|
+
typeof listDataModelRecordsResponseSchema
|
|
3186
|
+
>;
|
|
3187
|
+
|
|
3188
|
+
// ── Analytics: reading visitor counts ────────────────────────────────
|
|
3189
|
+
//
|
|
3190
|
+
// The read half of site analytics. The `<BIABAnalytics/>` tracker records
|
|
3191
|
+
// pageviews (write-only); these read the totals back so a site can show them.
|
|
3192
|
+
|
|
3193
|
+
export const viewCountSchema = z.object({
|
|
3194
|
+
/** Pageviews (`kind = 'pageview'` events). */
|
|
3195
|
+
views: z.number().int().nonnegative(),
|
|
3196
|
+
/**
|
|
3197
|
+
* Distinct anonId — an approximation of unique visitors. anonId rotates every
|
|
3198
|
+
* UTC day, so this is exact within a day and, over a longer window, counts a
|
|
3199
|
+
* returning visitor once per day they came (visitor-days). Never identifies
|
|
3200
|
+
* anyone; never undercounts.
|
|
3201
|
+
*/
|
|
3202
|
+
visitors: z.number().int().nonnegative(),
|
|
3203
|
+
});
|
|
3204
|
+
export type ViewCount = z.infer<typeof viewCountSchema>;
|
|
3205
|
+
|
|
3206
|
+
export const pageViewsInputSchema = z.object({
|
|
3207
|
+
/**
|
|
3208
|
+
* Exact paths to count. Omit (or pass `[]`) for a SITE-WIDE total. Each path
|
|
3209
|
+
* comes back in `paths`, zero-filled if it has no views yet.
|
|
3210
|
+
*/
|
|
3211
|
+
paths: z.array(z.string().min(1)).max(50).optional(),
|
|
3212
|
+
/** Window in days. Omit for all-time. */
|
|
3213
|
+
days: z.number().int().min(1).optional(),
|
|
3214
|
+
});
|
|
3215
|
+
export type PageViewsInput = z.infer<typeof pageViewsInputSchema>;
|
|
3216
|
+
|
|
3217
|
+
export const pageViewsAvailableSchema = z.object({
|
|
3218
|
+
available: z.literal(true),
|
|
3219
|
+
/** ISO start of the window, or null for all-time. */
|
|
3220
|
+
since: z.string().nullable(),
|
|
3221
|
+
/** Over the whole scope: site-wide when no paths, else summed over them. */
|
|
3222
|
+
total: viewCountSchema,
|
|
3223
|
+
/** One entry per requested path (empty for a site-wide query). */
|
|
3224
|
+
paths: z.array(viewCountSchema.extend({ path: z.string() })),
|
|
3225
|
+
});
|
|
3226
|
+
export type PageViewsAvailable = z.infer<typeof pageViewsAvailableSchema>;
|
|
3227
|
+
|
|
3228
|
+
/**
|
|
3229
|
+
* Discriminated union — branch on `.available`. Visitor analytics is an
|
|
3230
|
+
* entitlement (`analytics.web`), so an org without it gets the upgrade shape
|
|
3231
|
+
* rather than an exception.
|
|
3232
|
+
*/
|
|
3233
|
+
export const pageViewsResponseSchema = z.union([
|
|
3234
|
+
pageViewsAvailableSchema,
|
|
3235
|
+
planGatedUnavailableSchema,
|
|
3236
|
+
]);
|
|
3237
|
+
export type PageViewsResponse = z.infer<typeof pageViewsResponseSchema>;
|
|
3238
|
+
|
|
3239
|
+
// ── AI-crawler visibility (AEO) ──────────────────────────────────────
|
|
3240
|
+
|
|
3241
|
+
export const aiVisibilityInputSchema = z.object({
|
|
3242
|
+
/** Window in days (default 30 server-side). */
|
|
3243
|
+
days: z.number().int().min(1).optional(),
|
|
3244
|
+
});
|
|
3245
|
+
export type AiVisibilityInput = z.infer<typeof aiVisibilityInputSchema>;
|
|
3246
|
+
|
|
3247
|
+
export const aiCrawlerVisibilitySchema = z.object({
|
|
3248
|
+
/** Stable crawler name, e.g. "GPTBot". */
|
|
3249
|
+
name: z.string(),
|
|
3250
|
+
/** Operator, e.g. "OpenAI". Null when the platform can't say. */
|
|
3251
|
+
vendor: z.string().nullable(),
|
|
3252
|
+
/**
|
|
3253
|
+
* What the fetch means: "training" (corpus building), "search" (answer-
|
|
3254
|
+
* engine indexing — citation supply), or "user" (a live assistant reading
|
|
3255
|
+
* on a person's behalf). Null when unknown.
|
|
3256
|
+
*/
|
|
3257
|
+
intent: z.enum(["training", "search", "user"]).nullable(),
|
|
3258
|
+
/** Fetches in the window. */
|
|
3259
|
+
fetches: z.number().int().nonnegative(),
|
|
3260
|
+
/** Distinct paths this crawler read. */
|
|
3261
|
+
distinctPages: z.number().int().nonnegative(),
|
|
3262
|
+
/** ISO timestamp of the crawler's most recent fetch. */
|
|
3263
|
+
lastSeenAt: z.string(),
|
|
3264
|
+
});
|
|
3265
|
+
export type AiCrawlerVisibility = z.infer<typeof aiCrawlerVisibilitySchema>;
|
|
3266
|
+
|
|
3267
|
+
export const aiVisibilityAvailableSchema = z.object({
|
|
3268
|
+
available: z.literal(true),
|
|
3269
|
+
/** ISO start of the window. */
|
|
3270
|
+
since: z.string(),
|
|
3271
|
+
/** Ordered most-fetches-first. Empty = no AI crawler came. */
|
|
3272
|
+
crawlers: z.array(aiCrawlerVisibilitySchema),
|
|
3273
|
+
});
|
|
3274
|
+
export type AiVisibilityAvailable = z.infer<typeof aiVisibilityAvailableSchema>;
|
|
3275
|
+
|
|
3276
|
+
/** Branch on `.available` — same entitlement gating as pageViews. */
|
|
3277
|
+
export const aiVisibilityResponseSchema = z.union([
|
|
3278
|
+
aiVisibilityAvailableSchema,
|
|
3279
|
+
planGatedUnavailableSchema,
|
|
3280
|
+
]);
|
|
3281
|
+
export type AiVisibilityResponse = z.infer<typeof aiVisibilityResponseSchema>;
|
|
3282
|
+
|
|
3283
|
+
// ── Attribution footer ───────────────────────────────────────────────
|
|
3284
|
+
//
|
|
3285
|
+
// The "Powered by BusinessDash" badge's one narrow function. READ its policy;
|
|
3286
|
+
// WRITE an (untrusted) heartbeat. See the plan in the app repo.
|
|
3287
|
+
|
|
3288
|
+
export const attributionPolicySchema = z.object({
|
|
3289
|
+
/** The site the policy is for — build the badge's `?ref=<siteId>` from this. */
|
|
3290
|
+
siteId: z.string().uuid(),
|
|
3291
|
+
/**
|
|
3292
|
+
* Whether the badge must stay. `true` on launch plans (which can't remove
|
|
3293
|
+
* it); `false` on growth/scale. Advisory — the real check is server-side, but
|
|
3294
|
+
* an honest site uses this to decide whether it may hide the badge.
|
|
3295
|
+
*/
|
|
3296
|
+
required: z.boolean(),
|
|
3297
|
+
});
|
|
3298
|
+
export type AttributionPolicy = z.infer<typeof attributionPolicySchema>;
|
|
3299
|
+
|
|
3300
|
+
export const attributionHeartbeatInputSchema = z.object({
|
|
3301
|
+
/** Absolute URL the badge rendered on. Normalized + de-duped server-side. */
|
|
3302
|
+
url: z.string().min(1).max(2048),
|
|
3303
|
+
});
|
|
3304
|
+
export type AttributionHeartbeatInput = z.infer<
|
|
3305
|
+
typeof attributionHeartbeatInputSchema
|
|
3306
|
+
>;
|
|
3307
|
+
|
|
3308
|
+
export const attributionHeartbeatResponseSchema = z.object({
|
|
3309
|
+
/** `false` when the URL was unparseable — a heartbeat never errors. */
|
|
3310
|
+
recorded: z.boolean(),
|
|
3311
|
+
});
|
|
3312
|
+
export type AttributionHeartbeatResponse = z.infer<
|
|
3313
|
+
typeof attributionHeartbeatResponseSchema
|
|
3314
|
+
>;
|
|
3315
|
+
|
|
3316
|
+
/* ---------------------------------------------------------------------------
|
|
3317
|
+
Hiring — public job postings for a consumer-built careers page.
|
|
3318
|
+
|
|
3319
|
+
Read-only and deliberately narrower than the dashboard's job row: internal
|
|
3320
|
+
hiring state (openings filled, application caps, default permissions/roles,
|
|
3321
|
+
the role-ladder pointer, interview questions) never crosses this boundary.
|
|
3322
|
+
Applications ride the existing forms surface via `applicationFormId`.
|
|
3323
|
+
--------------------------------------------------------------------------- */
|
|
3324
|
+
|
|
3325
|
+
export const jobCategorySchema = z.object({
|
|
3326
|
+
id: z.string(),
|
|
3327
|
+
name: z.string(),
|
|
3328
|
+
slug: z.string(),
|
|
3329
|
+
description: z.string().nullable(),
|
|
3330
|
+
/** Free-form chip tint set in the dashboard (hex or CSS colour). */
|
|
3331
|
+
color: z.string().nullable(),
|
|
3332
|
+
});
|
|
3333
|
+
export type JobCategory = z.infer<typeof jobCategorySchema>;
|
|
3334
|
+
|
|
3335
|
+
export const jobCategoryWithCountSchema = jobCategorySchema.extend({
|
|
3336
|
+
/** Open postings currently in this category. */
|
|
3337
|
+
openCount: z.number(),
|
|
3338
|
+
});
|
|
3339
|
+
export type JobCategoryWithCount = z.infer<typeof jobCategoryWithCountSchema>;
|
|
3340
|
+
|
|
3341
|
+
export const jobPostingSchema = z.object({
|
|
3342
|
+
id: z.string(),
|
|
3343
|
+
slug: z.string(),
|
|
3344
|
+
title: z.string(),
|
|
3345
|
+
/** Rich-text HTML, sanitized server-side. Safe to render. */
|
|
3346
|
+
description: z.string(),
|
|
3347
|
+
department: z.string().nullable(),
|
|
3348
|
+
level: z.string().nullable(),
|
|
3349
|
+
employmentType: z.string(),
|
|
3350
|
+
workMode: z.string(),
|
|
3351
|
+
location: z.string().nullable(),
|
|
3352
|
+
region: z.object({ id: z.string(), name: z.string() }).nullable(),
|
|
3353
|
+
/** The posting's single group. `null` when uncategorized. */
|
|
3354
|
+
category: jobCategorySchema.nullable(),
|
|
3355
|
+
/** Free-form labels for filtering/search; never a grouping. */
|
|
3356
|
+
tags: z.array(z.string()),
|
|
3357
|
+
responsibilities: z.array(z.string()).nullable(),
|
|
3358
|
+
requirements: z.string().nullable(),
|
|
3359
|
+
languages: z.array(z.string()).nullable(),
|
|
3360
|
+
payRangeText: z.string().nullable(),
|
|
3361
|
+
salaryMin: z.number().nullable(),
|
|
3362
|
+
salaryMax: z.number().nullable(),
|
|
3363
|
+
currency: z.string().nullable(),
|
|
3364
|
+
/**
|
|
3365
|
+
* Pass to `client.forms.schema()` / `<BiabForm/>` to render the application
|
|
3366
|
+
* form. `null` means the posting is listed but not yet accepting applications.
|
|
3367
|
+
*/
|
|
3368
|
+
applicationFormId: z.string().nullable(),
|
|
3369
|
+
publishedAt: z.string().nullable(),
|
|
3370
|
+
updatedAt: z.string(),
|
|
3371
|
+
});
|
|
3372
|
+
export type JobPosting = z.infer<typeof jobPostingSchema>;
|
|
3373
|
+
|
|
3374
|
+
export const jobPostingListResponseSchema = z.object({
|
|
3375
|
+
items: z.array(jobPostingSchema),
|
|
3376
|
+
/** Total open postings matching the (optional) category filter. */
|
|
3377
|
+
totalCount: z.number(),
|
|
3378
|
+
/** Offset to pass back for the next page, or null when exhausted. */
|
|
3379
|
+
nextOffset: z.number().nullable(),
|
|
3380
|
+
});
|
|
3381
|
+
export type JobPostingListResponse = z.infer<
|
|
3382
|
+
typeof jobPostingListResponseSchema
|
|
3383
|
+
>;
|
|
3384
|
+
|
|
3385
|
+
export const jobCategoryListResponseSchema = z.object({
|
|
3386
|
+
items: z.array(jobCategoryWithCountSchema),
|
|
3387
|
+
});
|
|
3388
|
+
export type JobCategoryListResponse = z.infer<
|
|
3389
|
+
typeof jobCategoryListResponseSchema
|
|
3390
|
+
>;
|