@businessdash/sdk 0.9.60 → 0.9.80
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/README.md +991 -295
- package/assets/biab-sw.js +92 -0
- package/dist/account-core/index.d.ts +126 -0
- package/dist/account-core/index.d.ts.map +1 -0
- package/dist/account-core/index.js +191 -0
- package/dist/account-core/index.js.map +1 -0
- package/dist/account.d.ts +11 -0
- package/dist/account.d.ts.map +1 -0
- package/dist/account.js +11 -0
- package/dist/account.js.map +1 -0
- package/dist/auth-handler.d.ts +1 -1
- package/dist/auth-links.d.ts +33 -0
- package/dist/auth-links.d.ts.map +1 -0
- package/dist/auth-links.js +64 -0
- package/dist/auth-links.js.map +1 -0
- package/dist/blog-core/index.d.ts +112 -0
- package/dist/blog-core/index.d.ts.map +1 -0
- package/dist/blog-core/index.js +330 -0
- package/dist/blog-core/index.js.map +1 -0
- package/dist/blog.d.ts +11 -0
- package/dist/blog.d.ts.map +1 -0
- package/dist/blog.js +11 -0
- package/dist/blog.js.map +1 -0
- package/dist/chat-core/index.d.ts +97 -0
- package/dist/chat-core/index.d.ts.map +1 -0
- package/dist/chat-core/index.js +237 -0
- package/dist/chat-core/index.js.map +1 -0
- package/dist/chat.d.ts +11 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +11 -0
- package/dist/chat.js.map +1 -0
- package/dist/checkout-core/index.d.ts +65 -0
- package/dist/checkout-core/index.d.ts.map +1 -0
- package/dist/checkout-core/index.js +159 -0
- package/dist/checkout-core/index.js.map +1 -0
- package/dist/checkout.d.ts +10 -0
- package/dist/checkout.d.ts.map +1 -0
- package/dist/checkout.js +10 -0
- package/dist/checkout.js.map +1 -0
- package/dist/cli.js +133 -25
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +622 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +888 -1
- package/dist/client.js.map +1 -1
- package/dist/consent-core/index.cjs +107 -0
- package/dist/consent-core/index.d.ts +103 -0
- package/dist/consent-core/index.d.ts.map +1 -0
- package/dist/consent-core/index.js +157 -0
- package/dist/consent-core/index.js.map +1 -0
- package/dist/contracts.d.ts +3526 -767
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +571 -4
- package/dist/contracts.js.map +1 -1
- package/dist/data-model-schema.d.ts +1 -1
- package/dist/data-model-schema.d.ts.map +1 -1
- package/dist/element.d.ts +30 -0
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +56 -0
- package/dist/element.js.map +1 -1
- package/dist/embed-protocol.d.ts +86 -0
- package/dist/embed-protocol.d.ts.map +1 -0
- package/dist/embed-protocol.js +60 -0
- package/dist/embed-protocol.js.map +1 -0
- package/dist/framework-support.d.ts +122 -0
- package/dist/framework-support.d.ts.map +1 -0
- package/dist/framework-support.js +323 -0
- package/dist/framework-support.js.map +1 -0
- package/dist/index.cjs +1355 -24
- package/dist/legal-core/index.cjs +115 -0
- package/dist/legal-core/index.d.ts +160 -0
- package/dist/legal-core/index.d.ts.map +1 -0
- package/dist/legal-core/index.js +220 -0
- package/dist/legal-core/index.js.map +1 -0
- package/dist/marketing-core/index.d.ts +66 -0
- package/dist/marketing-core/index.d.ts.map +1 -0
- package/dist/marketing-core/index.js +154 -0
- package/dist/marketing-core/index.js.map +1 -0
- package/dist/marketing.d.ts +7 -0
- package/dist/marketing.d.ts.map +1 -0
- package/dist/marketing.js +7 -0
- package/dist/marketing.js.map +1 -0
- package/dist/mcp-adapters/index.d.ts +135 -0
- package/dist/mcp-adapters/index.d.ts.map +1 -0
- package/dist/mcp-adapters/index.js +187 -0
- package/dist/mcp-adapters/index.js.map +1 -0
- package/dist/mcp-adapters.d.ts +7 -0
- package/dist/mcp-adapters.d.ts.map +1 -0
- package/dist/mcp-adapters.js +7 -0
- package/dist/mcp-adapters.js.map +1 -0
- package/dist/model3d-core/index.cjs +95 -0
- package/dist/model3d-core/index.d.ts +167 -0
- package/dist/model3d-core/index.d.ts.map +1 -0
- package/dist/model3d-core/index.js +158 -0
- package/dist/model3d-core/index.js.map +1 -0
- package/dist/notifications-core/index.cjs +315 -0
- package/dist/notifications-core/index.d.ts +229 -0
- package/dist/notifications-core/index.d.ts.map +1 -0
- package/dist/notifications-core/index.js +445 -0
- package/dist/notifications-core/index.js.map +1 -0
- package/dist/portal-core/index.d.ts +451 -0
- package/dist/portal-core/index.d.ts.map +1 -0
- package/dist/portal-core/index.js +819 -0
- package/dist/portal-core/index.js.map +1 -0
- package/dist/portal.d.ts +7 -0
- package/dist/portal.d.ts.map +1 -0
- package/dist/portal.js +7 -0
- package/dist/portal.js.map +1 -0
- package/dist/push-core/index.cjs +171 -0
- package/dist/push-core/index.d.ts +149 -0
- package/dist/push-core/index.d.ts.map +1 -0
- package/dist/push-core/index.js +231 -0
- package/dist/push-core/index.js.map +1 -0
- package/dist/react-marketing.d.ts +1 -17
- package/dist/react-marketing.d.ts.map +1 -1
- package/dist/react-marketing.js +4 -0
- package/dist/react-marketing.js.map +1 -1
- package/dist/react.d.ts +294 -101
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +302 -246
- package/dist/react.js.map +1 -1
- package/dist/seed-core/index.d.ts +354 -0
- package/dist/seed-core/index.d.ts.map +1 -0
- package/dist/seed-core/index.js +420 -0
- package/dist/seed-core/index.js.map +1 -0
- package/dist/seed.d.ts +7 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +7 -0
- package/dist/seed.js.map +1 -0
- package/dist/seo-core/blog.d.ts +94 -0
- package/dist/seo-core/blog.d.ts.map +1 -0
- package/dist/seo-core/blog.js +149 -0
- package/dist/seo-core/blog.js.map +1 -0
- package/dist/seo-core/commerce.d.ts +64 -0
- package/dist/seo-core/commerce.d.ts.map +1 -0
- package/dist/seo-core/commerce.js +87 -0
- package/dist/seo-core/commerce.js.map +1 -0
- package/dist/seo-core/index.cjs +751 -0
- package/dist/seo-core/index.d.ts +226 -0
- package/dist/seo-core/index.d.ts.map +1 -0
- package/dist/seo-core/index.js +455 -0
- package/dist/seo-core/index.js.map +1 -0
- package/dist/seo-core/local.d.ts +181 -0
- package/dist/seo-core/local.d.ts.map +1 -0
- package/dist/seo-core/local.js +193 -0
- package/dist/seo-core/local.js.map +1 -0
- package/dist/sitemap-core/index.cjs +167 -0
- package/dist/sitemap-core/index.d.ts +155 -0
- package/dist/sitemap-core/index.d.ts.map +1 -0
- package/dist/sitemap-core/index.js +195 -0
- package/dist/sitemap-core/index.js.map +1 -0
- package/dist/social-core/index.d.ts +74 -0
- package/dist/social-core/index.d.ts.map +1 -0
- package/dist/social-core/index.js +72 -0
- package/dist/social-core/index.js.map +1 -0
- package/dist/social.d.ts +7 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +7 -0
- package/dist/social.js.map +1 -0
- package/dist/solid-compat.d.ts +204 -0
- package/dist/solid-compat.d.ts.map +1 -0
- package/dist/solid-compat.js +221 -0
- package/dist/solid-compat.js.map +1 -0
- package/dist/store-core/index.d.ts +141 -0
- package/dist/store-core/index.d.ts.map +1 -0
- package/dist/store-core/index.js +277 -0
- package/dist/store-core/index.js.map +1 -0
- package/dist/store.d.ts +12 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +12 -0
- package/dist/store.js.map +1 -0
- package/dist/vanilla.d.ts +111 -0
- package/dist/vanilla.d.ts.map +1 -1
- package/dist/vanilla.js +206 -0
- package/dist/vanilla.js.map +1 -1
- package/dist/verify-core/index.d.ts +98 -0
- package/dist/verify-core/index.d.ts.map +1 -0
- package/dist/verify-core/index.js +186 -0
- package/dist/verify-core/index.js.map +1 -0
- package/dist/verify.d.ts +8 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +8 -0
- package/dist/verify.js.map +1 -0
- package/openapi.json +4516 -1311
- package/package.json +128 -11
- package/src/account-core/index.ts +309 -0
- package/src/account.ts +11 -0
- package/src/angular/headless.ts +519 -0
- package/src/angular/index.ts +3 -0
- package/src/auth-links.ts +76 -0
- package/src/blog-core/index.ts +423 -0
- package/src/blog.ts +11 -0
- package/src/chat-core/index.ts +335 -0
- package/src/chat.ts +11 -0
- package/src/checkout-core/index.ts +225 -0
- package/src/checkout.ts +10 -0
- package/src/cli.ts +184 -28
- package/src/client.ts +1097 -0
- package/src/consent-core/index.ts +213 -0
- package/src/contracts.ts +768 -11
- package/src/data-model-schema.ts +1 -1
- package/src/element.ts +102 -0
- package/src/embed-protocol.ts +100 -0
- package/src/framework-support.ts +440 -0
- package/src/legal-core/index.ts +276 -0
- package/src/marketing-core/index.ts +209 -0
- package/src/marketing.ts +7 -0
- package/src/mcp-adapters/index.ts +264 -0
- package/src/mcp-adapters.ts +7 -0
- package/src/model3d-core/index.ts +248 -0
- package/src/notifications-core/index.ts +650 -0
- package/src/portal-core/index.ts +1564 -0
- package/src/portal.ts +7 -0
- package/src/push-core/index.ts +335 -0
- package/src/qwik.tsx +602 -2
- package/src/react-marketing.tsx +5 -0
- package/src/react.tsx +434 -319
- package/src/seed-core/index.ts +735 -0
- package/src/seed.ts +7 -0
- package/src/seo-core/blog.ts +216 -0
- package/src/seo-core/commerce.ts +138 -0
- package/src/seo-core/index.ts +605 -0
- package/src/seo-core/local.ts +333 -0
- package/src/sitemap-core/index.ts +296 -0
- package/src/social-core/index.ts +103 -0
- package/src/social.ts +7 -0
- package/src/solid-compat.tsx +347 -0
- package/src/solid-surfaces.tsx +1311 -0
- package/src/solid.tsx +82 -60
- package/src/store-core/index.ts +426 -0
- package/src/store.ts +12 -0
- package/src/svelte/SocialLinks.svelte +84 -0
- package/src/svelte/headless.ts +435 -0
- package/src/svelte/index.ts +9 -0
- package/src/vanilla.ts +367 -0
- package/src/verify-core/index.ts +288 -0
- package/src/verify.ts +8 -0
- package/src/vue/headless.ts +527 -0
- package/src/vue/index.ts +3 -0
package/dist/contracts.js
CHANGED
|
@@ -417,7 +417,8 @@ export const chatbotFormFieldSchema = z
|
|
|
417
417
|
options: z.array(z.string()).optional(),
|
|
418
418
|
})
|
|
419
419
|
.passthrough();
|
|
420
|
-
export const chatbotFormDefinitionSchema = z
|
|
420
|
+
export const chatbotFormDefinitionSchema = z
|
|
421
|
+
.object({
|
|
421
422
|
id: z.string(),
|
|
422
423
|
slug: z.string(),
|
|
423
424
|
name: z.string(),
|
|
@@ -425,11 +426,33 @@ export const chatbotFormDefinitionSchema = z.object({
|
|
|
425
426
|
fields: z.array(chatbotFormFieldSchema),
|
|
426
427
|
addsInquiry: z.boolean(),
|
|
427
428
|
addsLead: z.boolean(),
|
|
428
|
-
|
|
429
|
+
/**
|
|
430
|
+
* The RENDER contract — animation, multi-step + progress style, exit
|
|
431
|
+
* confirmation, URL safety. The server has always sent this; the schema
|
|
432
|
+
* dropped it, so anyone building a form UI on the typed client got a
|
|
433
|
+
* bare field list and none of the behaviour the dashboard configured.
|
|
434
|
+
*/
|
|
435
|
+
settings: z.record(z.unknown()).nullish(),
|
|
436
|
+
/** Org avatar for the form header, when the form opts in. */
|
|
437
|
+
orgIcon: z.string().nullish(),
|
|
438
|
+
/** Public action previews ("submitting adds you to the list"). */
|
|
439
|
+
actions: z.array(z.record(z.unknown())).nullish(),
|
|
440
|
+
/** Non-sensitive business functions so `calculation` fields compute. */
|
|
441
|
+
businessFunctions: z.array(z.record(z.unknown())).nullish(),
|
|
442
|
+
})
|
|
443
|
+
// Additive server fields reach consumers instead of being silently dropped
|
|
444
|
+
// — the failure this schema just demonstrated.
|
|
445
|
+
.passthrough();
|
|
429
446
|
export const chatbotFormSubmitInputSchema = z.object({
|
|
430
447
|
data: z.record(z.unknown()),
|
|
431
448
|
submitterEmail: z.string().email().optional(),
|
|
432
449
|
submitterName: z.string().optional(),
|
|
450
|
+
/** Validate and run the full pipeline WITHOUT persisting. */
|
|
451
|
+
dryRun: z.boolean().optional(),
|
|
452
|
+
/** Provenance the route accepts and this schema previously omitted. */
|
|
453
|
+
source: z.string().max(200).optional(),
|
|
454
|
+
referrer: z.string().max(500).optional(),
|
|
455
|
+
metadata: z.record(z.unknown()).optional(),
|
|
433
456
|
priorChat: z
|
|
434
457
|
.array(z.object({
|
|
435
458
|
role: z.string().min(1).max(40),
|
|
@@ -441,8 +464,14 @@ export const chatbotFormSubmitInputSchema = z.object({
|
|
|
441
464
|
});
|
|
442
465
|
export const chatbotFormSubmitResponseSchema = z.object({
|
|
443
466
|
success: z.literal(true),
|
|
444
|
-
|
|
467
|
+
/**
|
|
468
|
+
* NULL on a dry run — the submission validated but was deliberately not
|
|
469
|
+
* persisted. Requiring a string here made every test submit throw.
|
|
470
|
+
*/
|
|
471
|
+
submissionId: z.string().nullable(),
|
|
445
472
|
slug: z.string(),
|
|
473
|
+
/** True when nothing was written. Pairs with a null `submissionId`. */
|
|
474
|
+
dryRun: z.boolean().optional(),
|
|
446
475
|
});
|
|
447
476
|
// ── Front Desk programmatic surface (Batch JJ, 0.9.0) ────────────────
|
|
448
477
|
export const chatbotPersistedSessionInputSchema = z.object({
|
|
@@ -684,6 +713,36 @@ export const customerPortalProfileSchema = z
|
|
|
684
713
|
joinedAt: timestampSchema.nullable(),
|
|
685
714
|
})
|
|
686
715
|
.nullable();
|
|
716
|
+
/**
|
|
717
|
+
* One quiz outcome for the signed-in customer. `scoreWithheld` marks an
|
|
718
|
+
* interview-mode quiz: the attempt exists, the verdict is staff-only —
|
|
719
|
+
* render "completed" and nothing else.
|
|
720
|
+
*/
|
|
721
|
+
export const customerPortalQuizResultSchema = z.object({
|
|
722
|
+
invitationId: z.string(),
|
|
723
|
+
quizName: z.string(),
|
|
724
|
+
competency: z.string().nullable(),
|
|
725
|
+
status: z.string(),
|
|
726
|
+
completedAt: z.string().nullable(),
|
|
727
|
+
scoreWithheld: z.boolean(),
|
|
728
|
+
result: z
|
|
729
|
+
.object({
|
|
730
|
+
status: z.string(),
|
|
731
|
+
scorePercent: z.number().nullable(),
|
|
732
|
+
provisionalScorePercent: z.number().nullable(),
|
|
733
|
+
passed: z.boolean().nullable(),
|
|
734
|
+
pointsEarned: z.number().nullable(),
|
|
735
|
+
pointsPossible: z.number().nullable(),
|
|
736
|
+
passingScore: z.number(),
|
|
737
|
+
results: z.array(z.record(z.unknown())),
|
|
738
|
+
submittedAt: z.string().nullable(),
|
|
739
|
+
durationSeconds: z.number().nullable(),
|
|
740
|
+
})
|
|
741
|
+
.nullable(),
|
|
742
|
+
});
|
|
743
|
+
export const customerPortalQuizResultsResponseSchema = z.object({
|
|
744
|
+
items: z.array(customerPortalQuizResultSchema),
|
|
745
|
+
});
|
|
687
746
|
export const customerPortalUpdateProfileInputSchema = z.object({
|
|
688
747
|
displayName: z.string().min(1).max(120).optional(),
|
|
689
748
|
profile: z.record(z.unknown()).optional(),
|
|
@@ -1045,6 +1104,16 @@ export const customerReturnRequestSchema = z.object({
|
|
|
1045
1104
|
* are separate enums and each is spelled as the database spells it.
|
|
1046
1105
|
*/
|
|
1047
1106
|
status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
|
|
1107
|
+
/**
|
|
1108
|
+
* What was asked for: money back, or a replacement. Mirrors
|
|
1109
|
+
* `order_return_request_kind`. Rows created before this existed read
|
|
1110
|
+
* `refund`, which is what they were.
|
|
1111
|
+
*/
|
|
1112
|
+
kind: openEnum("refund", "exchange"),
|
|
1113
|
+
/** On an exchange: the variant wanted instead, when one was chosen. */
|
|
1114
|
+
exchangeForVariantId: z.string().nullable(),
|
|
1115
|
+
/** Set once a replacement ships — track it like any other shipment. */
|
|
1116
|
+
exchangeShipmentId: z.string().nullable(),
|
|
1048
1117
|
reason: z.string().nullable(),
|
|
1049
1118
|
items: z
|
|
1050
1119
|
.array(z.object({
|
|
@@ -1078,6 +1147,16 @@ export const customerOrderDetailSchema = z.object({
|
|
|
1078
1147
|
});
|
|
1079
1148
|
/** Input for `submitReturnRequest`. Omit `items` for a whole-order request. */
|
|
1080
1149
|
export const customerReturnRequestInputSchema = z.object({
|
|
1150
|
+
/**
|
|
1151
|
+
* `refund` returns the money; `exchange` asks for a replacement. Defaults
|
|
1152
|
+
* to `refund`, so an omitted field means what it always meant.
|
|
1153
|
+
*/
|
|
1154
|
+
kind: z.enum(["refund", "exchange"]).optional(),
|
|
1155
|
+
/**
|
|
1156
|
+
* On an exchange, the variant wanted instead. Optional even then — "a
|
|
1157
|
+
* different size" is a real request that needs a conversation first.
|
|
1158
|
+
*/
|
|
1159
|
+
exchangeForVariantId: z.string().uuid().optional(),
|
|
1081
1160
|
reason: z.string().max(120).optional(),
|
|
1082
1161
|
customerNote: z.string().max(2000).optional(),
|
|
1083
1162
|
items: z
|
|
@@ -1092,6 +1171,292 @@ export const customerReturnRequestCreatedSchema = z.object({
|
|
|
1092
1171
|
id: z.string().nullable(),
|
|
1093
1172
|
/** Mirrors the `order_return_request_status` pg enum. */
|
|
1094
1173
|
status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
|
|
1174
|
+
/** Echoes back what was opened, so a UI need not assume. */
|
|
1175
|
+
kind: openEnum("refund", "exchange"),
|
|
1176
|
+
createdAt: z.string().nullable(),
|
|
1177
|
+
});
|
|
1178
|
+
/**
|
|
1179
|
+
* Result of reporting that a parcel never arrived.
|
|
1180
|
+
*
|
|
1181
|
+
* `carrierStatus` is the carrier's own verdict, UNTOUCHED by the report — the
|
|
1182
|
+
* two facts travel together on purpose. When `contested` is true the carrier
|
|
1183
|
+
* says delivered and the customer says otherwise, which is the case an org
|
|
1184
|
+
* needs to see first.
|
|
1185
|
+
*/
|
|
1186
|
+
export const customerNotReceivedResultSchema = z.object({
|
|
1187
|
+
id: z.string(),
|
|
1188
|
+
/** True when a claim was already open; the original report is unchanged. */
|
|
1189
|
+
alreadyOpen: z.boolean(),
|
|
1190
|
+
reportedAt: z.string(),
|
|
1191
|
+
carrierStatus: openEnum("pending", "label_created", "in_transit", "delivered", "returned"),
|
|
1192
|
+
carrierDeliveredAt: z.string().nullable(),
|
|
1193
|
+
contested: z.boolean().optional(),
|
|
1194
|
+
});
|
|
1195
|
+
/** How an org's legal pages are dressed. Shared by all of them. */
|
|
1196
|
+
export const legalPresentationSchema = z.object({
|
|
1197
|
+
/** Already resolved against the company logo — render as given. */
|
|
1198
|
+
logoUrl: z.string().nullable(),
|
|
1199
|
+
showLogoInHeader: z.boolean(),
|
|
1200
|
+
showLogoInFooter: z.boolean(),
|
|
1201
|
+
headerHtml: z.string().nullable(),
|
|
1202
|
+
footerHtml: z.string().nullable(),
|
|
1203
|
+
});
|
|
1204
|
+
/** A legal page in a list — enough for a footer, without the body. */
|
|
1205
|
+
export const legalDocumentSummarySchema = z.object({
|
|
1206
|
+
/** The URL path, without a leading slash: `privacy`, `terms`, … */
|
|
1207
|
+
slug: z.string(),
|
|
1208
|
+
title: z.string(),
|
|
1209
|
+
version: z.number(),
|
|
1210
|
+
/** ISO 8601. Render as "Last updated". */
|
|
1211
|
+
effectiveDate: z.string().nullable(),
|
|
1212
|
+
showInFooter: z.boolean(),
|
|
1213
|
+
updatedAt: z.string(),
|
|
1214
|
+
});
|
|
1215
|
+
export const legalListResponseSchema = z.object({
|
|
1216
|
+
items: z.array(legalDocumentSummarySchema),
|
|
1217
|
+
presentation: legalPresentationSchema,
|
|
1218
|
+
});
|
|
1219
|
+
/** One legal page, body included. */
|
|
1220
|
+
export const legalDocumentSchema = legalDocumentSummarySchema.extend({
|
|
1221
|
+
/** Sanitised server-side on write, so this is safe to render as HTML. */
|
|
1222
|
+
contentHtml: z.string(),
|
|
1223
|
+
presentation: legalPresentationSchema,
|
|
1224
|
+
});
|
|
1225
|
+
/** One browser a customer has registered for push. */
|
|
1226
|
+
export const customerPushDeviceSchema = z.object({
|
|
1227
|
+
id: z.string(),
|
|
1228
|
+
endpoint: z.string(),
|
|
1229
|
+
userAgent: z.string().nullable(),
|
|
1230
|
+
label: z.string().nullable(),
|
|
1231
|
+
/** Null = every category. `[]` = none, but the device stays registered. */
|
|
1232
|
+
categories: z.array(z.string()).nullable(),
|
|
1233
|
+
lastSeenAt: z.string().nullable(),
|
|
1234
|
+
createdAt: z.string(),
|
|
1235
|
+
});
|
|
1236
|
+
/**
|
|
1237
|
+
* Push config plus this customer's devices.
|
|
1238
|
+
*
|
|
1239
|
+
* `vapidPublicKey` rides along because a page cannot call `subscribe()` without
|
|
1240
|
+
* it, and a second round trip for one string is one nobody remembers to make.
|
|
1241
|
+
* `enabled: false` means the platform has no key configured — render nothing
|
|
1242
|
+
* rather than a button that cannot work.
|
|
1243
|
+
*/
|
|
1244
|
+
export const customerPushConfigSchema = z.object({
|
|
1245
|
+
vapidPublicKey: z.string().nullable(),
|
|
1246
|
+
enabled: z.boolean(),
|
|
1247
|
+
devices: z.array(customerPushDeviceSchema),
|
|
1248
|
+
});
|
|
1249
|
+
/** Result of handing a customer-portal invite link out again. */
|
|
1250
|
+
export const customerInviteResendResponseSchema = z.object({
|
|
1251
|
+
id: z.string(),
|
|
1252
|
+
/** The NEW link. The previous one no longer works. */
|
|
1253
|
+
url: z.string(),
|
|
1254
|
+
expiresAt: z.string().nullable(),
|
|
1255
|
+
/** Includes this send, so a list can show "sent 3 times". */
|
|
1256
|
+
sendCount: z.number(),
|
|
1257
|
+
});
|
|
1258
|
+
// ── Scheduling / conference calls ─────────────────────────────────────
|
|
1259
|
+
//
|
|
1260
|
+
// The booking surface: what an org offers, when it is free, and the three
|
|
1261
|
+
// token-gated things an invitee can do afterwards.
|
|
1262
|
+
//
|
|
1263
|
+
// `conferenceLink` is the conference-call half — a Meet/Zoom/Teams URL minted
|
|
1264
|
+
// when the event type has a provider. It is null until the booking is
|
|
1265
|
+
// confirmed, and re-minted on reschedule, so a UI must read it back after
|
|
1266
|
+
// either rather than caching the one it first saw.
|
|
1267
|
+
export const schedulingEventTypeSchema = z.object({
|
|
1268
|
+
id: z.string(),
|
|
1269
|
+
name: z.string(),
|
|
1270
|
+
slug: z.string(),
|
|
1271
|
+
description: z.string().nullable(),
|
|
1272
|
+
durationMinutes: z.number(),
|
|
1273
|
+
locationType: z.string(),
|
|
1274
|
+
/** google_meet | zoom | teams | null when the meeting is in person. */
|
|
1275
|
+
meetingProvider: z.string().nullable(),
|
|
1276
|
+
color: z.string().nullable(),
|
|
1277
|
+
/** When true a booking lands `pending`, not `confirmed`. */
|
|
1278
|
+
requiresApproval: z.boolean(),
|
|
1279
|
+
});
|
|
1280
|
+
export const schedulingQuestionSchema = z
|
|
1281
|
+
.object({
|
|
1282
|
+
id: z.string(),
|
|
1283
|
+
label: z.string(),
|
|
1284
|
+
})
|
|
1285
|
+
.passthrough();
|
|
1286
|
+
export const schedulingEventTypeListSchema = z.object({
|
|
1287
|
+
eventTypes: z.array(schedulingEventTypeSchema),
|
|
1288
|
+
});
|
|
1289
|
+
export const schedulingEventTypeDetailSchema = z.object({
|
|
1290
|
+
eventType: schedulingEventTypeSchema,
|
|
1291
|
+
questions: z.array(schedulingQuestionSchema),
|
|
1292
|
+
});
|
|
1293
|
+
export const schedulingSlotSchema = z.object({
|
|
1294
|
+
startAt: z.string(),
|
|
1295
|
+
endAt: z.string(),
|
|
1296
|
+
});
|
|
1297
|
+
export const schedulingSlotsResponseSchema = z.object({
|
|
1298
|
+
slots: z.array(schedulingSlotSchema),
|
|
1299
|
+
});
|
|
1300
|
+
export const schedulingBookingStatusSchema = openEnum("pending", "confirmed", "canceled", "rescheduled");
|
|
1301
|
+
export const schedulingBookingViewSchema = z.object({
|
|
1302
|
+
booking: z.object({
|
|
1303
|
+
id: z.string(),
|
|
1304
|
+
startAt: z.string(),
|
|
1305
|
+
endAt: z.string(),
|
|
1306
|
+
status: schedulingBookingStatusSchema,
|
|
1307
|
+
title: z.string(),
|
|
1308
|
+
notes: z.string().nullable(),
|
|
1309
|
+
/** The conference URL, once there is one. */
|
|
1310
|
+
conferenceLink: z.string().nullable(),
|
|
1311
|
+
}),
|
|
1312
|
+
invitee: z
|
|
1313
|
+
.object({ name: z.string(), email: z.string(), timezone: z.string() })
|
|
1314
|
+
.nullable(),
|
|
1315
|
+
});
|
|
1316
|
+
export const schedulingBookingResultSchema = z
|
|
1317
|
+
.object({
|
|
1318
|
+
bookingId: z.string(),
|
|
1319
|
+
status: schedulingBookingStatusSchema,
|
|
1320
|
+
/**
|
|
1321
|
+
* Three separate signed tokens, deliberately.
|
|
1322
|
+
*
|
|
1323
|
+
* A cancel link in an email must not also be able to reschedule, and
|
|
1324
|
+
* neither should read the whole booking. Handing out one token for all
|
|
1325
|
+
* three would make every emailed link as powerful as the strongest
|
|
1326
|
+
* action it enables.
|
|
1327
|
+
*/
|
|
1328
|
+
manageToken: z.string(),
|
|
1329
|
+
rescheduleToken: z.string(),
|
|
1330
|
+
cancelToken: z.string(),
|
|
1331
|
+
})
|
|
1332
|
+
.passthrough();
|
|
1333
|
+
export const schedulingRescheduleResultSchema = z
|
|
1334
|
+
.object({
|
|
1335
|
+
bookingId: z.string(),
|
|
1336
|
+
startAt: z.string(),
|
|
1337
|
+
endAt: z.string(),
|
|
1338
|
+
/** Re-minted on reschedule — read it back rather than reusing the old one. */
|
|
1339
|
+
conferenceLink: z.string().nullable().optional(),
|
|
1340
|
+
})
|
|
1341
|
+
.passthrough();
|
|
1342
|
+
export const schedulingCancelResultSchema = z
|
|
1343
|
+
.object({ ok: z.boolean() })
|
|
1344
|
+
.passthrough();
|
|
1345
|
+
/** One of the org's live subscription offerings. */
|
|
1346
|
+
export const customerSubscriptionOfferingSchema = z.object({
|
|
1347
|
+
id: z.string(),
|
|
1348
|
+
name: z.string(),
|
|
1349
|
+
description: z.string().nullable(),
|
|
1350
|
+
imageUrl: z.string().nullable(),
|
|
1351
|
+
amountCents: z.number(),
|
|
1352
|
+
interval: z.string(),
|
|
1353
|
+
});
|
|
1354
|
+
/** The customer's subscription state with one org. */
|
|
1355
|
+
export const customerSubscriptionSchema = z.object({
|
|
1356
|
+
/** active | expired | cancelled | pending | failed */
|
|
1357
|
+
status: openEnum("active", "expired", "cancelled", "pending", "failed"),
|
|
1358
|
+
/** trial | monthly_subscribed | annual_subscribed | lifetime */
|
|
1359
|
+
type: openEnum("trial", "monthly_subscribed", "annual_subscribed", "lifetime").nullable(),
|
|
1360
|
+
/**
|
|
1361
|
+
* The entitlement decision, computed server-side by the same function the
|
|
1362
|
+
* content gates use.
|
|
1363
|
+
*
|
|
1364
|
+
* **Render from this, not from `status`.** Two cases break a naive
|
|
1365
|
+
* `status === "active"` check: a lifetime purchase has no period to expire,
|
|
1366
|
+
* and a cancelled subscription keeps access until the period already paid
|
|
1367
|
+
* for runs out.
|
|
1368
|
+
*/
|
|
1369
|
+
hasAccess: z.boolean(),
|
|
1370
|
+
/**
|
|
1371
|
+
* Cancelled, but the paid period has not ended. The honest label is
|
|
1372
|
+
* "active until {expiresAt}" — not "cancelled".
|
|
1373
|
+
*/
|
|
1374
|
+
cancelledButActive: z.boolean(),
|
|
1375
|
+
startedAt: z.string().nullable(),
|
|
1376
|
+
/** End of the paid period. Null for lifetime. */
|
|
1377
|
+
expiresAt: z.string().nullable(),
|
|
1378
|
+
cancelledAt: z.string().nullable(),
|
|
1379
|
+
renewedAt: z.string().nullable(),
|
|
1380
|
+
trialEndsAt: z.string().nullable(),
|
|
1381
|
+
trialUsed: z.boolean(),
|
|
1382
|
+
provider: z.string().nullable(),
|
|
1383
|
+
/** The offering they are on, when it still exists. */
|
|
1384
|
+
plan: customerSubscriptionOfferingSchema.nullable(),
|
|
1385
|
+
/** Null means the plan includes no service calls at all. */
|
|
1386
|
+
includedServiceCallsPerPeriod: z.number().nullable(),
|
|
1387
|
+
serviceCallsUsedInPeriod: z.number(),
|
|
1388
|
+
periodStartedAt: z.string().nullable(),
|
|
1389
|
+
});
|
|
1390
|
+
export const customerSubscriptionResponseSchema = z.object({
|
|
1391
|
+
/** Null when this customer has never subscribed to this org. */
|
|
1392
|
+
subscription: customerSubscriptionSchema.nullable(),
|
|
1393
|
+
/** The org's live offerings, cheapest first. */
|
|
1394
|
+
offerings: z.array(customerSubscriptionOfferingSchema),
|
|
1395
|
+
});
|
|
1396
|
+
/** Result of cancelling — or un-cancelling — a subscription. */
|
|
1397
|
+
export const customerSubscriptionCancelResponseSchema = z.object({
|
|
1398
|
+
/** True when a cancellation is now pending at the end of the period. */
|
|
1399
|
+
cancelAtPeriodEnd: z.boolean(),
|
|
1400
|
+
resumed: z.boolean(),
|
|
1401
|
+
/**
|
|
1402
|
+
* Access continues until this moment. Say "active until {this}", because
|
|
1403
|
+
* that is what is true — cancelling ends the renewal, not the access.
|
|
1404
|
+
*/
|
|
1405
|
+
accessUntil: z.string().nullable(),
|
|
1406
|
+
/**
|
|
1407
|
+
* The stored status still reads its old value until the provider's webhook
|
|
1408
|
+
* lands. Render from `cancelAtPeriodEnd`, not from a re-read.
|
|
1409
|
+
*/
|
|
1410
|
+
statusPending: z.boolean(),
|
|
1411
|
+
});
|
|
1412
|
+
/** One piece of subscriber-gated content. */
|
|
1413
|
+
export const customerSubscriberContentItemSchema = z.object({
|
|
1414
|
+
id: z.string(),
|
|
1415
|
+
slug: z.string(),
|
|
1416
|
+
title: z.string(),
|
|
1417
|
+
/** The pitch. Travels even when locked — the BODY never does. */
|
|
1418
|
+
excerpt: z.string().nullable(),
|
|
1419
|
+
featuredImageUrl: z.string().nullable(),
|
|
1420
|
+
publishedAt: z.string().nullable(),
|
|
1421
|
+
tags: z.array(z.string()),
|
|
1422
|
+
accessLevel: z.string(),
|
|
1423
|
+
downloadAccessLevel: z.string(),
|
|
1424
|
+
/** False when the post is readable but its downloads are not. */
|
|
1425
|
+
downloadsAvailable: z.boolean(),
|
|
1426
|
+
});
|
|
1427
|
+
export const customerSubscriberContentResponseSchema = z.object({
|
|
1428
|
+
items: z.array(customerSubscriberContentItemSchema),
|
|
1429
|
+
/**
|
|
1430
|
+
* False means these are LOCKED previews, not an empty entitlement. Show
|
|
1431
|
+
* them with the offer — an empty list would hide the pitch at exactly the
|
|
1432
|
+
* moment it is most relevant.
|
|
1433
|
+
*/
|
|
1434
|
+
entitled: z.boolean(),
|
|
1435
|
+
});
|
|
1436
|
+
/** One product review the customer has written. */
|
|
1437
|
+
export const customerProductReviewSchema = z.object({
|
|
1438
|
+
id: z.string(),
|
|
1439
|
+
productId: z.string(),
|
|
1440
|
+
productName: z.string(),
|
|
1441
|
+
rating: z.number(),
|
|
1442
|
+
content: z.string(),
|
|
1443
|
+
/**
|
|
1444
|
+
* False means QUEUED for the org to approve, not rejected. Surface it as
|
|
1445
|
+
* "awaiting review" — a customer who thinks their review vanished writes it
|
|
1446
|
+
* again, or writes to support.
|
|
1447
|
+
*/
|
|
1448
|
+
isApproved: z.boolean(),
|
|
1449
|
+
createdAt: z.string(),
|
|
1450
|
+
});
|
|
1451
|
+
export const customerProductReviewListSchema = z.object({
|
|
1452
|
+
items: z.array(customerProductReviewSchema),
|
|
1453
|
+
});
|
|
1454
|
+
/** Result of submitting a product review from the portal. */
|
|
1455
|
+
export const customerProductReviewCreatedSchema = z.object({
|
|
1456
|
+
id: z.string().nullable(),
|
|
1457
|
+
isApproved: z.boolean(),
|
|
1458
|
+
/** The inverse of `isApproved`, named for what the UI should say. */
|
|
1459
|
+
awaitingModeration: z.boolean(),
|
|
1095
1460
|
createdAt: z.string().nullable(),
|
|
1096
1461
|
});
|
|
1097
1462
|
/** One downloadable digital deliverable for a paid order (P10d). */
|
|
@@ -1134,6 +1499,100 @@ export const siteBrandingSchema = z.object({
|
|
|
1134
1499
|
orgName: z.string().nullable(),
|
|
1135
1500
|
orgIcon: z.string().nullable(),
|
|
1136
1501
|
});
|
|
1502
|
+
// ── Customer portal: list endpoints (0.9.61) ──────────────────────────
|
|
1503
|
+
//
|
|
1504
|
+
// The portal had detail-by-id routes for invoices, quotes, contracts and
|
|
1505
|
+
// shipments and no way to LIST any of them, so "show me everything I owe" had
|
|
1506
|
+
// no call behind it.
|
|
1507
|
+
//
|
|
1508
|
+
// The rows reuse the existing `customer*SummarySchema` shapes rather than
|
|
1509
|
+
// defining parallel ones — those already carry the right open enums, and two
|
|
1510
|
+
// definitions of "an invoice summary" would drift.
|
|
1511
|
+
export const customerInvoiceListResponseSchema = z.object({
|
|
1512
|
+
items: z.array(customerInvoiceSummarySchema),
|
|
1513
|
+
});
|
|
1514
|
+
export const customerQuoteListResponseSchema = z.object({
|
|
1515
|
+
items: z.array(customerQuoteSummarySchema),
|
|
1516
|
+
});
|
|
1517
|
+
export const customerContractListResponseSchema = z.object({
|
|
1518
|
+
items: z.array(customerContractSummarySchema),
|
|
1519
|
+
});
|
|
1520
|
+
/** No summary shape existed for shipments, so this is the first one. */
|
|
1521
|
+
export const customerShipmentSummarySchema = z.object({
|
|
1522
|
+
id: z.string(),
|
|
1523
|
+
orderId: z.string(),
|
|
1524
|
+
orderNumber: z.string().nullable(),
|
|
1525
|
+
carrier: z.string().nullable(),
|
|
1526
|
+
trackingNumber: z.string().nullable(),
|
|
1527
|
+
trackingUrl: z.string().nullable(),
|
|
1528
|
+
serviceName: z.string().nullable(),
|
|
1529
|
+
status: z.string(),
|
|
1530
|
+
/** A date STRING, not a timestamp — the column is text. */
|
|
1531
|
+
estimatedDelivery: z.string().nullable(),
|
|
1532
|
+
shippedAt: z.string().nullable(),
|
|
1533
|
+
deliveredAt: z.string().nullable(),
|
|
1534
|
+
});
|
|
1535
|
+
export const customerShipmentListResponseSchema = z.object({
|
|
1536
|
+
items: z.array(customerShipmentSummarySchema),
|
|
1537
|
+
});
|
|
1538
|
+
// ── Customer portal: staff chat (0.9.61) ──────────────────────────────
|
|
1539
|
+
//
|
|
1540
|
+
// `org_customer_messages` has always carried a `portal` channel and staff have
|
|
1541
|
+
// always read and written it from the CRM timeline. What was missing was any
|
|
1542
|
+
// package-api route, so a customer could be messaged and had no way to read it
|
|
1543
|
+
// back or reply. Both directions land in the SAME table the staff timeline
|
|
1544
|
+
// reads — a reply appears in the CRM beside everything else about that
|
|
1545
|
+
// customer, not in a parallel inbox nobody watches.
|
|
1546
|
+
export const customerPortalMessageSchema = z.object({
|
|
1547
|
+
id: z.string(),
|
|
1548
|
+
/** `inbound` = the customer wrote it; `outbound` = staff did. */
|
|
1549
|
+
direction: openEnum("inbound", "outbound"),
|
|
1550
|
+
channel: openEnum("portal", "email", "sms"),
|
|
1551
|
+
authorName: z.string().nullable(),
|
|
1552
|
+
body: z.string(),
|
|
1553
|
+
attachment: z
|
|
1554
|
+
.object({
|
|
1555
|
+
url: z.string(),
|
|
1556
|
+
name: z.string(),
|
|
1557
|
+
mimeType: z.string(),
|
|
1558
|
+
sizeBytes: z.number(),
|
|
1559
|
+
})
|
|
1560
|
+
.nullable(),
|
|
1561
|
+
jobId: z.string().nullable(),
|
|
1562
|
+
readByCustomerAt: z.string().nullable(),
|
|
1563
|
+
createdAt: z.string(),
|
|
1564
|
+
});
|
|
1565
|
+
export const customerPortalMessageListResponseSchema = z.object({
|
|
1566
|
+
items: z.array(customerPortalMessageSchema),
|
|
1567
|
+
});
|
|
1568
|
+
export const customerPortalMessagePostInputSchema = z.object({
|
|
1569
|
+
body: z.string().min(1).max(4000),
|
|
1570
|
+
/** Thread the message under a specific job. */
|
|
1571
|
+
jobId: z.string().uuid().optional(),
|
|
1572
|
+
});
|
|
1573
|
+
export const customerPortalMessagesMarkReadResponseSchema = z.object({
|
|
1574
|
+
marked: z.number(),
|
|
1575
|
+
});
|
|
1576
|
+
// ── Customer portal: return status readback (0.9.61) ──────────────────
|
|
1577
|
+
//
|
|
1578
|
+
// Closes the audit's gap #5. The submit route existed and nothing read the
|
|
1579
|
+
// status back, so a customer could start a return and then had no way to see
|
|
1580
|
+
// what happened to it — which is the exact moment they pick up the phone.
|
|
1581
|
+
export const customerReturnRequestStatusSchema = z.object({
|
|
1582
|
+
id: z.string(),
|
|
1583
|
+
/** Mirrors the `order_return_request_status` pg enum. */
|
|
1584
|
+
status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
|
|
1585
|
+
reason: z.string().nullable(),
|
|
1586
|
+
customerNote: z.string().nullable(),
|
|
1587
|
+
/** Staff's answer. The whole point when a return is denied. */
|
|
1588
|
+
resolutionNote: z.string().nullable(),
|
|
1589
|
+
items: z.array(z.record(z.string(), z.unknown())).nullable(),
|
|
1590
|
+
resolvedAt: z.string().nullable(),
|
|
1591
|
+
createdAt: z.string(),
|
|
1592
|
+
});
|
|
1593
|
+
export const customerReturnRequestListResponseSchema = z.object({
|
|
1594
|
+
items: z.array(customerReturnRequestStatusSchema),
|
|
1595
|
+
});
|
|
1137
1596
|
// ── Customer-portal: first-party reviews ──────────────────────────────
|
|
1138
1597
|
export const customerReviewSummarySchema = z.object({
|
|
1139
1598
|
id: z.string(),
|
|
@@ -1190,7 +1649,12 @@ export const customerContactCompanyResponseSchema = z.object({
|
|
|
1190
1649
|
export const customerJobEtaAssignmentSchema = z.object({
|
|
1191
1650
|
displayName: z.string(),
|
|
1192
1651
|
role: z.string().nullable(),
|
|
1193
|
-
|
|
1652
|
+
/**
|
|
1653
|
+
* This technician's own stage. `openEnum` because the column is TEXT and
|
|
1654
|
+
* an org's board can grow a value before the SDK knows about it — an
|
|
1655
|
+
* unknown status should render as itself, not break the response.
|
|
1656
|
+
*/
|
|
1657
|
+
dispatchStatus: openEnum("pending", "en_route", "on_site", "in_progress", "completed", "declined"),
|
|
1194
1658
|
lastKnownLocation: z
|
|
1195
1659
|
.object({
|
|
1196
1660
|
lat: z.number(),
|
|
@@ -1200,6 +1664,28 @@ export const customerJobEtaAssignmentSchema = z.object({
|
|
|
1200
1664
|
.nullable(),
|
|
1201
1665
|
});
|
|
1202
1666
|
export const customerJobEtaResponseSchema = z.object({
|
|
1667
|
+
/**
|
|
1668
|
+
* Where the JOB is — promoted from the assignees by the dispatch board,
|
|
1669
|
+
* NOT the same as any one technician's status.
|
|
1670
|
+
*
|
|
1671
|
+
* The promotion rules are why this is returned rather than left to the
|
|
1672
|
+
* consumer: `en_route`/`arrived`/`started` land on the FIRST assignee to
|
|
1673
|
+
* reach them, while `completed` waits for the LAST. Aggregating that
|
|
1674
|
+
* client-side gets it wrong in the direction that tells a customer the work
|
|
1675
|
+
* is finished when one of three techs has packed up.
|
|
1676
|
+
*/
|
|
1677
|
+
dispatchStatus: openEnum("unscheduled", "scheduled", "en_route", "arrived", "started", "completed"),
|
|
1678
|
+
/**
|
|
1679
|
+
* When each stage happened. A customer reading "en route" wants to know
|
|
1680
|
+
* since when — a status with no timestamp is why people phone to ask.
|
|
1681
|
+
*/
|
|
1682
|
+
timeline: z.object({
|
|
1683
|
+
scheduledStartAt: z.string().nullable(),
|
|
1684
|
+
enRouteAt: z.string().nullable(),
|
|
1685
|
+
arrivedAt: z.string().nullable(),
|
|
1686
|
+
startedAt: z.string().nullable(),
|
|
1687
|
+
completedAt: z.string().nullable(),
|
|
1688
|
+
}),
|
|
1203
1689
|
assignments: z.array(customerJobEtaAssignmentSchema),
|
|
1204
1690
|
eta: z
|
|
1205
1691
|
.object({
|
|
@@ -1355,6 +1841,30 @@ export const storefrontProductRowSchema = z
|
|
|
1355
1841
|
/** Resolved category (name/description), when the product is categorized. */
|
|
1356
1842
|
category: storefrontProductCategorySchema.nullable().optional(),
|
|
1357
1843
|
images: z.array(z.string()).nullable().optional(),
|
|
1844
|
+
/**
|
|
1845
|
+
* Web-renderable 3D model — `.glb`, `.gltf`, `.obj` or `.stl`.
|
|
1846
|
+
*
|
|
1847
|
+
* All four fields below are ABSENT (not null) unless the org has the
|
|
1848
|
+
* `ecommerce.product_3d` add-on. Absence is the normal case: most
|
|
1849
|
+
* businesses do not sell anything with a model. Use
|
|
1850
|
+
* `@businessdash/sdk/model3d` to turn these into something renderable.
|
|
1851
|
+
*/
|
|
1852
|
+
model3dUrl: z.string().nullable().optional(),
|
|
1853
|
+
/** Apple AR Quick Look companion, `.usdz`. iOS/iPadOS/visionOS Safari. */
|
|
1854
|
+
model3dUsdzUrl: z.string().nullable().optional(),
|
|
1855
|
+
/**
|
|
1856
|
+
* True when the org attached a CAD file (`.step`/`.iges`/native).
|
|
1857
|
+
*
|
|
1858
|
+
* The URL itself is deliberately NOT here. CAD files are the largest
|
|
1859
|
+
* thing the platform serves and are the org's intellectual property, so
|
|
1860
|
+
* they are issued by `GET storefront/products/{id}/cad`, which requires
|
|
1861
|
+
* cookies, rate-limits the caller and re-checks the entitlement.
|
|
1862
|
+
* Publishing the raw URL in a payload any visitor can fetch would route
|
|
1863
|
+
* around all three.
|
|
1864
|
+
*/
|
|
1865
|
+
model3dCadAvailable: z.boolean().optional(),
|
|
1866
|
+
/** Filename for the download link — "bracket-v3.step". */
|
|
1867
|
+
model3dCadName: z.string().nullable().optional(),
|
|
1358
1868
|
createdAt: z.string(),
|
|
1359
1869
|
})
|
|
1360
1870
|
.passthrough();
|
|
@@ -1362,6 +1872,61 @@ export const storefrontListProductsResponseSchema = z.object({
|
|
|
1362
1872
|
items: z.array(storefrontProductRowSchema),
|
|
1363
1873
|
nextCursor: z.number().nullable(),
|
|
1364
1874
|
});
|
|
1875
|
+
/**
|
|
1876
|
+
* Result of pushing the catalog to the org's own Stripe account.
|
|
1877
|
+
* Per-product outcomes rather than one all-or-nothing verdict: a single bad
|
|
1878
|
+
* product shouldn't abandon a batch, and the caller needs to know which.
|
|
1879
|
+
*/
|
|
1880
|
+
export const couponSchema = z.object({
|
|
1881
|
+
id: z.string(),
|
|
1882
|
+
code: z.string(),
|
|
1883
|
+
discountType: z.string(),
|
|
1884
|
+
value: z.number(),
|
|
1885
|
+
isActive: z.boolean(),
|
|
1886
|
+
maxUses: z.number().nullable(),
|
|
1887
|
+
usedCount: z.number(),
|
|
1888
|
+
validUntil: z.string().nullable(),
|
|
1889
|
+
stripeCouponId: z.string().nullable(),
|
|
1890
|
+
stripePromotionCodeId: z.string().nullable(),
|
|
1891
|
+
});
|
|
1892
|
+
export const couponCreateInputSchema = z.object({
|
|
1893
|
+
code: z.string().min(3).max(64),
|
|
1894
|
+
discountType: z.enum(["percent", "amount"]),
|
|
1895
|
+
value: z.number().positive(),
|
|
1896
|
+
validFrom: z.coerce.date().optional(),
|
|
1897
|
+
validUntil: z.coerce.date().nullish(),
|
|
1898
|
+
maxUses: z.number().int().positive().nullish(),
|
|
1899
|
+
isActive: z.boolean().optional(),
|
|
1900
|
+
duration: z.enum(["once", "forever", "repeating"]).optional(),
|
|
1901
|
+
durationInMonths: z.number().int().positive().nullish(),
|
|
1902
|
+
appliesTo: z.enum(["order", "shipping", "both"]).optional(),
|
|
1903
|
+
});
|
|
1904
|
+
/**
|
|
1905
|
+
* `stripeSynced: false` means the coupon EXISTS locally but is not yet live in
|
|
1906
|
+
* Stripe — reported rather than thrown, so the caller can retry the sync
|
|
1907
|
+
* without re-creating the row.
|
|
1908
|
+
*/
|
|
1909
|
+
export const couponCreateResponseSchema = z.object({
|
|
1910
|
+
coupon: couponSchema,
|
|
1911
|
+
stripeSynced: z.boolean(),
|
|
1912
|
+
stripeError: z.string().nullable(),
|
|
1913
|
+
});
|
|
1914
|
+
export const couponListResponseSchema = z.object({
|
|
1915
|
+
coupons: z.array(couponSchema),
|
|
1916
|
+
});
|
|
1917
|
+
export const storefrontSyncToStripeResponseSchema = z.object({
|
|
1918
|
+
synced: z.array(z.object({
|
|
1919
|
+
productId: z.string(),
|
|
1920
|
+
stripeProductId: z.string(),
|
|
1921
|
+
/** Variant combinations that got a Stripe Price. */
|
|
1922
|
+
combinationsSynced: z.number(),
|
|
1923
|
+
})),
|
|
1924
|
+
failed: z.array(z.object({ productId: z.string(), error: z.string() })),
|
|
1925
|
+
/** Products still unsynced after this call — keep calling until 0. */
|
|
1926
|
+
remaining: z.number(),
|
|
1927
|
+
/** Connected account written to; null means the platform account. */
|
|
1928
|
+
connectedAccountId: z.string().nullable(),
|
|
1929
|
+
});
|
|
1365
1930
|
export const storefrontProductDetailSchema = storefrontProductRowSchema.and(z.object({
|
|
1366
1931
|
variants: z.array(z.record(z.string(), z.unknown())),
|
|
1367
1932
|
productImages: z.array(z.record(z.string(), z.unknown())),
|
|
@@ -1969,6 +2534,8 @@ const bundleCompanySchema = z.object({
|
|
|
1969
2534
|
});
|
|
1970
2535
|
const bundleGalleryItemSchema = z.object({
|
|
1971
2536
|
id: z.string(),
|
|
2537
|
+
/** Sent by the server on every item and dropped here until now. */
|
|
2538
|
+
description: z.string().nullish(),
|
|
1972
2539
|
type: z.enum(["image", "video"]),
|
|
1973
2540
|
src: z.string(),
|
|
1974
2541
|
width: z.number().nullable(),
|