@mohasinac/appkit 4.33.0 → 4.34.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 (89) hide show
  1. package/dist/_internal/server/features/checkout/actions.js +9 -1
  2. package/dist/_internal/server/features/checkout/expand-order-items.js +9 -0
  3. package/dist/_internal/server/features/media/contextGuards.d.ts +3 -3
  4. package/dist/_internal/server/features/media/contextGuards.js +19 -5
  5. package/dist/_internal/server/features/orders/actions.d.ts +9 -0
  6. package/dist/_internal/server/features/orders/actions.js +47 -4
  7. package/dist/_internal/server/features/orders/adapters.js +10 -0
  8. package/dist/_internal/server/features/refunds/actions.d.ts +9 -1
  9. package/dist/_internal/server/features/refunds/actions.js +36 -3
  10. package/dist/_internal/server/features/site-settings/adapters.js +1 -1
  11. package/dist/_internal/shared/features/orders/refund-copy.d.ts +12 -0
  12. package/dist/_internal/shared/features/orders/refund-copy.js +12 -0
  13. package/dist/_internal/shared/features/orders/return-reasons.d.ts +59 -0
  14. package/dist/_internal/shared/features/orders/return-reasons.js +96 -0
  15. package/dist/_internal/shared/features/orders/schema.d.ts +30 -0
  16. package/dist/_internal/shared/features/orders/schema.js +20 -0
  17. package/dist/_internal/shared/features/products/schema.d.ts +12 -0
  18. package/dist/_internal/shared/features/products/schema.js +4 -1
  19. package/dist/_internal/shared/media/limits.d.ts +14 -0
  20. package/dist/_internal/shared/media/limits.js +29 -0
  21. package/dist/client.d.ts +9 -1
  22. package/dist/client.js +19 -1
  23. package/dist/features/about/components/PolicyPageView.js +1 -1
  24. package/dist/features/admin/components/AdminProductEditorView.js +6 -1
  25. package/dist/features/admin/components/AdminSiteSettingsView.js +2 -6
  26. package/dist/features/admin/components/index.d.ts +0 -2
  27. package/dist/features/admin/components/index.js +0 -1
  28. package/dist/features/admin/schemas/admin-editor-forms.js +1 -1
  29. package/dist/features/admin/schemas/firestore.d.ts +0 -2
  30. package/dist/features/auctions/components/AuctionDetailPageView.js +4 -3
  31. package/dist/features/contact/components/ContactForm.js +46 -31
  32. package/dist/features/media/upload/MediaUploadList.d.ts +16 -1
  33. package/dist/features/media/upload/MediaUploadList.js +58 -8
  34. package/dist/features/orders/components/RefundRequestView.d.ts +26 -10
  35. package/dist/features/orders/components/RefundRequestView.js +108 -24
  36. package/dist/features/orders/schemas/firestore.d.ts +42 -0
  37. package/dist/features/orders/types/index.d.ts +24 -0
  38. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +5 -3
  39. package/dist/features/products/components/ProductDetailPageView.js +14 -2
  40. package/dist/features/products/components/ProductFeatureBadges.d.ts +9 -1
  41. package/dist/features/products/components/ProductFeatureBadges.js +17 -1
  42. package/dist/features/products/components/ProductForm.js +46 -10
  43. package/dist/features/products/components/ProductGrid.js +10 -1
  44. package/dist/features/products/constants/final-sale.d.ts +40 -0
  45. package/dist/features/products/constants/final-sale.js +40 -0
  46. package/dist/features/products/index.d.ts +1 -0
  47. package/dist/features/products/index.js +1 -0
  48. package/dist/features/products/schemas/firestore.d.ts +19 -2
  49. package/dist/features/products/schemas/firestore.js +4 -0
  50. package/dist/features/products/types/index.d.ts +2 -0
  51. package/dist/features/seller/components/SellerProductShell.d.ts +7 -0
  52. package/dist/features/seller/components/SellerProductShell.js +82 -11
  53. package/dist/features/seller/index.d.ts +1 -0
  54. package/dist/features/seller/index.js +1 -0
  55. package/dist/features/seller/schemas/product-form.d.ts +304 -0
  56. package/dist/features/seller/schemas/product-form.js +132 -0
  57. package/dist/features/seller/utils/product-draft-mapping.d.ts +117 -0
  58. package/dist/features/seller/utils/product-draft-mapping.js +147 -0
  59. package/dist/features/shell/SectionForm.js +62 -7
  60. package/dist/features/shell/form-sections.js +4 -0
  61. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +200 -0
  62. package/dist/index.d.ts +6 -3
  63. package/dist/index.js +12 -3
  64. package/dist/next/routing/route-map.d.ts +2 -0
  65. package/dist/next/routing/route-map.js +1 -0
  66. package/dist/schemas/registry.d.ts +271 -0
  67. package/dist/schemas/registry.js +9 -0
  68. package/dist/seed/_helpers/final-sale-fixtures.d.ts +35 -0
  69. package/dist/seed/_helpers/final-sale-fixtures.js +61 -0
  70. package/dist/seed/products-art-seed-data.js +9 -1
  71. package/dist/seed/products-auctions-seed-data.js +2 -1
  72. package/dist/seed/products-classifieds-seed-data.js +2 -1
  73. package/dist/seed/products-digital-codes-seed-data.js +2 -1
  74. package/dist/seed/products-live-items-seed-data.js +2 -1
  75. package/dist/seed/products-preorders-seed-data.d.ts +2 -138
  76. package/dist/seed/products-preorders-seed-data.js +2 -1
  77. package/dist/seed/products-prize-draws-seed-data.js +2 -1
  78. package/dist/seed/products-standard-seed-data.js +2 -1
  79. package/dist/seed/products-stickers-seed-data.js +9 -1
  80. package/dist/server.d.ts +6 -2
  81. package/dist/server.js +15 -2
  82. package/dist/styles.css +1 -1
  83. package/dist/tailwind-utilities.css +1 -1
  84. package/dist/tokens/motion.d.ts +12 -0
  85. package/dist/tokens/motion.js +12 -0
  86. package/dist/ui/components/CollapsibleSection.d.ts +18 -1
  87. package/dist/ui/components/CollapsibleSection.js +20 -3
  88. package/dist/ui/components/Motion.js +17 -1
  89. package/package.json +1 -1
@@ -0,0 +1,304 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The seller listing form's DRAFT schema — the flat, form-shaped view of a
4
+ * product, registered as `SCHEMAS.forms.product`.
5
+ *
6
+ * ## Why this exists as a separate file
7
+ *
8
+ * It is registered so `roundtrip-diff` can check it. That tool reads
9
+ * `SCHEMAS.forms`, parses real documents through each registered schema, and
10
+ * reports every key the schema would DELETE — and its own header names
11
+ * `productBaseSchema` eating the `classified*` / `liveItem*` fields as the
12
+ * reason it was written.
13
+ *
14
+ * `product` was never registered. So the one tool built to catch exactly this
15
+ * bug could not see the entity the bug was about, and the registry's own
16
+ * docstring said why: *"an entity absent from it has no such protection,
17
+ * which is the whole reason to register early."*
18
+ *
19
+ * ## Draft-shaped, deliberately
20
+ *
21
+ * This models what the user TYPES — flat `classifiedCity`, `liveSpecies`,
22
+ * `printSize` — not what is stored. `draftToProductInput` translates between
23
+ * the two. `roundtrip-diff` lists `product` in its `DRAFT_SHAPED` set for the
24
+ * same reason `blog` and `event` are there: real documents legitimately fail
25
+ * to parse against a draft, and only the DROPPED-KEY half of the check is
26
+ * meaningful. That half is the half that matters.
27
+ *
28
+ * Kept permissive (`.passthrough()`, almost everything optional): a draft is
29
+ * saved continuously while half-filled, so this must accept an in-progress
30
+ * listing. The strict rules live in `productCreateSchema` at the write
31
+ * boundary, which is where refusing a bad listing is useful.
32
+ */
33
+ export declare const productDraftSchema: z.ZodObject<{
34
+ title: z.ZodOptional<z.ZodString>;
35
+ slug: z.ZodOptional<z.ZodString>;
36
+ description: z.ZodOptional<z.ZodString>;
37
+ category: z.ZodOptional<z.ZodString>;
38
+ brand: z.ZodOptional<z.ZodString>;
39
+ condition: z.ZodOptional<z.ZodString>;
40
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
41
+ mainImage: z.ZodOptional<z.ZodString>;
42
+ images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
43
+ video: z.ZodOptional<z.ZodObject<{
44
+ url: z.ZodString;
45
+ type: z.ZodOptional<z.ZodLiteral<"video">>;
46
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
47
+ source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
48
+ youtubeId: z.ZodOptional<z.ZodString>;
49
+ duration: z.ZodOptional<z.ZodNumber>;
50
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
51
+ url: z.ZodString;
52
+ type: z.ZodOptional<z.ZodLiteral<"video">>;
53
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
54
+ source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
55
+ youtubeId: z.ZodOptional<z.ZodString>;
56
+ duration: z.ZodOptional<z.ZodNumber>;
57
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
58
+ url: z.ZodString;
59
+ type: z.ZodOptional<z.ZodLiteral<"video">>;
60
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
61
+ source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
62
+ youtubeId: z.ZodOptional<z.ZodString>;
63
+ duration: z.ZodOptional<z.ZodNumber>;
64
+ }, z.ZodTypeAny, "passthrough">>>;
65
+ youtubeId: z.ZodOptional<z.ZodString>;
66
+ externalVideoUrl: z.ZodOptional<z.ZodString>;
67
+ price: z.ZodOptional<z.ZodNumber>;
68
+ compareAtPrice: z.ZodOptional<z.ZodNumber>;
69
+ stockQuantity: z.ZodOptional<z.ZodNumber>;
70
+ featured: z.ZodOptional<z.ZodBoolean>;
71
+ isPromoted: z.ZodOptional<z.ZodBoolean>;
72
+ isNew: z.ZodOptional<z.ZodBoolean>;
73
+ isOnSale: z.ZodOptional<z.ZodBoolean>;
74
+ allowOffers: z.ZodOptional<z.ZodBoolean>;
75
+ minOfferPercent: z.ZodOptional<z.ZodNumber>;
76
+ shippingPaidBy: z.ZodOptional<z.ZodEnum<["buyer", "seller"]>>;
77
+ pickupAddressId: z.ZodOptional<z.ZodString>;
78
+ insurance: z.ZodOptional<z.ZodBoolean>;
79
+ insuranceCost: z.ZodOptional<z.ZodNumber>;
80
+ gstRate: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<0>, z.ZodLiteral<5>, z.ZodLiteral<12>, z.ZodLiteral<18>, z.ZodLiteral<28>]>>;
81
+ hsnCode: z.ZodOptional<z.ZodString>;
82
+ finalSale: z.ZodOptional<z.ZodBoolean>;
83
+ returnPolicy: z.ZodOptional<z.ZodString>;
84
+ status: z.ZodOptional<z.ZodEnum<["draft", "published"]>>;
85
+ seoTitle: z.ZodOptional<z.ZodString>;
86
+ seoDescription: z.ZodOptional<z.ZodString>;
87
+ barcodeId: z.ZodOptional<z.ZodString>;
88
+ startingBid: z.ZodOptional<z.ZodNumber>;
89
+ reservePrice: z.ZodOptional<z.ZodNumber>;
90
+ buyNowPrice: z.ZodOptional<z.ZodNumber>;
91
+ minBidIncrement: z.ZodOptional<z.ZodNumber>;
92
+ auctionEndDate: z.ZodOptional<z.ZodString>;
93
+ auctionShippingPaidBy: z.ZodOptional<z.ZodEnum<["winner", "seller"]>>;
94
+ autoExtendable: z.ZodOptional<z.ZodBoolean>;
95
+ auctionExtensionMinutes: z.ZodOptional<z.ZodNumber>;
96
+ preOrderDeliveryDate: z.ZodOptional<z.ZodString>;
97
+ preOrderDepositPercent: z.ZodOptional<z.ZodNumber>;
98
+ preOrderMaxQuantity: z.ZodOptional<z.ZodNumber>;
99
+ preOrderProductionStatus: z.ZodOptional<z.ZodEnum<["upcoming", "in_production", "ready_to_ship"]>>;
100
+ preOrderCancellable: z.ZodOptional<z.ZodBoolean>;
101
+ classifiedCity: z.ZodOptional<z.ZodString>;
102
+ classifiedLocality: z.ZodOptional<z.ZodString>;
103
+ classifiedPincode: z.ZodOptional<z.ZodString>;
104
+ classifiedContactMethod: z.ZodOptional<z.ZodEnum<["chat", "phone", "both"]>>;
105
+ classifiedAcceptsShipping: z.ZodOptional<z.ZodBoolean>;
106
+ classifiedNegotiable: z.ZodOptional<z.ZodBoolean>;
107
+ digitalCodeDelivery: z.ZodOptional<z.ZodEnum<["auto-claim", "manual-email"]>>;
108
+ digitalCodePoolSize: z.ZodOptional<z.ZodNumber>;
109
+ digitalCodeRedemptionInstructions: z.ZodOptional<z.ZodString>;
110
+ digitalCodeExpiresAt: z.ZodOptional<z.ZodString>;
111
+ liveSpecies: z.ZodOptional<z.ZodString>;
112
+ liveAgeMonths: z.ZodOptional<z.ZodNumber>;
113
+ liveSex: z.ZodOptional<z.ZodEnum<["male", "female", "unknown", "n/a"]>>;
114
+ liveCareInfo: z.ZodOptional<z.ZodString>;
115
+ liveTransportMethod: z.ZodOptional<z.ZodEnum<["courier", "in-person", "specialist"]>>;
116
+ liveHandlingFee: z.ZodOptional<z.ZodNumber>;
117
+ liveJurisdictions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
118
+ liveCites: z.ZodOptional<z.ZodBoolean>;
119
+ printSize: z.ZodOptional<z.ZodString>;
120
+ printMaterial: z.ZodOptional<z.ZodString>;
121
+ printFinish: z.ZodOptional<z.ZodString>;
122
+ printEditionSize: z.ZodOptional<z.ZodNumber>;
123
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
124
+ title: z.ZodOptional<z.ZodString>;
125
+ slug: z.ZodOptional<z.ZodString>;
126
+ description: z.ZodOptional<z.ZodString>;
127
+ category: z.ZodOptional<z.ZodString>;
128
+ brand: z.ZodOptional<z.ZodString>;
129
+ condition: z.ZodOptional<z.ZodString>;
130
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
131
+ mainImage: z.ZodOptional<z.ZodString>;
132
+ images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
133
+ video: z.ZodOptional<z.ZodObject<{
134
+ url: z.ZodString;
135
+ type: z.ZodOptional<z.ZodLiteral<"video">>;
136
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
137
+ source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
138
+ youtubeId: z.ZodOptional<z.ZodString>;
139
+ duration: z.ZodOptional<z.ZodNumber>;
140
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
141
+ url: z.ZodString;
142
+ type: z.ZodOptional<z.ZodLiteral<"video">>;
143
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
144
+ source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
145
+ youtubeId: z.ZodOptional<z.ZodString>;
146
+ duration: z.ZodOptional<z.ZodNumber>;
147
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
148
+ url: z.ZodString;
149
+ type: z.ZodOptional<z.ZodLiteral<"video">>;
150
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
151
+ source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
152
+ youtubeId: z.ZodOptional<z.ZodString>;
153
+ duration: z.ZodOptional<z.ZodNumber>;
154
+ }, z.ZodTypeAny, "passthrough">>>;
155
+ youtubeId: z.ZodOptional<z.ZodString>;
156
+ externalVideoUrl: z.ZodOptional<z.ZodString>;
157
+ price: z.ZodOptional<z.ZodNumber>;
158
+ compareAtPrice: z.ZodOptional<z.ZodNumber>;
159
+ stockQuantity: z.ZodOptional<z.ZodNumber>;
160
+ featured: z.ZodOptional<z.ZodBoolean>;
161
+ isPromoted: z.ZodOptional<z.ZodBoolean>;
162
+ isNew: z.ZodOptional<z.ZodBoolean>;
163
+ isOnSale: z.ZodOptional<z.ZodBoolean>;
164
+ allowOffers: z.ZodOptional<z.ZodBoolean>;
165
+ minOfferPercent: z.ZodOptional<z.ZodNumber>;
166
+ shippingPaidBy: z.ZodOptional<z.ZodEnum<["buyer", "seller"]>>;
167
+ pickupAddressId: z.ZodOptional<z.ZodString>;
168
+ insurance: z.ZodOptional<z.ZodBoolean>;
169
+ insuranceCost: z.ZodOptional<z.ZodNumber>;
170
+ gstRate: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<0>, z.ZodLiteral<5>, z.ZodLiteral<12>, z.ZodLiteral<18>, z.ZodLiteral<28>]>>;
171
+ hsnCode: z.ZodOptional<z.ZodString>;
172
+ finalSale: z.ZodOptional<z.ZodBoolean>;
173
+ returnPolicy: z.ZodOptional<z.ZodString>;
174
+ status: z.ZodOptional<z.ZodEnum<["draft", "published"]>>;
175
+ seoTitle: z.ZodOptional<z.ZodString>;
176
+ seoDescription: z.ZodOptional<z.ZodString>;
177
+ barcodeId: z.ZodOptional<z.ZodString>;
178
+ startingBid: z.ZodOptional<z.ZodNumber>;
179
+ reservePrice: z.ZodOptional<z.ZodNumber>;
180
+ buyNowPrice: z.ZodOptional<z.ZodNumber>;
181
+ minBidIncrement: z.ZodOptional<z.ZodNumber>;
182
+ auctionEndDate: z.ZodOptional<z.ZodString>;
183
+ auctionShippingPaidBy: z.ZodOptional<z.ZodEnum<["winner", "seller"]>>;
184
+ autoExtendable: z.ZodOptional<z.ZodBoolean>;
185
+ auctionExtensionMinutes: z.ZodOptional<z.ZodNumber>;
186
+ preOrderDeliveryDate: z.ZodOptional<z.ZodString>;
187
+ preOrderDepositPercent: z.ZodOptional<z.ZodNumber>;
188
+ preOrderMaxQuantity: z.ZodOptional<z.ZodNumber>;
189
+ preOrderProductionStatus: z.ZodOptional<z.ZodEnum<["upcoming", "in_production", "ready_to_ship"]>>;
190
+ preOrderCancellable: z.ZodOptional<z.ZodBoolean>;
191
+ classifiedCity: z.ZodOptional<z.ZodString>;
192
+ classifiedLocality: z.ZodOptional<z.ZodString>;
193
+ classifiedPincode: z.ZodOptional<z.ZodString>;
194
+ classifiedContactMethod: z.ZodOptional<z.ZodEnum<["chat", "phone", "both"]>>;
195
+ classifiedAcceptsShipping: z.ZodOptional<z.ZodBoolean>;
196
+ classifiedNegotiable: z.ZodOptional<z.ZodBoolean>;
197
+ digitalCodeDelivery: z.ZodOptional<z.ZodEnum<["auto-claim", "manual-email"]>>;
198
+ digitalCodePoolSize: z.ZodOptional<z.ZodNumber>;
199
+ digitalCodeRedemptionInstructions: z.ZodOptional<z.ZodString>;
200
+ digitalCodeExpiresAt: z.ZodOptional<z.ZodString>;
201
+ liveSpecies: z.ZodOptional<z.ZodString>;
202
+ liveAgeMonths: z.ZodOptional<z.ZodNumber>;
203
+ liveSex: z.ZodOptional<z.ZodEnum<["male", "female", "unknown", "n/a"]>>;
204
+ liveCareInfo: z.ZodOptional<z.ZodString>;
205
+ liveTransportMethod: z.ZodOptional<z.ZodEnum<["courier", "in-person", "specialist"]>>;
206
+ liveHandlingFee: z.ZodOptional<z.ZodNumber>;
207
+ liveJurisdictions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
208
+ liveCites: z.ZodOptional<z.ZodBoolean>;
209
+ printSize: z.ZodOptional<z.ZodString>;
210
+ printMaterial: z.ZodOptional<z.ZodString>;
211
+ printFinish: z.ZodOptional<z.ZodString>;
212
+ printEditionSize: z.ZodOptional<z.ZodNumber>;
213
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
214
+ title: z.ZodOptional<z.ZodString>;
215
+ slug: z.ZodOptional<z.ZodString>;
216
+ description: z.ZodOptional<z.ZodString>;
217
+ category: z.ZodOptional<z.ZodString>;
218
+ brand: z.ZodOptional<z.ZodString>;
219
+ condition: z.ZodOptional<z.ZodString>;
220
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
221
+ mainImage: z.ZodOptional<z.ZodString>;
222
+ images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
223
+ video: z.ZodOptional<z.ZodObject<{
224
+ url: z.ZodString;
225
+ type: z.ZodOptional<z.ZodLiteral<"video">>;
226
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
227
+ source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
228
+ youtubeId: z.ZodOptional<z.ZodString>;
229
+ duration: z.ZodOptional<z.ZodNumber>;
230
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
231
+ url: z.ZodString;
232
+ type: z.ZodOptional<z.ZodLiteral<"video">>;
233
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
234
+ source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
235
+ youtubeId: z.ZodOptional<z.ZodString>;
236
+ duration: z.ZodOptional<z.ZodNumber>;
237
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
238
+ url: z.ZodString;
239
+ type: z.ZodOptional<z.ZodLiteral<"video">>;
240
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
241
+ source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
242
+ youtubeId: z.ZodOptional<z.ZodString>;
243
+ duration: z.ZodOptional<z.ZodNumber>;
244
+ }, z.ZodTypeAny, "passthrough">>>;
245
+ youtubeId: z.ZodOptional<z.ZodString>;
246
+ externalVideoUrl: z.ZodOptional<z.ZodString>;
247
+ price: z.ZodOptional<z.ZodNumber>;
248
+ compareAtPrice: z.ZodOptional<z.ZodNumber>;
249
+ stockQuantity: z.ZodOptional<z.ZodNumber>;
250
+ featured: z.ZodOptional<z.ZodBoolean>;
251
+ isPromoted: z.ZodOptional<z.ZodBoolean>;
252
+ isNew: z.ZodOptional<z.ZodBoolean>;
253
+ isOnSale: z.ZodOptional<z.ZodBoolean>;
254
+ allowOffers: z.ZodOptional<z.ZodBoolean>;
255
+ minOfferPercent: z.ZodOptional<z.ZodNumber>;
256
+ shippingPaidBy: z.ZodOptional<z.ZodEnum<["buyer", "seller"]>>;
257
+ pickupAddressId: z.ZodOptional<z.ZodString>;
258
+ insurance: z.ZodOptional<z.ZodBoolean>;
259
+ insuranceCost: z.ZodOptional<z.ZodNumber>;
260
+ gstRate: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<0>, z.ZodLiteral<5>, z.ZodLiteral<12>, z.ZodLiteral<18>, z.ZodLiteral<28>]>>;
261
+ hsnCode: z.ZodOptional<z.ZodString>;
262
+ finalSale: z.ZodOptional<z.ZodBoolean>;
263
+ returnPolicy: z.ZodOptional<z.ZodString>;
264
+ status: z.ZodOptional<z.ZodEnum<["draft", "published"]>>;
265
+ seoTitle: z.ZodOptional<z.ZodString>;
266
+ seoDescription: z.ZodOptional<z.ZodString>;
267
+ barcodeId: z.ZodOptional<z.ZodString>;
268
+ startingBid: z.ZodOptional<z.ZodNumber>;
269
+ reservePrice: z.ZodOptional<z.ZodNumber>;
270
+ buyNowPrice: z.ZodOptional<z.ZodNumber>;
271
+ minBidIncrement: z.ZodOptional<z.ZodNumber>;
272
+ auctionEndDate: z.ZodOptional<z.ZodString>;
273
+ auctionShippingPaidBy: z.ZodOptional<z.ZodEnum<["winner", "seller"]>>;
274
+ autoExtendable: z.ZodOptional<z.ZodBoolean>;
275
+ auctionExtensionMinutes: z.ZodOptional<z.ZodNumber>;
276
+ preOrderDeliveryDate: z.ZodOptional<z.ZodString>;
277
+ preOrderDepositPercent: z.ZodOptional<z.ZodNumber>;
278
+ preOrderMaxQuantity: z.ZodOptional<z.ZodNumber>;
279
+ preOrderProductionStatus: z.ZodOptional<z.ZodEnum<["upcoming", "in_production", "ready_to_ship"]>>;
280
+ preOrderCancellable: z.ZodOptional<z.ZodBoolean>;
281
+ classifiedCity: z.ZodOptional<z.ZodString>;
282
+ classifiedLocality: z.ZodOptional<z.ZodString>;
283
+ classifiedPincode: z.ZodOptional<z.ZodString>;
284
+ classifiedContactMethod: z.ZodOptional<z.ZodEnum<["chat", "phone", "both"]>>;
285
+ classifiedAcceptsShipping: z.ZodOptional<z.ZodBoolean>;
286
+ classifiedNegotiable: z.ZodOptional<z.ZodBoolean>;
287
+ digitalCodeDelivery: z.ZodOptional<z.ZodEnum<["auto-claim", "manual-email"]>>;
288
+ digitalCodePoolSize: z.ZodOptional<z.ZodNumber>;
289
+ digitalCodeRedemptionInstructions: z.ZodOptional<z.ZodString>;
290
+ digitalCodeExpiresAt: z.ZodOptional<z.ZodString>;
291
+ liveSpecies: z.ZodOptional<z.ZodString>;
292
+ liveAgeMonths: z.ZodOptional<z.ZodNumber>;
293
+ liveSex: z.ZodOptional<z.ZodEnum<["male", "female", "unknown", "n/a"]>>;
294
+ liveCareInfo: z.ZodOptional<z.ZodString>;
295
+ liveTransportMethod: z.ZodOptional<z.ZodEnum<["courier", "in-person", "specialist"]>>;
296
+ liveHandlingFee: z.ZodOptional<z.ZodNumber>;
297
+ liveJurisdictions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
298
+ liveCites: z.ZodOptional<z.ZodBoolean>;
299
+ printSize: z.ZodOptional<z.ZodString>;
300
+ printMaterial: z.ZodOptional<z.ZodString>;
301
+ printFinish: z.ZodOptional<z.ZodString>;
302
+ printEditionSize: z.ZodOptional<z.ZodNumber>;
303
+ }, z.ZodTypeAny, "passthrough">>;
304
+ export type ProductDraftInput = z.infer<typeof productDraftSchema>;
@@ -0,0 +1,132 @@
1
+ import { z } from "zod";
2
+ import { PRODUCT_MAX_IMAGES } from "../../../_internal/shared/media/limits";
3
+ /**
4
+ * The seller listing form's DRAFT schema — the flat, form-shaped view of a
5
+ * product, registered as `SCHEMAS.forms.product`.
6
+ *
7
+ * ## Why this exists as a separate file
8
+ *
9
+ * It is registered so `roundtrip-diff` can check it. That tool reads
10
+ * `SCHEMAS.forms`, parses real documents through each registered schema, and
11
+ * reports every key the schema would DELETE — and its own header names
12
+ * `productBaseSchema` eating the `classified*` / `liveItem*` fields as the
13
+ * reason it was written.
14
+ *
15
+ * `product` was never registered. So the one tool built to catch exactly this
16
+ * bug could not see the entity the bug was about, and the registry's own
17
+ * docstring said why: *"an entity absent from it has no such protection,
18
+ * which is the whole reason to register early."*
19
+ *
20
+ * ## Draft-shaped, deliberately
21
+ *
22
+ * This models what the user TYPES — flat `classifiedCity`, `liveSpecies`,
23
+ * `printSize` — not what is stored. `draftToProductInput` translates between
24
+ * the two. `roundtrip-diff` lists `product` in its `DRAFT_SHAPED` set for the
25
+ * same reason `blog` and `event` are there: real documents legitimately fail
26
+ * to parse against a draft, and only the DROPPED-KEY half of the check is
27
+ * meaningful. That half is the half that matters.
28
+ *
29
+ * Kept permissive (`.passthrough()`, almost everything optional): a draft is
30
+ * saved continuously while half-filled, so this must accept an in-progress
31
+ * listing. The strict rules live in `productCreateSchema` at the write
32
+ * boundary, which is where refusing a bad listing is useful.
33
+ */
34
+ export const productDraftSchema = z
35
+ .object({
36
+ title: z.string().max(200).optional(),
37
+ slug: z.string().optional(),
38
+ description: z.string().max(10000).optional(),
39
+ category: z.string().optional(),
40
+ brand: z.string().optional(),
41
+ condition: z.string().optional(),
42
+ tags: z.array(z.string()).optional(),
43
+ // Media
44
+ mainImage: z.string().optional(),
45
+ images: z.array(z.string()).max(PRODUCT_MAX_IMAGES).optional(),
46
+ /*
47
+ * Spelled out rather than `z.record(z.unknown())`: a draft's video can
48
+ * come from an upload, a YouTube id or an external URL, and each source
49
+ * fills a different subset. Naming the fields is what lets this schema
50
+ * report a video the form would fail to carry.
51
+ */
52
+ video: z
53
+ .object({
54
+ url: z.string(),
55
+ type: z.literal("video").optional(),
56
+ thumbnailUrl: z.string().optional(),
57
+ source: z.enum(["upload", "youtube", "external"]).optional(),
58
+ youtubeId: z.string().optional(),
59
+ duration: z.number().optional(),
60
+ })
61
+ .passthrough()
62
+ .optional(),
63
+ youtubeId: z.string().optional(),
64
+ externalVideoUrl: z.string().optional(),
65
+ // Pricing
66
+ price: z.number().optional(),
67
+ compareAtPrice: z.number().optional(),
68
+ stockQuantity: z.number().optional(),
69
+ featured: z.boolean().optional(),
70
+ isPromoted: z.boolean().optional(),
71
+ isNew: z.boolean().optional(),
72
+ isOnSale: z.boolean().optional(),
73
+ allowOffers: z.boolean().optional(),
74
+ minOfferPercent: z.number().optional(),
75
+ // Shipping
76
+ shippingPaidBy: z.enum(["buyer", "seller"]).optional(),
77
+ pickupAddressId: z.string().optional(),
78
+ insurance: z.boolean().optional(),
79
+ insuranceCost: z.number().optional(),
80
+ gstRate: z.union([z.literal(0), z.literal(5), z.literal(12), z.literal(18), z.literal(28)]).optional(),
81
+ hsnCode: z.string().optional(),
82
+ // Returns
83
+ finalSale: z.boolean().optional(),
84
+ returnPolicy: z.string().max(1000).optional(),
85
+ // Publish
86
+ status: z.enum(["draft", "published"]).optional(),
87
+ seoTitle: z.string().optional(),
88
+ seoDescription: z.string().optional(),
89
+ barcodeId: z.string().optional(),
90
+ // Auction
91
+ startingBid: z.number().optional(),
92
+ reservePrice: z.number().optional(),
93
+ buyNowPrice: z.number().optional(),
94
+ minBidIncrement: z.number().optional(),
95
+ auctionEndDate: z.string().optional(),
96
+ auctionShippingPaidBy: z.enum(["winner", "seller"]).optional(),
97
+ autoExtendable: z.boolean().optional(),
98
+ auctionExtensionMinutes: z.number().optional(),
99
+ // Pre-order
100
+ preOrderDeliveryDate: z.string().optional(),
101
+ preOrderDepositPercent: z.number().optional(),
102
+ preOrderMaxQuantity: z.number().optional(),
103
+ preOrderProductionStatus: z.enum(["upcoming", "in_production", "ready_to_ship"]).optional(),
104
+ preOrderCancellable: z.boolean().optional(),
105
+ // Classified — FLAT here, nested on the document.
106
+ classifiedCity: z.string().optional(),
107
+ classifiedLocality: z.string().optional(),
108
+ classifiedPincode: z.string().optional(),
109
+ classifiedContactMethod: z.enum(["chat", "phone", "both"]).optional(),
110
+ classifiedAcceptsShipping: z.boolean().optional(),
111
+ classifiedNegotiable: z.boolean().optional(),
112
+ // Digital code — FLAT here, nested on the document.
113
+ digitalCodeDelivery: z.enum(["auto-claim", "manual-email"]).optional(),
114
+ digitalCodePoolSize: z.number().optional(),
115
+ digitalCodeRedemptionInstructions: z.string().optional(),
116
+ digitalCodeExpiresAt: z.string().optional(),
117
+ // Live item — FLAT here, nested on the document.
118
+ liveSpecies: z.string().optional(),
119
+ liveAgeMonths: z.number().optional(),
120
+ liveSex: z.enum(["male", "female", "unknown", "n/a"]).optional(),
121
+ liveCareInfo: z.string().optional(),
122
+ liveTransportMethod: z.enum(["courier", "in-person", "specialist"]).optional(),
123
+ liveHandlingFee: z.number().optional(),
124
+ liveJurisdictions: z.array(z.string()).optional(),
125
+ liveCites: z.boolean().optional(),
126
+ // Print meta (art + stickers) — FLAT here, nested on the document.
127
+ printSize: z.string().optional(),
128
+ printMaterial: z.string().optional(),
129
+ printFinish: z.string().optional(),
130
+ printEditionSize: z.number().optional(),
131
+ })
132
+ .passthrough();
@@ -0,0 +1,117 @@
1
+ import type { SellerProductDraft } from "../components/SellerProductShell";
2
+ /**
3
+ * Seller listing form draft <-> product write payload.
4
+ *
5
+ * ## The bug this closes
6
+ *
7
+ * `SellerProductDraft` is FLAT — `classifiedCity`, `liveSpecies`,
8
+ * `digitalCodeDelivery`, `printSize` — because that is the shape a form wants:
9
+ * one key per control, no nesting to thread through `onChange`.
10
+ *
11
+ * `productCreateSchema` is NESTED — `classified.meetupArea.city`,
12
+ * `liveItem.species`, `digitalCode.codeDeliveryMethod`, `printMeta.size` —
13
+ * because that is the shape `ProductDocument` stores.
14
+ *
15
+ * Nothing translated between them. Every `store/*\/new/page.tsx` spread the
16
+ * draft straight into `createSellerProductAction`, and `productBaseSchema` is
17
+ * a plain `z.object()` with no `.passthrough()`, so **every flat per-type key
18
+ * was stripped before the write**. A seller filled in a classified's meetup
19
+ * city, a live animal's species and permitted jurisdictions, a digital code's
20
+ * delivery method, or an art print's size and edition — pressed Publish, got a
21
+ * success toast — and the listing was saved without the fields that made it
22
+ * that KIND of listing.
23
+ *
24
+ * ## Why this lives in the ACTION, not in the pages
25
+ *
26
+ * There are ~10 `new/page.tsx` and ~10 `[id]/edit/page.tsx` files and every
27
+ * one of them does `{ ...draft, listingType, status }`. Applying the mapping
28
+ * at each is 20 chances to forget, and a new listing type's page would start
29
+ * out broken in exactly the way this fixes. `createSellerProductAction` and
30
+ * `sellerUpdateProductAction` are the single choke point every seller write
31
+ * already passes through, so the mapping happens once and cannot be bypassed
32
+ * by a page that did not know about it (the lesson of Root Cause #75).
33
+ *
34
+ * ## Round-trip
35
+ *
36
+ * `draftToProductInput` and `productToDraft` are a matched pair in one file so
37
+ * they cannot drift: a field added to one and not the other is visible in a
38
+ * single screen of code. The edit pages need the inverse to seed the form from
39
+ * a stored document.
40
+ */
41
+ /**
42
+ * The parts of a stored product this mapper reads.
43
+ *
44
+ * Declared structurally rather than importing `ProductDocument`: the inbound
45
+ * value comes from a server action's `JsonValue` payload where dates are
46
+ * strings, and every block is optional because documents written before the
47
+ * nested shapes existed carry none of them.
48
+ */
49
+ export interface StoredProductBlocks {
50
+ finalSale?: boolean;
51
+ returnPolicy?: string;
52
+ classified?: {
53
+ meetupArea?: {
54
+ city?: string;
55
+ locality?: string;
56
+ pincode?: string;
57
+ };
58
+ acceptsShipping?: boolean;
59
+ negotiable?: boolean;
60
+ };
61
+ digitalCode?: {
62
+ codeDeliveryMethod?: string;
63
+ codePoolSize?: number;
64
+ redemptionInstructions?: string;
65
+ expiresAt?: string;
66
+ };
67
+ liveItem?: {
68
+ species?: string;
69
+ ageMonths?: number;
70
+ sex?: string;
71
+ careInfo?: string;
72
+ transport?: {
73
+ method?: string;
74
+ handlingFee?: number;
75
+ };
76
+ jurisdictionAllowed?: string[];
77
+ cites?: string;
78
+ };
79
+ printMeta?: {
80
+ size?: string;
81
+ material?: string;
82
+ finish?: string;
83
+ editionSize?: number;
84
+ };
85
+ }
86
+ /**
87
+ * The nested blocks this mapper WRITES, plus whatever else the draft carried.
88
+ * `ProductWritePayload` is deliberately not `ProductDocument`: the draft holds
89
+ * form-shaped values (ISO date strings, a `MediaField` video) that the write
90
+ * schema coerces, so typing it as the document would be a lie the compiler
91
+ * would then make everyone cast around.
92
+ */
93
+ export interface ProductWritePayload {
94
+ classified?: NonNullable<StoredProductBlocks["classified"]>;
95
+ digitalCode?: NonNullable<StoredProductBlocks["digitalCode"]>;
96
+ liveItem?: NonNullable<StoredProductBlocks["liveItem"]>;
97
+ printMeta?: NonNullable<StoredProductBlocks["printMeta"]>;
98
+ [key: string]: unknown;
99
+ }
100
+ /**
101
+ * Fold the draft's flat per-type keys into the nested blocks the write schema
102
+ * expects. Every other key passes through untouched.
103
+ *
104
+ * A block is emitted only when the draft actually carries something for it, so
105
+ * saving a standard listing does not write an empty `classified: {}`.
106
+ */
107
+ export declare function draftToProductInput(draft: SellerProductDraft & {
108
+ [key: string]: unknown;
109
+ }): ProductWritePayload;
110
+ /**
111
+ * The inverse — seed the form from a stored document.
112
+ *
113
+ * Tolerant of documents written before the nested blocks existed: a missing
114
+ * block yields undefined flat keys, which render as empty inputs rather than
115
+ * throwing.
116
+ */
117
+ export declare function productToDraft(product: StoredProductBlocks): Partial<SellerProductDraft>;