@mohasinac/appkit 4.14.0 → 4.16.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.
Files changed (206) hide show
  1. package/dist/_internal/server/features/auctions/actions.js +2 -2
  2. package/dist/_internal/server/features/cart/service.d.ts +3 -3
  3. package/dist/_internal/server/features/checkout/actions.js +19 -0
  4. package/dist/_internal/server/features/checkout/source-context.d.ts +5 -0
  5. package/dist/_internal/server/features/checkout/source-context.js +157 -0
  6. package/dist/_internal/server/features/offers/adapters.d.ts +103 -0
  7. package/dist/_internal/server/features/offers/adapters.js +88 -0
  8. package/dist/_internal/server/features/offers/index.d.ts +2 -0
  9. package/dist/_internal/server/features/offers/index.js +1 -0
  10. package/dist/_internal/server/features/orders/adapters.js +24 -0
  11. package/dist/_internal/server/jobs/core/offerExpiry.js +10 -10
  12. package/dist/_internal/shared/actions/action-registry.d.ts +9 -2
  13. package/dist/_internal/shared/actions/action-registry.js +458 -7
  14. package/dist/_internal/shared/features/auctions/schema.d.ts +14 -2
  15. package/dist/_internal/shared/features/auctions/schema.js +13 -1
  16. package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
  17. package/dist/_internal/shared/features/cart/schema.d.ts +25 -20
  18. package/dist/_internal/shared/features/cart/schema.js +15 -0
  19. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +22 -22
  20. package/dist/_internal/shared/features/orders/schema.d.ts +4 -4
  21. package/dist/_internal/shared/features/products/schema.d.ts +27 -27
  22. package/dist/_internal/shared/features/promotions/schema.d.ts +4 -4
  23. package/dist/_internal/shared/features/reviews/schema.d.ts +2 -2
  24. package/dist/_internal/shared/history/history.d.ts +22 -0
  25. package/dist/_internal/shared/history/history.js +41 -1
  26. package/dist/client.d.ts +13 -2
  27. package/dist/client.js +20 -2
  28. package/dist/constants/field-names.d.ts +8 -6
  29. package/dist/constants/field-names.js +48 -6
  30. package/dist/contracts/extend.d.ts +29 -0
  31. package/dist/contracts/extend.js +20 -0
  32. package/dist/contracts/index.d.ts +2 -1
  33. package/dist/contracts/index.js +2 -0
  34. package/dist/features/account/components/UserBidsView.js +12 -4
  35. package/dist/features/account/components/UserReturnsView.d.ts +3 -0
  36. package/dist/features/account/components/UserReturnsView.js +17 -1
  37. package/dist/features/admin/components/AdminAuditLogView.js +1 -0
  38. package/dist/features/admin/components/AdminBidsView.js +15 -2
  39. package/dist/features/admin/components/AdminBlogEditorView.js +65 -33
  40. package/dist/features/admin/components/AdminFeatureEditorView.js +35 -6
  41. package/dist/features/admin/components/AdminNavEditorView.js +15 -5
  42. package/dist/features/admin/components/AdminNewsletterView.js +18 -2
  43. package/dist/features/admin/components/AdminOffersView.js +62 -18
  44. package/dist/features/admin/components/AdminPaymentMethodsView.js +24 -4
  45. package/dist/features/admin/components/AdminScammerEditorView.d.ts +2 -1
  46. package/dist/features/admin/components/AdminScammerEditorView.js +3 -13
  47. package/dist/features/admin/components/AdminScammersView.js +3 -8
  48. package/dist/features/admin/components/AdminSectionsView.js +32 -23
  49. package/dist/features/admin/components/AdminSessionsView.js +17 -2
  50. package/dist/features/admin/components/AdminViewCards.js +24 -17
  51. package/dist/features/admin/components/DataListingView.js +15 -1
  52. package/dist/features/admin/components/DataTable.js +7 -17
  53. package/dist/features/admin/components/ViewAuditLogEntryModal.js +1 -0
  54. package/dist/features/admin/components/sections/adminSectionsTypes.d.ts +4 -2
  55. package/dist/features/admin/components/sections/adminSectionsTypes.js +45 -27
  56. package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
  57. package/dist/features/admin/constants/filter-tabs.js +1 -0
  58. package/dist/features/admin/schemas/homepage-section-form.d.ts +22 -0
  59. package/dist/features/admin/schemas/homepage-section-form.js +68 -0
  60. package/dist/features/admin/schemas/nav-item-form.d.ts +74 -0
  61. package/dist/features/admin/schemas/nav-item-form.js +72 -0
  62. package/dist/features/auctions/components/AuctionBidsTable.d.ts +10 -1
  63. package/dist/features/auctions/components/AuctionBidsTable.js +22 -4
  64. package/dist/features/auctions/components/bid-detail-fields.d.ts +29 -0
  65. package/dist/features/auctions/components/bid-detail-fields.js +103 -0
  66. package/dist/features/auctions/schemas/index.d.ts +14 -14
  67. package/dist/features/audit-log/schemas/firestore.d.ts +14 -0
  68. package/dist/features/audit-log/schemas/firestore.js +7 -0
  69. package/dist/features/auth/schemas/index.d.ts +2 -2
  70. package/dist/features/blog/actions/blog-actions.d.ts +7 -7
  71. package/dist/features/blog/schemas/blog-form.d.ts +80 -0
  72. package/dist/features/blog/schemas/blog-form.js +100 -0
  73. package/dist/features/blog/schemas/index.d.ts +10 -10
  74. package/dist/features/cart/schemas/index.d.ts +3 -3
  75. package/dist/features/catalogue/schemas/validation.d.ts +12 -12
  76. package/dist/features/categories/schemas/index.d.ts +16 -16
  77. package/dist/features/collections/schemas/index.d.ts +8 -8
  78. package/dist/features/events/components/AdminEventEditorView.js +32 -54
  79. package/dist/features/events/components/AdminEventEntriesView.js +22 -3
  80. package/dist/features/events/schemas/event-form.d.ts +113 -0
  81. package/dist/features/events/schemas/event-form.js +82 -0
  82. package/dist/features/events/schemas/index.d.ts +2 -2
  83. package/dist/features/faq/actions/faq-actions.d.ts +6 -6
  84. package/dist/features/faq/schemas/index.d.ts +6 -6
  85. package/dist/features/grouped/schemas/grouped-listing-form.d.ts +101 -0
  86. package/dist/features/grouped/schemas/grouped-listing-form.js +89 -0
  87. package/dist/features/grouped/schemas/index.d.ts +4 -4
  88. package/dist/features/homepage/actions/homepage-section-actions.d.ts +11 -3
  89. package/dist/features/homepage/actions/homepage-section-actions.js +33 -1
  90. package/dist/features/homepage/schemas/index.d.ts +71 -40
  91. package/dist/features/homepage/schemas/index.js +24 -0
  92. package/dist/features/messages/schemas/index.d.ts +2 -2
  93. package/dist/features/orders/components/OrderStatusTimeline.d.ts +5 -1
  94. package/dist/features/orders/components/OrderStatusTimeline.js +39 -20
  95. package/dist/features/orders/repository/orders.repository.js +5 -0
  96. package/dist/features/orders/schemas/firestore.d.ts +36 -1
  97. package/dist/features/orders/schemas/index.d.ts +24 -24
  98. package/dist/features/orders/types/index.d.ts +35 -7
  99. package/dist/features/pre-orders/schemas/index.d.ts +4 -4
  100. package/dist/features/products/components/GroupSettingsPanel.js +5 -1
  101. package/dist/features/products/schemas/index.d.ts +38 -38
  102. package/dist/features/products/schemas/product-features.validators.d.ts +75 -2
  103. package/dist/features/products/schemas/product-features.validators.js +63 -0
  104. package/dist/features/promotions/schemas/index.d.ts +14 -14
  105. package/dist/features/reviews/schemas/index.d.ts +14 -14
  106. package/dist/features/scams/constants/scammer-status-display.d.ts +28 -0
  107. package/dist/features/scams/constants/scammer-status-display.js +79 -0
  108. package/dist/features/scams/schemas/index.d.ts +2 -2
  109. package/dist/features/search/schemas/index.d.ts +2 -2
  110. package/dist/features/seller/actions/offer-actions.js +32 -2
  111. package/dist/features/seller/components/OfferPhaseTimeline.d.ts +59 -0
  112. package/dist/features/seller/components/OfferPhaseTimeline.js +107 -0
  113. package/dist/features/seller/components/SellerBidsView.js +11 -4
  114. package/dist/features/seller/components/SellerCouponEditorView.js +17 -17
  115. package/dist/features/seller/components/SellerOffersView.js +2 -1
  116. package/dist/features/seller/components/SellerPayoutSettingsView.js +38 -14
  117. package/dist/features/seller/repository/offer.repository.d.ts +69 -8
  118. package/dist/features/seller/repository/offer.repository.js +127 -23
  119. package/dist/features/seller/schemas/coupon-form.d.ts +72 -0
  120. package/dist/features/seller/schemas/coupon-form.js +116 -0
  121. package/dist/features/seller/schemas/firestore.d.ts +56 -2
  122. package/dist/features/seller/schemas/firestore.js +27 -0
  123. package/dist/features/seller/schemas/index.d.ts +2 -2
  124. package/dist/features/seller/schemas/offer-forms.d.ts +21 -0
  125. package/dist/features/seller/schemas/offer-forms.js +21 -0
  126. package/dist/features/shell/build-sections.d.ts +63 -0
  127. package/dist/features/shell/build-sections.js +207 -0
  128. package/dist/features/shell/field-ui-meta.d.ts +115 -0
  129. package/dist/features/shell/field-ui-meta.js +324 -0
  130. package/dist/features/shell/index.d.ts +4 -0
  131. package/dist/features/shell/index.js +2 -0
  132. package/dist/features/shipments/schemas/validation.d.ts +22 -22
  133. package/dist/features/status-history/components/StatusTimeline.d.ts +74 -0
  134. package/dist/features/status-history/components/StatusTimeline.js +46 -0
  135. package/dist/features/status-history/index.d.ts +11 -0
  136. package/dist/features/status-history/index.js +10 -0
  137. package/dist/features/store-extensions/components/ReviewDecisionModal.d.ts +27 -0
  138. package/dist/features/store-extensions/components/ReviewDecisionModal.js +82 -0
  139. package/dist/features/store-extensions/index.d.ts +12 -0
  140. package/dist/features/store-extensions/index.js +12 -0
  141. package/dist/features/store-extensions/schemas/admin-notification-form.d.ts +36 -0
  142. package/dist/features/store-extensions/schemas/admin-notification-form.js +114 -0
  143. package/dist/features/store-extensions/schemas/analytics-forms.d.ts +57 -0
  144. package/dist/features/store-extensions/schemas/analytics-forms.js +158 -0
  145. package/dist/features/store-extensions/schemas/custom-role-form.d.ts +85 -0
  146. package/dist/features/store-extensions/schemas/custom-role-form.js +118 -0
  147. package/dist/features/store-extensions/schemas/firestore.d.ts +21 -1
  148. package/dist/features/store-extensions/schemas/firestore.js +60 -0
  149. package/dist/features/store-extensions/schemas/google-config-form.d.ts +53 -0
  150. package/dist/features/store-extensions/schemas/google-config-form.js +105 -0
  151. package/dist/features/store-extensions/schemas/item-request-create-form.d.ts +28 -0
  152. package/dist/features/store-extensions/schemas/item-request-create-form.js +80 -0
  153. package/dist/features/store-extensions/schemas/listing-template-form.d.ts +78 -0
  154. package/dist/features/store-extensions/schemas/listing-template-form.js +87 -0
  155. package/dist/features/store-extensions/schemas/moderation-review-form.d.ts +49 -0
  156. package/dist/features/store-extensions/schemas/moderation-review-form.js +101 -0
  157. package/dist/features/store-extensions/schemas/payout-method-form.d.ts +153 -0
  158. package/dist/features/store-extensions/schemas/payout-method-form.js +155 -0
  159. package/dist/features/store-extensions/schemas/report-create-form.d.ts +25 -0
  160. package/dist/features/store-extensions/schemas/report-create-form.js +79 -0
  161. package/dist/features/store-extensions/schemas/report-review-form.d.ts +47 -0
  162. package/dist/features/store-extensions/schemas/report-review-form.js +111 -0
  163. package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +148 -0
  164. package/dist/features/store-extensions/schemas/shipping-config-form.js +104 -0
  165. package/dist/features/store-extensions/schemas/store-category-form.d.ts +54 -0
  166. package/dist/features/store-extensions/schemas/store-category-form.js +73 -0
  167. package/dist/features/stores/schemas/index.d.ts +4 -4
  168. package/dist/features/support/schemas/index.d.ts +4 -4
  169. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +350 -0
  170. package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
  171. package/dist/index.d.ts +12 -2
  172. package/dist/index.js +12 -2
  173. package/dist/next/routing/route-map.d.ts +16 -2
  174. package/dist/next/routing/route-map.js +9 -2
  175. package/dist/schemas/firestore-value.d.ts +8 -0
  176. package/dist/schemas/firestore-value.js +44 -0
  177. package/dist/schemas/index.d.ts +1 -1
  178. package/dist/schemas/index.js +1 -1
  179. package/dist/schemas/registry.d.ts +734 -101
  180. package/dist/schemas/registry.js +64 -1
  181. package/dist/seed/bids-seed-data.js +30 -0
  182. package/dist/seed/offers-seed-data.js +212 -7
  183. package/dist/seed/orders-seed-data.js +336 -1
  184. package/dist/seed/products-auctions-seed-data.js +41 -0
  185. package/dist/server.d.ts +2 -0
  186. package/dist/server.js +5 -0
  187. package/dist/styles.css +1 -1
  188. package/dist/tailwind-utilities.css +1 -1
  189. package/dist/ui/DataTable.js +4 -2
  190. package/dist/ui/columns/build-columns.d.ts +6 -1
  191. package/dist/ui/columns/build-columns.js +53 -0
  192. package/dist/ui/columns/cell-renderers.d.ts +68 -0
  193. package/dist/ui/columns/cell-renderers.js +111 -0
  194. package/dist/ui/columns/column-renderers.d.ts +6 -1
  195. package/dist/ui/columns/column-renderers.js +6 -1
  196. package/dist/ui/columns/index.d.ts +2 -1
  197. package/dist/ui/columns/index.js +5 -1
  198. package/dist/ui/components/Button.js +27 -2
  199. package/dist/ui/components/RecordDetailModal.d.ts +10 -1
  200. package/dist/ui/components/RecordDetailModal.js +2 -2
  201. package/dist/ui/icons/icon-registry.d.ts +65 -0
  202. package/dist/ui/icons/icon-registry.js +115 -0
  203. package/package.json +2 -1
  204. package/scripts/node-esm-loader.mjs +65 -0
  205. package/scripts/seed-cli-loader.mjs +6 -49
  206. package/scripts/seed-cli.mjs +1 -1
@@ -0,0 +1,111 @@
1
+ /*
2
+ * WHY: `PATCH /api/admin/reports/[id]` spread the raw request body straight
3
+ * into Firestore, exactly as the moderation route did:
4
+ *
5
+ * const body = await parseJsonBody<Record<string, JsonValue>>(request);
6
+ * await reportsRepository.update(id, { ...body, assignedTo: user.uid });
7
+ *
8
+ * No Zod on the route, none on either client surface. `status` was never
9
+ * checked against the real `ReportStatus` union, and any key at all could
10
+ * be written onto a report — `reporterId`, `entityId`, `evidenceUrls`,
11
+ * or something invented.
12
+ *
13
+ * ## 🛑 And it was actively corrupting `resolvedAt`
14
+ *
15
+ * BOTH client surfaces — the list page's inline controls and
16
+ * `ReportDetailActions` — sent:
17
+ *
18
+ * resolvedAt: status === "actioned" || status === "dismissed"
19
+ * ? new Date() : undefined
20
+ *
21
+ * `new Date()` does not survive `JSON.stringify`. It arrives at the route
22
+ * as an ISO **string**, and the raw spread wrote that string into a field
23
+ * `ReportDocument` declares as `Date`. Seeded reports hold real
24
+ * Timestamps, so the collection carries BOTH shapes for one field —
25
+ * the sort of split that later reads as "the date filter is broken" long
26
+ * after the cause is out of sight (Recurrent Root Cause #47).
27
+ *
28
+ * Nothing renders `resolvedAt` today, which is the only reason it was
29
+ * never noticed. It is a latent corruption, not a cosmetic one.
30
+ *
31
+ * WHAT: One schema shared by the route and both client surfaces — and
32
+ * `resolvedAt` is REMOVED from the client contract entirely. The server
33
+ * stamps it, the same way the moderation route already stamps
34
+ * `reviewedAt`. A timestamp the client cannot send is a timestamp the
35
+ * client cannot send in the wrong type.
36
+ *
37
+ * ## A reviewer may only set the three statuses a REVIEW produces
38
+ *
39
+ * `ReportStatus` has four values; `pending` is the state a report is born in.
40
+ * The settable subset is declared `satisfies readonly ReportStatus[]` rather
41
+ * than restated, so a value outside the real union is a COMPILE error instead
42
+ * of a filter that silently matches nothing (Recurrent Root Cause #33).
43
+ *
44
+ * EXPORTS:
45
+ * reportReviewFormSchema, reportReviewUpdateSchema,
46
+ * REPORT_REVIEWER_STATUSES, REPORT_TERMINAL_STATUSES,
47
+ * type ReportReviewFormValues
48
+ *
49
+ * @tag domain:store-extensions,reports
50
+ * @tag layer:schema
51
+ * @tag pattern:none
52
+ * @tag access:isomorphic
53
+ * @tag consumers:admin/reports pages,/api/admin/reports/[id]
54
+ * @tag sideEffects:none
55
+ */
56
+ import { z } from "zod";
57
+ import { annotate } from "../../shell/field-ui-meta";
58
+ /** The statuses a human review can produce. `pending` is the initial state. */
59
+ export const REPORT_REVIEWER_STATUSES = [
60
+ "under-review",
61
+ "actioned",
62
+ "dismissed",
63
+ ];
64
+ /**
65
+ * The two that CLOSE a report — the server stamps `resolvedAt` for these and
66
+ * for no others. Exported so the route and any future consumer share one
67
+ * answer to "is this report finished", rather than each spelling out the pair.
68
+ */
69
+ export const REPORT_TERMINAL_STATUSES = [
70
+ "actioned",
71
+ "dismissed",
72
+ ];
73
+ /**
74
+ * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
75
+ * by schema instance and every zod wrapper returns a new one.
76
+ */
77
+ export const reportReviewFormSchema = z
78
+ .object({
79
+ status: annotate(z.enum(REPORT_REVIEWER_STATUSES), {
80
+ section: "review",
81
+ sectionLabel: "Report decision",
82
+ sectionRequired: true,
83
+ quick: true,
84
+ order: 1,
85
+ row: "pair",
86
+ }),
87
+ resolution: annotate(z.string().max(1000, "Keep the resolution under 1000 characters.").optional(), {
88
+ section: "review",
89
+ order: 2,
90
+ row: "full",
91
+ kind: "textarea",
92
+ help: "Required when actioning or dismissing — the reporter may see a summary.",
93
+ }),
94
+ // `resolvedAt` is deliberately NOT a field. See the header.
95
+ })
96
+ .strict()
97
+ .superRefine((v, ctx) => {
98
+ // Taking a report for review needs no note; closing one does. "Actioned"
99
+ // with an empty resolution tells the reporter, and the next admin, nothing
100
+ // about what was actually done.
101
+ if (REPORT_TERMINAL_STATUSES.includes(v.status) &&
102
+ !v.resolution?.trim()) {
103
+ ctx.addIssue({
104
+ code: z.ZodIssueCode.custom,
105
+ path: ["resolution"],
106
+ message: "Say what was done about this report before closing it.",
107
+ });
108
+ }
109
+ });
110
+ /** Route contract — identical; a review is one atomic decision. */
111
+ export const reportReviewUpdateSchema = reportReviewFormSchema;
@@ -0,0 +1,148 @@
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 shippingConfigFormSchema: z.ZodEffects<z.ZodObject<{
7
+ label: z.ZodString;
8
+ method: z.ZodEnum<["free", "flat", "weight", "express", "pickup", "custom"]>;
9
+ flatRate: z.ZodOptional<z.ZodNumber>;
10
+ pricePerKg: z.ZodOptional<z.ZodNumber>;
11
+ freeAbove: z.ZodOptional<z.ZodNumber>;
12
+ expressSurcharge: z.ZodOptional<z.ZodNumber>;
13
+ estimatedDays: z.ZodOptional<z.ZodNumber>;
14
+ isDefault: z.ZodBoolean;
15
+ isActive: z.ZodBoolean;
16
+ }, "strip", z.ZodTypeAny, {
17
+ label: string;
18
+ isDefault: boolean;
19
+ isActive: boolean;
20
+ method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
21
+ estimatedDays?: number | undefined;
22
+ freeAbove?: number | undefined;
23
+ flatRate?: number | undefined;
24
+ pricePerKg?: number | undefined;
25
+ expressSurcharge?: number | undefined;
26
+ }, {
27
+ label: string;
28
+ isDefault: boolean;
29
+ isActive: boolean;
30
+ method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
31
+ estimatedDays?: number | undefined;
32
+ freeAbove?: number | undefined;
33
+ flatRate?: number | undefined;
34
+ pricePerKg?: number | undefined;
35
+ expressSurcharge?: number | undefined;
36
+ }>, {
37
+ label: string;
38
+ isDefault: boolean;
39
+ isActive: boolean;
40
+ method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
41
+ estimatedDays?: number | undefined;
42
+ freeAbove?: number | undefined;
43
+ flatRate?: number | undefined;
44
+ pricePerKg?: number | undefined;
45
+ expressSurcharge?: number | undefined;
46
+ }, {
47
+ label: string;
48
+ isDefault: boolean;
49
+ isActive: boolean;
50
+ method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
51
+ estimatedDays?: number | undefined;
52
+ freeAbove?: number | undefined;
53
+ flatRate?: number | undefined;
54
+ pricePerKg?: number | undefined;
55
+ expressSurcharge?: number | undefined;
56
+ }>;
57
+ export type ShippingConfigFormValues = z.infer<typeof shippingConfigFormSchema>;
58
+ /** `storeId` is absent — the route sets it from the session, never the body. */
59
+ export declare const shippingConfigCreateSchema: z.ZodEffects<z.ZodObject<{
60
+ label: z.ZodString;
61
+ method: z.ZodEnum<["free", "flat", "weight", "express", "pickup", "custom"]>;
62
+ flatRate: z.ZodOptional<z.ZodNumber>;
63
+ pricePerKg: z.ZodOptional<z.ZodNumber>;
64
+ freeAbove: z.ZodOptional<z.ZodNumber>;
65
+ expressSurcharge: z.ZodOptional<z.ZodNumber>;
66
+ estimatedDays: z.ZodOptional<z.ZodNumber>;
67
+ isDefault: z.ZodBoolean;
68
+ isActive: z.ZodBoolean;
69
+ }, "strip", z.ZodTypeAny, {
70
+ label: string;
71
+ isDefault: boolean;
72
+ isActive: boolean;
73
+ method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
74
+ estimatedDays?: number | undefined;
75
+ freeAbove?: number | undefined;
76
+ flatRate?: number | undefined;
77
+ pricePerKg?: number | undefined;
78
+ expressSurcharge?: number | undefined;
79
+ }, {
80
+ label: string;
81
+ isDefault: boolean;
82
+ isActive: boolean;
83
+ method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
84
+ estimatedDays?: number | undefined;
85
+ freeAbove?: number | undefined;
86
+ flatRate?: number | undefined;
87
+ pricePerKg?: number | undefined;
88
+ expressSurcharge?: number | undefined;
89
+ }>, {
90
+ label: string;
91
+ isDefault: boolean;
92
+ isActive: boolean;
93
+ method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
94
+ estimatedDays?: number | undefined;
95
+ freeAbove?: number | undefined;
96
+ flatRate?: number | undefined;
97
+ pricePerKg?: number | undefined;
98
+ expressSurcharge?: number | undefined;
99
+ }, {
100
+ label: string;
101
+ isDefault: boolean;
102
+ isActive: boolean;
103
+ method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
104
+ estimatedDays?: number | undefined;
105
+ freeAbove?: number | undefined;
106
+ flatRate?: number | undefined;
107
+ pricePerKg?: number | undefined;
108
+ expressSurcharge?: number | undefined;
109
+ }>;
110
+ /**
111
+ * Update contract — partial, and `.strict()` so an unknown key is a 400 rather
112
+ * than a silent write. The per-method completeness rules do not apply: a PATCH
113
+ * may legitimately carry only `isActive`.
114
+ */
115
+ export declare const shippingConfigUpdateSchema: z.ZodObject<{
116
+ label: z.ZodOptional<z.ZodString>;
117
+ method: z.ZodOptional<z.ZodEnum<["free", "flat", "weight", "express", "pickup", "custom"]>>;
118
+ flatRate: z.ZodOptional<z.ZodNumber>;
119
+ pricePerKg: z.ZodOptional<z.ZodNumber>;
120
+ freeAbove: z.ZodOptional<z.ZodNumber>;
121
+ expressSurcharge: z.ZodOptional<z.ZodNumber>;
122
+ estimatedDays: z.ZodOptional<z.ZodNumber>;
123
+ zones: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
124
+ isDefault: z.ZodOptional<z.ZodBoolean>;
125
+ isActive: z.ZodOptional<z.ZodBoolean>;
126
+ }, "strict", z.ZodTypeAny, {
127
+ label?: string | undefined;
128
+ isDefault?: boolean | undefined;
129
+ isActive?: boolean | undefined;
130
+ method?: "flat" | "weight" | "custom" | "free" | "express" | "pickup" | undefined;
131
+ estimatedDays?: number | undefined;
132
+ freeAbove?: number | undefined;
133
+ flatRate?: number | undefined;
134
+ pricePerKg?: number | undefined;
135
+ expressSurcharge?: number | undefined;
136
+ zones?: string[] | undefined;
137
+ }, {
138
+ label?: string | undefined;
139
+ isDefault?: boolean | undefined;
140
+ isActive?: boolean | undefined;
141
+ method?: "flat" | "weight" | "custom" | "free" | "express" | "pickup" | undefined;
142
+ estimatedDays?: number | undefined;
143
+ freeAbove?: number | undefined;
144
+ flatRate?: number | undefined;
145
+ pricePerKg?: number | undefined;
146
+ expressSurcharge?: number | undefined;
147
+ zones?: string[] | undefined;
148
+ }>;
@@ -0,0 +1,104 @@
1
+ /*
2
+ * WHY: `POST /api/store/shipping-configs` spread the raw request body into
3
+ * `.create()` and the PATCH handed it straight to `.update()` — no Zod on
4
+ * either, and neither page validated anything. A shipping rule is money:
5
+ * a `flatRate` of `"abc"`, a negative `freeAbove`, or an invented key are
6
+ * all things that were persisted verbatim.
7
+ * WHAT: One schema for the pages AND the routes.
8
+ *
9
+ * ## The numeric fields are `.coerce` on purpose
10
+ *
11
+ * The pages hold them as strings from `<input type="number">`. Coercing here
12
+ * rather than at each call site means every consumer gets a real number and
13
+ * no page has to remember a `Number()` cast — the kind of per-callsite
14
+ * conversion that produces a `NaN` in Firestore the first time someone forgets.
15
+ *
16
+ * EXPORTS:
17
+ * shippingConfigFormSchema, shippingConfigCreateSchema,
18
+ * shippingConfigUpdateSchema, type ShippingConfigFormValues
19
+ *
20
+ * @tag domain:store-extensions
21
+ * @tag layer:schema
22
+ * @tag pattern:none
23
+ * @tag access:isomorphic
24
+ * @tag consumers:store/shipping-configs pages,/api/store/shipping-configs
25
+ * @tag sideEffects:none
26
+ */
27
+ import { z } from "zod";
28
+ import { annotate } from "../../shell/field-ui-meta";
29
+ /** Mirrors `ShippingMethod` in ./firestore. */
30
+ const shippingMethodSchema = z.enum(["free", "flat", "weight", "express", "pickup", "custom"]);
31
+ /** A non-negative money/threshold amount, coerced from the form's string. */
32
+ const amount = (label) => z.coerce.number({ invalid_type_error: `${label} must be a number.` }).min(0, `${label} cannot be negative.`);
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 shippingConfigFormSchema = z
38
+ .object({
39
+ label: annotate(z.string().min(1, "Give this rule a name.").max(80), {
40
+ section: "basics", sectionLabel: "Shipping Rule", sectionRequired: true,
41
+ quick: true, order: 1, row: "pair",
42
+ }),
43
+ method: annotate(shippingMethodSchema, {
44
+ section: "basics", quick: true, order: 2, row: "pair",
45
+ }),
46
+ flatRate: annotate(amount("Flat rate").optional(), {
47
+ section: "rates", sectionLabel: "Rates", order: 1, row: "pair",
48
+ }),
49
+ pricePerKg: annotate(amount("Price per kg").optional(), {
50
+ section: "rates", order: 2, row: "pair",
51
+ }),
52
+ freeAbove: annotate(amount("Free above").optional(), {
53
+ section: "rates", order: 3, row: "pair",
54
+ }),
55
+ expressSurcharge: annotate(amount("Express surcharge").optional(), {
56
+ section: "rates", order: 4, row: "pair",
57
+ }),
58
+ estimatedDays: annotate(z.coerce.number().int("Estimated days must be a whole number.").min(0).max(365).optional(), { section: "rates", order: 5, row: "pair" }),
59
+ isDefault: annotate(z.boolean(), {
60
+ section: "visibility", sectionLabel: "Visibility", order: 1, row: "quarter",
61
+ }),
62
+ isActive: annotate(z.boolean(), {
63
+ section: "visibility", order: 2, row: "quarter",
64
+ }),
65
+ })
66
+ .superRefine((v, ctx) => {
67
+ // A rate rule with no rate is not a rule. Checked per method, because the
68
+ // required field differs and a blanket `.min(1)` would make every rate
69
+ // mandatory for a free/pickup rule that has none.
70
+ const need = (path, label) => {
71
+ if (v[path] == null) {
72
+ ctx.addIssue({
73
+ code: z.ZodIssueCode.custom,
74
+ path: [path],
75
+ message: `A "${label}" rule needs a ${path === "flatRate" ? "flat rate" : "price per kg"}.`,
76
+ });
77
+ }
78
+ };
79
+ if (v.method === "flat")
80
+ need("flatRate", "flat");
81
+ if (v.method === "weight")
82
+ need("pricePerKg", "weight");
83
+ });
84
+ /** `storeId` is absent — the route sets it from the session, never the body. */
85
+ export const shippingConfigCreateSchema = shippingConfigFormSchema;
86
+ /**
87
+ * Update contract — partial, and `.strict()` so an unknown key is a 400 rather
88
+ * than a silent write. The per-method completeness rules do not apply: a PATCH
89
+ * may legitimately carry only `isActive`.
90
+ */
91
+ export const shippingConfigUpdateSchema = z
92
+ .object({
93
+ label: z.string().min(1).max(80).optional(),
94
+ method: shippingMethodSchema.optional(),
95
+ flatRate: amount("Flat rate").optional(),
96
+ pricePerKg: amount("Price per kg").optional(),
97
+ freeAbove: amount("Free above").optional(),
98
+ expressSurcharge: amount("Express surcharge").optional(),
99
+ estimatedDays: z.coerce.number().int().min(0).max(365).optional(),
100
+ zones: z.array(z.string()).optional(),
101
+ isDefault: z.boolean().optional(),
102
+ isActive: z.boolean().optional(),
103
+ })
104
+ .strict();
@@ -0,0 +1,54 @@
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, so annotating
5
+ * before `.optional()` silently loses the metadata.
6
+ */
7
+ export declare const storeCategoryFormSchema: z.ZodObject<{
8
+ label: z.ZodString;
9
+ slug: z.ZodOptional<z.ZodString>;
10
+ description: z.ZodOptional<z.ZodString>;
11
+ coverImageUrl: z.ZodOptional<z.ZodString>;
12
+ displayOrder: z.ZodNumber;
13
+ }, "strip", z.ZodTypeAny, {
14
+ label: string;
15
+ displayOrder: number;
16
+ slug?: string | undefined;
17
+ description?: string | undefined;
18
+ coverImageUrl?: string | undefined;
19
+ }, {
20
+ label: string;
21
+ displayOrder: number;
22
+ slug?: string | undefined;
23
+ description?: string | undefined;
24
+ coverImageUrl?: string | undefined;
25
+ }>;
26
+ export type StoreCategoryFormValues = z.infer<typeof storeCategoryFormSchema>;
27
+ /**
28
+ * The server's create contract.
29
+ *
30
+ * Identical fields, but `slug` is REQUIRED here: the client derives it from
31
+ * the label when blank, and a category with no slug has no URL. Keeping the
32
+ * derivation on the client and the requirement on the server means the route
33
+ * never has to guess what the user meant.
34
+ */
35
+ export declare const storeCategoryCreateSchema: z.ZodObject<{
36
+ label: z.ZodString;
37
+ description: z.ZodOptional<z.ZodString>;
38
+ coverImageUrl: z.ZodOptional<z.ZodString>;
39
+ displayOrder: z.ZodNumber;
40
+ } & {
41
+ slug: z.ZodString;
42
+ }, "strip", z.ZodTypeAny, {
43
+ label: string;
44
+ slug: string;
45
+ displayOrder: number;
46
+ description?: string | undefined;
47
+ coverImageUrl?: string | undefined;
48
+ }, {
49
+ label: string;
50
+ slug: string;
51
+ displayOrder: number;
52
+ description?: string | undefined;
53
+ coverImageUrl?: string | undefined;
54
+ }>;
@@ -0,0 +1,73 @@
1
+ /*
2
+ * WHY: `/store/categories/new` collected five fields through raw <Input>s with
3
+ * no <Form>, no schema and no check of any kind, and `POST
4
+ * /api/store/categories` spread the raw request body straight into
5
+ * `storeCategoriesRepository.create()`. Neither side validated anything,
6
+ * so an entirely empty storefront category could be created — and any
7
+ * extra key a caller sent was written verbatim into Firestore.
8
+ * WHAT: One schema for both sides. The client parses it to show inline errors
9
+ * before submitting; the route parses it to decide what is allowed to
10
+ * reach the database.
11
+ *
12
+ * ## Why one schema and not two
13
+ *
14
+ * A client copy and a server copy of the same rules is how they come to
15
+ * disagree — and when they do, the client's is the one users see while the
16
+ * server's is the one that decides. The route strips unknown keys BECAUSE it
17
+ * parses this: `storeId` is set from the session, never from the body, so a
18
+ * caller cannot write a category into someone else's store by adding a field.
19
+ *
20
+ * `productIds` and `isActive` are deliberately absent: they are managed after
21
+ * creation (assigning products, toggling visibility), not typed into the
22
+ * create form, and the repository supplies their defaults.
23
+ *
24
+ * EXPORTS:
25
+ * storeCategoryFormSchema, storeCategoryCreateSchema,
26
+ * type StoreCategoryFormValues
27
+ *
28
+ * @tag domain:store-extensions
29
+ * @tag layer:schema
30
+ * @tag pattern:none
31
+ * @tag access:isomorphic
32
+ * @tag consumers:store/categories/new,POST /api/store/categories
33
+ * @tag sideEffects:none
34
+ */
35
+ import { z } from "zod";
36
+ import { annotate } from "../../shell/field-ui-meta";
37
+ /**
38
+ * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
39
+ * by schema instance and every zod wrapper returns a new one, so annotating
40
+ * before `.optional()` silently loses the metadata.
41
+ */
42
+ export const storeCategoryFormSchema = z.object({
43
+ label: annotate(z.string().min(1, "Label is required").max(80, "Keep the label under 80 characters."), {
44
+ section: "basics", sectionLabel: "Category", sectionRequired: true,
45
+ quick: true, order: 1, row: "full",
46
+ }),
47
+ slug: annotate(z.string().max(80, "Keep the slug under 80 characters.").optional(), {
48
+ section: "basics", order: 2, row: "full",
49
+ // T1 calculated: derived from the label when left blank, then frozen —
50
+ // the same create-only rule every other slug in this codebase follows,
51
+ // because renaming one breaks inbound links.
52
+ tier: "t1-derive",
53
+ help: "Left blank, this is generated from the label.",
54
+ }),
55
+ description: annotate(z.string().max(500, "Keep the description under 500 characters.").optional(), {
56
+ section: "basics", order: 3, row: "full",
57
+ }),
58
+ coverImageUrl: annotate(z.string().optional(), {
59
+ section: "media", sectionLabel: "Media", order: 1, row: "full", kind: "media",
60
+ }),
61
+ displayOrder: annotate(z.coerce.number().int("Display order must be a whole number.").min(0, "Display order cannot be negative."), { section: "visibility", sectionLabel: "Visibility", order: 1, row: "pair" }),
62
+ });
63
+ /**
64
+ * The server's create contract.
65
+ *
66
+ * Identical fields, but `slug` is REQUIRED here: the client derives it from
67
+ * the label when blank, and a category with no slug has no URL. Keeping the
68
+ * derivation on the client and the requirement on the server means the route
69
+ * never has to guess what the user meant.
70
+ */
71
+ export const storeCategoryCreateSchema = storeCategoryFormSchema.extend({
72
+ slug: z.string().min(1, "Slug is required").max(80),
73
+ });
@@ -410,8 +410,8 @@ export declare const storeFirestoreSchema: z.ZodObject<{
410
410
  updatedAt: string | Date | z.objectOutputType<{
411
411
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
412
412
  }, z.ZodTypeAny, "passthrough">;
413
- ownerId: string;
414
413
  storeName: string;
414
+ ownerId: string;
415
415
  storeSlug: string;
416
416
  isPublic: boolean;
417
417
  location?: string | undefined;
@@ -495,8 +495,8 @@ export declare const storeFirestoreSchema: z.ZodObject<{
495
495
  updatedAt: string | Date | z.objectInputType<{
496
496
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
497
497
  }, z.ZodTypeAny, "passthrough">;
498
- ownerId: string;
499
498
  storeName: string;
499
+ ownerId: string;
500
500
  storeSlug: string;
501
501
  isPublic: boolean;
502
502
  location?: string | undefined;
@@ -607,8 +607,8 @@ export declare const storeListItemSchema: z.ZodObject<{
607
607
  }, "strip", z.ZodTypeAny, {
608
608
  status: string;
609
609
  id: string;
610
- ownerId: string;
611
610
  storeName: string;
611
+ ownerId: string;
612
612
  storeSlug: string;
613
613
  isPublic: boolean;
614
614
  createdAt?: string | undefined;
@@ -623,8 +623,8 @@ export declare const storeListItemSchema: z.ZodObject<{
623
623
  }, {
624
624
  status: string;
625
625
  id: string;
626
- ownerId: string;
627
626
  storeName: string;
627
+ ownerId: string;
628
628
  storeSlug: string;
629
629
  isPublic: boolean;
630
630
  createdAt?: string | undefined;
@@ -158,12 +158,12 @@ export declare const supportTicketFirestoreSchema: z.ZodObject<{
158
158
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
159
159
  }, z.ZodTypeAny, "passthrough">;
160
160
  userId: string;
161
- description: string;
162
161
  category: "general" | "account" | "order_issue" | "billing_payment" | "listing_dispute" | "scam_report" | "refund_request" | "auction_dispute" | "store_change_request" | "order_modification_request" | "unban_request";
162
+ priority: "normal" | "high" | "low" | "urgent";
163
+ description: string;
163
164
  userEmail: string;
164
165
  userDisplayName: string;
165
166
  subject: string;
166
- priority: "normal" | "high" | "low" | "urgent";
167
167
  messages: {
168
168
  id: string;
169
169
  body: string;
@@ -201,12 +201,12 @@ export declare const supportTicketFirestoreSchema: z.ZodObject<{
201
201
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
202
202
  }, z.ZodTypeAny, "passthrough">;
203
203
  userId: string;
204
- description: string;
205
204
  category: "general" | "account" | "order_issue" | "billing_payment" | "listing_dispute" | "scam_report" | "refund_request" | "auction_dispute" | "store_change_request" | "order_modification_request" | "unban_request";
205
+ priority: "normal" | "high" | "low" | "urgent";
206
+ description: string;
206
207
  userEmail: string;
207
208
  userDisplayName: string;
208
209
  subject: string;
209
- priority: "normal" | "high" | "low" | "urgent";
210
210
  messages: {
211
211
  id: string;
212
212
  body: string;