@mohasinac/appkit 4.11.2 → 4.11.3

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 (165) hide show
  1. package/dist/_internal/server/features/checkout/actions.d.ts +4 -0
  2. package/dist/_internal/server/features/checkout/actions.js +111 -4
  3. package/dist/_internal/server/features/checkout/data.d.ts +10 -0
  4. package/dist/_internal/server/features/orders/adapters.js +7 -0
  5. package/dist/_internal/server/features/products/data.d.ts +12 -3
  6. package/dist/_internal/server/features/products/data.js +55 -4
  7. package/dist/_internal/server/features/products/index.d.ts +1 -1
  8. package/dist/_internal/server/features/products/index.js +1 -1
  9. package/dist/_internal/server/features/products/list-public.d.ts +19 -0
  10. package/dist/_internal/server/features/products/list-public.js +39 -6
  11. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  12. package/dist/_internal/server/features/reviews/data.js +3 -2
  13. package/dist/_internal/server/features/tester/visibility.js +3 -3
  14. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -1
  15. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
  16. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +2 -0
  17. package/dist/_internal/server/jobs/handlers/_helpers.js +4 -1
  18. package/dist/_internal/shared/features/promotions/schema.d.ts +0 -16
  19. package/dist/_internal/shared/features/promotions/schema.js +0 -2
  20. package/dist/_internal/shared/features/reviews/config.d.ts +8 -1
  21. package/dist/_internal/shared/features/reviews/config.js +8 -1
  22. package/dist/_internal/shared/listing-types/_registry.d.ts +88 -1
  23. package/dist/_internal/shared/listing-types/_registry.js +63 -1
  24. package/dist/_internal/shared/listing-types/art/config.d.ts +59 -0
  25. package/dist/_internal/shared/listing-types/art/config.js +12 -0
  26. package/dist/_internal/shared/listing-types/auction/config.d.ts +53 -0
  27. package/dist/_internal/shared/listing-types/auction/config.js +17 -0
  28. package/dist/_internal/shared/listing-types/classified/config.d.ts +59 -0
  29. package/dist/_internal/shared/listing-types/classified/config.js +14 -0
  30. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +59 -0
  31. package/dist/_internal/shared/listing-types/digital-code/config.js +12 -0
  32. package/dist/_internal/shared/listing-types/live/config.d.ts +59 -0
  33. package/dist/_internal/shared/listing-types/live/config.js +14 -0
  34. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +110 -0
  35. package/dist/_internal/shared/listing-types/pre-order/config.js +14 -0
  36. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +44 -0
  37. package/dist/_internal/shared/listing-types/prize-draw/config.js +10 -0
  38. package/dist/_internal/shared/listing-types/standard/config.d.ts +59 -0
  39. package/dist/_internal/shared/listing-types/standard/config.js +9 -0
  40. package/dist/_internal/shared/listing-types/stickers/config.d.ts +59 -0
  41. package/dist/_internal/shared/listing-types/stickers/config.js +12 -0
  42. package/dist/client.d.ts +5 -1
  43. package/dist/client.js +4 -1
  44. package/dist/constants/field-names.d.ts +12 -0
  45. package/dist/constants/field-names.js +12 -0
  46. package/dist/constants/table-keys.d.ts +2 -0
  47. package/dist/constants/table-keys.js +2 -0
  48. package/dist/features/account/components/UserOrdersView.js +27 -5
  49. package/dist/features/admin/components/AdminAddressesView.js +3 -1
  50. package/dist/features/admin/components/AdminCouponEditorView.js +1 -4
  51. package/dist/features/admin/components/AdminPaymentMethodsView.js +3 -1
  52. package/dist/features/admin/components/AdminProductsView.js +34 -18
  53. package/dist/features/admin/constants/filter-tabs.d.ts +26 -9
  54. package/dist/features/admin/constants/filter-tabs.js +22 -9
  55. package/dist/features/auctions/components/AuctionBidsTable.js +1 -1
  56. package/dist/features/auctions/components/AuctionBottomActions.js +1 -0
  57. package/dist/features/auctions/components/AuctionDetailPageView.js +13 -11
  58. package/dist/features/cart/schemas/firestore.d.ts +0 -2
  59. package/dist/features/categories/components/BrandDetailTabs.js +31 -11
  60. package/dist/features/categories/components/CategoryDetailTabs.js +27 -7
  61. package/dist/features/categories/components/CategoryProductsListing.d.ts +9 -1
  62. package/dist/features/categories/components/CategoryProductsListing.js +5 -2
  63. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -1
  64. package/dist/features/contact/email.js +5 -1
  65. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +17 -16
  66. package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
  67. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  68. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  69. package/dist/features/layout/AppLayoutShell.js +9 -2
  70. package/dist/features/layout/BottomActions.js +26 -3
  71. package/dist/features/layout/BottomActionsContext.d.ts +14 -0
  72. package/dist/features/layout/BottomActionsContext.js +13 -1
  73. package/dist/features/layout/hooks/useBottomActions.d.ts +6 -1
  74. package/dist/features/layout/hooks/useBottomActions.js +4 -1
  75. package/dist/features/layout/index.d.ts +1 -1
  76. package/dist/features/live/components/LiveItemDetailPageView.js +7 -6
  77. package/dist/features/orders/repository/orders.repository.d.ts +4 -0
  78. package/dist/features/orders/repository/orders.repository.js +4 -0
  79. package/dist/features/orders/types/index.d.ts +16 -0
  80. package/dist/features/pre-orders/components/PreOrderBottomActions.d.ts +1 -1
  81. package/dist/features/pre-orders/components/PreOrderBottomActions.js +3 -19
  82. package/dist/features/products/components/AuctionsIndexListing.js +1 -2
  83. package/dist/features/products/components/ListingBottomActions.d.ts +28 -0
  84. package/dist/features/products/components/ListingBottomActions.js +25 -0
  85. package/dist/features/products/components/PrizeDrawBottomActions.d.ts +1 -1
  86. package/dist/features/products/components/PrizeDrawBottomActions.js +3 -21
  87. package/dist/features/products/components/ProductDetailActions.js +4 -0
  88. package/dist/features/products/components/ProductDetailPageView.js +7 -7
  89. package/dist/features/products/constants/listing-tabs.d.ts +43 -198
  90. package/dist/features/products/constants/listing-tabs.js +71 -58
  91. package/dist/features/products/constants/sieve.d.ts +104 -1
  92. package/dist/features/products/constants/sieve.js +27 -7
  93. package/dist/features/products/repository/products.repository.d.ts +45 -1
  94. package/dist/features/products/repository/products.repository.js +26 -3
  95. package/dist/features/products/utils/listing-badge-variant.d.ts +20 -1
  96. package/dist/features/products/utils/listing-badge-variant.js +26 -1
  97. package/dist/features/products/utils/listing-type.d.ts +24 -0
  98. package/dist/features/products/utils/listing-type.js +48 -0
  99. package/dist/features/promotions/actions/coupon-actions.d.ts +7 -0
  100. package/dist/features/promotions/actions/coupon-actions.js +12 -1
  101. package/dist/features/promotions/actions/coupon-stacking.d.ts +24 -0
  102. package/dist/features/promotions/actions/coupon-stacking.js +21 -0
  103. package/dist/features/promotions/actions/index.d.ts +1 -0
  104. package/dist/features/promotions/actions/index.js +1 -0
  105. package/dist/features/promotions/actions/seller-coupon-actions.d.ts +0 -1
  106. package/dist/features/promotions/actions/seller-coupon-actions.js +0 -1
  107. package/dist/features/promotions/components/CouponHelpDetails.d.ts +20 -0
  108. package/dist/features/promotions/components/CouponHelpDetails.js +17 -0
  109. package/dist/features/promotions/components/CouponsIndexListing.js +2 -1
  110. package/dist/features/promotions/components/index.d.ts +2 -0
  111. package/dist/features/promotions/components/index.js +1 -0
  112. package/dist/features/promotions/constants/coupon-help.d.ts +27 -0
  113. package/dist/features/promotions/constants/coupon-help.js +42 -0
  114. package/dist/features/promotions/repository/coupons.repository.d.ts +10 -1
  115. package/dist/features/promotions/repository/coupons.repository.js +22 -8
  116. package/dist/features/promotions/schemas/firestore.d.ts +0 -1
  117. package/dist/features/promotions/schemas/firestore.js +1 -1
  118. package/dist/features/promotions/schemas/index.d.ts +0 -20
  119. package/dist/features/promotions/schemas/index.js +0 -1
  120. package/dist/features/reviews/actions/review-actions.js +5 -1
  121. package/dist/features/reviews/components/ReviewDetailPageView.js +2 -1
  122. package/dist/features/reviews/components/ReviewFilters.d.ts +7 -1
  123. package/dist/features/reviews/components/ReviewFilters.js +4 -3
  124. package/dist/features/reviews/components/ReviewsIndexListing.d.ts +6 -0
  125. package/dist/features/reviews/components/ReviewsIndexListing.js +9 -109
  126. package/dist/features/reviews/components/ReviewsListingPanel.d.ts +41 -0
  127. package/dist/features/reviews/components/ReviewsListingPanel.js +226 -0
  128. package/dist/features/reviews/components/index.d.ts +2 -0
  129. package/dist/features/reviews/components/index.js +1 -0
  130. package/dist/features/reviews/hooks/useReviews.d.ts +8 -0
  131. package/dist/features/reviews/hooks/useReviews.js +14 -6
  132. package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
  133. package/dist/features/reviews/repository/reviews.repository.js +77 -41
  134. package/dist/features/reviews/types/index.d.ts +8 -1
  135. package/dist/features/seller/components/SellerProductsCards.js +2 -2
  136. package/dist/features/seller/components/SellerProductsView.js +21 -18
  137. package/dist/features/stores/actions/store-query-actions.js +1 -1
  138. package/dist/features/stores/components/StoreAuctionsListing.js +5 -11
  139. package/dist/features/stores/components/StorePreOrdersListing.js +6 -11
  140. package/dist/features/stores/components/StoreReviewsListing.d.ts +4 -0
  141. package/dist/features/stores/components/StoreReviewsListing.js +7 -108
  142. package/dist/features/stores/schemas/index.d.ts +2 -2
  143. package/dist/features/tester/seed-data/coupons-tester-seed-data.js +3 -3
  144. package/dist/features/tester/seed-data/index.d.ts +1 -0
  145. package/dist/features/tester/seed-data/index.js +1 -0
  146. package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +2 -0
  147. package/dist/features/tester/seed-data/offers-tester-seed-data.js +61 -0
  148. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +337 -1
  149. package/dist/index.d.ts +9 -3
  150. package/dist/index.js +11 -2
  151. package/dist/react/hooks/useListingTypeFlags.d.ts +12 -19
  152. package/dist/react/hooks/useListingTypeFlags.js +4 -9
  153. package/dist/schemas/registry.d.ts +0 -12
  154. package/dist/seed/claimed-coupons-seed-data.js +3 -3
  155. package/dist/seed/coupons-seed-data.js +29 -10
  156. package/dist/seed/faq-seed-data.js +2 -2
  157. package/dist/seed/manifest.js +2 -2
  158. package/dist/styles.css +44 -39
  159. package/dist/tailwind-utilities.css +1 -1
  160. package/dist/ui/components/FilterChipGroup.d.ts +21 -3
  161. package/dist/ui/components/FilterChipGroup.js +33 -4
  162. package/dist/ui/components/HorizontalScroller.d.ts +1 -2
  163. package/dist/ui/components/HorizontalScroller.js +9 -5
  164. package/dist/ui/components/HorizontalScroller.style.css +43 -38
  165. package/package.json +1 -1
@@ -23,7 +23,6 @@ export declare const createCouponSchema: z.ZodObject<{
23
23
  name: z.ZodString;
24
24
  type: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
25
25
  scope: z.ZodEnum<["admin", "seller"]>;
26
- sellerId: z.ZodOptional<z.ZodString>;
27
26
  discount: z.ZodObject<{
28
27
  value: z.ZodNumber;
29
28
  maxDiscount: z.ZodOptional<z.ZodNumber>;
@@ -62,17 +61,14 @@ export declare const createCouponSchema: z.ZodObject<{
62
61
  }>;
63
62
  restrictions: z.ZodOptional<z.ZodObject<{
64
63
  firstTimeUserOnly: z.ZodDefault<z.ZodBoolean>;
65
- combineWithSellerCoupons: z.ZodDefault<z.ZodBoolean>;
66
64
  applicableProducts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
67
65
  applicableCategories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
68
66
  }, "strip", z.ZodTypeAny, {
69
67
  firstTimeUserOnly: boolean;
70
- combineWithSellerCoupons: boolean;
71
68
  applicableProducts?: string[] | undefined;
72
69
  applicableCategories?: string[] | undefined;
73
70
  }, {
74
71
  firstTimeUserOnly?: boolean | undefined;
75
- combineWithSellerCoupons?: boolean | undefined;
76
72
  applicableProducts?: string[] | undefined;
77
73
  applicableCategories?: string[] | undefined;
78
74
  }>>;
@@ -95,10 +91,8 @@ export declare const createCouponSchema: z.ZodObject<{
95
91
  endDate: string;
96
92
  startDate: string;
97
93
  };
98
- sellerId?: string | undefined;
99
94
  restrictions?: {
100
95
  firstTimeUserOnly: boolean;
101
- combineWithSellerCoupons: boolean;
102
96
  applicableProducts?: string[] | undefined;
103
97
  applicableCategories?: string[] | undefined;
104
98
  } | undefined;
@@ -121,10 +115,8 @@ export declare const createCouponSchema: z.ZodObject<{
121
115
  startDate: string;
122
116
  isActive?: boolean | undefined;
123
117
  };
124
- sellerId?: string | undefined;
125
118
  restrictions?: {
126
119
  firstTimeUserOnly?: boolean | undefined;
127
- combineWithSellerCoupons?: boolean | undefined;
128
120
  applicableProducts?: string[] | undefined;
129
121
  applicableCategories?: string[] | undefined;
130
122
  } | undefined;
@@ -134,7 +126,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
134
126
  name: z.ZodOptional<z.ZodString>;
135
127
  type: z.ZodOptional<z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>>;
136
128
  scope: z.ZodOptional<z.ZodEnum<["admin", "seller"]>>;
137
- sellerId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
138
129
  discount: z.ZodOptional<z.ZodObject<{
139
130
  value: z.ZodNumber;
140
131
  maxDiscount: z.ZodOptional<z.ZodNumber>;
@@ -173,17 +164,14 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
173
164
  }>>;
174
165
  restrictions: z.ZodOptional<z.ZodOptional<z.ZodObject<{
175
166
  firstTimeUserOnly: z.ZodDefault<z.ZodBoolean>;
176
- combineWithSellerCoupons: z.ZodDefault<z.ZodBoolean>;
177
167
  applicableProducts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
178
168
  applicableCategories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
179
169
  }, "strip", z.ZodTypeAny, {
180
170
  firstTimeUserOnly: boolean;
181
- combineWithSellerCoupons: boolean;
182
171
  applicableProducts?: string[] | undefined;
183
172
  applicableCategories?: string[] | undefined;
184
173
  }, {
185
174
  firstTimeUserOnly?: boolean | undefined;
186
- combineWithSellerCoupons?: boolean | undefined;
187
175
  applicableProducts?: string[] | undefined;
188
176
  applicableCategories?: string[] | undefined;
189
177
  }>>>;
@@ -191,7 +179,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
191
179
  type?: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y" | undefined;
192
180
  scope?: "seller" | "admin" | undefined;
193
181
  name?: string | undefined;
194
- sellerId?: string | undefined;
195
182
  discount?: {
196
183
  value: number;
197
184
  minPurchase: number;
@@ -208,7 +195,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
208
195
  } | undefined;
209
196
  restrictions?: {
210
197
  firstTimeUserOnly: boolean;
211
- combineWithSellerCoupons: boolean;
212
198
  applicableProducts?: string[] | undefined;
213
199
  applicableCategories?: string[] | undefined;
214
200
  } | undefined;
@@ -216,7 +202,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
216
202
  type?: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y" | undefined;
217
203
  scope?: "seller" | "admin" | undefined;
218
204
  name?: string | undefined;
219
- sellerId?: string | undefined;
220
205
  discount?: {
221
206
  value: number;
222
207
  maxDiscount?: number | undefined;
@@ -233,7 +218,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
233
218
  } | undefined;
234
219
  restrictions?: {
235
220
  firstTimeUserOnly?: boolean | undefined;
236
- combineWithSellerCoupons?: boolean | undefined;
237
221
  applicableProducts?: string[] | undefined;
238
222
  applicableCategories?: string[] | undefined;
239
223
  } | undefined;
@@ -12,7 +12,6 @@ export const createCouponSchema = z.object({
12
12
  name: z.string().min(1).max(100),
13
13
  type: z.enum(["percentage", "fixed", "free_shipping", "buy_x_get_y"]),
14
14
  scope: z.enum(["admin", "seller"]),
15
- sellerId: z.string().optional(),
16
15
  discount: z.object({
17
16
  value: z.number().min(0),
18
17
  maxDiscount: z.number().min(0).optional(),
@@ -29,7 +28,6 @@ export const createCouponSchema = z.object({
29
28
  }),
30
29
  restrictions: z.object({
31
30
  firstTimeUserOnly: z.boolean().default(false),
32
- combineWithSellerCoupons: z.boolean().default(false),
33
31
  applicableProducts: z.array(z.string()).optional(),
34
32
  applicableCategories: z.array(z.string()).optional(),
35
33
  }).optional(),
@@ -1,4 +1,11 @@
1
- export declare const REVIEWS_PAGE_SIZE = 20;
1
+ /** Page size for the dedicated review listing surfaces (`/reviews`, `/stores/[slug]/reviews`). */
2
+ export declare const REVIEWS_PAGE_SIZE = 12;
3
+ /**
4
+ * Page size for the reviews section embedded in a listing detail page. Smaller than
5
+ * REVIEWS_PAGE_SIZE on purpose — the tab sits inside a page that already has a lot of
6
+ * content, and an unpaginated list was what made those pages endlessly long.
7
+ */
8
+ export declare const REVIEWS_DETAIL_PAGE_SIZE = 10;
2
9
  export declare const REVIEW_BODY_MAX_LENGTH = 2000;
3
10
  export declare const REVIEW_TITLE_MAX_LENGTH = 100;
4
11
  export declare const REVIEW_MIN_RATING = 1;
@@ -1,4 +1,11 @@
1
- export const REVIEWS_PAGE_SIZE = 20;
1
+ /** Page size for the dedicated review listing surfaces (`/reviews`, `/stores/[slug]/reviews`). */
2
+ export const REVIEWS_PAGE_SIZE = 12;
3
+ /**
4
+ * Page size for the reviews section embedded in a listing detail page. Smaller than
5
+ * REVIEWS_PAGE_SIZE on purpose — the tab sits inside a page that already has a lot of
6
+ * content, and an unpaginated list was what made those pages endlessly long.
7
+ */
8
+ export const REVIEWS_DETAIL_PAGE_SIZE = 10;
2
9
  export const REVIEW_BODY_MAX_LENGTH = 2000;
3
10
  export const REVIEW_TITLE_MAX_LENGTH = 100;
4
11
  export const REVIEW_MIN_RATING = 1;
@@ -4,12 +4,37 @@
4
4
  * Adding a new listing type later means:
5
5
  * 1. Add a folder `<type>/{config,schema,ctas,og,seed-factory}.ts`
6
6
  * 2. Add a row to `LISTING_TYPE_CAPABILITIES` in `./capabilities.ts`
7
- * 3. Add the import + entry below
7
+ * 3. Add a row to `ALL_LISTING_TYPES_MAP` in `./feature-flags.ts`
8
+ * 4. Add the import + entry below
8
9
  *
9
10
  * No grep-the-codebase sweep required. Future-expansion Pattern 3.
11
+ *
12
+ * BROWSE CHROME (2026-08-21) — the plugin also carries the tab/sort/filter
13
+ * metadata every listing surface needs, so tab bars, chip groups, sort
14
+ * dropdowns and filter drawers are DERIVED from this one map instead of
15
+ * hand-copied. Before this, at least nine independent "list every listing
16
+ * type" arrays existed (tab constants, admin type-filter maps, the seller
17
+ * TypeDropdown, useListingTypeFlags, SORT_OPTIONS_BY_LISTING_TYPE, …) and
18
+ * they had silently drifted: `art` and `stickers` were missing from most of
19
+ * them, `bundle` — which stopped being a listingType in SB-UNI-D — was still
20
+ * present in others, and the public /products chips offered only 4 of 9
21
+ * types. `scripts/audit-listing-type-tab-coverage.mjs` now blocks any array
22
+ * that doesn't cover the full union.
10
23
  */
11
24
  import { LISTING_TYPE_CAPABILITIES } from "./capabilities";
12
25
  import type { ListingType } from "../../../features/products/types/index";
26
+ import type { SortOption } from "../../../features/products/constants/sieve";
27
+ /**
28
+ * Which "Show X" quick-filter a listing type's browse surfaces default to
29
+ * hiding. Drives both the toolbar toggle label and the SSR default filter,
30
+ * which MUST agree or the first paint disagrees with the first refetch
31
+ * (Root Cause #30).
32
+ *
33
+ * "sold" — hide rows with no stock (`inStock`)
34
+ * "ended" — hide rows whose end date has passed (`dateFrom` = now)
35
+ * "closed" — hide rows that stopped accepting entries/orders
36
+ */
37
+ export type ListingHideDefault = "sold" | "ended" | "closed";
13
38
  export interface ListingTypePlugin {
14
39
  listingType: ListingType;
15
40
  slugPrefix: string;
@@ -27,6 +52,44 @@ export interface ListingTypePlugin {
27
52
  typeLabel: string;
28
53
  /** Whether the seller create/edit form shows a Stock Quantity field. */
29
54
  showsStockQuantity: boolean;
55
+ /**
56
+ * URL-facing tab slug — the segment in `/stores/{slug}/<tabSlug>` and the
57
+ * `id` of every route-backed tab bar (category, brand, store, seller).
58
+ *
59
+ * DELIBERATELY NOT the same id space as `listingType`: these slugs are
60
+ * plural-kebab ("pre-orders", "digital-codes") and are baked into live URLs
61
+ * and bookmarks, whereas `listingType` is the canonical singular value that
62
+ * goes straight into a Firestore `==` filter. Tab bars key on `tabSlug`;
63
+ * filter chips key on `listingType`. Conflating them either breaks existing
64
+ * URLs or silently returns zero rows (Root Cause #33).
65
+ */
66
+ tabSlug: string;
67
+ /**
68
+ * Plural browse label ("Pre-Orders", "Digital Codes"). Used for tab bars
69
+ * and page headings, where `typeLabel`'s singular reads wrong.
70
+ */
71
+ pluralLabel: string;
72
+ /** Short label for the dense type-chip row on /products and /admin/products. */
73
+ chipLabel: string;
74
+ /**
75
+ * Dedicated public browse page for this type, or `null` when it browses on
76
+ * `/products` (art and stickers — see CLAUDE.md's Listing Types Reference).
77
+ * A non-null value also drives the "full <type> filters →" link shown on
78
+ * /products when exactly this type is selected.
79
+ */
80
+ browseRoute: string | null;
81
+ /** Which "Show X" default this type's browse surfaces apply. */
82
+ hideDefault: ListingHideDefault;
83
+ /** Sort set offered on admin/seller dashboards for this type. */
84
+ sortOptions: readonly SortOption[];
85
+ /** Narrower sort set offered on public browse surfaces for this type. */
86
+ publicSortOptions: readonly SortOption[];
87
+ /**
88
+ * `TABLE_KEYS` values for facets that only make sense for this type
89
+ * (auction bid range, classified meetup city, live-item species, …). The
90
+ * shared product facets (category/brand/price/condition) are implicit.
91
+ */
92
+ extraFacetKeys: readonly string[];
30
93
  }
31
94
  export declare const LISTING_TYPE_REGISTRY: Record<ListingType, ListingTypePlugin>;
32
95
  export declare function pluginFor(type: ListingType): ListingTypePlugin;
@@ -37,4 +100,28 @@ export declare function pluginFor(type: ListingType): ListingTypePlugin;
37
100
  * explicit `listingType` field. Falls back to "standard" when no prefix matches.
38
101
  */
39
102
  export declare function detectListingTypeFromSlug(slug: string): ListingType;
103
+ /**
104
+ * Sort options for a listing type. Replaces the hand-written
105
+ * `SORT_OPTIONS_BY_LISTING_TYPE` that used to live in
106
+ * `features/products/constants/sieve.ts`, which was missing five of the nine
107
+ * types and still carried a `bundle` key that stopped being a listingType in
108
+ * SB-UNI-D.
109
+ */
110
+ export declare function sortOptionsFor(type: ListingType, variant?: "admin" | "public"): readonly SortOption[];
111
+ /**
112
+ * Sort options common to EVERY type in `types` — what a multi-type selection
113
+ * (or the unfiltered "all types" view) can safely offer, since a sort field
114
+ * only valid for one type would silently no-op (or trip FAILED_PRECONDITION)
115
+ * on the others.
116
+ *
117
+ * With a single type this returns that type's own set, so the caller doesn't
118
+ * need to special-case "exactly one selected".
119
+ */
120
+ export declare function commonSortOptionsFor(types: readonly ListingType[], variant?: "admin" | "public"): readonly SortOption[];
121
+ /**
122
+ * Which "Show X" quick-filter applies to a set of listing types. A mixed
123
+ * selection can need more than one toggle (auctions end, products sell out),
124
+ * so this returns every distinct default across the set.
125
+ */
126
+ export declare function hideDefaultsFor(types: readonly ListingType[]): ListingHideDefault[];
40
127
  export { LISTING_TYPE_CAPABILITIES };
@@ -4,9 +4,22 @@
4
4
  * Adding a new listing type later means:
5
5
  * 1. Add a folder `<type>/{config,schema,ctas,og,seed-factory}.ts`
6
6
  * 2. Add a row to `LISTING_TYPE_CAPABILITIES` in `./capabilities.ts`
7
- * 3. Add the import + entry below
7
+ * 3. Add a row to `ALL_LISTING_TYPES_MAP` in `./feature-flags.ts`
8
+ * 4. Add the import + entry below
8
9
  *
9
10
  * No grep-the-codebase sweep required. Future-expansion Pattern 3.
11
+ *
12
+ * BROWSE CHROME (2026-08-21) — the plugin also carries the tab/sort/filter
13
+ * metadata every listing surface needs, so tab bars, chip groups, sort
14
+ * dropdowns and filter drawers are DERIVED from this one map instead of
15
+ * hand-copied. Before this, at least nine independent "list every listing
16
+ * type" arrays existed (tab constants, admin type-filter maps, the seller
17
+ * TypeDropdown, useListingTypeFlags, SORT_OPTIONS_BY_LISTING_TYPE, …) and
18
+ * they had silently drifted: `art` and `stickers` were missing from most of
19
+ * them, `bundle` — which stopped being a listingType in SB-UNI-D — was still
20
+ * present in others, and the public /products chips offered only 4 of 9
21
+ * types. `scripts/audit-listing-type-tab-coverage.mjs` now blocks any array
22
+ * that doesn't cover the full union.
10
23
  */
11
24
  import * as standard from "./standard/config";
12
25
  import * as auction from "./auction/config";
@@ -50,6 +63,55 @@ export function detectListingTypeFromSlug(slug) {
50
63
  }
51
64
  return "standard";
52
65
  }
66
+ // ---------------------------------------------------------------------------
67
+ // Derived browse-chrome lookups
68
+ // ---------------------------------------------------------------------------
69
+ //
70
+ // Everything below is COMPUTED from LISTING_TYPE_REGISTRY. Never hand-write a
71
+ // parallel map — that is the exact drift this section exists to end.
72
+ /**
73
+ * Sort options for a listing type. Replaces the hand-written
74
+ * `SORT_OPTIONS_BY_LISTING_TYPE` that used to live in
75
+ * `features/products/constants/sieve.ts`, which was missing five of the nine
76
+ * types and still carried a `bundle` key that stopped being a listingType in
77
+ * SB-UNI-D.
78
+ */
79
+ export function sortOptionsFor(type, variant = "admin") {
80
+ const plugin = LISTING_TYPE_REGISTRY[type];
81
+ return variant === "public" ? plugin.publicSortOptions : plugin.sortOptions;
82
+ }
83
+ /**
84
+ * Sort options common to EVERY type in `types` — what a multi-type selection
85
+ * (or the unfiltered "all types" view) can safely offer, since a sort field
86
+ * only valid for one type would silently no-op (or trip FAILED_PRECONDITION)
87
+ * on the others.
88
+ *
89
+ * With a single type this returns that type's own set, so the caller doesn't
90
+ * need to special-case "exactly one selected".
91
+ */
92
+ export function commonSortOptionsFor(types, variant = "admin") {
93
+ if (types.length === 0)
94
+ return sortOptionsFor("standard", variant);
95
+ if (types.length === 1)
96
+ return sortOptionsFor(types[0], variant);
97
+ const [first, ...rest] = types;
98
+ const restSets = rest.map((t) => new Set(sortOptionsFor(t, variant).map((o) => o.value)));
99
+ const shared = sortOptionsFor(first, variant).filter((option) => restSets.every((set) => set.has(option.value)));
100
+ // Every type derives from BASE_SORT_OPTIONS, so the intersection is never
101
+ // empty in practice — but fall back rather than render a sortless toolbar.
102
+ return shared.length > 0 ? shared : sortOptionsFor("standard", variant);
103
+ }
104
+ /**
105
+ * Which "Show X" quick-filter applies to a set of listing types. A mixed
106
+ * selection can need more than one toggle (auctions end, products sell out),
107
+ * so this returns every distinct default across the set.
108
+ */
109
+ export function hideDefaultsFor(types) {
110
+ const seen = new Set();
111
+ for (const type of types)
112
+ seen.add(LISTING_TYPE_REGISTRY[type].hideDefault);
113
+ return [...seen];
114
+ }
53
115
  // Re-export the capability map so consumers can pull both surfaces from
54
116
  // the same module path.
55
117
  export { LISTING_TYPE_CAPABILITIES };
@@ -12,4 +12,63 @@ export declare const config: {
12
12
  priceLabel: string;
13
13
  typeLabel: string;
14
14
  showsStockQuantity: boolean;
15
+ tabSlug: string;
16
+ pluralLabel: string;
17
+ chipLabel: string;
18
+ browseRoute: string;
19
+ hideDefault: "sold";
20
+ sortOptions: readonly [{
21
+ readonly value: string;
22
+ readonly label: "Newest First";
23
+ }, {
24
+ readonly value: string;
25
+ readonly label: "Oldest First";
26
+ }, {
27
+ readonly value: string;
28
+ readonly label: "Price: Low to High";
29
+ }, {
30
+ readonly value: string;
31
+ readonly label: "Price: High to Low";
32
+ }, {
33
+ readonly value: string;
34
+ readonly label: "Name: A–Z";
35
+ }, {
36
+ readonly value: string;
37
+ readonly label: "Name: Z–A";
38
+ }, {
39
+ readonly value: string;
40
+ readonly label: "Featured First";
41
+ }, {
42
+ readonly value: string;
43
+ readonly label: "Promoted First";
44
+ }, {
45
+ readonly value: string;
46
+ readonly label: "Recently Updated";
47
+ }, {
48
+ readonly value: string;
49
+ readonly label: "Most Viewed";
50
+ }];
51
+ publicSortOptions: readonly [{
52
+ readonly value: string;
53
+ readonly label: "Newest First";
54
+ }, {
55
+ readonly value: string;
56
+ readonly label: "Oldest First";
57
+ }, {
58
+ readonly value: string;
59
+ readonly label: "Price: Low to High";
60
+ }, {
61
+ readonly value: string;
62
+ readonly label: "Price: High to Low";
63
+ }, {
64
+ readonly value: string;
65
+ readonly label: "Name: A–Z";
66
+ }, {
67
+ readonly value: string;
68
+ readonly label: "Name: Z–A";
69
+ }, {
70
+ readonly value: string;
71
+ readonly label: "Most Viewed";
72
+ }];
73
+ extraFacetKeys: readonly string[];
15
74
  };
@@ -1,5 +1,6 @@
1
1
  import { LISTING_TYPE_CAPABILITIES } from "../capabilities";
2
2
  import { ROUTES } from "../../../../next/routing/route-map";
3
+ import { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, } from "../../../../features/products/constants/sieve";
3
4
  export const LISTING_TYPE = "art";
4
5
  export const capability = LISTING_TYPE_CAPABILITIES.art;
5
6
  export const config = {
@@ -11,4 +12,15 @@ export const config = {
11
12
  priceLabel: "Price (₹)",
12
13
  typeLabel: "Art Print",
13
14
  showsStockQuantity: true,
15
+ tabSlug: "art",
16
+ pluralLabel: "Art",
17
+ chipLabel: "Art",
18
+ // `/art` is a COMBINED browse page spanning art + stickers, and art
19
+ // detail pages use the standard product route — so this is a real
20
+ // dedicated browse surface even though detailRoute points at /products.
21
+ browseRoute: String(ROUTES.PUBLIC.ART),
22
+ hideDefault: "sold",
23
+ sortOptions: STANDARD_SORT_OPTIONS,
24
+ publicSortOptions: STANDARD_PUBLIC_SORT_OPTIONS,
25
+ extraFacetKeys: [],
14
26
  };
@@ -12,4 +12,57 @@ export declare const config: {
12
12
  priceLabel: string;
13
13
  typeLabel: string;
14
14
  showsStockQuantity: boolean;
15
+ tabSlug: string;
16
+ pluralLabel: string;
17
+ chipLabel: string;
18
+ browseRoute: string;
19
+ hideDefault: "ended";
20
+ sortOptions: readonly [{
21
+ readonly value: string;
22
+ readonly label: "Ending Soon";
23
+ }, {
24
+ readonly value: string;
25
+ readonly label: "Just Started";
26
+ }, {
27
+ readonly value: string;
28
+ readonly label: "Lowest Current Bid";
29
+ }, {
30
+ readonly value: string;
31
+ readonly label: "Highest Current Bid";
32
+ }, {
33
+ readonly value: string;
34
+ readonly label: "Lowest Starting Bid";
35
+ }, {
36
+ readonly value: string;
37
+ readonly label: "Most Bids";
38
+ }, {
39
+ readonly value: string;
40
+ readonly label: "Fewest Bids";
41
+ }, {
42
+ readonly value: string;
43
+ readonly label: "Buy It Now: Low–High";
44
+ }, {
45
+ readonly value: string;
46
+ readonly label: "Oldest Listed";
47
+ }];
48
+ publicSortOptions: readonly [{
49
+ readonly value: string;
50
+ readonly label: "Ending Soon";
51
+ }, {
52
+ readonly value: string;
53
+ readonly label: "Just Started";
54
+ }, {
55
+ readonly value: string;
56
+ readonly label: "Lowest Current Bid";
57
+ }, {
58
+ readonly value: string;
59
+ readonly label: "Highest Current Bid";
60
+ }, {
61
+ readonly value: string;
62
+ readonly label: "Most Bids";
63
+ }, {
64
+ readonly value: string;
65
+ readonly label: "Buy It Now: Low–High";
66
+ }];
67
+ extraFacetKeys: readonly string[];
15
68
  };
@@ -1,5 +1,7 @@
1
1
  import { LISTING_TYPE_CAPABILITIES } from "../capabilities";
2
2
  import { ROUTES } from "../../../../next/routing/route-map";
3
+ import { TABLE_KEYS } from "../../../../constants/table-keys";
4
+ import { AUCTION_SORT_OPTIONS, AUCTION_PUBLIC_SORT_OPTIONS, } from "../../../../features/products/constants/sieve";
3
5
  export const LISTING_TYPE = "auction";
4
6
  export const capability = LISTING_TYPE_CAPABILITIES.auction;
5
7
  export const config = {
@@ -11,4 +13,19 @@ export const config = {
11
13
  priceLabel: "Suggested Retail Price (₹)",
12
14
  typeLabel: "Auction",
13
15
  showsStockQuantity: false,
16
+ tabSlug: "auctions",
17
+ pluralLabel: "Auctions",
18
+ chipLabel: "Auctions",
19
+ browseRoute: String(ROUTES.PUBLIC.AUCTIONS),
20
+ // An auction is never "sold out" — it ends. Hiding by stock would hide
21
+ // every live auction, since auctions don't carry meaningful stock.
22
+ hideDefault: "ended",
23
+ sortOptions: AUCTION_SORT_OPTIONS,
24
+ publicSortOptions: AUCTION_PUBLIC_SORT_OPTIONS,
25
+ extraFacetKeys: [
26
+ TABLE_KEYS.MIN_BID,
27
+ TABLE_KEYS.MAX_BID,
28
+ TABLE_KEYS.DATE_FROM,
29
+ TABLE_KEYS.DATE_TO,
30
+ ],
14
31
  };
@@ -12,4 +12,63 @@ export declare const config: {
12
12
  priceLabel: string;
13
13
  typeLabel: string;
14
14
  showsStockQuantity: boolean;
15
+ tabSlug: string;
16
+ pluralLabel: string;
17
+ chipLabel: string;
18
+ browseRoute: string;
19
+ hideDefault: "sold";
20
+ sortOptions: readonly [{
21
+ readonly value: string;
22
+ readonly label: "Newest First";
23
+ }, {
24
+ readonly value: string;
25
+ readonly label: "Oldest First";
26
+ }, {
27
+ readonly value: string;
28
+ readonly label: "Price: Low to High";
29
+ }, {
30
+ readonly value: string;
31
+ readonly label: "Price: High to Low";
32
+ }, {
33
+ readonly value: string;
34
+ readonly label: "Name: A–Z";
35
+ }, {
36
+ readonly value: string;
37
+ readonly label: "Name: Z–A";
38
+ }, {
39
+ readonly value: string;
40
+ readonly label: "Featured First";
41
+ }, {
42
+ readonly value: string;
43
+ readonly label: "Promoted First";
44
+ }, {
45
+ readonly value: string;
46
+ readonly label: "Recently Updated";
47
+ }, {
48
+ readonly value: string;
49
+ readonly label: "Most Viewed";
50
+ }];
51
+ publicSortOptions: readonly [{
52
+ readonly value: string;
53
+ readonly label: "Newest First";
54
+ }, {
55
+ readonly value: string;
56
+ readonly label: "Oldest First";
57
+ }, {
58
+ readonly value: string;
59
+ readonly label: "Price: Low to High";
60
+ }, {
61
+ readonly value: string;
62
+ readonly label: "Price: High to Low";
63
+ }, {
64
+ readonly value: string;
65
+ readonly label: "Name: A–Z";
66
+ }, {
67
+ readonly value: string;
68
+ readonly label: "Name: Z–A";
69
+ }, {
70
+ readonly value: string;
71
+ readonly label: "Most Viewed";
72
+ }];
73
+ extraFacetKeys: readonly string[];
15
74
  };
@@ -1,5 +1,7 @@
1
1
  import { LISTING_TYPE_CAPABILITIES } from "../capabilities";
2
2
  import { ROUTES } from "../../../../next/routing/route-map";
3
+ import { TABLE_KEYS } from "../../../../constants/table-keys";
4
+ import { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, } from "../../../../features/products/constants/sieve";
3
5
  export const LISTING_TYPE = "classified";
4
6
  export const capability = LISTING_TYPE_CAPABILITIES.classified;
5
7
  export const config = {
@@ -11,4 +13,16 @@ export const config = {
11
13
  priceLabel: "Asking Price (₹)",
12
14
  typeLabel: "Classified",
13
15
  showsStockQuantity: true,
16
+ tabSlug: "classifieds",
17
+ pluralLabel: "Classifieds",
18
+ chipLabel: "Classifieds",
19
+ browseRoute: String(ROUTES.PUBLIC.CLASSIFIED),
20
+ hideDefault: "sold",
21
+ sortOptions: STANDARD_SORT_OPTIONS,
22
+ publicSortOptions: STANDARD_PUBLIC_SORT_OPTIONS,
23
+ extraFacetKeys: [
24
+ TABLE_KEYS.CITY,
25
+ TABLE_KEYS.ACCEPTS_SHIPPING,
26
+ TABLE_KEYS.NEGOTIABLE,
27
+ ],
14
28
  };
@@ -12,4 +12,63 @@ export declare const config: {
12
12
  priceLabel: string;
13
13
  typeLabel: string;
14
14
  showsStockQuantity: boolean;
15
+ tabSlug: string;
16
+ pluralLabel: string;
17
+ chipLabel: string;
18
+ browseRoute: string;
19
+ hideDefault: "sold";
20
+ sortOptions: readonly [{
21
+ readonly value: string;
22
+ readonly label: "Newest First";
23
+ }, {
24
+ readonly value: string;
25
+ readonly label: "Oldest First";
26
+ }, {
27
+ readonly value: string;
28
+ readonly label: "Price: Low to High";
29
+ }, {
30
+ readonly value: string;
31
+ readonly label: "Price: High to Low";
32
+ }, {
33
+ readonly value: string;
34
+ readonly label: "Name: A–Z";
35
+ }, {
36
+ readonly value: string;
37
+ readonly label: "Name: Z–A";
38
+ }, {
39
+ readonly value: string;
40
+ readonly label: "Featured First";
41
+ }, {
42
+ readonly value: string;
43
+ readonly label: "Promoted First";
44
+ }, {
45
+ readonly value: string;
46
+ readonly label: "Recently Updated";
47
+ }, {
48
+ readonly value: string;
49
+ readonly label: "Most Viewed";
50
+ }];
51
+ publicSortOptions: readonly [{
52
+ readonly value: string;
53
+ readonly label: "Newest First";
54
+ }, {
55
+ readonly value: string;
56
+ readonly label: "Oldest First";
57
+ }, {
58
+ readonly value: string;
59
+ readonly label: "Price: Low to High";
60
+ }, {
61
+ readonly value: string;
62
+ readonly label: "Price: High to Low";
63
+ }, {
64
+ readonly value: string;
65
+ readonly label: "Name: A–Z";
66
+ }, {
67
+ readonly value: string;
68
+ readonly label: "Name: Z–A";
69
+ }, {
70
+ readonly value: string;
71
+ readonly label: "Most Viewed";
72
+ }];
73
+ extraFacetKeys: readonly string[];
15
74
  };