@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
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
- import { productRepository, reviewRepository } from "../../../repositories";
3
+ import { productRepository } from "../../../repositories";
4
4
  const __P = {
5
5
  p3: "p-[var(--appkit-space-3)]",
6
6
  p5: "p-[var(--appkit-space-5)]",
@@ -25,7 +25,7 @@ import { getDefaultCurrency } from "../../../core/baseline-resolver";
25
25
  import { Button, Container, Div, Heading, Main, Nav, PriceDisplay, RichText, Row, Section, Span, Stack, Text, Ul, Ol, Li, Dl, Dt, Dd, } from "../../../ui";
26
26
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
27
27
  import { safeDisplayName } from "../../../security";
28
- import { ReviewsList } from "../../reviews/components/ReviewsList";
28
+ import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPanel";
29
29
  import { ProductDetailView } from "./ProductDetailView";
30
30
  import { ProductGalleryClient } from "./ProductGalleryClient";
31
31
  import { ProductTabsShell } from "./ProductTabsShell";
@@ -37,7 +37,7 @@ import { SublistingCarouselSection } from "./SublistingCarouselSection";
37
37
  import { ShowGroupSection } from "./ShowGroupSection";
38
38
  import { HistoryTracker } from "../../history/components/HistoryTracker";
39
39
  import { RelatedItemsSection } from "./RelatedItemsSection";
40
- import { computeRelatedItems, toReview } from "../../../_internal/server/features/products/data";
40
+ import { computeRelatedItems, getReviewPageForProduct } from "../../../_internal/server/features/products/data";
41
41
  import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
42
42
  import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
43
43
  // ---------------------------------------------------------------------------
@@ -165,12 +165,12 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
165
165
  const groupTitle = typeof p.groupTitle === "string" ? p.groupTitle : undefined;
166
166
  const storeId = typeof p.storeId === "string" ? p.storeId : null;
167
167
  // -- Fetch reviews + the shared 4-signal related-items computation + grouped listings in parallel --
168
- const [reviewDocs, related, groups] = await Promise.all([
169
- reviewRepository.findApprovedByProduct(product.id).catch(() => []),
168
+ // Only page 1 of reviews is fetched here; <ReviewsListingPanel> pages the rest client-side.
169
+ const [initialReviews, related, groups] = await Promise.all([
170
+ getReviewPageForProduct(product.id),
170
171
  computeRelatedItems(product),
171
172
  getGroupsWithItemsForProduct(product.id),
172
173
  ]);
173
- const reviews = reviewDocs.map(toReview);
174
174
  const { relatedItems, relatedByBrand, relatedByTags, relatedByStore } = related;
175
175
  return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: product.id, productType: "product", snapshot: {
176
176
  title: product.title,
@@ -231,7 +231,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
231
231
  ? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
232
232
  : undefined, renderGroupSection: groupId
233
233
  ? () => (_jsx(ShowGroupSection, { groupId: groupId, currentSlug: String(p.slug ?? product.id), isParent: isGroupParent, groupTitle: groupTitle }))
234
- : undefined, renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: allSpecs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: allSpecs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx(Ul, { spacing: "comfortable", children: ingredients.map((item, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 bg-primary-400", rounded: "full" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx(Ol, { spacing: "loose", children: howToUse.map((step, i) => (_jsxs(Li, { layout: "flex-start", gap: "3", textSize: "sm", color: "muted", children: [_jsx(Span, { layout: "flex-center", size: "xs", weight: "bold", className: "flex-shrink-0 h-6 w-6 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300", rounded: "full", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsList, { reviews: reviews, context: "listing", emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
234
+ : undefined, renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: allSpecs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: allSpecs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx(Ul, { spacing: "comfortable", children: ingredients.map((item, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 bg-primary-400", rounded: "full" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx(Ol, { spacing: "loose", children: howToUse.map((step, i) => (_jsxs(Li, { layout: "flex-start", gap: "3", textSize: "sm", color: "muted", children: [_jsx(Span, { layout: "flex-center", size: "xs", weight: "bold", className: "flex-shrink-0 h-6 w-6 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300", rounded: "full", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsListingPanel, { source: { kind: "product", productId: product.id }, stateMode: "local", context: "listing", initialData: initialReviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
235
235
  id: s.id,
236
236
  label: s.title,
237
237
  content: _jsx(CustomSectionTabContent, { section: s }),
@@ -6,16 +6,20 @@
6
6
  * filter on the `products` collection or to a separate collection
7
7
  * (currently only `bundles`).
8
8
  *
9
- * Add new tab variants here never inline the tab list inside the view.
9
+ * DERIVED, NOT HAND-WRITTEN (2026-08-21). Every array below is built from
10
+ * `ALL_LISTING_TYPES` + the listing-type plugin registry, so a new listing
11
+ * type appears on every tab bar automatically. They used to be nine separate
12
+ * hand-maintained literals and had drifted badly: `/products` offered only 4
13
+ * of 9 types (missing auctions, pre-orders, prize draws, art, stickers — the
14
+ * bug that started this sweep), the admin type chips used display LABELS as
15
+ * Sieve filter ids, and several arrays still listed `bundle`, which stopped
16
+ * being a listingType in SB-UNI-D. `scripts/audit-listing-type-tab-coverage.mjs`
17
+ * blocks any regression.
18
+ *
19
+ * To add a tab that ISN'T a listing type (bundles, stores), append it to the
20
+ * relevant array explicitly — those are genuinely different collections.
10
21
  */
11
22
  import type { ListingType } from "../types";
12
- /**
13
- * The consolidated generic `/products` browse tab covers these 4 listing
14
- * types (auctions/pre-orders/bundles/prize-draws/art+stickers each have
15
- * their own dedicated public page). Shared between the server-rendered
16
- * page view and the client listing component so they can't drift apart.
17
- */
18
- export declare const GENERIC_PRODUCT_LISTING_TYPES: readonly ["standard", "classified", "digital-code", "live"];
19
23
  export interface ListingTab {
20
24
  id: string;
21
25
  label: string;
@@ -28,197 +32,38 @@ export interface ListingTab {
28
32
  /** Set when the tab queries a non-product entity (e.g. `stores`). */
29
33
  entity?: "stores";
30
34
  }
35
+ /**
36
+ * The generic `/products` browse page spans EVERY listing type (2026-08-21).
37
+ *
38
+ * It used to span only `standard|classified|digital-code|live` on the theory
39
+ * that auctions/pre-orders/prize-draws/art each had a dedicated page — but
40
+ * that made five of nine types unreachable from the main catalogue, broke
41
+ * seven legacy `/search/.../tab/<type>` permanent redirects that all point at
42
+ * `/products`, and made the cross-type Compare + bulk-cart features (which
43
+ * only exist on this page) unusable for those types. The dedicated pages
44
+ * remain, and `/products` links to them when a single time-boxed type is
45
+ * selected.
46
+ */
47
+ export declare const GENERIC_PRODUCT_LISTING_TYPES: readonly ListingType[];
48
+ /** The listing types the combined `/art` page spans. */
49
+ export declare const ART_STICKERS_LISTING_TYPES: readonly ListingType[];
31
50
  /** Tabs shown on `/categories/[slug]` and `/brands/[slug]` detail pages. */
32
- export declare const CATEGORY_PAGE_TABS: readonly [{
33
- readonly id: "products";
34
- readonly label: "Products";
35
- readonly listingType: "standard";
36
- }, {
37
- readonly id: "auctions";
38
- readonly label: "Auctions";
39
- readonly listingType: "auction";
40
- }, {
41
- readonly id: "pre-orders";
42
- readonly label: "Pre-Orders";
43
- readonly listingType: "pre-order";
44
- }, {
45
- readonly id: "prize-draws";
46
- readonly label: "Prize Draws";
47
- readonly listingType: "prize-draw";
48
- }, {
49
- readonly id: "bundles";
50
- readonly label: "Bundles";
51
- readonly collection: "bundles";
52
- }, {
53
- readonly id: "classifieds";
54
- readonly label: "Classifieds";
55
- readonly listingType: "classified";
56
- }, {
57
- readonly id: "digital-codes";
58
- readonly label: "Digital Codes";
59
- readonly listingType: "digital-code";
60
- }, {
61
- readonly id: "live";
62
- readonly label: "Live Items";
63
- readonly listingType: "live";
64
- }, {
65
- readonly id: "art";
66
- readonly label: "Art & Stickers";
67
- readonly listingType: "art|stickers";
68
- }, {
69
- readonly id: "stores";
70
- readonly label: "Stores";
71
- readonly entity: "stores";
72
- }];
73
- export type CategoryTabId = (typeof CATEGORY_PAGE_TABS)[number]["id"];
51
+ export declare const CATEGORY_PAGE_TABS: readonly ListingTab[];
52
+ export type CategoryTabId = string;
74
53
  /** Tabs shown on the public `/stores/[slug]` nav bar. */
75
- export declare const STORE_PAGE_TABS: readonly [{
76
- readonly id: "products";
77
- readonly label: "Products";
78
- readonly listingType: "standard";
79
- }, {
80
- readonly id: "auctions";
81
- readonly label: "Auctions";
82
- readonly listingType: "auction";
83
- }, {
84
- readonly id: "pre-orders";
85
- readonly label: "Pre-Orders";
86
- readonly listingType: "pre-order";
87
- }, {
88
- readonly id: "prize-draws";
89
- readonly label: "Prize Draws";
90
- readonly listingType: "prize-draw";
91
- }, {
92
- readonly id: "bundles";
93
- readonly label: "Bundles";
94
- readonly collection: "bundles";
95
- }, {
96
- readonly id: "classifieds";
97
- readonly label: "Classifieds";
98
- readonly listingType: "classified";
99
- }, {
100
- readonly id: "digital-codes";
101
- readonly label: "Digital Codes";
102
- readonly listingType: "digital-code";
103
- }, {
104
- readonly id: "live";
105
- readonly label: "Live Items";
106
- readonly listingType: "live";
107
- }, {
108
- readonly id: "art";
109
- readonly label: "Art & Stickers";
110
- readonly listingType: "art|stickers";
111
- }];
112
- export type StoreTabId = (typeof STORE_PAGE_TABS)[number]["id"];
54
+ export declare const STORE_PAGE_TABS: readonly ListingTab[];
55
+ export type StoreTabId = string;
113
56
  /** Tabs shown on the seller-dashboard listings view + admin products list. */
114
- export declare const SELLER_LISTING_TABS: readonly [{
115
- readonly id: "all";
116
- readonly label: "All";
117
- }, {
118
- readonly id: "products";
119
- readonly label: "Products";
120
- readonly listingType: "standard";
121
- }, {
122
- readonly id: "auctions";
123
- readonly label: "Auctions";
124
- readonly listingType: "auction";
125
- }, {
126
- readonly id: "pre-orders";
127
- readonly label: "Pre-Orders";
128
- readonly listingType: "pre-order";
129
- }, {
130
- readonly id: "prize-draws";
131
- readonly label: "Prize Draws";
132
- readonly listingType: "prize-draw";
133
- }, {
134
- readonly id: "classifieds";
135
- readonly label: "Classifieds";
136
- readonly listingType: "classified";
137
- }, {
138
- readonly id: "digital-codes";
139
- readonly label: "Digital Codes";
140
- readonly listingType: "digital-code";
141
- }, {
142
- readonly id: "live";
143
- readonly label: "Live Items";
144
- readonly listingType: "live";
145
- }, {
146
- readonly id: "art";
147
- readonly label: "Art";
148
- readonly listingType: "art";
149
- }, {
150
- readonly id: "stickers";
151
- readonly label: "Stickers";
152
- readonly listingType: "stickers";
153
- }];
154
- export type SellerListingTabId = (typeof SELLER_LISTING_TABS)[number]["id"];
155
- /** Tabs shown on `/search` results. */
156
- export declare const SEARCH_RESULT_TABS: readonly [{
157
- readonly id: "all";
158
- readonly label: "All";
159
- }, {
160
- readonly id: "products";
161
- readonly label: "Products";
162
- readonly listingType: "standard";
163
- }, {
164
- readonly id: "auctions";
165
- readonly label: "Auctions";
166
- readonly listingType: "auction";
167
- }, {
168
- readonly id: "pre-orders";
169
- readonly label: "Pre-Orders";
170
- readonly listingType: "pre-order";
171
- }, {
172
- readonly id: "prize-draws";
173
- readonly label: "Prize Draws";
174
- readonly listingType: "prize-draw";
175
- }, {
176
- readonly id: "bundles";
177
- readonly label: "Bundles";
178
- readonly collection: "bundles";
179
- }, {
180
- readonly id: "classifieds";
181
- readonly label: "Classifieds";
182
- readonly listingType: "classified";
183
- }, {
184
- readonly id: "digital-codes";
185
- readonly label: "Digital Codes";
186
- readonly listingType: "digital-code";
187
- }, {
188
- readonly id: "live";
189
- readonly label: "Live Items";
190
- readonly listingType: "live";
191
- }, {
192
- readonly id: "art";
193
- readonly label: "Art & Stickers";
194
- readonly listingType: "art|stickers";
195
- }];
196
- export type SearchTabId = (typeof SEARCH_RESULT_TABS)[number]["id"];
197
- /** In-page type-filter chips on the generic `/products` browse page. */
198
- export declare const PRODUCT_TYPE_FILTER_TABS: readonly [{
199
- readonly id: "All";
200
- readonly label: "All";
201
- }, {
202
- readonly id: "standard";
203
- readonly label: "Standard";
204
- }, {
205
- readonly id: "classified";
206
- readonly label: "Classified";
207
- }, {
208
- readonly id: "digital-code";
209
- readonly label: "Digital Codes";
210
- }, {
211
- readonly id: "live";
212
- readonly label: "Live Items";
213
- }];
57
+ export declare const SELLER_LISTING_TABS: readonly ListingTab[];
58
+ export type SellerListingTabId = string;
59
+ /**
60
+ * In-page type-filter chips on the generic `/products` browse page.
61
+ *
62
+ * Multi-select (checkbox semantics) — no "All" sentinel chip, because "none
63
+ * checked" already means "every type". A sentinel would be a second way to
64
+ * express the same state and would have to be cleared whenever a real chip
65
+ * was ticked.
66
+ */
67
+ export declare const PRODUCT_TYPE_FILTER_TABS: readonly ListingTab[];
214
68
  /** In-page type-filter chips on the combined `/art` (Art & Stickers) page. */
215
- export declare const ART_STICKERS_TYPE_FILTER_TABS: readonly [{
216
- readonly id: "All";
217
- readonly label: "All";
218
- }, {
219
- readonly id: "art";
220
- readonly label: "Art";
221
- }, {
222
- readonly id: "stickers";
223
- readonly label: "Stickers";
224
- }];
69
+ export declare const ART_STICKERS_TYPE_FILTER_TABS: readonly ListingTab[];
@@ -6,78 +6,91 @@
6
6
  * filter on the `products` collection or to a separate collection
7
7
  * (currently only `bundles`).
8
8
  *
9
- * Add new tab variants here never inline the tab list inside the view.
9
+ * DERIVED, NOT HAND-WRITTEN (2026-08-21). Every array below is built from
10
+ * `ALL_LISTING_TYPES` + the listing-type plugin registry, so a new listing
11
+ * type appears on every tab bar automatically. They used to be nine separate
12
+ * hand-maintained literals and had drifted badly: `/products` offered only 4
13
+ * of 9 types (missing auctions, pre-orders, prize draws, art, stickers — the
14
+ * bug that started this sweep), the admin type chips used display LABELS as
15
+ * Sieve filter ids, and several arrays still listed `bundle`, which stopped
16
+ * being a listingType in SB-UNI-D. `scripts/audit-listing-type-tab-coverage.mjs`
17
+ * blocks any regression.
18
+ *
19
+ * To add a tab that ISN'T a listing type (bundles, stores), append it to the
20
+ * relevant array explicitly — those are genuinely different collections.
21
+ */
22
+ import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags";
23
+ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
24
+ /**
25
+ * The generic `/products` browse page spans EVERY listing type (2026-08-21).
26
+ *
27
+ * It used to span only `standard|classified|digital-code|live` on the theory
28
+ * that auctions/pre-orders/prize-draws/art each had a dedicated page — but
29
+ * that made five of nine types unreachable from the main catalogue, broke
30
+ * seven legacy `/search/.../tab/<type>` permanent redirects that all point at
31
+ * `/products`, and made the cross-type Compare + bulk-cart features (which
32
+ * only exist on this page) unusable for those types. The dedicated pages
33
+ * remain, and `/products` links to them when a single time-boxed type is
34
+ * selected.
35
+ */
36
+ export const GENERIC_PRODUCT_LISTING_TYPES = ALL_LISTING_TYPES;
37
+ /** The listing types the combined `/art` page spans. */
38
+ export const ART_STICKERS_LISTING_TYPES = ["art", "stickers"];
39
+ /**
40
+ * A FILTER chip — `id` is the canonical `listingType`, because it is passed
41
+ * straight into `sieveFilter("listingType", EQ, id)`. Never a display label.
42
+ */
43
+ function chipTab(type) {
44
+ return { id: type, label: pluginFor(type).chipLabel, listingType: type };
45
+ }
46
+ /**
47
+ * A ROUTE-BACKED tab — `id` is the plugin's `tabSlug`, because it doubles as
48
+ * the URL segment (`/stores/{slug}/pre-orders`) and is live in bookmarks.
49
+ * The canonical type still rides along in `listingType` for the query.
10
50
  */
51
+ function pluralTab(type) {
52
+ const plugin = pluginFor(type);
53
+ return { id: plugin.tabSlug, label: plugin.pluralLabel, listingType: type };
54
+ }
11
55
  /**
12
- * The consolidated generic `/products` browse tab covers these 4 listing
13
- * types (auctions/pre-orders/bundles/prize-draws/art+stickers each have
14
- * their own dedicated public page). Shared between the server-rendered
15
- * page view and the client listing component so they can't drift apart.
56
+ * Art and stickers share the combined `/art` browse page, so category/brand/
57
+ * store tab bars show them as ONE tab backed by a two-value OR-group. Derived
58
+ * from both plugins rather than a hardcoded `"art|stickers"` string so the
59
+ * pipe-group can't drift from the registry.
16
60
  */
17
- export const GENERIC_PRODUCT_LISTING_TYPES = ["standard", "classified", "digital-code", "live"];
61
+ const ART_STICKERS_COMBINED_TAB = {
62
+ id: pluginFor("art").tabSlug,
63
+ label: `${pluginFor("art").pluralLabel} & ${pluginFor("stickers").pluralLabel}`,
64
+ listingType: ART_STICKERS_LISTING_TYPES.join("|"),
65
+ };
66
+ /** Listing types that get their own tab on a combined surface (art+stickers merge into one). */
67
+ const SEPARATELY_TABBED_TYPES = ALL_LISTING_TYPES.filter((t) => t !== "art" && t !== "stickers");
18
68
  /** Tabs shown on `/categories/[slug]` and `/brands/[slug]` detail pages. */
19
69
  export const CATEGORY_PAGE_TABS = [
20
- { id: "products", label: "Products", listingType: "standard" },
21
- { id: "auctions", label: "Auctions", listingType: "auction" },
22
- { id: "pre-orders", label: "Pre-Orders", listingType: "pre-order" },
23
- { id: "prize-draws", label: "Prize Draws", listingType: "prize-draw" },
70
+ ...SEPARATELY_TABBED_TYPES.map(pluralTab),
24
71
  { id: "bundles", label: "Bundles", collection: "bundles" },
25
- { id: "classifieds", label: "Classifieds", listingType: "classified" },
26
- { id: "digital-codes", label: "Digital Codes", listingType: "digital-code" },
27
- { id: "live", label: "Live Items", listingType: "live" },
28
- // Combined — one public browse page (`/art`) spans both listing types.
29
- { id: "art", label: "Art & Stickers", listingType: "art|stickers" },
72
+ ART_STICKERS_COMBINED_TAB,
30
73
  { id: "stores", label: "Stores", entity: "stores" },
31
74
  ];
32
75
  /** Tabs shown on the public `/stores/[slug]` nav bar. */
33
76
  export const STORE_PAGE_TABS = [
34
- { id: "products", label: "Products", listingType: "standard" },
35
- { id: "auctions", label: "Auctions", listingType: "auction" },
36
- { id: "pre-orders", label: "Pre-Orders", listingType: "pre-order" },
37
- { id: "prize-draws", label: "Prize Draws", listingType: "prize-draw" },
77
+ ...SEPARATELY_TABBED_TYPES.map(pluralTab),
38
78
  { id: "bundles", label: "Bundles", collection: "bundles" },
39
- { id: "classifieds", label: "Classifieds", listingType: "classified" },
40
- { id: "digital-codes", label: "Digital Codes", listingType: "digital-code" },
41
- { id: "live", label: "Live Items", listingType: "live" },
42
- { id: "art", label: "Art & Stickers", listingType: "art|stickers" },
79
+ ART_STICKERS_COMBINED_TAB,
43
80
  ];
44
81
  /** Tabs shown on the seller-dashboard listings view + admin products list. */
45
82
  export const SELLER_LISTING_TABS = [
46
83
  { id: "all", label: "All" },
47
- { id: "products", label: "Products", listingType: "standard" },
48
- { id: "auctions", label: "Auctions", listingType: "auction" },
49
- { id: "pre-orders", label: "Pre-Orders", listingType: "pre-order" },
50
- { id: "prize-draws", label: "Prize Draws", listingType: "prize-draw" },
51
- { id: "classifieds", label: "Classifieds", listingType: "classified" },
52
- { id: "digital-codes", label: "Digital Codes", listingType: "digital-code" },
53
- { id: "live", label: "Live Items", listingType: "live" },
54
- { id: "art", label: "Art", listingType: "art" },
55
- { id: "stickers", label: "Stickers", listingType: "stickers" },
56
- ];
57
- /** Tabs shown on `/search` results. */
58
- export const SEARCH_RESULT_TABS = [
59
- { id: "all", label: "All" },
60
- { id: "products", label: "Products", listingType: "standard" },
61
- { id: "auctions", label: "Auctions", listingType: "auction" },
62
- { id: "pre-orders", label: "Pre-Orders", listingType: "pre-order" },
63
- { id: "prize-draws", label: "Prize Draws", listingType: "prize-draw" },
64
- { id: "bundles", label: "Bundles", collection: "bundles" },
65
- { id: "classifieds", label: "Classifieds", listingType: "classified" },
66
- { id: "digital-codes", label: "Digital Codes", listingType: "digital-code" },
67
- { id: "live", label: "Live Items", listingType: "live" },
68
- { id: "art", label: "Art & Stickers", listingType: "art|stickers" },
69
- ];
70
- /** In-page type-filter chips on the generic `/products` browse page. */
71
- export const PRODUCT_TYPE_FILTER_TABS = [
72
- { id: "All", label: "All" },
73
- { id: "standard", label: "Standard" },
74
- { id: "classified", label: "Classified" },
75
- { id: "digital-code", label: "Digital Codes" },
76
- { id: "live", label: "Live Items" },
84
+ ...ALL_LISTING_TYPES.map(pluralTab),
77
85
  ];
86
+ /**
87
+ * In-page type-filter chips on the generic `/products` browse page.
88
+ *
89
+ * Multi-select (checkbox semantics) — no "All" sentinel chip, because "none
90
+ * checked" already means "every type". A sentinel would be a second way to
91
+ * express the same state and would have to be cleared whenever a real chip
92
+ * was ticked.
93
+ */
94
+ export const PRODUCT_TYPE_FILTER_TABS = ALL_LISTING_TYPES.map(chipTab);
78
95
  /** In-page type-filter chips on the combined `/art` (Art & Stickers) page. */
79
- export const ART_STICKERS_TYPE_FILTER_TABS = [
80
- { id: "All", label: "All" },
81
- { id: "art", label: "Art" },
82
- { id: "stickers", label: "Stickers" },
83
- ];
96
+ export const ART_STICKERS_TYPE_FILTER_TABS = ART_STICKERS_LISTING_TYPES.map(chipTab);
@@ -141,6 +141,90 @@ export declare const PREORDER_SORT_OPTIONS: readonly [{
141
141
  readonly value: string;
142
142
  readonly label: "Lowest Deposit First";
143
143
  }];
144
+ /**
145
+ * Public pre-order browse subset. Drops "Lowest Deposit First" — deposit
146
+ * amount is a seller-side pricing lever buyers don't shop by — but keeps
147
+ * both delivery-date directions, which are the primary browse intent.
148
+ */
149
+ export declare const PREORDER_PUBLIC_SORT_OPTIONS: readonly [{
150
+ readonly value: string;
151
+ readonly label: "Earliest Delivery";
152
+ }, {
153
+ readonly value: string;
154
+ readonly label: "Latest Delivery";
155
+ }, {
156
+ readonly value: string;
157
+ readonly label: "Newest First";
158
+ } | {
159
+ readonly value: string;
160
+ readonly label: "Oldest First";
161
+ } | {
162
+ readonly value: string;
163
+ readonly label: "Price: Low to High";
164
+ } | {
165
+ readonly value: string;
166
+ readonly label: "Price: High to Low";
167
+ } | {
168
+ readonly value: string;
169
+ readonly label: "Fewest Slots Left";
170
+ } | {
171
+ readonly value: string;
172
+ readonly label: "Lowest Deposit First";
173
+ }, {
174
+ readonly value: string;
175
+ readonly label: "Newest First";
176
+ } | {
177
+ readonly value: string;
178
+ readonly label: "Oldest First";
179
+ } | {
180
+ readonly value: string;
181
+ readonly label: "Price: Low to High";
182
+ } | {
183
+ readonly value: string;
184
+ readonly label: "Price: High to Low";
185
+ } | {
186
+ readonly value: string;
187
+ readonly label: "Fewest Slots Left";
188
+ } | {
189
+ readonly value: string;
190
+ readonly label: "Lowest Deposit First";
191
+ }, {
192
+ readonly value: string;
193
+ readonly label: "Newest First";
194
+ } | {
195
+ readonly value: string;
196
+ readonly label: "Oldest First";
197
+ } | {
198
+ readonly value: string;
199
+ readonly label: "Price: Low to High";
200
+ } | {
201
+ readonly value: string;
202
+ readonly label: "Price: High to Low";
203
+ } | {
204
+ readonly value: string;
205
+ readonly label: "Fewest Slots Left";
206
+ } | {
207
+ readonly value: string;
208
+ readonly label: "Lowest Deposit First";
209
+ }, {
210
+ readonly value: string;
211
+ readonly label: "Newest First";
212
+ } | {
213
+ readonly value: string;
214
+ readonly label: "Oldest First";
215
+ } | {
216
+ readonly value: string;
217
+ readonly label: "Price: Low to High";
218
+ } | {
219
+ readonly value: string;
220
+ readonly label: "Price: High to Low";
221
+ } | {
222
+ readonly value: string;
223
+ readonly label: "Fewest Slots Left";
224
+ } | {
225
+ readonly value: string;
226
+ readonly label: "Lowest Deposit First";
227
+ }];
144
228
  export declare const BUNDLE_SORT_OPTIONS: readonly [...({
145
229
  readonly value: string;
146
230
  readonly label: "Newest First";
@@ -173,4 +257,23 @@ export declare const PRIZE_DRAW_SORT_OPTIONS: readonly [{
173
257
  readonly value: string;
174
258
  readonly label: "Entry: High to Low";
175
259
  }];
176
- export declare const SORT_OPTIONS_BY_LISTING_TYPE: Record<string, readonly SortOption[]>;
260
+ /** Public prize-draw browse subset every option is buyer-meaningful. */
261
+ export declare const PRIZE_DRAW_PUBLIC_SORT_OPTIONS: readonly [{
262
+ readonly value: string;
263
+ readonly label: "Newest First";
264
+ }, {
265
+ readonly value: string;
266
+ readonly label: "Oldest First";
267
+ }, {
268
+ readonly value: string;
269
+ readonly label: "Reveal: Soonest";
270
+ }, {
271
+ readonly value: string;
272
+ readonly label: "Reveal: Furthest";
273
+ }, {
274
+ readonly value: string;
275
+ readonly label: "Entry: Low to High";
276
+ }, {
277
+ readonly value: string;
278
+ readonly label: "Entry: High to Low";
279
+ }];
@@ -73,6 +73,19 @@ export const PREORDER_SORT_OPTIONS = [
73
73
  { value: sortBy(PRODUCT_FIELDS.PRE_ORDER_CURRENT_COUNT), label: "Fewest Slots Left" },
74
74
  { value: sortBy(PRODUCT_FIELDS.PRE_ORDER_DEPOSIT_AMOUNT, "ASC"), label: "Lowest Deposit First" },
75
75
  ];
76
+ /**
77
+ * Public pre-order browse subset. Drops "Lowest Deposit First" — deposit
78
+ * amount is a seller-side pricing lever buyers don't shop by — but keeps
79
+ * both delivery-date directions, which are the primary browse intent.
80
+ */
81
+ export const PREORDER_PUBLIC_SORT_OPTIONS = [
82
+ PREORDER_SORT_OPTIONS[0],
83
+ PREORDER_SORT_OPTIONS[1],
84
+ PREORDER_SORT_OPTIONS[2],
85
+ PREORDER_SORT_OPTIONS[3],
86
+ PREORDER_SORT_OPTIONS[4],
87
+ PREORDER_SORT_OPTIONS[5],
88
+ ];
76
89
  // ---------------------------------------------------------------------------
77
90
  // Bundles
78
91
  // ---------------------------------------------------------------------------
@@ -94,13 +107,20 @@ export const PRIZE_DRAW_SORT_OPTIONS = [
94
107
  { value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Entry: Low to High" },
95
108
  { value: sortBy(PRODUCT_FIELDS.PRICE), label: "Entry: High to Low" },
96
109
  ];
110
+ /** Public prize-draw browse subset — every option is buyer-meaningful. */
111
+ export const PRIZE_DRAW_PUBLIC_SORT_OPTIONS = PRIZE_DRAW_SORT_OPTIONS;
97
112
  // ---------------------------------------------------------------------------
98
113
  // Lookup by listing type
99
114
  // ---------------------------------------------------------------------------
100
- export const SORT_OPTIONS_BY_LISTING_TYPE = {
101
- standard: STANDARD_SORT_OPTIONS,
102
- auction: AUCTION_SORT_OPTIONS,
103
- "pre-order": PREORDER_SORT_OPTIONS,
104
- bundle: BUNDLE_SORT_OPTIONS,
105
- "prize-draw": PRIZE_DRAW_SORT_OPTIONS,
106
- };
115
+ //
116
+ // The per-type lookup used to live here as a hand-written map keyed on plain
117
+ // strings. It carried a `bundle` key long after SB-UNI-D stopped bundles from
118
+ // being a listingType, and had no entry at all for classified / digital-code /
119
+ // live / art / stickers — five of nine types. It is now DERIVED from the
120
+ // listing-type plugin registry (`sortOptionsFor()` in
121
+ // `_internal/shared/listing-types/_registry.ts`), so a type can never be
122
+ // missing from it again.
123
+ //
124
+ // `BUNDLE_SORT_OPTIONS` above stays a standalone export on purpose — bundles
125
+ // are a `categoryType` on the `categories` collection, not a listingType, so
126
+ // they must not be reachable through a ListingType-keyed lookup.