@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,49 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The statuses a human review can produce.
4
+ *
5
+ * `satisfies readonly ModerationStatus[]` is load-bearing: it is what stops
6
+ * this becoming an independent, drifting copy of the union.
7
+ */
8
+ export declare const MODERATION_REVIEWER_STATUSES: readonly ["approved", "rejected"];
9
+ /**
10
+ * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
11
+ * by schema instance and every zod wrapper returns a new one.
12
+ */
13
+ export declare const moderationReviewFormSchema: z.ZodEffects<z.ZodObject<{
14
+ status: z.ZodEnum<["approved", "rejected"]>;
15
+ reason: z.ZodOptional<z.ZodString>;
16
+ }, "strict", z.ZodTypeAny, {
17
+ status: "approved" | "rejected";
18
+ reason?: string | undefined;
19
+ }, {
20
+ status: "approved" | "rejected";
21
+ reason?: string | undefined;
22
+ }>, {
23
+ status: "approved" | "rejected";
24
+ reason?: string | undefined;
25
+ }, {
26
+ status: "approved" | "rejected";
27
+ reason?: string | undefined;
28
+ }>;
29
+ export type ModerationReviewFormValues = z.infer<typeof moderationReviewFormSchema>;
30
+ /**
31
+ * Route contract. Identical to the form contract — a review is a single
32
+ * atomic decision, so there is no partial variant.
33
+ */
34
+ export declare const moderationReviewUpdateSchema: z.ZodEffects<z.ZodObject<{
35
+ status: z.ZodEnum<["approved", "rejected"]>;
36
+ reason: z.ZodOptional<z.ZodString>;
37
+ }, "strict", z.ZodTypeAny, {
38
+ status: "approved" | "rejected";
39
+ reason?: string | undefined;
40
+ }, {
41
+ status: "approved" | "rejected";
42
+ reason?: string | undefined;
43
+ }>, {
44
+ status: "approved" | "rejected";
45
+ reason?: string | undefined;
46
+ }, {
47
+ status: "approved" | "rejected";
48
+ reason?: string | undefined;
49
+ }>;
@@ -0,0 +1,101 @@
1
+ /*
2
+ * WHY: `PATCH /api/admin/moderation/[id]` spread the raw request body straight
3
+ * into Firestore:
4
+ *
5
+ * const body = await parseJsonBody<Record<string, JsonValue>>(request);
6
+ * await moderationQueueRepository.update(id, { ...body, reviewerId, reviewedAt });
7
+ *
8
+ * There was no Zod anywhere on this path — not on the route, not on
9
+ * either of the two client surfaces that call it. Under `ROLES_ADMIN_MOD`
10
+ * a caller could write ANY key onto a moderation record, including
11
+ * `entityId`, `ownerId`, `submittedAt` or an invented one, and could set
12
+ * `status` to any string at all — the real `ModerationStatus` union was
13
+ * never consulted.
14
+ *
15
+ * Two client surfaces call it and neither validated: the queue page's
16
+ * inline Approve/Reject controls and `ModerationDetailActions`. Both let
17
+ * a rejection be submitted with an EMPTY reason, so content could be
18
+ * blocked with no record of why.
19
+ * WHAT: One schema shared by the route and both client surfaces.
20
+ *
21
+ * ## A reviewer may only set the two statuses a REVIEW produces
22
+ *
23
+ * `ModerationStatus` has four values, but `pending` is the initial state and
24
+ * `auto-approved` is what the system writes when nothing needed a human. A
25
+ * reviewer setting either by hand would be falsifying the record — so the
26
+ * settable set is the subset below, declared `satisfies readonly
27
+ * ModerationStatus[]` rather than restated, which makes a value outside the
28
+ * real union a COMPILE error instead of a filter that matches nothing
29
+ * (Recurrent Root Cause #33).
30
+ *
31
+ * ## `reviewerId` / `reviewedAt` are absent on purpose
32
+ *
33
+ * The route sets both from the session and the server clock. Accepting them
34
+ * here would let a caller attribute a review to somebody else, or backdate it.
35
+ *
36
+ * EXPORTS:
37
+ * moderationReviewFormSchema, moderationReviewUpdateSchema,
38
+ * MODERATION_REVIEWER_STATUSES, type ModerationReviewFormValues
39
+ *
40
+ * @tag domain:store-extensions,moderation
41
+ * @tag layer:schema
42
+ * @tag pattern:none
43
+ * @tag access:isomorphic
44
+ * @tag consumers:admin/moderation pages,/api/admin/moderation/[id]
45
+ * @tag sideEffects:none
46
+ */
47
+ import { z } from "zod";
48
+ import { annotate } from "../../shell/field-ui-meta";
49
+ /**
50
+ * The statuses a human review can produce.
51
+ *
52
+ * `satisfies readonly ModerationStatus[]` is load-bearing: it is what stops
53
+ * this becoming an independent, drifting copy of the union.
54
+ */
55
+ export const MODERATION_REVIEWER_STATUSES = [
56
+ "approved",
57
+ "rejected",
58
+ ];
59
+ /**
60
+ * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
61
+ * by schema instance and every zod wrapper returns a new one.
62
+ */
63
+ export const moderationReviewFormSchema = z
64
+ .object({
65
+ status: annotate(z.enum(MODERATION_REVIEWER_STATUSES), {
66
+ section: "review",
67
+ sectionLabel: "Review decision",
68
+ sectionRequired: true,
69
+ quick: true,
70
+ order: 1,
71
+ row: "pair",
72
+ }),
73
+ reason: annotate(z.string().max(500, "Keep the reason under 500 characters.").optional(), {
74
+ section: "review",
75
+ order: 2,
76
+ row: "full",
77
+ kind: "textarea",
78
+ help: "Required when rejecting — this is the record of why the media was blocked.",
79
+ }),
80
+ })
81
+ // Unknown keys are a 400, not a silent drop. A plain `z.object()` would
82
+ // STRIP them, which is the quieter half of the same bug: the caller is told
83
+ // the write succeeded and the key it sent simply vanished.
84
+ .strict()
85
+ .superRefine((v, ctx) => {
86
+ // An approval needs no justification; a rejection does. Blocking someone's
87
+ // media with an empty `reason` leaves the owner, and the next reviewer,
88
+ // with no way to know what was wrong with it.
89
+ if (v.status === "rejected" && !v.reason?.trim()) {
90
+ ctx.addIssue({
91
+ code: z.ZodIssueCode.custom,
92
+ path: ["reason"],
93
+ message: "Give a reason for the rejection.",
94
+ });
95
+ }
96
+ });
97
+ /**
98
+ * Route contract. Identical to the form contract — a review is a single
99
+ * atomic decision, so there is no partial variant.
100
+ */
101
+ export const moderationReviewUpdateSchema = moderationReviewFormSchema;
@@ -0,0 +1,153 @@
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 payoutMethodFormSchema: z.ZodEffects<z.ZodObject<{
7
+ type: z.ZodEnum<["upi", "bank", "card", "other"]>;
8
+ label: z.ZodString;
9
+ upiVpa: z.ZodOptional<z.ZodString>;
10
+ accountHolderName: z.ZodOptional<z.ZodString>;
11
+ accountNumber: z.ZodOptional<z.ZodString>;
12
+ ifscCode: z.ZodOptional<z.ZodString>;
13
+ bankName: z.ZodOptional<z.ZodString>;
14
+ isDefault: z.ZodBoolean;
15
+ isActive: z.ZodBoolean;
16
+ }, "strip", z.ZodTypeAny, {
17
+ type: "card" | "other" | "upi" | "bank";
18
+ label: string;
19
+ isDefault: boolean;
20
+ isActive: boolean;
21
+ accountHolderName?: string | undefined;
22
+ ifscCode?: string | undefined;
23
+ bankName?: string | undefined;
24
+ accountNumber?: string | undefined;
25
+ upiVpa?: string | undefined;
26
+ }, {
27
+ type: "card" | "other" | "upi" | "bank";
28
+ label: string;
29
+ isDefault: boolean;
30
+ isActive: boolean;
31
+ accountHolderName?: string | undefined;
32
+ ifscCode?: string | undefined;
33
+ bankName?: string | undefined;
34
+ accountNumber?: string | undefined;
35
+ upiVpa?: string | undefined;
36
+ }>, {
37
+ type: "card" | "other" | "upi" | "bank";
38
+ label: string;
39
+ isDefault: boolean;
40
+ isActive: boolean;
41
+ accountHolderName?: string | undefined;
42
+ ifscCode?: string | undefined;
43
+ bankName?: string | undefined;
44
+ accountNumber?: string | undefined;
45
+ upiVpa?: string | undefined;
46
+ }, {
47
+ type: "card" | "other" | "upi" | "bank";
48
+ label: string;
49
+ isDefault: boolean;
50
+ isActive: boolean;
51
+ accountHolderName?: string | undefined;
52
+ ifscCode?: string | undefined;
53
+ bankName?: string | undefined;
54
+ accountNumber?: string | undefined;
55
+ upiVpa?: string | undefined;
56
+ }>;
57
+ export type PayoutMethodFormValues = z.infer<typeof payoutMethodFormSchema>;
58
+ /**
59
+ * Create contract. `sellerId`/`storeId` are absent on purpose — the route sets
60
+ * both from the session, so a caller cannot file a payout method against
61
+ * another seller by adding fields to the body.
62
+ */
63
+ export declare const payoutMethodCreateSchema: z.ZodEffects<z.ZodObject<{
64
+ type: z.ZodEnum<["upi", "bank", "card", "other"]>;
65
+ label: z.ZodString;
66
+ upiVpa: z.ZodOptional<z.ZodString>;
67
+ accountHolderName: z.ZodOptional<z.ZodString>;
68
+ accountNumber: z.ZodOptional<z.ZodString>;
69
+ ifscCode: z.ZodOptional<z.ZodString>;
70
+ bankName: z.ZodOptional<z.ZodString>;
71
+ isDefault: z.ZodBoolean;
72
+ isActive: z.ZodBoolean;
73
+ }, "strip", z.ZodTypeAny, {
74
+ type: "card" | "other" | "upi" | "bank";
75
+ label: string;
76
+ isDefault: boolean;
77
+ isActive: boolean;
78
+ accountHolderName?: string | undefined;
79
+ ifscCode?: string | undefined;
80
+ bankName?: string | undefined;
81
+ accountNumber?: string | undefined;
82
+ upiVpa?: string | undefined;
83
+ }, {
84
+ type: "card" | "other" | "upi" | "bank";
85
+ label: string;
86
+ isDefault: boolean;
87
+ isActive: boolean;
88
+ accountHolderName?: string | undefined;
89
+ ifscCode?: string | undefined;
90
+ bankName?: string | undefined;
91
+ accountNumber?: string | undefined;
92
+ upiVpa?: string | undefined;
93
+ }>, {
94
+ type: "card" | "other" | "upi" | "bank";
95
+ label: string;
96
+ isDefault: boolean;
97
+ isActive: boolean;
98
+ accountHolderName?: string | undefined;
99
+ ifscCode?: string | undefined;
100
+ bankName?: string | undefined;
101
+ accountNumber?: string | undefined;
102
+ upiVpa?: string | undefined;
103
+ }, {
104
+ type: "card" | "other" | "upi" | "bank";
105
+ label: string;
106
+ isDefault: boolean;
107
+ isActive: boolean;
108
+ accountHolderName?: string | undefined;
109
+ ifscCode?: string | undefined;
110
+ bankName?: string | undefined;
111
+ accountNumber?: string | undefined;
112
+ upiVpa?: string | undefined;
113
+ }>;
114
+ /**
115
+ * Update contract.
116
+ *
117
+ * The per-type rules deliberately do NOT apply here: a PATCH may carry a
118
+ * single field (`isDefault`, say) and requiring the whole bank block on every
119
+ * partial update would make toggling the default impossible. The bounds still
120
+ * apply to whatever IS sent, and the create path is where completeness is
121
+ * enforced.
122
+ */
123
+ export declare const payoutMethodUpdateSchema: z.ZodObject<{
124
+ type: z.ZodOptional<z.ZodEnum<["upi", "bank", "card", "other"]>>;
125
+ label: z.ZodOptional<z.ZodString>;
126
+ upiVpa: z.ZodOptional<z.ZodString>;
127
+ accountHolderName: z.ZodOptional<z.ZodString>;
128
+ accountNumber: z.ZodOptional<z.ZodString>;
129
+ ifscCode: z.ZodOptional<z.ZodString>;
130
+ bankName: z.ZodOptional<z.ZodString>;
131
+ isDefault: z.ZodOptional<z.ZodBoolean>;
132
+ isActive: z.ZodOptional<z.ZodBoolean>;
133
+ }, "strict", z.ZodTypeAny, {
134
+ type?: "card" | "other" | "upi" | "bank" | undefined;
135
+ label?: string | undefined;
136
+ isDefault?: boolean | undefined;
137
+ accountHolderName?: string | undefined;
138
+ ifscCode?: string | undefined;
139
+ bankName?: string | undefined;
140
+ isActive?: boolean | undefined;
141
+ accountNumber?: string | undefined;
142
+ upiVpa?: string | undefined;
143
+ }, {
144
+ type?: "card" | "other" | "upi" | "bank" | undefined;
145
+ label?: string | undefined;
146
+ isDefault?: boolean | undefined;
147
+ accountHolderName?: string | undefined;
148
+ ifscCode?: string | undefined;
149
+ bankName?: string | undefined;
150
+ isActive?: boolean | undefined;
151
+ accountNumber?: string | undefined;
152
+ upiVpa?: string | undefined;
153
+ }>;
@@ -0,0 +1,155 @@
1
+ /*
2
+ * WHY: A seller's payout method is where their money goes, and it was
3
+ * validated at ZERO layers. Measured 2026-08-24:
4
+ * · `store/payout-methods/new` — `accountNumber` and `ifscCode` are
5
+ * bare <Input>s with no `required` and no pattern; `onSave` has no
6
+ * validation at all and posts `form as Record<string, unknown>`.
7
+ * Both default to `""`, so an empty bank payout method saved cleanly.
8
+ * · `.../[id]/edit` — `onSave` has no validation either, and does not
9
+ * even render the account-number/IFSC inputs.
10
+ * · `POST /api/store/payout-methods` — `parseJsonBody` then `...body`
11
+ * spread into `.create()`. No Zod.
12
+ * · `PATCH .../[id]` — the raw body passed straight to `.update()`.
13
+ * No Zod, no field filtering.
14
+ * A payout method with a blank account number is not a validation nicety;
15
+ * it is a payout that cannot be made, discovered at payout time.
16
+ * WHAT: One schema for the pages AND the routes.
17
+ *
18
+ * ## The rules are per TYPE, which is why this needs a superRefine
19
+ *
20
+ * A UPI method needs a VPA and no bank fields; a bank method needs an account
21
+ * number, an IFSC and a holder name and no VPA. Per-field `.min(1)` cannot
22
+ * express that — it would make every field mandatory for every type.
23
+ *
24
+ * The IFSC and UPI patterns are the ones already used by
25
+ * `SellerPayoutSettingsView` (`^[A-Z]{4}0[A-Z0-9]{6}$` and the VPA shape), not
26
+ * a second pair invented here — two regexes for one format is how they come to
27
+ * disagree about what a valid IFSC is.
28
+ *
29
+ * EXPORTS:
30
+ * payoutMethodFormSchema, payoutMethodCreateSchema,
31
+ * payoutMethodUpdateSchema, type PayoutMethodFormValues
32
+ *
33
+ * @tag domain:store-extensions
34
+ * @tag layer:schema
35
+ * @tag pattern:none
36
+ * @tag access:isomorphic
37
+ * @tag consumers:store/payout-methods pages,/api/store/payout-methods
38
+ * @tag sideEffects:none
39
+ */
40
+ import { z } from "zod";
41
+ import { annotate } from "../../shell/field-ui-meta";
42
+ /** Same pattern `SellerPayoutSettingsView` enforces. */
43
+ const IFSC_RE = /^[A-Z]{4}0[A-Z0-9]{6}$/;
44
+ /** Same pattern `SellerPayoutSettingsView` enforces. */
45
+ const UPI_VPA_RE = /^[\w.\-_]{2,256}@[a-zA-Z]{2,64}$/;
46
+ /**
47
+ * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
48
+ * by schema instance and every zod wrapper returns a new one.
49
+ */
50
+ export const payoutMethodFormSchema = z
51
+ .object({
52
+ type: annotate(z.enum(["upi", "bank", "card", "other"]), {
53
+ section: "method", sectionLabel: "Payout Method", sectionRequired: true,
54
+ quick: true, order: 1, row: "pair",
55
+ }),
56
+ label: annotate(z.string().min(1, "Give this method a name you'll recognise.").max(80), {
57
+ section: "method", quick: true, order: 2, row: "pair",
58
+ }),
59
+ upiVpa: annotate(z.string().max(256).optional(), {
60
+ section: "method", order: 3, row: "full",
61
+ help: "For UPI methods, e.g. name@bank.",
62
+ }),
63
+ accountHolderName: annotate(z.string().max(120).optional(), {
64
+ section: "bank", sectionLabel: "Bank Account", order: 1, row: "full",
65
+ }),
66
+ accountNumber: annotate(z.string().max(34).optional(), {
67
+ section: "bank", order: 2, row: "pair",
68
+ }),
69
+ ifscCode: annotate(z.string().max(11).optional(), {
70
+ section: "bank", order: 3, row: "pair",
71
+ }),
72
+ bankName: annotate(z.string().max(120).optional(), {
73
+ section: "bank", order: 4, row: "pair",
74
+ }),
75
+ isDefault: annotate(z.boolean(), {
76
+ section: "visibility", sectionLabel: "Visibility", order: 1, row: "quarter",
77
+ }),
78
+ isActive: annotate(z.boolean(), {
79
+ section: "visibility", order: 2, row: "quarter",
80
+ }),
81
+ })
82
+ .superRefine((v, ctx) => {
83
+ const issue = (path, message) => ctx.addIssue({ code: z.ZodIssueCode.custom, path: [path], message });
84
+ /**
85
+ * "Required, and if present the right shape" — the same two-part check
86
+ * every one of these fields needs. One helper rather than a required
87
+ * branch plus a format branch per field, which is what turned this into
88
+ * an unreadable if/else chain.
89
+ */
90
+ const require = (path, raw, missing, format) => {
91
+ const value = raw?.trim() ?? "";
92
+ if (!value)
93
+ return issue(path, missing);
94
+ if (!format)
95
+ return;
96
+ const candidate = format.normalise ? format.normalise(value) : value;
97
+ if (!format.re.test(candidate))
98
+ issue(path, format.message);
99
+ };
100
+ // Per-type rules by lookup, not by branching. A new payout type adds an
101
+ // entry here rather than another arm of a chain.
102
+ const RULES = {
103
+ upi: () => require("upiVpa", v.upiVpa, "A UPI ID is required for a UPI payout method.", {
104
+ re: UPI_VPA_RE,
105
+ message: "Please enter a valid UPI ID, e.g. name@bank.",
106
+ }),
107
+ // These four are what make a payout actually possible. Every one of them
108
+ // could previously be left blank and saved.
109
+ bank: () => {
110
+ require("accountHolderName", v.accountHolderName, "The account holder's name is required.");
111
+ require("accountNumber", v.accountNumber, "An account number is required.", {
112
+ re: /^\d{6,18}$/,
113
+ message: "An account number is 6–18 digits.",
114
+ });
115
+ require("ifscCode", v.ifscCode, "An IFSC code is required.", {
116
+ re: IFSC_RE,
117
+ message: "Please enter a valid IFSC code, e.g. HDFC0001234.",
118
+ normalise: (x) => x.toUpperCase(),
119
+ });
120
+ require("bankName", v.bankName, "The bank name is required.");
121
+ },
122
+ // `card` and `other` carry no extra requirements today — the label is
123
+ // enough to identify them, and inventing rules for them would reject
124
+ // methods sellers can legitimately record.
125
+ };
126
+ RULES[v.type]?.();
127
+ });
128
+ /**
129
+ * Create contract. `sellerId`/`storeId` are absent on purpose — the route sets
130
+ * both from the session, so a caller cannot file a payout method against
131
+ * another seller by adding fields to the body.
132
+ */
133
+ export const payoutMethodCreateSchema = payoutMethodFormSchema;
134
+ /**
135
+ * Update contract.
136
+ *
137
+ * The per-type rules deliberately do NOT apply here: a PATCH may carry a
138
+ * single field (`isDefault`, say) and requiring the whole bank block on every
139
+ * partial update would make toggling the default impossible. The bounds still
140
+ * apply to whatever IS sent, and the create path is where completeness is
141
+ * enforced.
142
+ */
143
+ export const payoutMethodUpdateSchema = z
144
+ .object({
145
+ type: z.enum(["upi", "bank", "card", "other"]).optional(),
146
+ label: z.string().min(1).max(80).optional(),
147
+ upiVpa: z.string().max(256).regex(UPI_VPA_RE, "Please enter a valid UPI ID.").optional(),
148
+ accountHolderName: z.string().max(120).optional(),
149
+ accountNumber: z.string().regex(/^\d{6,18}$/, "An account number is 6–18 digits.").optional(),
150
+ ifscCode: z.string().regex(IFSC_RE, "Please enter a valid IFSC code.").optional(),
151
+ bankName: z.string().max(120).optional(),
152
+ isDefault: z.boolean().optional(),
153
+ isActive: z.boolean().optional(),
154
+ })
155
+ .strict();
@@ -0,0 +1,25 @@
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 reportCreateSchema: z.ZodObject<{
7
+ entityType: z.ZodEnum<[import("./firestore").ReportEntityType, ...import("./firestore").ReportEntityType[]]>;
8
+ entityId: z.ZodString;
9
+ reason: z.ZodEnum<[import("./firestore").ReportReason, ...import("./firestore").ReportReason[]]>;
10
+ detail: z.ZodString;
11
+ evidenceUrls: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
12
+ }, "strict", z.ZodTypeAny, {
13
+ reason: import("./firestore").ReportReason;
14
+ entityId: string;
15
+ entityType: import("./firestore").ReportEntityType;
16
+ detail: string;
17
+ evidenceUrls?: string[] | undefined;
18
+ }, {
19
+ reason: import("./firestore").ReportReason;
20
+ entityId: string;
21
+ entityType: import("./firestore").ReportEntityType;
22
+ detail: string;
23
+ evidenceUrls?: string[] | undefined;
24
+ }>;
25
+ export type ReportCreateValues = z.infer<typeof reportCreateSchema>;
@@ -0,0 +1,79 @@
1
+ /*
2
+ * WHY: `POST /api/reports` — the surface any signed-in user files an abuse
3
+ * report through — spread the raw request body into Firestore:
4
+ *
5
+ * const body = await parseJsonBody<Record<string, JsonValue>>(request);
6
+ * await reportsRepository.create({ ...body, reporterId, reporterEmail, status: "pending" });
7
+ *
8
+ * Nothing was checked. Two distinct consequences:
9
+ *
10
+ * 1. An EMPTY report could be filed. `entityType`, `entityId`, `reason`
11
+ * and `detail` are what make a report actionable and none was
12
+ * required, so a row could reach the moderation queue naming nothing
13
+ * and describing nothing.
14
+ * 2. A reporter could set fields that belong to the REVIEWER — a body
15
+ * carrying `assignedTo`, `resolution` or `resolvedAt` was written
16
+ * verbatim, so a user could file a report that already claimed to be
17
+ * resolved by a named admin.
18
+ *
19
+ * `status` was pinned server-side, which is the only reason (2) was not
20
+ * worse than it is.
21
+ *
22
+ * WHAT: The create contract. Four required fields, evidence optional, and
23
+ * nothing a reviewer owns.
24
+ *
25
+ * ## `reporterId` / `reporterEmail` / `status` are absent on purpose
26
+ *
27
+ * The route sets all three. Accepting `reporterId` would let one user file a
28
+ * report in another's name, which is a way to weaponise the abuse queue rather
29
+ * than a validation nicety.
30
+ *
31
+ * EXPORTS: reportCreateSchema, type ReportCreateValues
32
+ *
33
+ * @tag domain:store-extensions,reports
34
+ * @tag layer:schema
35
+ * @tag pattern:none
36
+ * @tag access:isomorphic
37
+ * @tag consumers:/api/reports
38
+ * @tag sideEffects:none
39
+ */
40
+ import { z } from "zod";
41
+ import { annotate } from "../../shell/field-ui-meta";
42
+ import { REPORT_ENTITY_TYPES, REPORT_REASONS } from "./firestore";
43
+ /**
44
+ * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
45
+ * by schema instance and every zod wrapper returns a new one.
46
+ */
47
+ export const reportCreateSchema = z
48
+ .object({
49
+ entityType: annotate(z.enum(REPORT_ENTITY_TYPES), {
50
+ section: "target",
51
+ sectionLabel: "What are you reporting?",
52
+ sectionRequired: true,
53
+ quick: true,
54
+ order: 1,
55
+ row: "pair",
56
+ }),
57
+ entityId: annotate(z.string().min(1, "Tell us which item this is about."), {
58
+ section: "target",
59
+ quick: true,
60
+ order: 2,
61
+ row: "pair",
62
+ }),
63
+ reason: annotate(z.enum(REPORT_REASONS), {
64
+ section: "detail",
65
+ sectionLabel: "Why",
66
+ sectionRequired: true,
67
+ quick: true,
68
+ order: 1,
69
+ row: "pair",
70
+ }),
71
+ detail: annotate(z
72
+ .string()
73
+ .min(10, "Please describe the problem in at least 10 characters.")
74
+ .max(5000, "Keep the description under 5000 characters."), { section: "detail", quick: true, order: 2, row: "full", kind: "textarea" }),
75
+ // Capped: an unbounded array on a user-writable document is a way to grow
76
+ // one row past what a list read can afford (Rule #6).
77
+ evidenceUrls: annotate(z.array(z.string().url("Each piece of evidence must be a URL.")).max(10).optional(), { section: "detail", order: 3, row: "full", kind: "list" }),
78
+ })
79
+ .strict();
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ /** The statuses a human review can produce. `pending` is the initial state. */
3
+ export declare const REPORT_REVIEWER_STATUSES: readonly ["under-review", "actioned", "dismissed"];
4
+ /**
5
+ * The two that CLOSE a report — the server stamps `resolvedAt` for these and
6
+ * for no others. Exported so the route and any future consumer share one
7
+ * answer to "is this report finished", rather than each spelling out the pair.
8
+ */
9
+ export declare const REPORT_TERMINAL_STATUSES: readonly ["actioned", "dismissed"];
10
+ /**
11
+ * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
12
+ * by schema instance and every zod wrapper returns a new one.
13
+ */
14
+ export declare const reportReviewFormSchema: z.ZodEffects<z.ZodObject<{
15
+ status: z.ZodEnum<["under-review", "actioned", "dismissed"]>;
16
+ resolution: z.ZodOptional<z.ZodString>;
17
+ }, "strict", z.ZodTypeAny, {
18
+ status: "dismissed" | "under-review" | "actioned";
19
+ resolution?: string | undefined;
20
+ }, {
21
+ status: "dismissed" | "under-review" | "actioned";
22
+ resolution?: string | undefined;
23
+ }>, {
24
+ status: "dismissed" | "under-review" | "actioned";
25
+ resolution?: string | undefined;
26
+ }, {
27
+ status: "dismissed" | "under-review" | "actioned";
28
+ resolution?: string | undefined;
29
+ }>;
30
+ export type ReportReviewFormValues = z.infer<typeof reportReviewFormSchema>;
31
+ /** Route contract — identical; a review is one atomic decision. */
32
+ export declare const reportReviewUpdateSchema: z.ZodEffects<z.ZodObject<{
33
+ status: z.ZodEnum<["under-review", "actioned", "dismissed"]>;
34
+ resolution: z.ZodOptional<z.ZodString>;
35
+ }, "strict", z.ZodTypeAny, {
36
+ status: "dismissed" | "under-review" | "actioned";
37
+ resolution?: string | undefined;
38
+ }, {
39
+ status: "dismissed" | "under-review" | "actioned";
40
+ resolution?: string | undefined;
41
+ }>, {
42
+ status: "dismissed" | "under-review" | "actioned";
43
+ resolution?: string | undefined;
44
+ }, {
45
+ status: "dismissed" | "under-review" | "actioned";
46
+ resolution?: string | undefined;
47
+ }>;