@mohasinac/appkit 4.14.0 → 4.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) 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.js +3 -1
  13. package/dist/_internal/shared/features/auctions/schema.d.ts +14 -2
  14. package/dist/_internal/shared/features/auctions/schema.js +13 -1
  15. package/dist/_internal/shared/features/cart/schema.d.ts +14 -9
  16. package/dist/_internal/shared/features/cart/schema.js +15 -0
  17. package/dist/_internal/shared/features/products/schema.d.ts +2 -2
  18. package/dist/_internal/shared/history/history.d.ts +22 -0
  19. package/dist/_internal/shared/history/history.js +41 -1
  20. package/dist/client.d.ts +13 -2
  21. package/dist/client.js +20 -2
  22. package/dist/constants/field-names.d.ts +8 -6
  23. package/dist/constants/field-names.js +48 -6
  24. package/dist/features/account/components/UserBidsView.js +12 -4
  25. package/dist/features/account/components/UserReturnsView.d.ts +3 -0
  26. package/dist/features/account/components/UserReturnsView.js +17 -1
  27. package/dist/features/admin/components/AdminAuditLogView.js +1 -0
  28. package/dist/features/admin/components/AdminBidsView.js +15 -2
  29. package/dist/features/admin/components/AdminBlogEditorView.js +65 -33
  30. package/dist/features/admin/components/AdminFeatureEditorView.js +35 -6
  31. package/dist/features/admin/components/AdminNavEditorView.js +15 -5
  32. package/dist/features/admin/components/AdminNewsletterView.js +18 -2
  33. package/dist/features/admin/components/AdminOffersView.js +62 -18
  34. package/dist/features/admin/components/AdminPaymentMethodsView.js +24 -4
  35. package/dist/features/admin/components/AdminScammerEditorView.d.ts +2 -1
  36. package/dist/features/admin/components/AdminScammerEditorView.js +3 -13
  37. package/dist/features/admin/components/AdminScammersView.js +3 -8
  38. package/dist/features/admin/components/AdminSectionsView.js +32 -23
  39. package/dist/features/admin/components/AdminSessionsView.js +17 -2
  40. package/dist/features/admin/components/ViewAuditLogEntryModal.js +1 -0
  41. package/dist/features/admin/components/sections/adminSectionsTypes.d.ts +4 -2
  42. package/dist/features/admin/components/sections/adminSectionsTypes.js +45 -27
  43. package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
  44. package/dist/features/admin/constants/filter-tabs.js +1 -0
  45. package/dist/features/admin/schemas/homepage-section-form.d.ts +22 -0
  46. package/dist/features/admin/schemas/homepage-section-form.js +68 -0
  47. package/dist/features/admin/schemas/nav-item-form.d.ts +74 -0
  48. package/dist/features/admin/schemas/nav-item-form.js +72 -0
  49. package/dist/features/auctions/components/AuctionBidsTable.d.ts +10 -1
  50. package/dist/features/auctions/components/AuctionBidsTable.js +22 -4
  51. package/dist/features/auctions/components/bid-detail-fields.d.ts +29 -0
  52. package/dist/features/auctions/components/bid-detail-fields.js +103 -0
  53. package/dist/features/audit-log/schemas/firestore.d.ts +14 -0
  54. package/dist/features/audit-log/schemas/firestore.js +7 -0
  55. package/dist/features/blog/schemas/blog-form.d.ts +80 -0
  56. package/dist/features/blog/schemas/blog-form.js +100 -0
  57. package/dist/features/events/components/AdminEventEditorView.js +32 -54
  58. package/dist/features/events/components/AdminEventEntriesView.js +22 -3
  59. package/dist/features/events/schemas/event-form.d.ts +113 -0
  60. package/dist/features/events/schemas/event-form.js +82 -0
  61. package/dist/features/grouped/schemas/grouped-listing-form.d.ts +101 -0
  62. package/dist/features/grouped/schemas/grouped-listing-form.js +89 -0
  63. package/dist/features/homepage/actions/homepage-section-actions.d.ts +11 -3
  64. package/dist/features/homepage/actions/homepage-section-actions.js +33 -1
  65. package/dist/features/homepage/schemas/index.d.ts +31 -0
  66. package/dist/features/homepage/schemas/index.js +24 -0
  67. package/dist/features/orders/components/OrderStatusTimeline.d.ts +5 -1
  68. package/dist/features/orders/components/OrderStatusTimeline.js +39 -20
  69. package/dist/features/orders/repository/orders.repository.js +5 -0
  70. package/dist/features/orders/schemas/firestore.d.ts +36 -1
  71. package/dist/features/orders/types/index.d.ts +35 -7
  72. package/dist/features/products/components/GroupSettingsPanel.js +5 -1
  73. package/dist/features/products/schemas/index.d.ts +2 -2
  74. package/dist/features/products/schemas/product-features.validators.d.ts +73 -0
  75. package/dist/features/products/schemas/product-features.validators.js +63 -0
  76. package/dist/features/scams/constants/scammer-status-display.d.ts +28 -0
  77. package/dist/features/scams/constants/scammer-status-display.js +79 -0
  78. package/dist/features/seller/actions/offer-actions.js +32 -2
  79. package/dist/features/seller/components/OfferPhaseTimeline.d.ts +59 -0
  80. package/dist/features/seller/components/OfferPhaseTimeline.js +107 -0
  81. package/dist/features/seller/components/SellerBidsView.js +11 -4
  82. package/dist/features/seller/components/SellerCouponEditorView.js +17 -17
  83. package/dist/features/seller/components/SellerOffersView.js +2 -1
  84. package/dist/features/seller/components/SellerPayoutSettingsView.js +38 -14
  85. package/dist/features/seller/repository/offer.repository.d.ts +69 -8
  86. package/dist/features/seller/repository/offer.repository.js +127 -23
  87. package/dist/features/seller/schemas/coupon-form.d.ts +72 -0
  88. package/dist/features/seller/schemas/coupon-form.js +116 -0
  89. package/dist/features/seller/schemas/firestore.d.ts +56 -2
  90. package/dist/features/seller/schemas/firestore.js +27 -0
  91. package/dist/features/seller/schemas/offer-forms.d.ts +21 -0
  92. package/dist/features/seller/schemas/offer-forms.js +21 -0
  93. package/dist/features/shell/build-sections.d.ts +63 -0
  94. package/dist/features/shell/build-sections.js +207 -0
  95. package/dist/features/shell/field-ui-meta.d.ts +115 -0
  96. package/dist/features/shell/field-ui-meta.js +324 -0
  97. package/dist/features/shell/index.d.ts +4 -0
  98. package/dist/features/shell/index.js +2 -0
  99. package/dist/features/status-history/components/StatusTimeline.d.ts +74 -0
  100. package/dist/features/status-history/components/StatusTimeline.js +46 -0
  101. package/dist/features/status-history/index.d.ts +11 -0
  102. package/dist/features/status-history/index.js +10 -0
  103. package/dist/features/store-extensions/components/ReviewDecisionModal.d.ts +27 -0
  104. package/dist/features/store-extensions/components/ReviewDecisionModal.js +82 -0
  105. package/dist/features/store-extensions/index.d.ts +12 -0
  106. package/dist/features/store-extensions/index.js +12 -0
  107. package/dist/features/store-extensions/schemas/admin-notification-form.d.ts +36 -0
  108. package/dist/features/store-extensions/schemas/admin-notification-form.js +114 -0
  109. package/dist/features/store-extensions/schemas/analytics-forms.d.ts +57 -0
  110. package/dist/features/store-extensions/schemas/analytics-forms.js +158 -0
  111. package/dist/features/store-extensions/schemas/custom-role-form.d.ts +85 -0
  112. package/dist/features/store-extensions/schemas/custom-role-form.js +118 -0
  113. package/dist/features/store-extensions/schemas/firestore.d.ts +21 -1
  114. package/dist/features/store-extensions/schemas/firestore.js +60 -0
  115. package/dist/features/store-extensions/schemas/google-config-form.d.ts +53 -0
  116. package/dist/features/store-extensions/schemas/google-config-form.js +105 -0
  117. package/dist/features/store-extensions/schemas/item-request-create-form.d.ts +28 -0
  118. package/dist/features/store-extensions/schemas/item-request-create-form.js +80 -0
  119. package/dist/features/store-extensions/schemas/listing-template-form.d.ts +78 -0
  120. package/dist/features/store-extensions/schemas/listing-template-form.js +87 -0
  121. package/dist/features/store-extensions/schemas/moderation-review-form.d.ts +49 -0
  122. package/dist/features/store-extensions/schemas/moderation-review-form.js +101 -0
  123. package/dist/features/store-extensions/schemas/payout-method-form.d.ts +153 -0
  124. package/dist/features/store-extensions/schemas/payout-method-form.js +155 -0
  125. package/dist/features/store-extensions/schemas/report-create-form.d.ts +25 -0
  126. package/dist/features/store-extensions/schemas/report-create-form.js +79 -0
  127. package/dist/features/store-extensions/schemas/report-review-form.d.ts +47 -0
  128. package/dist/features/store-extensions/schemas/report-review-form.js +111 -0
  129. package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +148 -0
  130. package/dist/features/store-extensions/schemas/shipping-config-form.js +104 -0
  131. package/dist/features/store-extensions/schemas/store-category-form.d.ts +54 -0
  132. package/dist/features/store-extensions/schemas/store-category-form.js +73 -0
  133. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +326 -0
  134. package/dist/index.d.ts +12 -2
  135. package/dist/index.js +12 -2
  136. package/dist/next/routing/route-map.d.ts +16 -2
  137. package/dist/next/routing/route-map.js +9 -2
  138. package/dist/schemas/firestore-value.d.ts +8 -0
  139. package/dist/schemas/firestore-value.js +44 -0
  140. package/dist/schemas/index.d.ts +1 -1
  141. package/dist/schemas/index.js +1 -1
  142. package/dist/schemas/registry.d.ts +634 -1
  143. package/dist/schemas/registry.js +64 -1
  144. package/dist/seed/bids-seed-data.js +30 -0
  145. package/dist/seed/offers-seed-data.js +212 -7
  146. package/dist/seed/orders-seed-data.js +336 -1
  147. package/dist/seed/products-auctions-seed-data.js +41 -0
  148. package/dist/server.d.ts +2 -0
  149. package/dist/server.js +5 -0
  150. package/dist/styles.css +1 -1
  151. package/dist/tailwind-utilities.css +1 -1
  152. package/dist/ui/components/RecordDetailModal.d.ts +10 -1
  153. package/dist/ui/components/RecordDetailModal.js +2 -2
  154. package/package.json +2 -1
  155. package/scripts/node-esm-loader.mjs +65 -0
  156. package/scripts/seed-cli-loader.mjs +6 -49
  157. package/scripts/seed-cli.mjs +1 -1
@@ -8,7 +8,7 @@ export declare const addToCartSchema: z.ZodObject<{
8
8
  storeId: z.ZodString;
9
9
  storeName: z.ZodDefault<z.ZodString>;
10
10
  quantity: z.ZodDefault<z.ZodNumber>;
11
- listingType: z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live"]>;
11
+ listingType: z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live", "art", "stickers"]>;
12
12
  isOffer: z.ZodOptional<z.ZodBoolean>;
13
13
  offerId: z.ZodOptional<z.ZodString>;
14
14
  lockedPrice: z.ZodOptional<z.ZodNumber>;
@@ -16,7 +16,7 @@ export declare const addToCartSchema: z.ZodObject<{
16
16
  currency: string;
17
17
  storeId: string;
18
18
  price: number;
19
- listingType: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
19
+ listingType: "art" | "stickers" | "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
20
20
  productId: string;
21
21
  productTitle: string;
22
22
  quantity: number;
@@ -28,7 +28,7 @@ export declare const addToCartSchema: z.ZodObject<{
28
28
  }, {
29
29
  storeId: string;
30
30
  price: number;
31
- listingType: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
31
+ listingType: "art" | "stickers" | "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
32
32
  productId: string;
33
33
  productTitle: string;
34
34
  currency?: string | undefined;
@@ -66,7 +66,7 @@ export declare const mergeGuestCartSchema: z.ZodObject<{
66
66
  storeId: z.ZodString;
67
67
  storeName: z.ZodDefault<z.ZodString>;
68
68
  quantity: z.ZodDefault<z.ZodNumber>;
69
- listingType: z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live"]>;
69
+ listingType: z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live", "art", "stickers"]>;
70
70
  isOffer: z.ZodOptional<z.ZodBoolean>;
71
71
  offerId: z.ZodOptional<z.ZodString>;
72
72
  lockedPrice: z.ZodOptional<z.ZodNumber>;
@@ -74,7 +74,7 @@ export declare const mergeGuestCartSchema: z.ZodObject<{
74
74
  currency: string;
75
75
  storeId: string;
76
76
  price: number;
77
- listingType: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
77
+ listingType: "art" | "stickers" | "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
78
78
  productId: string;
79
79
  productTitle: string;
80
80
  quantity: number;
@@ -86,7 +86,7 @@ export declare const mergeGuestCartSchema: z.ZodObject<{
86
86
  }, {
87
87
  storeId: string;
88
88
  price: number;
89
- listingType: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
89
+ listingType: "art" | "stickers" | "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
90
90
  productId: string;
91
91
  productTitle: string;
92
92
  currency?: string | undefined;
@@ -102,7 +102,7 @@ export declare const mergeGuestCartSchema: z.ZodObject<{
102
102
  currency: string;
103
103
  storeId: string;
104
104
  price: number;
105
- listingType: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
105
+ listingType: "art" | "stickers" | "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
106
106
  productId: string;
107
107
  productTitle: string;
108
108
  quantity: number;
@@ -116,7 +116,7 @@ export declare const mergeGuestCartSchema: z.ZodObject<{
116
116
  guestItems: {
117
117
  storeId: string;
118
118
  price: number;
119
- listingType: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
119
+ listingType: "art" | "stickers" | "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live";
120
120
  productId: string;
121
121
  productTitle: string;
122
122
  currency?: string | undefined;
@@ -128,5 +128,10 @@ export declare const mergeGuestCartSchema: z.ZodObject<{
128
128
  isOffer?: boolean | undefined;
129
129
  }[];
130
130
  }>;
131
- export type AddToCartInput = z.infer<typeof addToCartSchema>;
131
+ /**
132
+ * Renamed from `AddToCartInput` 2026-08-24 (W3) — `features/cart/schemas/
133
+ * firestore.ts` declares its own hand-written `AddToCartInput`, and a consumer
134
+ * importing the name had no way to tell which one it got.
135
+ */
136
+ export type AddToCartActionInput = z.infer<typeof addToCartSchema>;
132
137
  export type MergeGuestCartInput = z.infer<typeof mergeGuestCartSchema>;
@@ -10,6 +10,19 @@ const cartItemBaseSchema = z.object({
10
10
  quantity: z.number().int().min(1).max(99).default(1),
11
11
  // SB1-G Phase 4 — canonical listing-kind snapshot.
12
12
  // SB-UNI-D — "bundle" removed from the listing-kind enum.
13
+ /*
14
+ * All NINE listing types. `art` and `stickers` were missing until
15
+ * 2026-08-24 (W3) — added to the union, the plugin registry, the capability
16
+ * map and the Sieve alias map, but not here.
17
+ *
18
+ * Not a live bug today, and verified rather than assumed (Rule #4): the real
19
+ * add-to-cart path is `POST /api/cart`, which takes only {productId,
20
+ * quantity} and derives the type server-side via `normalizeListingType`,
21
+ * never validating it against an enum. This enum sits on paths an art or
22
+ * sticker listing cannot currently reach. It was a trap waiting for the
23
+ * first caller that did — the third instalment of Root Cause #58, where a
24
+ * union member is added everywhere except the one allowlist that gates it.
25
+ */
13
26
  listingType: z.enum([
14
27
  "standard",
15
28
  "auction",
@@ -18,6 +31,8 @@ const cartItemBaseSchema = z.object({
18
31
  "classified",
19
32
  "digital-code",
20
33
  "live",
34
+ "art",
35
+ "stickers",
21
36
  ]),
22
37
  isOffer: z.boolean().optional(),
23
38
  offerId: z.string().optional(),
@@ -289,7 +289,7 @@ export declare const preOrderInputSchema: z.ZodObject<{
289
289
  tags: string[];
290
290
  listingType: "pre-order";
291
291
  preOrderDeliveryDate: string;
292
- preOrderProductionStatus: "in_production" | "upcoming" | "ready_to_ship";
292
+ preOrderProductionStatus: "upcoming" | "in_production" | "ready_to_ship";
293
293
  preOrderCancellable: boolean;
294
294
  features?: string[] | undefined;
295
295
  description?: string | undefined;
@@ -330,7 +330,7 @@ export declare const preOrderInputSchema: z.ZodObject<{
330
330
  shippingPaidBy?: "seller" | "buyer" | undefined;
331
331
  preOrderDepositPercent?: number | undefined;
332
332
  preOrderMaxQuantity?: number | undefined;
333
- preOrderProductionStatus?: "in_production" | "upcoming" | "ready_to_ship" | undefined;
333
+ preOrderProductionStatus?: "upcoming" | "in_production" | "ready_to_ship" | undefined;
334
334
  preOrderCancellable?: boolean | undefined;
335
335
  seoTitle?: string | undefined;
336
336
  seoDescription?: string | undefined;
@@ -59,6 +59,28 @@ export declare function withHistory<TPatch extends FirestoreDocument>(current: F
59
59
  * a reader actually wants on the timeline.
60
60
  */
61
61
  extraChanges?: Record<string, FieldChange>;
62
+ /**
63
+ * This entity's PII field names — pass the same list the repository hands
64
+ * to `encryptPiiFields` (`ORDER_PII_FIELDS`, `OFFER_PII_FIELDS`, …).
65
+ *
66
+ * 🛑 Not optional in spirit. `encryptPiiFields` is a FLAT top-level loop:
67
+ * it reads `doc[field]`, skips anything that is not a string, and never
68
+ * descends into arrays or nested objects. `statusHistory` is an array of
69
+ * objects, so a PII value that reaches `changes` is written to Firestore
70
+ * **in plaintext** and `mapDoc`'s decrypt never touches it on the way
71
+ * back out — a leak with no error and no visible symptom.
72
+ *
73
+ * Scrubbing here rather than at each call site is deliberate: a
74
+ * convention that says "don't put PII in history" is one forgetful
75
+ * adopter away from that leak, and 9 more entities adopt this primitive.
76
+ *
77
+ * SCOPE: this scrubs `changes` structurally, at any depth — a key whose
78
+ * NAME is PII is dropped. It cannot scrub `note`, which is free text the
79
+ * user themselves authored (a seller's counter-offer note). The rule
80
+ * there is a call-site one: pass the author's own text, never an identity
81
+ * field lifted off the document.
82
+ */
83
+ piiFields?: readonly string[];
62
84
  historyField?: string;
63
85
  truncatedField?: string;
64
86
  now?: Date;
@@ -99,6 +99,46 @@ export function diffTrackedFields(current, patch, tracked) {
99
99
  }
100
100
  return changes;
101
101
  }
102
+ /**
103
+ * Remove PII-named keys from a `changes` map, at any depth.
104
+ *
105
+ * Two passes are needed because PII can arrive two ways: as a tracked field
106
+ * that happens to be PII (`buyerEmail` in `changes`), or nested inside a
107
+ * value a caller contributed via `extraChanges` (a refund's `reason`, an
108
+ * offer note object). `encryptPiiFields` catches neither — it only ever
109
+ * looks at top-level string properties of the document itself.
110
+ *
111
+ * Scrubbed rather than redacted-in-place: an entry that silently omits a
112
+ * field is honest, whereas one containing `"[redacted]"` invites a reader to
113
+ * assume the real value is recoverable somewhere.
114
+ */
115
+ function scrubPii(changes, piiFields) {
116
+ if (!piiFields?.length)
117
+ return changes;
118
+ const pii = new Set(piiFields);
119
+ const stripNested = (v) => {
120
+ if (v == null || typeof v !== "object")
121
+ return v;
122
+ if (v instanceof Date)
123
+ return v;
124
+ if (Array.isArray(v))
125
+ return v.map(stripNested);
126
+ const out = {};
127
+ for (const [k, val] of Object.entries(v)) {
128
+ if (pii.has(k))
129
+ continue;
130
+ out[k] = stripNested(val);
131
+ }
132
+ return out;
133
+ };
134
+ const result = {};
135
+ for (const [field, change] of Object.entries(changes)) {
136
+ if (pii.has(field))
137
+ continue; // the tracked field itself is PII
138
+ result[field] = { from: stripNested(change.from), to: stripNested(change.to) };
139
+ }
140
+ return result;
141
+ }
102
142
  export function buildHistoryEntry(input) {
103
143
  const entry = {
104
144
  at: input.now ?? new Date(),
@@ -136,7 +176,7 @@ export function appendHistoryEntry(existing, entry, previousTruncatedCount = 0,
136
176
  * writes its patch unchanged, so an untracked update never grows the array.
137
177
  */
138
178
  export function withHistory(current, patch, opts) {
139
- const changes = { ...diffTrackedFields(current, patch, opts.tracked), ...opts.extraChanges };
179
+ const changes = scrubPii({ ...diffTrackedFields(current, patch, opts.tracked), ...opts.extraChanges }, opts.piiFields);
140
180
  if (Object.keys(changes).length === 0)
141
181
  return null;
142
182
  const historyField = opts.historyField ?? "statusHistory";
package/dist/client.d.ts CHANGED
@@ -112,6 +112,17 @@ export { useBulkSelection } from "./react/hooks/useBulkSelection";
112
112
  export type { UseBulkSelectionOptions, UseBulkSelectionReturn } from "./react/hooks/useBulkSelection";
113
113
  export { useEntityDelete } from "./react/hooks/useEntityDelete";
114
114
  export type { UseEntityDeleteOptions, UseEntityDeleteReturn } from "./react/hooks/useEntityDelete";
115
+ export { storeCategoryFormSchema, storeCategoryCreateSchema, type StoreCategoryFormValues, } from "./features/store-extensions/schemas/store-category-form";
116
+ export { payoutMethodFormSchema, payoutMethodCreateSchema, payoutMethodUpdateSchema, type PayoutMethodFormValues, } from "./features/store-extensions/schemas/payout-method-form";
117
+ export { groupedListingFormSchema, groupedListingCreateSchema, groupedListingUpdateSchema, type GroupedListingFormValues, } from "./features/grouped/schemas/grouped-listing-form";
118
+ export { navItemFormSchema, navItemCreateSchema, navItemUpdateSchema, type NavItemFormValues, } from "./features/admin/schemas/nav-item-form";
119
+ export { customRoleFormSchema, customRoleCreateSchema, customRoleUpdateSchema, isKnownPermission, type CustomRoleFormValues, } from "./features/store-extensions/schemas/custom-role-form";
120
+ export { SCAMMER_STATUS_LABEL, SCAMMER_STATUS_BADGE, SCAMMER_STATUS_OPTIONS, toScammerStatus, } from "./features/scams/constants/scammer-status-display";
121
+ export { ReviewDecisionModal, type ReviewDecisionModalProps, } from "./features/store-extensions/components/ReviewDecisionModal";
122
+ export { moderationReviewFormSchema, moderationReviewUpdateSchema, MODERATION_REVIEWER_STATUSES, type ModerationReviewFormValues, } from "./features/store-extensions/schemas/moderation-review-form";
123
+ export { reportReviewFormSchema, reportReviewUpdateSchema, REPORT_REVIEWER_STATUSES, REPORT_TERMINAL_STATUSES, type ReportReviewFormValues, } from "./features/store-extensions/schemas/report-review-form";
124
+ export { shippingConfigFormSchema, shippingConfigCreateSchema, shippingConfigUpdateSchema, type ShippingConfigFormValues, } from "./features/store-extensions/schemas/shipping-config-form";
125
+ export { listingTemplateFormSchema, listingTemplateCreateSchema, listingTemplateUpdateSchema, type ListingTemplateFormValues, } from "./features/store-extensions/schemas/listing-template-form";
115
126
  export { ROUTES, PUBLIC_ROUTES, PROTECTED_ROUTES, AUTH_ROUTES } from "./constants/index";
116
127
  export { SORT_DIR, sortBy } from "./constants/sort";
117
128
  export { SIEVE_OP, sieveFilter, sieveAnd, sieveMultiEq } from "./utils/sieve-builder";
@@ -381,7 +392,7 @@ export { SellerDigitalCodesView } from "./features/seller/components/SellerDigit
381
392
  export type { SellerDigitalCodesViewProps } from "./features/seller/components/SellerDigitalCodesView";
382
393
  export { SellerLiveView } from "./features/seller/components/SellerLiveView";
383
394
  export type { SellerLiveViewProps } from "./features/seller/components/SellerLiveView";
384
- export { SCHEMAS, lookupApiSchema, lookupFirestoreSchema, } from "./schemas/index";
395
+ export { SCHEMAS, lookupApiSchema, lookupFormSchema, lookupFirestoreSchema, } from "./schemas/index";
385
396
  export type { ApiRouteKey, ApiRouteSchema, FirestoreDocument, FirestoreValue, FormFieldValue, FormValues, HttpVerb, JsonArray, JsonObject, JsonObjectWithUndefined, JsonPrimitive, JsonValue, JsonValueWithUndefined, RegisteredApiRouteKey, RegisteredFirestoreCollection, RegisteredFormId, RegisteredRtdbChannel, RegisteredSieveCollection, RegisteredStorageOp, RegisteredWebhookProvider, RegistryEntry, SchemaRegistry, SchemasShape, WebhookSchemaBucket, } from "./schemas/index";
386
397
  export { normalizeError, getErrorMessage, isApiNormalized, isAppNormalized, isFirebaseAuthNormalized, isFirebaseFirestoreNormalized, isFirebaseStorageNormalized, isNativeNormalized, isNetworkNormalized, isUnknownNormalized, isZodNormalized, } from "./errors/normalize";
387
398
  export type { NormalizedError, NormalizedApiError, NormalizedAppError, NormalizedFirebaseAuthError, NormalizedFirebaseFirestoreError, NormalizedFirebaseStorageError, NormalizedNativeError, NormalizedNetworkError, NormalizedUnknownThrownValue, NormalizedZodError, } from "./errors/normalize";
@@ -390,7 +401,7 @@ export type { PaymentProofReviewMessageInput } from "./features/whatsapp-bot/typ
390
401
  export type { AdminNotificationDocument, ItemRequestDocument, ModerationQueueDocument, ReportDocument, CustomRoleDocument, AnalyticsCardDocument, ListingTemplateDocument, } from "./features/store-extensions/index";
391
402
  export { CommandPalette, useCommandPaletteHotkey } from "./features/shell/index";
392
403
  export type { CommandPaletteGroup } from "./features/shell/index";
393
- export { AdminBlogEditorView, AdminBundleEditorView, AdminCarouselEditorView, AdminCategoryEditorView, AdminCouponEditorView, AdminFaqEditorView, AdminOrderEditorView, AdminPayoutMarkPaidModal, AdminAuditLogView, ViewAuditLogEntryModal, AdminProductEditorView, AdminScammerEditorView, AdminStoreEditorView, AdminSublistingCategoryEditorView, AdminSupportTicketDetailView, } from "./features/admin/index";
404
+ export { AdminBlogEditorView, AdminBundleEditorView, AdminCarouselEditorView, AdminCategoryEditorView, AdminCouponEditorView, AdminFaqEditorView, AdminFeatureEditorView, AdminOrderEditorView, AdminPayoutMarkPaidModal, AdminAuditLogView, ViewAuditLogEntryModal, AdminProductEditorView, AdminScammerEditorView, AdminStoreEditorView, AdminSublistingCategoryEditorView, AdminSupportTicketDetailView, } from "./features/admin/index";
394
405
  export type { AdminStoreEditorViewProps, AdminPayoutMarkPaidModalProps, AdminAuditLogViewProps, AuditLogEntryDetail, ViewAuditLogEntryModalProps } from "./features/admin/index";
395
406
  export { ADMIN_CHECKOUT_BYPASS_FLAG_KEY } from "./features/admin/schemas/firestore";
396
407
  export { SpinWheelView, EventRaffleEntryForm } from "./features/events/index";
package/dist/client.js CHANGED
@@ -178,6 +178,24 @@ export { useUrlTable } from "./react/hooks/useUrlTable";
178
178
  export { useBulkSelection } from "./react/hooks/useBulkSelection";
179
179
  export { useEntityDelete } from "./react/hooks/useEntityDelete";
180
180
  // Client-safe constants, UI primitives, and views
181
+ // Storefront-category form schema — isomorphic (the page parses it client-side,
182
+ // the route parses the same one server-side).
183
+ export { storeCategoryFormSchema, storeCategoryCreateSchema, } from "./features/store-extensions/schemas/store-category-form";
184
+ // Payout-method + listing-template form schemas — isomorphic: the pages parse
185
+ // them client-side and the routes parse the SAME ones server-side.
186
+ export { payoutMethodFormSchema, payoutMethodCreateSchema, payoutMethodUpdateSchema, } from "./features/store-extensions/schemas/payout-method-form";
187
+ export { groupedListingFormSchema, groupedListingCreateSchema, groupedListingUpdateSchema, } from "./features/grouped/schemas/grouped-listing-form";
188
+ export { navItemFormSchema, navItemCreateSchema, navItemUpdateSchema, } from "./features/admin/schemas/nav-item-form";
189
+ export { customRoleFormSchema, customRoleCreateSchema, customRoleUpdateSchema, isKnownPermission, } from "./features/store-extensions/schemas/custom-role-form";
190
+ // Client-only: deliberately NOT added to `features/store-extensions/index.ts`,
191
+ // which the schema registry's import chain reaches — a "use client" React tree
192
+ // re-exported from there would be pulled into every API route (the W4 trap).
193
+ export { SCAMMER_STATUS_LABEL, SCAMMER_STATUS_BADGE, SCAMMER_STATUS_OPTIONS, toScammerStatus, } from "./features/scams/constants/scammer-status-display";
194
+ export { ReviewDecisionModal, } from "./features/store-extensions/components/ReviewDecisionModal";
195
+ export { moderationReviewFormSchema, moderationReviewUpdateSchema, MODERATION_REVIEWER_STATUSES, } from "./features/store-extensions/schemas/moderation-review-form";
196
+ export { reportReviewFormSchema, reportReviewUpdateSchema, REPORT_REVIEWER_STATUSES, REPORT_TERMINAL_STATUSES, } from "./features/store-extensions/schemas/report-review-form";
197
+ export { shippingConfigFormSchema, shippingConfigCreateSchema, shippingConfigUpdateSchema, } from "./features/store-extensions/schemas/shipping-config-form";
198
+ export { listingTemplateFormSchema, listingTemplateCreateSchema, listingTemplateUpdateSchema, } from "./features/store-extensions/schemas/listing-template-form";
181
199
  export { ROUTES, PUBLIC_ROUTES, PROTECTED_ROUTES, AUTH_ROUTES } from "./constants/index";
182
200
  export { SORT_DIR, sortBy } from "./constants/sort";
183
201
  export { SIEVE_OP, sieveFilter, sieveAnd, sieveMultiEq } from "./utils/sieve-builder";
@@ -368,7 +386,7 @@ export { SellerLiveView } from "./features/seller/components/SellerLiveView";
368
386
  // ── Central schema registry (W1) ──────────────────────────────────────────────
369
387
  // Pure Zod, isomorphic — re-exported here so client code can resolve schemas
370
388
  // without pulling in the full server barrel.
371
- export { SCHEMAS, lookupApiSchema, lookupFirestoreSchema, } from "./schemas/index";
389
+ export { SCHEMAS, lookupApiSchema, lookupFormSchema, lookupFirestoreSchema, } from "./schemas/index";
372
390
  // ── W3 catch-clause normalizer ──────────────────────────────────────────────
373
391
  export { normalizeError, getErrorMessage, isApiNormalized, isAppNormalized, isFirebaseAuthNormalized, isFirebaseFirestoreNormalized, isFirebaseStorageNormalized, isNativeNormalized, isNetworkNormalized, isUnknownNormalized, isZodNormalized, } from "./errors/normalize";
374
392
  // [CLIENT-SAFE] Pure wa.me share-link message builder — imported directly
@@ -376,7 +394,7 @@ export { normalizeError, getErrorMessage, isApiNormalized, isAppNormalized, isFi
376
394
  // never touches the sibling crypto-importing helpers.
377
395
  export { buildPaymentProofReviewMessage } from "./features/whatsapp-bot/helpers/payment-proof-message";
378
396
  export { CommandPalette, useCommandPaletteHotkey } from "./features/shell/index";
379
- export { AdminBlogEditorView, AdminBundleEditorView, AdminCarouselEditorView, AdminCategoryEditorView, AdminCouponEditorView, AdminFaqEditorView, AdminOrderEditorView, AdminPayoutMarkPaidModal, AdminAuditLogView, ViewAuditLogEntryModal, AdminProductEditorView, AdminScammerEditorView, AdminStoreEditorView, AdminSublistingCategoryEditorView, AdminSupportTicketDetailView, } from "./features/admin/index";
397
+ export { AdminBlogEditorView, AdminBundleEditorView, AdminCarouselEditorView, AdminCategoryEditorView, AdminCouponEditorView, AdminFaqEditorView, AdminFeatureEditorView, AdminOrderEditorView, AdminPayoutMarkPaidModal, AdminAuditLogView, ViewAuditLogEntryModal, AdminProductEditorView, AdminScammerEditorView, AdminStoreEditorView, AdminSublistingCategoryEditorView, AdminSupportTicketDetailView, } from "./features/admin/index";
380
398
  export { ADMIN_CHECKOUT_BYPASS_FLAG_KEY } from "./features/admin/schemas/firestore";
381
399
  export { SpinWheelView, EventRaffleEntryForm } from "./features/events/index";
382
400
  export { Card, CardBody } from "./ui/index";
@@ -151,10 +151,9 @@ export declare const PRODUCT_FIELDS: {
151
151
  readonly WINNER: "winner";
152
152
  };
153
153
  readonly PRE_ORDER_PRODUCTION_STATUS_VALUES: {
154
+ readonly UPCOMING: "upcoming";
154
155
  readonly IN_PRODUCTION: "in_production";
155
- readonly READY: "ready";
156
- readonly DELAYED: "delayed";
157
- readonly CANCELLED: "cancelled";
156
+ readonly READY_TO_SHIP: "ready_to_ship";
158
157
  };
159
158
  readonly PRIZE_REVEAL_STATUS_VALUES: {
160
159
  readonly PENDING: "pending";
@@ -246,9 +245,11 @@ export declare const ORDER_FIELDS: {
246
245
  };
247
246
  readonly PAYMENT_STATUS_VALUES: {
248
247
  readonly PENDING: "pending";
248
+ readonly PROCESSING: "processing";
249
249
  readonly PAID: "paid";
250
250
  readonly FAILED: "failed";
251
251
  readonly REFUNDED: "refunded";
252
+ readonly PARTIAL_REFUND: "partial_refund";
252
253
  };
253
254
  readonly PAYOUT_STATUS_VALUES: {
254
255
  readonly ELIGIBLE: "eligible";
@@ -322,6 +323,8 @@ export declare const BID_FIELDS: {
322
323
  readonly WON: "won";
323
324
  readonly LOST: "lost";
324
325
  readonly CANCELLED: "cancelled";
326
+ /** Won, then the buyer let the payment window lapse. Was missing here. */
327
+ readonly FORFEITED: "forfeited";
325
328
  };
326
329
  };
327
330
  export declare const AD_FIELDS: {
@@ -443,7 +446,6 @@ export declare const PAYOUT_FIELDS: {
443
446
  readonly PROCESSING: "processing";
444
447
  readonly PAID: "paid";
445
448
  readonly FAILED: "failed";
446
- readonly CANCELLED: "cancelled";
447
449
  };
448
450
  };
449
451
  export declare const STORE_FIELDS: {
@@ -687,9 +689,10 @@ export declare const SCAMMER_FIELDS: {
687
689
  readonly VERIFIED_BY: "verifiedBy";
688
690
  readonly CREATED_AT: "createdAt";
689
691
  readonly STATUS_VALUES: {
690
- readonly PENDING: "pending";
692
+ readonly PENDING_REVIEW: "pending_review";
691
693
  readonly VERIFIED: "verified";
692
694
  readonly REJECTED: "rejected";
695
+ readonly REMOVED: "removed";
693
696
  };
694
697
  };
695
698
  export declare const SUPPORT_TICKET_FIELDS: {
@@ -712,7 +715,6 @@ export declare const SUPPORT_TICKET_FIELDS: {
712
715
  readonly PRIORITY_VALUES: {
713
716
  readonly LOW: "low";
714
717
  readonly NORMAL: "normal";
715
- readonly MEDIUM: "medium";
716
718
  readonly HIGH: "high";
717
719
  readonly URGENT: "urgent";
718
720
  };
@@ -159,11 +159,19 @@ export const PRODUCT_FIELDS = {
159
159
  SELLER: "seller",
160
160
  WINNER: "winner",
161
161
  },
162
+ /*
163
+ * Corrected 2026-08-24 (W3). This block used to read
164
+ * {in_production, ready, delayed, cancelled} — and `ready`, `delayed` and
165
+ * `cancelled` were values no product has ever held. `ProductDocument`
166
+ * declares "upcoming" | "in_production" | "ready_to_ship", the write schema
167
+ * agrees, and all 9 seeded pre-orders store one of those three. It had zero
168
+ * consumers, which is the only reason it never produced a visibly broken
169
+ * filter chip (Recurrent Root Cause #33).
170
+ */
162
171
  PRE_ORDER_PRODUCTION_STATUS_VALUES: {
172
+ UPCOMING: "upcoming",
163
173
  IN_PRODUCTION: "in_production",
164
- READY: "ready",
165
- DELAYED: "delayed",
166
- CANCELLED: "cancelled",
174
+ READY_TO_SHIP: "ready_to_ship",
167
175
  },
168
176
  PRIZE_REVEAL_STATUS_VALUES: {
169
177
  PENDING: "pending",
@@ -261,11 +269,19 @@ export const ORDER_FIELDS = {
261
269
  RETURN_REQUESTED: "return_requested",
262
270
  REFUNDED: "refunded",
263
271
  },
272
+ /*
273
+ * Corrected 2026-08-24 (W3) — `processing` and `partial_refund` were
274
+ * missing, so this listed 4 of the real 6. `PaymentStatus`
275
+ * (features/orders/types/index.ts) is the source; a partial refund is a
276
+ * state real orders reach, and W2 put it on the order timeline.
277
+ */
264
278
  PAYMENT_STATUS_VALUES: {
265
279
  PENDING: "pending",
280
+ PROCESSING: "processing",
266
281
  PAID: "paid",
267
282
  FAILED: "failed",
268
283
  REFUNDED: "refunded",
284
+ PARTIAL_REFUND: "partial_refund",
269
285
  },
270
286
  PAYOUT_STATUS_VALUES: {
271
287
  ELIGIBLE: "eligible",
@@ -339,12 +355,22 @@ export const BID_FIELDS = {
339
355
  AUTO_MAX_BID: "autoMaxBid",
340
356
  CREATED_AT: "createdAt",
341
357
  UPDATED_AT: "updatedAt",
358
+ /*
359
+ * Corrected 2026-08-24 (W3). Verified against the feature's own union AND
360
+ * against what is actually stored. This file is a deliberate LEAF — it
361
+ * imports nothing, and `features/products/types/index.ts` imports IT — so it
362
+ * cannot re-export the feature unions without a cycle. The values are
363
+ * therefore duplicated on purpose, and `audit-field-names-union-parity`
364
+ * blocks them drifting again.
365
+ */
342
366
  STATUS_VALUES: {
343
367
  ACTIVE: "active",
344
368
  OUTBID: "outbid",
345
369
  WON: "won",
346
370
  LOST: "lost",
347
371
  CANCELLED: "cancelled",
372
+ /** Won, then the buyer let the payment window lapse. Was missing here. */
373
+ FORFEITED: "forfeited",
348
374
  },
349
375
  };
350
376
  // ============================================================================
@@ -476,12 +502,17 @@ export const PAYOUT_FIELDS = {
476
502
  STATUS: "status",
477
503
  CREATED_AT: "createdAt",
478
504
  UPDATED_AT: "updatedAt",
505
+ /*
506
+ * Corrected 2026-08-24 (W3) — `cancelled` was listed here and is NOT a
507
+ * member of `PayoutStatus` (features/payments/schemas/firestore.ts:18,
508
+ * pending|processing|paid|failed). A chip or badge keyed on it could only
509
+ * ever match zero rows (Recurrent Root Cause #33).
510
+ */
479
511
  STATUS_VALUES: {
480
512
  PENDING: "pending",
481
513
  PROCESSING: "processing",
482
514
  PAID: "paid",
483
515
  FAILED: "failed",
484
- CANCELLED: "cancelled",
485
516
  },
486
517
  };
487
518
  // ============================================================================
@@ -771,10 +802,17 @@ export const SCAMMER_FIELDS = {
771
802
  REPORTED_BY: "reportedBy",
772
803
  VERIFIED_BY: "verifiedBy",
773
804
  CREATED_AT: "createdAt",
805
+ /*
806
+ * Corrected 2026-08-24 (W3). Two defects: the real stored value is
807
+ * `pending_review`, not `pending` (the seeded scammer profile stores
808
+ * `pending_review`), and `removed` was missing entirely. Already flagged in
809
+ * CLAUDE.md's Root Cause #34 and never actioned.
810
+ */
774
811
  STATUS_VALUES: {
775
- PENDING: "pending",
812
+ PENDING_REVIEW: "pending_review",
776
813
  VERIFIED: "verified",
777
814
  REJECTED: "rejected",
815
+ REMOVED: "removed",
778
816
  },
779
817
  };
780
818
  // ============================================================================
@@ -797,10 +835,14 @@ export const SUPPORT_TICKET_FIELDS = {
797
835
  RESOLVED: "resolved",
798
836
  CLOSED: "closed",
799
837
  },
838
+ /*
839
+ * Corrected 2026-08-24 (W3) — `medium` is not a member of
840
+ * `TicketPriorityValues` (low|normal|high|urgent). `normal` is the
841
+ * middle value.
842
+ */
800
843
  PRIORITY_VALUES: {
801
844
  LOW: "low",
802
845
  NORMAL: "normal",
803
- MEDIUM: "medium",
804
846
  HIGH: "high",
805
847
  URGENT: "urgent",
806
848
  },
@@ -1,9 +1,11 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { sortBy, sieveFilter, SIEVE_OP } from "../../../utils/sieve-builder";
4
4
  import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
5
5
  import { ROUTES } from "../../../constants/index";
6
- import { Div, Span, Stack, TextLink } from "../../../ui";
6
+ import { useState } from "react";
7
+ import { Div, Span, Stack, TextLink, RecordDetailModal } from "../../../ui";
8
+ import { buildBidDetailFields, bidStatusBadge } from "../../auctions/components/bid-detail-fields";
7
9
  import { FieldSelect } from "../../../ui/forms";
8
10
  import { AuctionBidsTable } from "../../auctions/components/AuctionBidsTable";
9
11
  import { DataListingView } from "../../admin/components/DataListingView";
@@ -21,6 +23,11 @@ const STATUS_OPTIONS = [
21
23
  { value: "cancelled", label: "Cancelled" },
22
24
  ];
23
25
  export function UserBidsView() {
26
+ // A bid could be seen in this list and never opened — no row click, no row
27
+ // action, no detail page. The "Browse auctions" link in the empty state is
28
+ // not an affordance: it is not row-scoped, and it only shows when there are
29
+ // no bids at all.
30
+ const [detail, setDetail] = useState(null);
24
31
  const config = {
25
32
  portal: "user",
26
33
  title: "My Bids",
@@ -40,8 +47,9 @@ export function UserBidsView() {
40
47
  if (isLoading) {
41
48
  return (_jsx(Stack, { gap: "md", children: Array.from({ length: 3 }).map((_, i) => (_jsx(Div, { className: "h-16 animate-pulse border border-[var(--appkit-color-border)]", rounded: "xl" }, i))) }));
42
49
  }
43
- return (_jsx(AuctionBidsTable, { bids: rows, portal: "buyer", totalPages: 1, currentPage: 1, onPageChange: () => { }, emptyLabel: _jsxs(Span, { children: ["You haven't placed any bids yet.", " ", _jsx(TextLink, { href: String(ROUTES.PUBLIC.AUCTIONS), children: "Browse auctions" })] }) }));
50
+ return (_jsx(AuctionBidsTable, { bids: rows, portal: "buyer", onRowClick: (bid) => setDetail(bid), totalPages: 1, currentPage: 1, onPageChange: () => { }, emptyLabel: _jsxs(Span, { children: ["You haven't placed any bids yet.", " ", _jsx(TextLink, { href: String(ROUTES.PUBLIC.AUCTIONS), children: "Browse auctions" })] }) }));
44
51
  },
45
52
  };
46
- return _jsx(DataListingView, { config: config });
53
+ const badge = detail ? bidStatusBadge(detail.status) : null;
54
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(RecordDetailModal, { isOpen: detail !== null, onClose: () => setDetail(null), title: detail?.productTitle || "Bid", badges: badge ? [{ label: badge.label }] : undefined, fields: detail ? buildBidDetailFields(detail, "buyer") : undefined, footer: detail ? (_jsx(TextLink, { href: String(ROUTES.PUBLIC.AUCTION_DETAIL(detail.productId)), children: "View auction \u2192" })) : undefined })] }));
47
55
  }
@@ -1,5 +1,8 @@
1
1
  import type { Order } from "../../orders/types";
2
2
  export interface UserReturnsViewProps {
3
+ /**
4
+ * Optional override. When omitted the card still opens — see below.
5
+ */
3
6
  onOrderClick?: (order: Order) => void;
4
7
  }
5
8
  export declare function UserReturnsView({ onOrderClick }: UserReturnsViewProps): import("react").JSX.Element;
@@ -3,6 +3,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { sortBy } from "../../../constants/sort";
4
4
  import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
5
5
  import { Div, Grid, Stack, Text } from "../../../ui";
6
+ import { useRouter } from "next/navigation";
7
+ import { ROUTES } from "../../../next/routing/route-map";
6
8
  import { DataListingView } from "../../admin/components/DataListingView";
7
9
  import { OrderCard } from "../../orders/components/OrdersList";
8
10
  const SORT_OPTIONS = [
@@ -11,6 +13,11 @@ const SORT_OPTIONS = [
11
13
  { value: sortBy("createdAt", "ASC"), label: "Oldest" },
12
14
  ];
13
15
  export function UserReturnsView({ onOrderClick }) {
16
+ const router = useRouter();
17
+ // Client-side navigation, not a full reload — the row is a link in behaviour
18
+ // even though the card itself contains its own controls, so wrapping it in
19
+ // an <a> would nest interactive content inside a link.
20
+ const openOrder = (order) => router.push(String(ROUTES.USER.ORDER_DETAIL(order.id)));
14
21
  const config = {
15
22
  portal: "user",
16
23
  title: "Returns & Refunds",
@@ -32,7 +39,16 @@ export function UserReturnsView({ onOrderClick }) {
32
39
  if (rows.length === 0) {
33
40
  return _jsx(Text, { color: "muted", children: "You have no return or refund requests." });
34
41
  }
35
- return (_jsx(Grid, { gap: "md", className: "grid-cols-1", children: rows.map((order) => (_jsx(OrderCard, { order: order, onClick: onOrderClick }, order.id))) }));
42
+ return (_jsx(Grid, { gap: "md", className: "grid-cols-1", children: rows.map((order) => (_jsx(OrderCard, { order: order,
43
+ // A return IS an order, and its detail page already exists — so
44
+ // the row opens whether or not a consumer supplies a handler.
45
+ //
46
+ // It was previously openable ONLY because the single consumer
47
+ // happened to pass `onOrderClick`; a second one that forgot
48
+ // would have produced a listing whose rows do nothing, and no
49
+ // audit could have seen it because the affordance lived in the
50
+ // caller. Defaulting here makes it structural.
51
+ onClick: onOrderClick ?? openOrder }, order.id))) }));
36
52
  },
37
53
  };
38
54
  return _jsx(DataListingView, { config: config });
@@ -23,6 +23,7 @@ const ACTION_LABELS = {
23
23
  checkout_bypass: "Admin checkout bypass",
24
24
  coupon_update: "Coupon updated",
25
25
  payout_mark_paid: "Payout marked paid",
26
+ offer_cancel: "Offer cancelled",
26
27
  store_status_change: "Store status changed",
27
28
  user_role_change: "User role changed",
28
29
  };
@@ -5,11 +5,12 @@ import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit/client";
5
5
  import { sortBy } from "@mohasinac/appkit/client";
6
6
  import React, { useState } from "react";
7
7
  import { useQueryClient } from "@tanstack/react-query";
8
- import { ConfirmDeleteModal, FilterChipGroup, ListingLayout, RowActionMenu, useToast } from "../../../ui";
8
+ import { ConfirmDeleteModal, FilterChipGroup, ListingLayout, RecordDetailModal, RowActionMenu, useToast, } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
10
  import { ADMIN_BID_STATUS_TABS } from "../constants/filter-tabs";
11
11
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
12
12
  import { ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
13
+ import { buildBidDetailFields, bidStatusBadge } from "../../auctions/components/bid-detail-fields";
13
14
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
14
15
  import { DataListingView } from "./DataListingView";
15
16
  import { apiClient } from "../../../http";
@@ -18,6 +19,7 @@ export function AdminBidsView({ children, ...props }) {
18
19
  const { showToast } = useToast();
19
20
  const [cancelOpen, setCancelOpen] = useState(false);
20
21
  const [selectedRow, setSelectedRow] = useState(null);
22
+ const [detail, setDetail] = useState(null);
21
23
  const cancelMutation = useApiMutation({
22
24
  mutationFn: async (bidId) => {
23
25
  await apiClient.patch(ADMIN_ENDPOINTS.BID_BY_ID(bidId), { status: "cancelled" });
@@ -58,6 +60,7 @@ export function AdminBidsView({ children, ...props }) {
58
60
  secondary: toStringValue(item.bidderId ?? item.bidderName ?? item.userId, "Unknown bidder"),
59
61
  status: toStringValue(item.status, "active"),
60
62
  updatedAt: toRelativeDate(item.bidDate ?? item.updatedAt ?? item.createdAt),
63
+ detail: item,
61
64
  })),
62
65
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
63
66
  buildFilters: (state) => state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined,
@@ -72,6 +75,13 @@ export function AdminBidsView({ children, ...props }) {
72
75
  renderRowActions: (row) => {
73
76
  const isCancelled = row.status === "cancelled" || row.status === "voided";
74
77
  return (_jsx(RowActionMenu, { actions: [
78
+ // A menu of pure MUTATIONS is not a detail affordance (Root Cause
79
+ // #56): it let an admin cancel a bid they had never been able to
80
+ // read. View comes first for that reason.
81
+ {
82
+ label: ROW_ACTION_META[ROW_ACTION_ID.VIEW].label,
83
+ onClick: () => setDetail(row),
84
+ },
75
85
  {
76
86
  label: ROW_ACTION_META[ROW_ACTION_ID.CANCEL].label,
77
87
  destructive: ROW_ACTION_META[ROW_ACTION_ID.CANCEL].destructive,
@@ -85,7 +95,10 @@ export function AdminBidsView({ children, ...props }) {
85
95
  },
86
96
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_BID_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
87
97
  };
88
- return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(ConfirmDeleteModal, { isOpen: cancelOpen, onClose: () => {
98
+ const detailBadge = detail ? bidStatusBadge(detail.status) : null;
99
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(RecordDetailModal, { isOpen: detail !== null, onClose: () => setDetail(null), title: toStringValue(detail?.detail?.productTitle, "Bid"), badges: detailBadge ? [{ label: detailBadge.label }] : undefined, fields: detail
100
+ ? buildBidDetailFields(detail.detail, "admin")
101
+ : undefined }), _jsx(ConfirmDeleteModal, { isOpen: cancelOpen, onClose: () => {
89
102
  setCancelOpen(false);
90
103
  setSelectedRow(null);
91
104
  }, onConfirm: () => {