@mohasinac/appkit 4.14.0 → 4.15.0
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/dist/_internal/server/features/auctions/actions.js +2 -2
- package/dist/_internal/server/features/cart/service.d.ts +3 -3
- package/dist/_internal/server/features/checkout/actions.js +19 -0
- package/dist/_internal/server/features/checkout/source-context.d.ts +5 -0
- package/dist/_internal/server/features/checkout/source-context.js +157 -0
- package/dist/_internal/server/features/offers/adapters.d.ts +103 -0
- package/dist/_internal/server/features/offers/adapters.js +88 -0
- package/dist/_internal/server/features/offers/index.d.ts +2 -0
- package/dist/_internal/server/features/offers/index.js +1 -0
- package/dist/_internal/server/features/orders/adapters.js +24 -0
- package/dist/_internal/server/jobs/core/offerExpiry.js +10 -10
- package/dist/_internal/shared/actions/action-registry.js +3 -1
- package/dist/_internal/shared/features/auctions/schema.d.ts +14 -2
- package/dist/_internal/shared/features/auctions/schema.js +13 -1
- package/dist/_internal/shared/features/cart/schema.d.ts +14 -9
- package/dist/_internal/shared/features/cart/schema.js +15 -0
- package/dist/_internal/shared/features/products/schema.d.ts +2 -2
- package/dist/_internal/shared/history/history.d.ts +22 -0
- package/dist/_internal/shared/history/history.js +41 -1
- package/dist/client.d.ts +13 -2
- package/dist/client.js +20 -2
- package/dist/constants/field-names.d.ts +8 -6
- package/dist/constants/field-names.js +48 -6
- package/dist/features/account/components/UserBidsView.js +12 -4
- package/dist/features/account/components/UserReturnsView.d.ts +3 -0
- package/dist/features/account/components/UserReturnsView.js +17 -1
- package/dist/features/admin/components/AdminAuditLogView.js +1 -0
- package/dist/features/admin/components/AdminBidsView.js +15 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +65 -33
- package/dist/features/admin/components/AdminFeatureEditorView.js +35 -6
- package/dist/features/admin/components/AdminNavEditorView.js +15 -5
- package/dist/features/admin/components/AdminNewsletterView.js +18 -2
- package/dist/features/admin/components/AdminOffersView.js +62 -18
- package/dist/features/admin/components/AdminPaymentMethodsView.js +24 -4
- package/dist/features/admin/components/AdminScammerEditorView.d.ts +2 -1
- package/dist/features/admin/components/AdminScammerEditorView.js +3 -13
- package/dist/features/admin/components/AdminScammersView.js +3 -8
- package/dist/features/admin/components/AdminSectionsView.js +32 -23
- package/dist/features/admin/components/AdminSessionsView.js +17 -2
- package/dist/features/admin/components/ViewAuditLogEntryModal.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.d.ts +4 -2
- package/dist/features/admin/components/sections/adminSectionsTypes.js +45 -27
- package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
- package/dist/features/admin/constants/filter-tabs.js +1 -0
- package/dist/features/admin/schemas/homepage-section-form.d.ts +22 -0
- package/dist/features/admin/schemas/homepage-section-form.js +68 -0
- package/dist/features/admin/schemas/nav-item-form.d.ts +74 -0
- package/dist/features/admin/schemas/nav-item-form.js +72 -0
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +10 -1
- package/dist/features/auctions/components/AuctionBidsTable.js +22 -4
- package/dist/features/auctions/components/bid-detail-fields.d.ts +29 -0
- package/dist/features/auctions/components/bid-detail-fields.js +103 -0
- package/dist/features/audit-log/schemas/firestore.d.ts +14 -0
- package/dist/features/audit-log/schemas/firestore.js +7 -0
- package/dist/features/blog/schemas/blog-form.d.ts +80 -0
- package/dist/features/blog/schemas/blog-form.js +100 -0
- package/dist/features/events/components/AdminEventEditorView.js +32 -54
- package/dist/features/events/components/AdminEventEntriesView.js +22 -3
- package/dist/features/events/schemas/event-form.d.ts +113 -0
- package/dist/features/events/schemas/event-form.js +82 -0
- package/dist/features/grouped/schemas/grouped-listing-form.d.ts +101 -0
- package/dist/features/grouped/schemas/grouped-listing-form.js +89 -0
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +11 -3
- package/dist/features/homepage/actions/homepage-section-actions.js +33 -1
- package/dist/features/homepage/schemas/index.d.ts +31 -0
- package/dist/features/homepage/schemas/index.js +24 -0
- package/dist/features/orders/components/OrderStatusTimeline.d.ts +5 -1
- package/dist/features/orders/components/OrderStatusTimeline.js +39 -20
- package/dist/features/orders/repository/orders.repository.js +5 -0
- package/dist/features/orders/schemas/firestore.d.ts +36 -1
- package/dist/features/orders/types/index.d.ts +35 -7
- package/dist/features/products/components/GroupSettingsPanel.js +5 -1
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/products/schemas/product-features.validators.d.ts +73 -0
- package/dist/features/products/schemas/product-features.validators.js +63 -0
- package/dist/features/scams/constants/scammer-status-display.d.ts +28 -0
- package/dist/features/scams/constants/scammer-status-display.js +79 -0
- package/dist/features/seller/actions/offer-actions.js +32 -2
- package/dist/features/seller/components/OfferPhaseTimeline.d.ts +59 -0
- package/dist/features/seller/components/OfferPhaseTimeline.js +107 -0
- package/dist/features/seller/components/SellerBidsView.js +11 -4
- package/dist/features/seller/components/SellerCouponEditorView.js +17 -17
- package/dist/features/seller/components/SellerOffersView.js +2 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +38 -14
- package/dist/features/seller/repository/offer.repository.d.ts +69 -8
- package/dist/features/seller/repository/offer.repository.js +127 -23
- package/dist/features/seller/schemas/coupon-form.d.ts +72 -0
- package/dist/features/seller/schemas/coupon-form.js +116 -0
- package/dist/features/seller/schemas/firestore.d.ts +56 -2
- package/dist/features/seller/schemas/firestore.js +27 -0
- package/dist/features/seller/schemas/offer-forms.d.ts +21 -0
- package/dist/features/seller/schemas/offer-forms.js +21 -0
- package/dist/features/shell/build-sections.d.ts +63 -0
- package/dist/features/shell/build-sections.js +207 -0
- package/dist/features/shell/field-ui-meta.d.ts +115 -0
- package/dist/features/shell/field-ui-meta.js +324 -0
- package/dist/features/shell/index.d.ts +4 -0
- package/dist/features/shell/index.js +2 -0
- package/dist/features/status-history/components/StatusTimeline.d.ts +74 -0
- package/dist/features/status-history/components/StatusTimeline.js +46 -0
- package/dist/features/status-history/index.d.ts +11 -0
- package/dist/features/status-history/index.js +10 -0
- package/dist/features/store-extensions/components/ReviewDecisionModal.d.ts +27 -0
- package/dist/features/store-extensions/components/ReviewDecisionModal.js +82 -0
- package/dist/features/store-extensions/index.d.ts +12 -0
- package/dist/features/store-extensions/index.js +12 -0
- package/dist/features/store-extensions/schemas/admin-notification-form.d.ts +36 -0
- package/dist/features/store-extensions/schemas/admin-notification-form.js +114 -0
- package/dist/features/store-extensions/schemas/analytics-forms.d.ts +57 -0
- package/dist/features/store-extensions/schemas/analytics-forms.js +158 -0
- package/dist/features/store-extensions/schemas/custom-role-form.d.ts +85 -0
- package/dist/features/store-extensions/schemas/custom-role-form.js +118 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +21 -1
- package/dist/features/store-extensions/schemas/firestore.js +60 -0
- package/dist/features/store-extensions/schemas/google-config-form.d.ts +53 -0
- package/dist/features/store-extensions/schemas/google-config-form.js +105 -0
- package/dist/features/store-extensions/schemas/item-request-create-form.d.ts +28 -0
- package/dist/features/store-extensions/schemas/item-request-create-form.js +80 -0
- package/dist/features/store-extensions/schemas/listing-template-form.d.ts +78 -0
- package/dist/features/store-extensions/schemas/listing-template-form.js +87 -0
- package/dist/features/store-extensions/schemas/moderation-review-form.d.ts +49 -0
- package/dist/features/store-extensions/schemas/moderation-review-form.js +101 -0
- package/dist/features/store-extensions/schemas/payout-method-form.d.ts +153 -0
- package/dist/features/store-extensions/schemas/payout-method-form.js +155 -0
- package/dist/features/store-extensions/schemas/report-create-form.d.ts +25 -0
- package/dist/features/store-extensions/schemas/report-create-form.js +79 -0
- package/dist/features/store-extensions/schemas/report-review-form.d.ts +47 -0
- package/dist/features/store-extensions/schemas/report-review-form.js +111 -0
- package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +148 -0
- package/dist/features/store-extensions/schemas/shipping-config-form.js +104 -0
- package/dist/features/store-extensions/schemas/store-category-form.d.ts +54 -0
- package/dist/features/store-extensions/schemas/store-category-form.js +73 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +326 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +12 -2
- package/dist/next/routing/route-map.d.ts +16 -2
- package/dist/next/routing/route-map.js +9 -2
- package/dist/schemas/firestore-value.d.ts +8 -0
- package/dist/schemas/firestore-value.js +44 -0
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +1 -1
- package/dist/schemas/registry.d.ts +634 -1
- package/dist/schemas/registry.js +64 -1
- package/dist/seed/bids-seed-data.js +30 -0
- package/dist/seed/offers-seed-data.js +212 -7
- package/dist/seed/orders-seed-data.js +336 -1
- package/dist/seed/products-auctions-seed-data.js +41 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +5 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/RecordDetailModal.d.ts +10 -1
- package/dist/ui/components/RecordDetailModal.js +2 -2
- package/package.json +2 -1
- package/scripts/node-esm-loader.mjs +65 -0
- package/scripts/seed-cli-loader.mjs +6 -49
- package/scripts/seed-cli.mjs +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* ## `.passthrough()` STAYS here, deliberately
|
|
4
|
+
*
|
|
5
|
+
* `EventDraft` carries ~40 fields; this schema names seven. The blog form's
|
|
6
|
+
* equivalent was safe to tighten because its draft had fourteen fields and all
|
|
7
|
+
* fourteen could be named in one sitting. This one cannot: most of its fields
|
|
8
|
+
* are per-event-type (poll options, survey fields, spin prizes, raffle config)
|
|
9
|
+
* and only apply under a `type` discriminator, so naming them correctly means
|
|
10
|
+
* modelling the discriminated union rather than listing keys.
|
|
11
|
+
*
|
|
12
|
+
* Tightening it blind is the single most dangerous edit available in this
|
|
13
|
+
* codebase — `z.object()` strips unknown keys, so every field this schema
|
|
14
|
+
* failed to name would vanish from the draft the moment passthrough came off.
|
|
15
|
+
* That is exactly how `productBaseSchema` came to eat every `classified*` /
|
|
16
|
+
* `digitalCode*` / `liveItem*` / `prize*` field.
|
|
17
|
+
*
|
|
18
|
+
* The wire payload is NOT at risk either way: it is built explicitly below
|
|
19
|
+
* (`type`, `title`, `slug`, … plus `buildEventTypeConfig` /
|
|
20
|
+
* `buildEventRaffleFields`), never by spreading the draft.
|
|
21
|
+
*
|
|
22
|
+
* Prerequisite for removing it: a FULL-collection `roundtrip-diff --entity
|
|
23
|
+
* event --full`, not a sample — per the data-integrity protocol.
|
|
24
|
+
*
|
|
25
|
+
* 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
|
|
26
|
+
* by schema instance and every zod wrapper returns a new one.
|
|
27
|
+
*/
|
|
28
|
+
export declare const eventDraftSchema: z.ZodEffects<z.ZodObject<{
|
|
29
|
+
type: z.ZodString;
|
|
30
|
+
title: z.ZodString;
|
|
31
|
+
startsAt: z.ZodString;
|
|
32
|
+
endsAt: z.ZodString;
|
|
33
|
+
couponId: z.ZodString;
|
|
34
|
+
displayCode: z.ZodString;
|
|
35
|
+
pollOptions: z.ZodArray<z.ZodObject<{
|
|
36
|
+
id: z.ZodString;
|
|
37
|
+
label: z.ZodString;
|
|
38
|
+
}, "strip", z.ZodTypeAny, {
|
|
39
|
+
id: string;
|
|
40
|
+
label: string;
|
|
41
|
+
}, {
|
|
42
|
+
id: string;
|
|
43
|
+
label: string;
|
|
44
|
+
}>, "many">;
|
|
45
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
46
|
+
type: z.ZodString;
|
|
47
|
+
title: z.ZodString;
|
|
48
|
+
startsAt: z.ZodString;
|
|
49
|
+
endsAt: z.ZodString;
|
|
50
|
+
couponId: z.ZodString;
|
|
51
|
+
displayCode: z.ZodString;
|
|
52
|
+
pollOptions: z.ZodArray<z.ZodObject<{
|
|
53
|
+
id: z.ZodString;
|
|
54
|
+
label: z.ZodString;
|
|
55
|
+
}, "strip", z.ZodTypeAny, {
|
|
56
|
+
id: string;
|
|
57
|
+
label: string;
|
|
58
|
+
}, {
|
|
59
|
+
id: string;
|
|
60
|
+
label: string;
|
|
61
|
+
}>, "many">;
|
|
62
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
63
|
+
type: z.ZodString;
|
|
64
|
+
title: z.ZodString;
|
|
65
|
+
startsAt: z.ZodString;
|
|
66
|
+
endsAt: z.ZodString;
|
|
67
|
+
couponId: z.ZodString;
|
|
68
|
+
displayCode: z.ZodString;
|
|
69
|
+
pollOptions: z.ZodArray<z.ZodObject<{
|
|
70
|
+
id: z.ZodString;
|
|
71
|
+
label: z.ZodString;
|
|
72
|
+
}, "strip", z.ZodTypeAny, {
|
|
73
|
+
id: string;
|
|
74
|
+
label: string;
|
|
75
|
+
}, {
|
|
76
|
+
id: string;
|
|
77
|
+
label: string;
|
|
78
|
+
}>, "many">;
|
|
79
|
+
}, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{
|
|
80
|
+
type: z.ZodString;
|
|
81
|
+
title: z.ZodString;
|
|
82
|
+
startsAt: z.ZodString;
|
|
83
|
+
endsAt: z.ZodString;
|
|
84
|
+
couponId: z.ZodString;
|
|
85
|
+
displayCode: z.ZodString;
|
|
86
|
+
pollOptions: z.ZodArray<z.ZodObject<{
|
|
87
|
+
id: z.ZodString;
|
|
88
|
+
label: z.ZodString;
|
|
89
|
+
}, "strip", z.ZodTypeAny, {
|
|
90
|
+
id: string;
|
|
91
|
+
label: string;
|
|
92
|
+
}, {
|
|
93
|
+
id: string;
|
|
94
|
+
label: string;
|
|
95
|
+
}>, "many">;
|
|
96
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
97
|
+
type: z.ZodString;
|
|
98
|
+
title: z.ZodString;
|
|
99
|
+
startsAt: z.ZodString;
|
|
100
|
+
endsAt: z.ZodString;
|
|
101
|
+
couponId: z.ZodString;
|
|
102
|
+
displayCode: z.ZodString;
|
|
103
|
+
pollOptions: z.ZodArray<z.ZodObject<{
|
|
104
|
+
id: z.ZodString;
|
|
105
|
+
label: z.ZodString;
|
|
106
|
+
}, "strip", z.ZodTypeAny, {
|
|
107
|
+
id: string;
|
|
108
|
+
label: string;
|
|
109
|
+
}, {
|
|
110
|
+
id: string;
|
|
111
|
+
label: string;
|
|
112
|
+
}>, "many">;
|
|
113
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: Same reason as the blog form schema — keeping it in the `.tsx` view
|
|
3
|
+
* would have dragged a React component tree into every API route through
|
|
4
|
+
* `SCHEMAS` -> `routeHandler`.
|
|
5
|
+
* WHAT: The event editor's form schema, with its UI metadata.
|
|
6
|
+
*
|
|
7
|
+
* EXPORTS:
|
|
8
|
+
* eventDraftSchema
|
|
9
|
+
*
|
|
10
|
+
* @tag domain:events
|
|
11
|
+
* @tag layer:schema
|
|
12
|
+
* @tag pattern:none
|
|
13
|
+
* @tag access:isomorphic
|
|
14
|
+
* @tag consumers:AdminEventEditorView,SCHEMAS.forms
|
|
15
|
+
* @tag sideEffects:none
|
|
16
|
+
*/
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { annotate } from "../../shell/field-ui-meta";
|
|
19
|
+
/**
|
|
20
|
+
* ## `.passthrough()` STAYS here, deliberately
|
|
21
|
+
*
|
|
22
|
+
* `EventDraft` carries ~40 fields; this schema names seven. The blog form's
|
|
23
|
+
* equivalent was safe to tighten because its draft had fourteen fields and all
|
|
24
|
+
* fourteen could be named in one sitting. This one cannot: most of its fields
|
|
25
|
+
* are per-event-type (poll options, survey fields, spin prizes, raffle config)
|
|
26
|
+
* and only apply under a `type` discriminator, so naming them correctly means
|
|
27
|
+
* modelling the discriminated union rather than listing keys.
|
|
28
|
+
*
|
|
29
|
+
* Tightening it blind is the single most dangerous edit available in this
|
|
30
|
+
* codebase — `z.object()` strips unknown keys, so every field this schema
|
|
31
|
+
* failed to name would vanish from the draft the moment passthrough came off.
|
|
32
|
+
* That is exactly how `productBaseSchema` came to eat every `classified*` /
|
|
33
|
+
* `digitalCode*` / `liveItem*` / `prize*` field.
|
|
34
|
+
*
|
|
35
|
+
* The wire payload is NOT at risk either way: it is built explicitly below
|
|
36
|
+
* (`type`, `title`, `slug`, … plus `buildEventTypeConfig` /
|
|
37
|
+
* `buildEventRaffleFields`), never by spreading the draft.
|
|
38
|
+
*
|
|
39
|
+
* Prerequisite for removing it: a FULL-collection `roundtrip-diff --entity
|
|
40
|
+
* event --full`, not a sample — per the data-integrity protocol.
|
|
41
|
+
*
|
|
42
|
+
* 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
|
|
43
|
+
* by schema instance and every zod wrapper returns a new one.
|
|
44
|
+
*/
|
|
45
|
+
export const eventDraftSchema = z.object({
|
|
46
|
+
type: annotate(z.string(), {
|
|
47
|
+
section: "details", sectionLabel: "Details", sectionRequired: true,
|
|
48
|
+
quick: true, order: 1, row: "pair", kind: "select",
|
|
49
|
+
}),
|
|
50
|
+
title: annotate(z.string().min(1, "Title is required"), {
|
|
51
|
+
section: "details", quick: true, order: 2, row: "full",
|
|
52
|
+
}),
|
|
53
|
+
startsAt: annotate(z.string().min(1, "Start date is required"), {
|
|
54
|
+
section: "details", quick: true, order: 3, row: "pair", kind: "date",
|
|
55
|
+
}),
|
|
56
|
+
endsAt: annotate(z.string().min(1, "End date is required"), {
|
|
57
|
+
section: "details", quick: true, order: 4, row: "pair", kind: "date",
|
|
58
|
+
}),
|
|
59
|
+
couponId: annotate(z.string(), {
|
|
60
|
+
section: "settings", sectionLabel: "Settings", order: 1, row: "pair",
|
|
61
|
+
help: "Required for offer events.",
|
|
62
|
+
}),
|
|
63
|
+
displayCode: annotate(z.string(), {
|
|
64
|
+
section: "settings", order: 2, row: "pair",
|
|
65
|
+
help: "Required for offer events.",
|
|
66
|
+
}),
|
|
67
|
+
pollOptions: annotate(z.array(z.object({ id: z.string(), label: z.string() })), {
|
|
68
|
+
section: "settings", order: 3, row: "full", kind: "list",
|
|
69
|
+
}),
|
|
70
|
+
}).passthrough().superRefine((v, ctx) => {
|
|
71
|
+
if (v.type === "offer") {
|
|
72
|
+
if (!v.couponId.trim()) {
|
|
73
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["couponId"], message: "Coupon is required for offer events" });
|
|
74
|
+
}
|
|
75
|
+
if (!v.displayCode.trim()) {
|
|
76
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["displayCode"], message: "Display code is required for offer events" });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (v.type === "poll" && v.pollOptions.filter((o) => o.label.trim()).length < 2) {
|
|
80
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["pollOptions"], message: "Poll events require at least 2 options" });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
|
|
4
|
+
* by schema instance and every zod wrapper returns a new one.
|
|
5
|
+
*/
|
|
6
|
+
export declare const groupedListingFormSchema: z.ZodObject<{
|
|
7
|
+
title: z.ZodString;
|
|
8
|
+
description: z.ZodOptional<z.ZodString>;
|
|
9
|
+
groupTheme: z.ZodEnum<["related", "character", "lineage", "set", "generic"]>;
|
|
10
|
+
productIds: z.ZodArray<z.ZodString, "many">;
|
|
11
|
+
minActiveMembers: z.ZodNumber;
|
|
12
|
+
coverImage: z.ZodOptional<z.ZodString>;
|
|
13
|
+
isActive: z.ZodBoolean;
|
|
14
|
+
isFeatured: z.ZodBoolean;
|
|
15
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
title: string;
|
|
17
|
+
isActive: boolean;
|
|
18
|
+
isFeatured: boolean;
|
|
19
|
+
productIds: string[];
|
|
20
|
+
groupTheme: "set" | "related" | "character" | "lineage" | "generic";
|
|
21
|
+
minActiveMembers: number;
|
|
22
|
+
description?: string | undefined;
|
|
23
|
+
coverImage?: string | undefined;
|
|
24
|
+
}, {
|
|
25
|
+
title: string;
|
|
26
|
+
isActive: boolean;
|
|
27
|
+
isFeatured: boolean;
|
|
28
|
+
productIds: string[];
|
|
29
|
+
groupTheme: "set" | "related" | "character" | "lineage" | "generic";
|
|
30
|
+
minActiveMembers: number;
|
|
31
|
+
description?: string | undefined;
|
|
32
|
+
coverImage?: string | undefined;
|
|
33
|
+
}>;
|
|
34
|
+
export type GroupedListingFormValues = z.infer<typeof groupedListingFormSchema>;
|
|
35
|
+
/**
|
|
36
|
+
* Create contract. `storeId` is absent — the store route sets it from the
|
|
37
|
+
* session, so a seller cannot file a group under another store.
|
|
38
|
+
*/
|
|
39
|
+
export declare const groupedListingCreateSchema: z.ZodObject<{
|
|
40
|
+
title: z.ZodString;
|
|
41
|
+
description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
42
|
+
isActive: z.ZodBoolean;
|
|
43
|
+
isFeatured: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
coverImage: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
45
|
+
productIds: z.ZodArray<z.ZodString, "many">;
|
|
46
|
+
groupTheme: z.ZodEnum<["related", "character", "lineage", "set", "generic"]>;
|
|
47
|
+
minActiveMembers: z.ZodOptional<z.ZodNumber>;
|
|
48
|
+
}, "strip", z.ZodTypeAny, {
|
|
49
|
+
title: string;
|
|
50
|
+
isActive: boolean;
|
|
51
|
+
productIds: string[];
|
|
52
|
+
groupTheme: "set" | "related" | "character" | "lineage" | "generic";
|
|
53
|
+
description?: string | undefined;
|
|
54
|
+
isFeatured?: boolean | undefined;
|
|
55
|
+
coverImage?: string | undefined;
|
|
56
|
+
minActiveMembers?: number | undefined;
|
|
57
|
+
}, {
|
|
58
|
+
title: string;
|
|
59
|
+
isActive: boolean;
|
|
60
|
+
productIds: string[];
|
|
61
|
+
groupTheme: "set" | "related" | "character" | "lineage" | "generic";
|
|
62
|
+
description?: string | undefined;
|
|
63
|
+
isFeatured?: boolean | undefined;
|
|
64
|
+
coverImage?: string | undefined;
|
|
65
|
+
minActiveMembers?: number | undefined;
|
|
66
|
+
}>;
|
|
67
|
+
/** Update contract — partial, `.strict()` so an unknown key is a 400. */
|
|
68
|
+
export declare const groupedListingUpdateSchema: z.ZodObject<{
|
|
69
|
+
title: z.ZodOptional<z.ZodString>;
|
|
70
|
+
description: z.ZodOptional<z.ZodString>;
|
|
71
|
+
groupTheme: z.ZodOptional<z.ZodEnum<["related", "character", "lineage", "set", "generic"]>>;
|
|
72
|
+
productIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
73
|
+
minActiveMembers: z.ZodOptional<z.ZodNumber>;
|
|
74
|
+
coverImage: z.ZodOptional<z.ZodString>;
|
|
75
|
+
isActive: z.ZodOptional<z.ZodBoolean>;
|
|
76
|
+
isFeatured: z.ZodOptional<z.ZodBoolean>;
|
|
77
|
+
brandSlug: z.ZodOptional<z.ZodString>;
|
|
78
|
+
categorySlug: z.ZodOptional<z.ZodString>;
|
|
79
|
+
}, "strict", z.ZodTypeAny, {
|
|
80
|
+
title?: string | undefined;
|
|
81
|
+
description?: string | undefined;
|
|
82
|
+
categorySlug?: string | undefined;
|
|
83
|
+
brandSlug?: string | undefined;
|
|
84
|
+
isActive?: boolean | undefined;
|
|
85
|
+
isFeatured?: boolean | undefined;
|
|
86
|
+
coverImage?: string | undefined;
|
|
87
|
+
productIds?: string[] | undefined;
|
|
88
|
+
groupTheme?: "set" | "related" | "character" | "lineage" | "generic" | undefined;
|
|
89
|
+
minActiveMembers?: number | undefined;
|
|
90
|
+
}, {
|
|
91
|
+
title?: string | undefined;
|
|
92
|
+
description?: string | undefined;
|
|
93
|
+
categorySlug?: string | undefined;
|
|
94
|
+
brandSlug?: string | undefined;
|
|
95
|
+
isActive?: boolean | undefined;
|
|
96
|
+
isFeatured?: boolean | undefined;
|
|
97
|
+
coverImage?: string | undefined;
|
|
98
|
+
productIds?: string[] | undefined;
|
|
99
|
+
groupTheme?: "set" | "related" | "character" | "lineage" | "generic" | undefined;
|
|
100
|
+
minActiveMembers?: number | undefined;
|
|
101
|
+
}>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: `POST /api/store/grouped-listings` spread the raw request body into
|
|
3
|
+
* `.create()` and its PATCH built `{...body}` — no Zod on either. The two
|
|
4
|
+
* pages behind them validated one field between them (`new` checked
|
|
5
|
+
* `title` was non-empty and toasted; `[id]/edit` checked nothing at all).
|
|
6
|
+
* WHAT: One schema for the pages AND the store routes.
|
|
7
|
+
*
|
|
8
|
+
* ## `activeMemberCount` is NOT in this schema
|
|
9
|
+
*
|
|
10
|
+
* It is derived — both routes already recompute it from `productIds.length`,
|
|
11
|
+
* which is correct and was verified rather than assumed. Accepting it from the
|
|
12
|
+
* body would let a caller set a count that disagrees with the array, and the
|
|
13
|
+
* count is what the public visibility check reads.
|
|
14
|
+
*
|
|
15
|
+
* `slug`, `visibilityStatus` and `createdBy` are absent for the same reason:
|
|
16
|
+
* derived or session-owned, never typed.
|
|
17
|
+
*
|
|
18
|
+
* EXPORTS:
|
|
19
|
+
* groupedListingFormSchema, groupedListingCreateSchema,
|
|
20
|
+
* groupedListingUpdateSchema, type GroupedListingFormValues
|
|
21
|
+
*
|
|
22
|
+
* @tag domain:grouped
|
|
23
|
+
* @tag layer:schema
|
|
24
|
+
* @tag pattern:none
|
|
25
|
+
* @tag access:isomorphic
|
|
26
|
+
* @tag consumers:store/grouped-listings pages,/api/store/grouped-listings
|
|
27
|
+
* @tag sideEffects:none
|
|
28
|
+
*/
|
|
29
|
+
import { z } from "zod";
|
|
30
|
+
import { annotate } from "../../shell/field-ui-meta";
|
|
31
|
+
/** Mirrors `GroupTheme` in ./firestore. */
|
|
32
|
+
const groupThemeSchema = z.enum(["related", "character", "lineage", "set", "generic"]);
|
|
33
|
+
/**
|
|
34
|
+
* 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
|
|
35
|
+
* by schema instance and every zod wrapper returns a new one.
|
|
36
|
+
*/
|
|
37
|
+
export const groupedListingFormSchema = z.object({
|
|
38
|
+
title: annotate(z.string().min(1, "Title is required").max(120, "Keep the title under 120 characters."), {
|
|
39
|
+
section: "basics", sectionLabel: "Group", sectionRequired: true,
|
|
40
|
+
quick: true, order: 1, row: "full",
|
|
41
|
+
}),
|
|
42
|
+
description: annotate(z.string().max(500, "Keep the description under 500 characters.").optional(), {
|
|
43
|
+
section: "basics", order: 2, row: "full",
|
|
44
|
+
}),
|
|
45
|
+
groupTheme: annotate(groupThemeSchema, {
|
|
46
|
+
section: "basics", quick: true, order: 3, row: "pair",
|
|
47
|
+
}),
|
|
48
|
+
productIds: annotate(z.array(z.string()), {
|
|
49
|
+
section: "members", sectionLabel: "Members", order: 1, row: "full", kind: "list",
|
|
50
|
+
}),
|
|
51
|
+
minActiveMembers: annotate(z.coerce.number().int("Must be a whole number.").min(1, "A group needs at least one member to show."), {
|
|
52
|
+
section: "members", order: 2, row: "pair",
|
|
53
|
+
help: "Below this many in-stock members, the group hides itself.",
|
|
54
|
+
}),
|
|
55
|
+
coverImage: annotate(z.string().optional(), {
|
|
56
|
+
section: "media", sectionLabel: "Media", order: 1, row: "full", kind: "media",
|
|
57
|
+
}),
|
|
58
|
+
isActive: annotate(z.boolean(), {
|
|
59
|
+
section: "visibility", sectionLabel: "Visibility", order: 1, row: "quarter",
|
|
60
|
+
}),
|
|
61
|
+
isFeatured: annotate(z.boolean(), {
|
|
62
|
+
section: "visibility", order: 2, row: "quarter",
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* Create contract. `storeId` is absent — the store route sets it from the
|
|
67
|
+
* session, so a seller cannot file a group under another store.
|
|
68
|
+
*/
|
|
69
|
+
export const groupedListingCreateSchema = groupedListingFormSchema.partial({
|
|
70
|
+
description: true,
|
|
71
|
+
coverImage: true,
|
|
72
|
+
isFeatured: true,
|
|
73
|
+
minActiveMembers: true,
|
|
74
|
+
});
|
|
75
|
+
/** Update contract — partial, `.strict()` so an unknown key is a 400. */
|
|
76
|
+
export const groupedListingUpdateSchema = z
|
|
77
|
+
.object({
|
|
78
|
+
title: z.string().min(1).max(120).optional(),
|
|
79
|
+
description: z.string().max(500).optional(),
|
|
80
|
+
groupTheme: groupThemeSchema.optional(),
|
|
81
|
+
productIds: z.array(z.string()).optional(),
|
|
82
|
+
minActiveMembers: z.coerce.number().int().min(1).optional(),
|
|
83
|
+
coverImage: z.string().optional(),
|
|
84
|
+
isActive: z.boolean().optional(),
|
|
85
|
+
isFeatured: z.boolean().optional(),
|
|
86
|
+
brandSlug: z.string().optional(),
|
|
87
|
+
categorySlug: z.string().optional(),
|
|
88
|
+
})
|
|
89
|
+
.strict();
|
|
@@ -2,21 +2,29 @@ import { z } from "zod";
|
|
|
2
2
|
import type { HomepageSectionDocument } from "../schemas";
|
|
3
3
|
import type { FirebaseSieveResult } from "../../../providers/db-firebase";
|
|
4
4
|
export declare const createSectionSchema: z.ZodObject<{
|
|
5
|
-
type: z.
|
|
5
|
+
type: z.ZodEnum<["welcome", "carousel", "stats", "trust-indicators", "categories", "brands", "products", "pre-orders", "auctions", "banner", "features", "reviews", "whatsapp-community", "faq", "blog-articles", "newsletter", "stores", "events", "social-feed", "custom-cards", "google-reviews", "featured-bundles", "prize-draws", "event-raffles", "collection-cards"]>;
|
|
6
6
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
7
7
|
order: z.ZodDefault<z.ZodNumber>;
|
|
8
8
|
config: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
|
|
9
9
|
}, "strip", z.ZodTypeAny, {
|
|
10
|
-
type:
|
|
10
|
+
type: "banner" | "stores" | "products" | "pre-orders" | "prize-draws" | "auctions" | "reviews" | "events" | "categories" | "brands" | "carousel" | "newsletter" | "features" | "stats" | "welcome" | "trust-indicators" | "whatsapp-community" | "faq" | "blog-articles" | "social-feed" | "custom-cards" | "google-reviews" | "featured-bundles" | "event-raffles" | "collection-cards";
|
|
11
11
|
enabled: boolean;
|
|
12
12
|
order: number;
|
|
13
13
|
config?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
14
14
|
}, {
|
|
15
|
-
type:
|
|
15
|
+
type: "banner" | "stores" | "products" | "pre-orders" | "prize-draws" | "auctions" | "reviews" | "events" | "categories" | "brands" | "carousel" | "newsletter" | "features" | "stats" | "welcome" | "trust-indicators" | "whatsapp-community" | "faq" | "blog-articles" | "social-feed" | "custom-cards" | "google-reviews" | "featured-bundles" | "event-raffles" | "collection-cards";
|
|
16
16
|
enabled?: boolean | undefined;
|
|
17
17
|
order?: number | undefined;
|
|
18
18
|
config?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
19
19
|
}>;
|
|
20
|
+
/**
|
|
21
|
+
* The next free `order` value.
|
|
22
|
+
*
|
|
23
|
+
* `HomepageSectionCreateInput.order` is REQUIRED, and both API create routes
|
|
24
|
+
* need the same answer for "the caller did not choose one". Two copies of this
|
|
25
|
+
* query is how they would come to disagree about where a new section lands.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveNextSectionOrder(): Promise<number>;
|
|
20
28
|
export declare const updateSectionSchema: z.ZodObject<{
|
|
21
29
|
order: z.ZodOptional<z.ZodNumber>;
|
|
22
30
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { serverLogger } from "../../../monitoring";
|
|
3
|
+
import { sortBy } from "../../../constants/sort";
|
|
4
|
+
import { HOMEPAGE_SECTION_FIELDS } from "../../../constants/field-names";
|
|
3
5
|
import { homepageSectionsRepository } from "../repository/homepage-sections.repository";
|
|
6
|
+
import { homepageSectionTypeSchema } from "../schemas";
|
|
4
7
|
// --- Schemas --------------------------------------------------------------
|
|
8
|
+
/*
|
|
9
|
+
* 🛑 `type` was `z.string().min(1)` — ANY non-empty string.
|
|
10
|
+
*
|
|
11
|
+
* This is the third of three create paths for `homepageSections`
|
|
12
|
+
* (`POST /api/admin/sections`, `POST /api/homepage-sections`, and this server
|
|
13
|
+
* action via `createHomepageSectionAction`), and it was the loosest: a caller
|
|
14
|
+
* could store a section whose `type` matches no renderer, which the homepage
|
|
15
|
+
* then skips in silence — a row that exists, occupies an order slot, and draws
|
|
16
|
+
* nothing.
|
|
17
|
+
*
|
|
18
|
+
* All three now agree on `homepageSectionTypeSchema`, which is derived from
|
|
19
|
+
* the `SectionType` union.
|
|
20
|
+
*/
|
|
5
21
|
export const createSectionSchema = z.object({
|
|
6
|
-
type:
|
|
22
|
+
type: homepageSectionTypeSchema,
|
|
7
23
|
enabled: z.boolean().default(true),
|
|
8
24
|
order: z.number().int().default(0),
|
|
9
25
|
config: z.object({}).passthrough().optional(),
|
|
10
26
|
});
|
|
27
|
+
/**
|
|
28
|
+
* The next free `order` value.
|
|
29
|
+
*
|
|
30
|
+
* `HomepageSectionCreateInput.order` is REQUIRED, and both API create routes
|
|
31
|
+
* need the same answer for "the caller did not choose one". Two copies of this
|
|
32
|
+
* query is how they would come to disagree about where a new section lands.
|
|
33
|
+
*/
|
|
34
|
+
export async function resolveNextSectionOrder() {
|
|
35
|
+
const latest = await homepageSectionsRepository.list({
|
|
36
|
+
sorts: sortBy(HOMEPAGE_SECTION_FIELDS.ORDER),
|
|
37
|
+
page: "1",
|
|
38
|
+
pageSize: "1",
|
|
39
|
+
});
|
|
40
|
+
const top = latest.items[0]?.order;
|
|
41
|
+
return typeof top === "number" ? top + 1 : 1;
|
|
42
|
+
}
|
|
11
43
|
export const updateSectionSchema = z.object({
|
|
12
44
|
order: z.number().int().optional(),
|
|
13
45
|
enabled: z.boolean().optional(),
|
|
@@ -47,6 +47,37 @@ export declare const homepageSectionFirestoreSchema: z.ZodObject<{
|
|
|
47
47
|
order: number;
|
|
48
48
|
config: Record<string, JsonValue>;
|
|
49
49
|
}>;
|
|
50
|
+
/**
|
|
51
|
+
* The create contract for a homepage section, shared by BOTH routes that can
|
|
52
|
+
* create one — `POST /api/admin/sections` (what the editor calls) and
|
|
53
|
+
* `POST /api/homepage-sections` (reachable by URL only).
|
|
54
|
+
*
|
|
55
|
+
* It exists because those two had drifted into being one validated path and
|
|
56
|
+
* one raw `{...body}` spread, and because the validated one's hand-written
|
|
57
|
+
* type list had fallen four values behind `SectionType` — so an admin could
|
|
58
|
+
* configure a Featured Bundles / Prize Draws / Event Raffles / Collection
|
|
59
|
+
* Cards section in full and get a 400 on save.
|
|
60
|
+
*
|
|
61
|
+
* `type` comes from `homepageSectionTypeSchema`, which is derived from the
|
|
62
|
+
* union, so that gap cannot reopen.
|
|
63
|
+
*/
|
|
64
|
+
export declare const homepageSectionCreateSchema: z.ZodObject<{
|
|
65
|
+
type: z.ZodEnum<["welcome", "carousel", "stats", "trust-indicators", "categories", "brands", "products", "pre-orders", "auctions", "banner", "features", "reviews", "whatsapp-community", "faq", "blog-articles", "newsletter", "stores", "events", "social-feed", "custom-cards", "google-reviews", "featured-bundles", "prize-draws", "event-raffles", "collection-cards"]>;
|
|
66
|
+
enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
67
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
config: z.ZodRecord<z.ZodString, z.ZodType<JsonValue, z.ZodTypeDef, JsonValue>>;
|
|
69
|
+
}, "strip", z.ZodTypeAny, {
|
|
70
|
+
type: "banner" | "stores" | "products" | "pre-orders" | "prize-draws" | "auctions" | "reviews" | "events" | "categories" | "brands" | "carousel" | "newsletter" | "features" | "stats" | "welcome" | "trust-indicators" | "whatsapp-community" | "faq" | "blog-articles" | "social-feed" | "custom-cards" | "google-reviews" | "featured-bundles" | "event-raffles" | "collection-cards";
|
|
71
|
+
enabled: boolean;
|
|
72
|
+
config: Record<string, JsonValue>;
|
|
73
|
+
order?: number | undefined;
|
|
74
|
+
}, {
|
|
75
|
+
type: "banner" | "stores" | "products" | "pre-orders" | "prize-draws" | "auctions" | "reviews" | "events" | "categories" | "brands" | "carousel" | "newsletter" | "features" | "stats" | "welcome" | "trust-indicators" | "whatsapp-community" | "faq" | "blog-articles" | "social-feed" | "custom-cards" | "google-reviews" | "featured-bundles" | "event-raffles" | "collection-cards";
|
|
76
|
+
config: Record<string, JsonValue>;
|
|
77
|
+
enabled?: boolean | undefined;
|
|
78
|
+
order?: number | undefined;
|
|
79
|
+
}>;
|
|
80
|
+
export type HomepageSectionCreateValues = z.infer<typeof homepageSectionCreateSchema>;
|
|
50
81
|
export declare const carouselBackgroundSchema: z.ZodObject<{
|
|
51
82
|
type: z.ZodEnum<["image", "video", "color", "gradient"]>;
|
|
52
83
|
url: z.ZodOptional<z.ZodString>;
|
|
@@ -56,6 +56,30 @@ export const homepageSectionFirestoreSchema = z.object({
|
|
|
56
56
|
config: z.record(z.string(), jsonValueSchema),
|
|
57
57
|
...auditTimestampsShape,
|
|
58
58
|
});
|
|
59
|
+
/**
|
|
60
|
+
* The create contract for a homepage section, shared by BOTH routes that can
|
|
61
|
+
* create one — `POST /api/admin/sections` (what the editor calls) and
|
|
62
|
+
* `POST /api/homepage-sections` (reachable by URL only).
|
|
63
|
+
*
|
|
64
|
+
* It exists because those two had drifted into being one validated path and
|
|
65
|
+
* one raw `{...body}` spread, and because the validated one's hand-written
|
|
66
|
+
* type list had fallen four values behind `SectionType` — so an admin could
|
|
67
|
+
* configure a Featured Bundles / Prize Draws / Event Raffles / Collection
|
|
68
|
+
* Cards section in full and get a 400 on save.
|
|
69
|
+
*
|
|
70
|
+
* `type` comes from `homepageSectionTypeSchema`, which is derived from the
|
|
71
|
+
* union, so that gap cannot reopen.
|
|
72
|
+
*/
|
|
73
|
+
export const homepageSectionCreateSchema = z.object({
|
|
74
|
+
type: homepageSectionTypeSchema,
|
|
75
|
+
enabled: z.boolean().optional().default(true),
|
|
76
|
+
order: z.number().int().optional(),
|
|
77
|
+
// Deliberately open. Every section type carries a different config shape,
|
|
78
|
+
// and a closed schema here would silently STRIP the builder's output for any
|
|
79
|
+
// type it did not enumerate — the `productBaseSchema` failure, one domain
|
|
80
|
+
// over.
|
|
81
|
+
config: z.record(z.string(), jsonValueSchema),
|
|
82
|
+
});
|
|
59
83
|
export const carouselBackgroundSchema = z.object({
|
|
60
84
|
type: z.enum(["image", "video", "color", "gradient"]),
|
|
61
85
|
url: z.string().optional(),
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { type TimelineEntry } from "../../status-history/components/StatusTimeline";
|
|
1
2
|
export interface OrderStatusTimelineProps {
|
|
2
3
|
orderStatus: string;
|
|
4
|
+
/** Recorded history. When present it wins over the scalar-date fallback. */
|
|
5
|
+
timeline?: TimelineEntry[];
|
|
6
|
+
timelineTruncated?: number;
|
|
3
7
|
orderDate?: string;
|
|
4
8
|
shippingDate?: string;
|
|
5
9
|
deliveryDate?: string;
|
|
@@ -9,4 +13,4 @@ export interface OrderStatusTimelineProps {
|
|
|
9
13
|
trackingUrl?: string;
|
|
10
14
|
className?: string;
|
|
11
15
|
}
|
|
12
|
-
export declare function OrderStatusTimeline({ orderStatus, orderDate, shippingDate, deliveryDate, cancellationDate, trackingNumber, shippingCarrier, trackingUrl, className, }: OrderStatusTimelineProps): import("react").JSX.Element;
|
|
16
|
+
export declare function OrderStatusTimeline({ orderStatus, timeline, timelineTruncated, orderDate, shippingDate, deliveryDate, cancellationDate, trackingNumber, shippingCarrier, trackingUrl, className, }: OrderStatusTimelineProps): import("react").JSX.Element;
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
|
-
* OrderStatusTimeline —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* OrderStatusTimeline — the order-shaped wrapper around `<StatusTimeline>`.
|
|
5
|
+
*
|
|
6
|
+
* Two branches, and the fallback matters as much as the primary path:
|
|
7
|
+
*
|
|
8
|
+
* **Branch A — real history.** When the order carries `timeline` entries
|
|
9
|
+
* (written by the repository since W2), each status change becomes a step
|
|
10
|
+
* dated by when it actually happened, stamped with who did it. This finally
|
|
11
|
+
* makes transitions with no dedicated date field visible at all — a payment
|
|
12
|
+
* review, a re-upload request, a partial refund.
|
|
13
|
+
*
|
|
14
|
+
* **Branch B — legacy orders.** Every order written before `statusHistory`
|
|
15
|
+
* existed has none, and back-filling one would mean inventing timestamps. So
|
|
16
|
+
* the original derivation from the four scalar dates is kept verbatim for
|
|
17
|
+
* those. Statuses with no dedicated date field render without one.
|
|
18
|
+
*
|
|
19
|
+
* No live courier API: `trackingUrl` is the passthrough link a seller entered.
|
|
12
20
|
*/
|
|
13
|
-
import { Anchor, Badge,
|
|
21
|
+
import { Anchor, Badge, Row, Stack, Text } from "../../../ui";
|
|
22
|
+
import { StatusTimeline, stepsFromEntries, } from "../../status-history/components/StatusTimeline";
|
|
14
23
|
const STAGE_SEQUENCE = ["pending", "confirmed", "processing", "shipped", "delivered"];
|
|
15
24
|
const STAGE_LABELS = {
|
|
16
25
|
pending: "Order placed",
|
|
@@ -23,10 +32,16 @@ const STAGE_LABELS = {
|
|
|
23
32
|
returned: "Returned",
|
|
24
33
|
refunded: "Refunded",
|
|
25
34
|
};
|
|
26
|
-
|
|
35
|
+
const NEGATIVE_STATUSES = new Set(["cancelled"]);
|
|
36
|
+
const labelFor = (status) => STAGE_LABELS[status] ?? status;
|
|
37
|
+
const isNegative = (status) => NEGATIVE_STATUSES.has(status);
|
|
38
|
+
/** Branch B — derive from the four scalar dates, for orders with no history. */
|
|
39
|
+
function buildLegacySteps(props) {
|
|
27
40
|
const status = (props.orderStatus ?? "").toLowerCase();
|
|
28
41
|
const currentIndex = STAGE_SEQUENCE.indexOf(status);
|
|
29
|
-
const steps = [
|
|
42
|
+
const steps = [
|
|
43
|
+
{ key: "pending", label: STAGE_LABELS.pending, date: props.orderDate },
|
|
44
|
+
];
|
|
30
45
|
if ((status === "confirmed" || status === "processing") && !props.shippingDate) {
|
|
31
46
|
steps.push({ key: status, label: STAGE_LABELS[status] });
|
|
32
47
|
}
|
|
@@ -37,19 +52,23 @@ function buildSteps(props) {
|
|
|
37
52
|
steps.push({ key: "delivered", label: STAGE_LABELS.delivered, date: props.deliveryDate });
|
|
38
53
|
}
|
|
39
54
|
if (status === "cancelled" || props.cancellationDate) {
|
|
40
|
-
steps.push({
|
|
55
|
+
steps.push({
|
|
56
|
+
key: "cancelled",
|
|
57
|
+
label: STAGE_LABELS.cancelled,
|
|
58
|
+
date: props.cancellationDate,
|
|
59
|
+
negative: true,
|
|
60
|
+
});
|
|
41
61
|
}
|
|
42
62
|
else if (status === "return_requested" || status === "returned" || status === "refunded") {
|
|
43
63
|
steps.push({ key: status, label: STAGE_LABELS[status] ?? status });
|
|
44
64
|
}
|
|
45
65
|
return steps;
|
|
46
66
|
}
|
|
47
|
-
export function OrderStatusTimeline({ orderStatus, orderDate, shippingDate, deliveryDate, cancellationDate, trackingNumber, shippingCarrier, trackingUrl, className = "", }) {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}) }), (trackingNumber || shippingCarrier || trackingUrl) && !isTerminalNegative && (_jsx(Div, { className: "mt-3 border-t border-[var(--appkit-color-border)]", padding: "t-sm", children: _jsxs(Stack, { gap: "xs", children: [shippingCarrier && (_jsxs(Row, { justify: "between", children: [_jsx(Text, { size: "sm", color: "muted", children: "Carrier" }), _jsx(Text, { size: "sm", weight: "medium", children: shippingCarrier })] })), trackingNumber && (_jsxs(Row, { justify: "between", children: [_jsx(Text, { size: "sm", color: "muted", children: "Tracking number" }), _jsx(Text, { size: "sm", weight: "medium", children: trackingNumber })] })), trackingUrl && (_jsx(Row, { justify: "end", children: _jsx(Anchor, { href: trackingUrl, tone: "brand", weight: "semibold", size: "sm", children: "Track with carrier \u2192" }) }))] }) })), !trackingNumber && !trackingUrl && !isTerminalNegative && (_jsx(Badge, { variant: "default", className: "mt-3", children: "No tracking details yet" }))] }));
|
|
67
|
+
export function OrderStatusTimeline({ orderStatus, timeline, timelineTruncated, orderDate, shippingDate, deliveryDate, cancellationDate, trackingNumber, shippingCarrier, trackingUrl, className = "", }) {
|
|
68
|
+
const fromHistory = stepsFromEntries(timeline, labelFor, isNegative);
|
|
69
|
+
const steps = fromHistory.length
|
|
70
|
+
? fromHistory
|
|
71
|
+
: buildLegacySteps({ orderStatus, orderDate, shippingDate, deliveryDate, cancellationDate });
|
|
72
|
+
const hasTracking = Boolean(trackingNumber || shippingCarrier || trackingUrl);
|
|
73
|
+
return (_jsx(StatusTimeline, { title: "Tracking", steps: steps, truncatedCount: timelineTruncated, className: className, footer: hasTracking ? (_jsxs(Stack, { gap: "xs", children: [shippingCarrier && (_jsxs(Row, { justify: "between", children: [_jsx(Text, { size: "sm", color: "muted", children: "Carrier" }), _jsx(Text, { size: "sm", weight: "medium", children: shippingCarrier })] })), trackingNumber && (_jsxs(Row, { justify: "between", children: [_jsx(Text, { size: "sm", color: "muted", children: "Tracking number" }), _jsx(Text, { size: "sm", weight: "medium", children: trackingNumber })] })), trackingUrl && (_jsx(Row, { justify: "end", children: _jsx(Anchor, { href: trackingUrl, tone: "brand", weight: "semibold", size: "sm", children: "Track with carrier \u2192" }) }))] })) : (_jsx(Badge, { variant: "default", children: "No tracking details yet" })) }));
|
|
55
74
|
}
|