@mohasinac/appkit 4.11.1 → 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 (212) hide show
  1. package/dist/_internal/server/features/checkout/actions.d.ts +4 -0
  2. package/dist/_internal/server/features/checkout/actions.js +138 -27
  3. package/dist/_internal/server/features/checkout/data.d.ts +10 -0
  4. package/dist/_internal/server/features/checkout/locked-lines.d.ts +30 -0
  5. package/dist/_internal/server/features/checkout/locked-lines.js +116 -0
  6. package/dist/_internal/server/features/orders/adapters.js +7 -0
  7. package/dist/_internal/server/features/products/data.d.ts +12 -3
  8. package/dist/_internal/server/features/products/data.js +55 -4
  9. package/dist/_internal/server/features/products/index.d.ts +2 -1
  10. package/dist/_internal/server/features/products/index.js +2 -1
  11. package/dist/_internal/server/features/products/list-public.d.ts +130 -0
  12. package/dist/_internal/server/features/products/list-public.js +375 -0
  13. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  14. package/dist/_internal/server/features/reviews/data.js +3 -2
  15. package/dist/_internal/server/features/tester/visibility.js +3 -3
  16. package/dist/_internal/server/jobs/core/auctionSettlement.js +89 -16
  17. package/dist/_internal/server/jobs/core/offerExpiry.js +116 -4
  18. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -1
  19. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
  20. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +2 -0
  21. package/dist/_internal/server/jobs/handlers/_helpers.js +4 -1
  22. package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -0
  23. package/dist/_internal/server/jobs/handlers/messages.js +5 -0
  24. package/dist/_internal/shared/checkout/lanes.d.ts +41 -0
  25. package/dist/_internal/shared/checkout/lanes.js +106 -0
  26. package/dist/_internal/shared/checkout/order-math.d.ts +19 -0
  27. package/dist/_internal/shared/checkout/order-math.js +30 -6
  28. package/dist/_internal/shared/features/promotions/schema.d.ts +0 -16
  29. package/dist/_internal/shared/features/promotions/schema.js +0 -2
  30. package/dist/_internal/shared/features/reviews/config.d.ts +8 -1
  31. package/dist/_internal/shared/features/reviews/config.js +8 -1
  32. package/dist/_internal/shared/listing-types/_registry.d.ts +88 -1
  33. package/dist/_internal/shared/listing-types/_registry.js +63 -1
  34. package/dist/_internal/shared/listing-types/art/config.d.ts +59 -0
  35. package/dist/_internal/shared/listing-types/art/config.js +12 -0
  36. package/dist/_internal/shared/listing-types/auction/config.d.ts +53 -0
  37. package/dist/_internal/shared/listing-types/auction/config.js +17 -0
  38. package/dist/_internal/shared/listing-types/classified/config.d.ts +59 -0
  39. package/dist/_internal/shared/listing-types/classified/config.js +14 -0
  40. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +59 -0
  41. package/dist/_internal/shared/listing-types/digital-code/config.js +12 -0
  42. package/dist/_internal/shared/listing-types/feature-flags.d.ts +1 -0
  43. package/dist/_internal/shared/listing-types/feature-flags.js +24 -10
  44. package/dist/_internal/shared/listing-types/live/config.d.ts +59 -0
  45. package/dist/_internal/shared/listing-types/live/config.js +14 -0
  46. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +110 -0
  47. package/dist/_internal/shared/listing-types/pre-order/config.js +14 -0
  48. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +44 -0
  49. package/dist/_internal/shared/listing-types/prize-draw/config.js +10 -0
  50. package/dist/_internal/shared/listing-types/standard/config.d.ts +59 -0
  51. package/dist/_internal/shared/listing-types/standard/config.js +9 -0
  52. package/dist/_internal/shared/listing-types/stickers/config.d.ts +59 -0
  53. package/dist/_internal/shared/listing-types/stickers/config.js +12 -0
  54. package/dist/client.d.ts +7 -2
  55. package/dist/client.js +8 -2
  56. package/dist/constants/field-names.d.ts +12 -0
  57. package/dist/constants/field-names.js +12 -0
  58. package/dist/constants/table-keys.d.ts +2 -0
  59. package/dist/constants/table-keys.js +2 -0
  60. package/dist/features/account/components/NotificationBell.js +1 -0
  61. package/dist/features/account/components/UserOffersPanel.js +8 -1
  62. package/dist/features/account/components/UserOrdersView.js +27 -5
  63. package/dist/features/admin/actions/notification-actions.js +1 -1
  64. package/dist/features/admin/components/AdminAddressesView.js +3 -1
  65. package/dist/features/admin/components/AdminCouponEditorView.js +1 -4
  66. package/dist/features/admin/components/AdminPaymentMethodsView.js +3 -1
  67. package/dist/features/admin/components/AdminProductsView.js +34 -18
  68. package/dist/features/admin/constants/filter-tabs.d.ts +26 -9
  69. package/dist/features/admin/constants/filter-tabs.js +22 -9
  70. package/dist/features/admin/schemas/firestore.d.ts +2 -1
  71. package/dist/features/admin/schemas/firestore.js +1 -0
  72. package/dist/features/auctions/actions/bid-actions.d.ts +11 -1
  73. package/dist/features/auctions/actions/bid-actions.js +29 -15
  74. package/dist/features/auctions/components/AuctionBidsTable.js +6 -2
  75. package/dist/features/auctions/components/AuctionBottomActions.js +1 -0
  76. package/dist/features/auctions/components/AuctionDetailPageView.js +13 -11
  77. package/dist/features/auctions/components/AuctionsListView.js +12 -53
  78. package/dist/features/auctions/components/PlaceBidFormClient.js +10 -1
  79. package/dist/features/auctions/repository/bid.repository.d.ts +15 -0
  80. package/dist/features/auctions/repository/bid.repository.js +19 -0
  81. package/dist/features/auctions/schemas/firestore.d.ts +11 -1
  82. package/dist/features/auctions/schemas/firestore.js +1 -0
  83. package/dist/features/cart/actions/cart-actions.d.ts +11 -0
  84. package/dist/features/cart/actions/cart-actions.js +24 -0
  85. package/dist/features/cart/repository/cart.repository.d.ts +24 -1
  86. package/dist/features/cart/repository/cart.repository.js +71 -6
  87. package/dist/features/cart/schemas/firestore.d.ts +23 -4
  88. package/dist/features/categories/components/BrandDetailTabs.js +31 -11
  89. package/dist/features/categories/components/CategoryDetailTabs.js +27 -7
  90. package/dist/features/categories/components/CategoryProductsListing.d.ts +9 -1
  91. package/dist/features/categories/components/CategoryProductsListing.js +5 -2
  92. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -1
  93. package/dist/features/contact/email.js +56 -1
  94. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +17 -16
  95. package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
  96. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  97. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  98. package/dist/features/layout/AppLayoutShell.js +9 -2
  99. package/dist/features/layout/BottomActions.js +26 -3
  100. package/dist/features/layout/BottomActionsContext.d.ts +14 -0
  101. package/dist/features/layout/BottomActionsContext.js +13 -1
  102. package/dist/features/layout/hooks/useBottomActions.d.ts +6 -1
  103. package/dist/features/layout/hooks/useBottomActions.js +4 -1
  104. package/dist/features/layout/index.d.ts +1 -1
  105. package/dist/features/live/components/LiveItemDetailPageView.js +7 -6
  106. package/dist/features/orders/repository/orders.repository.d.ts +4 -15
  107. package/dist/features/orders/repository/orders.repository.js +4 -27
  108. package/dist/features/orders/types/index.d.ts +16 -0
  109. package/dist/features/orders/utils/order-splitter.js +14 -2
  110. package/dist/features/pre-orders/components/PreOrderBottomActions.d.ts +1 -1
  111. package/dist/features/pre-orders/components/PreOrderBottomActions.js +3 -19
  112. package/dist/features/pre-orders/components/PreOrdersListView.js +11 -39
  113. package/dist/features/products/components/ArtStickersListView.js +10 -49
  114. package/dist/features/products/components/AuctionsIndexListing.js +1 -2
  115. package/dist/features/products/components/ListingBottomActions.d.ts +28 -0
  116. package/dist/features/products/components/ListingBottomActions.js +25 -0
  117. package/dist/features/products/components/PrizeDrawBottomActions.d.ts +1 -1
  118. package/dist/features/products/components/PrizeDrawBottomActions.js +3 -21
  119. package/dist/features/products/components/ProductDetailActions.js +4 -0
  120. package/dist/features/products/components/ProductDetailPageView.js +7 -7
  121. package/dist/features/products/components/ProductsIndexPageView.js +9 -62
  122. package/dist/features/products/constants/listing-tabs.d.ts +43 -198
  123. package/dist/features/products/constants/listing-tabs.js +71 -58
  124. package/dist/features/products/constants/sieve.d.ts +104 -1
  125. package/dist/features/products/constants/sieve.js +27 -7
  126. package/dist/features/products/repository/products.repository.d.ts +45 -1
  127. package/dist/features/products/repository/products.repository.js +67 -8
  128. package/dist/features/products/utils/listing-badge-variant.d.ts +20 -1
  129. package/dist/features/products/utils/listing-badge-variant.js +26 -1
  130. package/dist/features/products/utils/listing-type.d.ts +24 -0
  131. package/dist/features/products/utils/listing-type.js +48 -0
  132. package/dist/features/promotions/actions/coupon-actions.d.ts +7 -0
  133. package/dist/features/promotions/actions/coupon-actions.js +12 -1
  134. package/dist/features/promotions/actions/coupon-stacking.d.ts +24 -0
  135. package/dist/features/promotions/actions/coupon-stacking.js +21 -0
  136. package/dist/features/promotions/actions/index.d.ts +1 -0
  137. package/dist/features/promotions/actions/index.js +1 -0
  138. package/dist/features/promotions/actions/seller-coupon-actions.d.ts +0 -1
  139. package/dist/features/promotions/actions/seller-coupon-actions.js +0 -1
  140. package/dist/features/promotions/components/CouponHelpDetails.d.ts +20 -0
  141. package/dist/features/promotions/components/CouponHelpDetails.js +17 -0
  142. package/dist/features/promotions/components/CouponsIndexListing.js +2 -1
  143. package/dist/features/promotions/components/index.d.ts +2 -0
  144. package/dist/features/promotions/components/index.js +1 -0
  145. package/dist/features/promotions/constants/coupon-help.d.ts +27 -0
  146. package/dist/features/promotions/constants/coupon-help.js +42 -0
  147. package/dist/features/promotions/repository/coupons.repository.d.ts +10 -1
  148. package/dist/features/promotions/repository/coupons.repository.js +22 -8
  149. package/dist/features/promotions/schemas/firestore.d.ts +0 -1
  150. package/dist/features/promotions/schemas/firestore.js +1 -1
  151. package/dist/features/promotions/schemas/index.d.ts +0 -20
  152. package/dist/features/promotions/schemas/index.js +0 -1
  153. package/dist/features/reviews/actions/review-actions.js +5 -1
  154. package/dist/features/reviews/components/ReviewDetailPageView.js +2 -1
  155. package/dist/features/reviews/components/ReviewFilters.d.ts +7 -1
  156. package/dist/features/reviews/components/ReviewFilters.js +4 -3
  157. package/dist/features/reviews/components/ReviewsIndexListing.d.ts +6 -0
  158. package/dist/features/reviews/components/ReviewsIndexListing.js +9 -109
  159. package/dist/features/reviews/components/ReviewsListingPanel.d.ts +41 -0
  160. package/dist/features/reviews/components/ReviewsListingPanel.js +226 -0
  161. package/dist/features/reviews/components/index.d.ts +2 -0
  162. package/dist/features/reviews/components/index.js +1 -0
  163. package/dist/features/reviews/hooks/useReviews.d.ts +8 -0
  164. package/dist/features/reviews/hooks/useReviews.js +14 -6
  165. package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
  166. package/dist/features/reviews/repository/reviews.repository.js +77 -41
  167. package/dist/features/reviews/types/index.d.ts +8 -1
  168. package/dist/features/seller/actions/offer-actions.d.ts +6 -0
  169. package/dist/features/seller/actions/offer-actions.js +8 -9
  170. package/dist/features/seller/components/SellerOffersView.d.ts +6 -1
  171. package/dist/features/seller/components/SellerOffersView.js +59 -8
  172. package/dist/features/seller/components/SellerProductsCards.js +2 -2
  173. package/dist/features/seller/components/SellerProductsView.js +21 -18
  174. package/dist/features/seller/repository/offer.repository.d.ts +18 -2
  175. package/dist/features/seller/repository/offer.repository.js +38 -2
  176. package/dist/features/seller/schemas/firestore.d.ts +7 -2
  177. package/dist/features/seller/schemas/firestore.js +3 -0
  178. package/dist/features/seller/schemas/offer-forms.d.ts +12 -0
  179. package/dist/features/seller/schemas/offer-forms.js +28 -0
  180. package/dist/features/stores/actions/store-query-actions.js +1 -1
  181. package/dist/features/stores/components/StoreAuctionsListing.js +5 -11
  182. package/dist/features/stores/components/StorePreOrdersListing.js +6 -11
  183. package/dist/features/stores/components/StoreReviewsListing.d.ts +4 -0
  184. package/dist/features/stores/components/StoreReviewsListing.js +7 -108
  185. package/dist/features/stores/schemas/index.d.ts +2 -2
  186. package/dist/features/tester/seed-data/coupons-tester-seed-data.js +3 -3
  187. package/dist/features/tester/seed-data/index.d.ts +1 -0
  188. package/dist/features/tester/seed-data/index.js +1 -0
  189. package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +2 -0
  190. package/dist/features/tester/seed-data/offers-tester-seed-data.js +61 -0
  191. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +337 -1
  192. package/dist/index.d.ts +11 -4
  193. package/dist/index.js +15 -3
  194. package/dist/react/hooks/useListingTypeFlags.d.ts +12 -19
  195. package/dist/react/hooks/useListingTypeFlags.js +4 -9
  196. package/dist/schemas/registry.d.ts +0 -12
  197. package/dist/seed/claimed-coupons-seed-data.js +3 -3
  198. package/dist/seed/coupons-seed-data.js +29 -10
  199. package/dist/seed/faq-seed-data.js +2 -2
  200. package/dist/seed/manifest.js +2 -2
  201. package/dist/server-entry.d.ts +1 -0
  202. package/dist/server-entry.js +1 -0
  203. package/dist/server.d.ts +1 -0
  204. package/dist/server.js +3 -0
  205. package/dist/styles.css +44 -39
  206. package/dist/tailwind-utilities.css +1 -1
  207. package/dist/ui/components/FilterChipGroup.d.ts +21 -3
  208. package/dist/ui/components/FilterChipGroup.js +33 -4
  209. package/dist/ui/components/HorizontalScroller.d.ts +1 -2
  210. package/dist/ui/components/HorizontalScroller.js +9 -5
  211. package/dist/ui/components/HorizontalScroller.style.css +43 -38
  212. package/package.json +1 -1
@@ -8,16 +8,36 @@ import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersInde
8
8
  import { PrizeDrawsIndexListing } from "../../products/components/PrizeDrawsIndexListing";
9
9
  import { CategoryBundlesListing } from "./CategoryBundlesListing";
10
10
  import { CategoryStoresListing } from "./CategoryStoresListing";
11
- import { CATEGORY_PAGE_TABS } from "../../products/constants/listing-tabs";
12
- /** Maps CATEGORY_PAGE_TABS id listing type / category type key for flag filtering. */
11
+ import { CATEGORY_PAGE_TABS, ART_STICKERS_LISTING_TYPES, } from "../../products/constants/listing-tabs";
12
+ import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags";
13
+ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
14
+ /**
15
+ * Maps CATEGORY_PAGE_TABS id (a `tabSlug`) → listing type / category type key
16
+ * for feature-flag filtering.
17
+ *
18
+ * The listing-type half is DERIVED (2026-08-21). It used to be hand-written
19
+ * and covered only 6 of the 10 tabs — `classifieds`, `digital-codes`, `live`
20
+ * and `art` had no entry, so they skipped the flag check entirely AND (worse)
21
+ * had no render branch below, meaning all four rendered a tab you could click
22
+ * that showed an empty page.
23
+ */
13
24
  const TAB_TYPE_MAP = {
14
- products: { kind: "listing", type: "standard" },
15
- auctions: { kind: "listing", type: "auction" },
16
- "pre-orders": { kind: "listing", type: "pre-order" },
17
- "prize-draws": { kind: "listing", type: "prize-draw" },
25
+ ...Object.fromEntries(ALL_LISTING_TYPES.map((type) => [
26
+ pluginFor(type).tabSlug,
27
+ { kind: "listing", type },
28
+ ])),
18
29
  bundles: { kind: "category", type: "bundle" },
19
30
  stores: { kind: "entity", type: "stores" },
20
31
  };
32
+ /** Listing types rendered by the shared products listing rather than a dedicated one. */
33
+ const GENERIC_TAB_LISTING_TYPES = {
34
+ [pluginFor("standard").tabSlug]: ["standard"],
35
+ [pluginFor("classified").tabSlug]: ["classified"],
36
+ [pluginFor("digital-code").tabSlug]: ["digital-code"],
37
+ [pluginFor("live").tabSlug]: ["live"],
38
+ // The combined Art & Stickers tab spans both types under the `art` slug.
39
+ [pluginFor("art").tabSlug]: ART_STICKERS_LISTING_TYPES,
40
+ };
21
41
  export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles = [], initialStores = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
22
42
  const countFor = (id) => {
23
43
  switch (id) {
@@ -51,5 +71,5 @@ export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsDa
51
71
  });
52
72
  const firstTabId = (visibleTabs[0]?.id ?? "products");
53
73
  const [activeTab, setActiveTab] = useState(firstTabId);
54
- return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), activeTab === "products" && (_jsx(CategoryProductsListing, { categorySlug: categorySlug, categoryId: categoryId, initialData: initialProductsData })), activeTab === "auctions" && (_jsx(AuctionsIndexListing, { categorySlug: categorySlug })), activeTab === "pre-orders" && (_jsx(PreOrdersIndexListing, { categorySlug: categorySlug })), activeTab === "prize-draws" && (_jsx(PrizeDrawsIndexListing, { categorySlug: categorySlug })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles })), activeTab === "stores" && (_jsx(CategoryStoresListing, { stores: initialStores }))] }));
74
+ return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), GENERIC_TAB_LISTING_TYPES[activeTab] && (_jsx(CategoryProductsListing, { categorySlug: categorySlug, categoryId: categoryId, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { categorySlug: categorySlug })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { categorySlug: categorySlug })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { categorySlug: categorySlug })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles })), activeTab === "stores" && (_jsx(CategoryStoresListing, { stores: initialStores }))] }));
55
75
  }
@@ -4,6 +4,14 @@ export interface CategoryProductsListingProps {
4
4
  categoryId?: string;
5
5
  /** Filter products by brand name (for brand detail pages) */
6
6
  brandName?: string;
7
+ /**
8
+ * Listing types this tab spans. Defaults to `["standard"]` — the original,
9
+ * only behaviour. Category/brand pages pass a single non-standard type (or
10
+ * the `["art","stickers"]` pair) so the classifieds / digital-codes / live /
11
+ * art tabs render real content instead of a blank panel: those four tabs
12
+ * existed in CATEGORY_PAGE_TABS but had no render branch at all.
13
+ */
14
+ listingTypes?: readonly string[];
7
15
  initialData?: any;
8
16
  }
9
- export declare function CategoryProductsListing({ categorySlug, categoryId, brandName, initialData, }: CategoryProductsListingProps): React.JSX.Element;
17
+ export declare function CategoryProductsListing({ categorySlug, categoryId, brandName, listingTypes, initialData, }: CategoryProductsListingProps): React.JSX.Element;
@@ -27,7 +27,7 @@ function renderCategoryProductGrid(props) {
27
27
  }
28
28
  return (_jsx(ProductGrid, { products: products, getProductHref: getProductHref, view: view, emptyLabel: "No products found in this category.", onWishlistToggle: onWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: onAddToCart }));
29
29
  }
30
- export function CategoryProductsListing({ categorySlug, categoryId, brandName, initialData, }) {
30
+ export function CategoryProductsListing({ categorySlug, categoryId, brandName, listingTypes = ["standard"], initialData, }) {
31
31
  const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
32
32
  const { showToast } = useToast();
33
33
  const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
@@ -48,7 +48,10 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, i
48
48
  sort: table.get("sort") || "-createdAt",
49
49
  page: table.getNumber("page", 1),
50
50
  perPage: table.getNumber("pageSize", 24),
51
- listingType: "standard",
51
+ // Pipe-joined OR-group when the tab spans several types (art + stickers);
52
+ // sievejs parses it as a same-field OR and the Firebase adapter upgrades
53
+ // it to a `.where(…, "in", …)` query.
54
+ listingType: listingTypes.join("|"),
52
55
  };
53
56
  const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
54
57
  const commitSearch = useCallback(() => {
@@ -21,6 +21,7 @@ import { computeRelatedItems } from "../../../_internal/server/features/products
21
21
  import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
22
22
  import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
23
23
  import { ClassifiedContactSellerPanel } from "./ClassifiedContactSellerPanel";
24
+ import { ListingBottomActions } from "../../products/components/ListingBottomActions";
24
25
  function toDescriptionHtml(raw) {
25
26
  if (!raw)
26
27
  return "";
@@ -65,5 +66,5 @@ export async function ClassifiedDetailPageView({ slug, initialProduct }) {
65
66
  id: s.id,
66
67
  label: s.title,
67
68
  content: _jsx(CustomSectionTabContent, { section: s }),
68
- })) })), renderBuyBar: () => (_jsx(Stack, { id: "classified-contact-bar", className: "p-[var(--appkit-space-5)]", border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: _jsx(ClassifiedContactSellerPanel, { productId: String(product.id), onContactSeller: startClassifiedConversationAction }) })), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand, storeLabel: storeName ?? undefined })] })) })] })] }));
69
+ })) })), renderBuyBar: () => (_jsx(Stack, { id: "classified-contact-bar", className: "p-[var(--appkit-space-5)]", border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: _jsx(ClassifiedContactSellerPanel, { productId: String(product.id), onContactSeller: startClassifiedConversationAction }) })), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand, storeLabel: storeName ?? undefined })] })) }), _jsx(ListingBottomActions, { listingType: "classified", anchorId: "classified-contact-bar", price: price, currency: currency })] })] }));
69
70
  }
@@ -13,6 +13,8 @@ function renderToStaticMarkup(el) {
13
13
  }
14
14
  import { normalizeError } from "../../errors/normalize";
15
15
  import { getProviders } from "../../contracts";
16
+ import { createResendProvider } from "../../providers/email-resend/provider";
17
+ import { siteSettingsRepository } from "../admin/repository/site-settings.repository";
16
18
  import { serverLogger } from "../../monitoring";
17
19
  import { currentYear, formatCurrency, formatDateTime, nowMs, } from "../../utils";
18
20
  import { EmailBold, EmailButton, EmailContainer, EmailDivider, EmailDoc, EmailFooter, EmailHeader, EmailLink, EmailRow, } from "../email/primitives";
@@ -25,9 +27,62 @@ function getSiteUrl() {
25
27
  function getSupportEmail() {
26
28
  return process.env.EMAIL_SUPPORT?.trim() || "support@example.com";
27
29
  }
30
+ /**
31
+ * Resolve an email provider that works in BOTH runtimes.
32
+ *
33
+ * The provider registry is populated by the consumer's `providers.config.ts`,
34
+ * which only runs from the Next.js instrumentation hook. Firebase Functions
35
+ * never call `registerProviders()`, so `getProviders()` throws there —
36
+ * silently failing every Functions-side `sendEmail()` with
37
+ * "Call registerProviders() before getProviders()". That is why the scheduled
38
+ * dailyStatusDigest never delivered: `sendEmail` catches the throw and returns
39
+ * it as `{ error }`, so it surfaced only as a logged "send failed".
40
+ *
41
+ * Falling back to a directly-constructed Resend provider mirrors exactly what
42
+ * `sendNotification()` already does for Functions-side email — Firestore
43
+ * credentials first, env vars second — so both paths resolve identically
44
+ * instead of one runtime silently having no provider at all.
45
+ */
46
+ async function resolveEmailProvider() {
47
+ try {
48
+ return getProviders().email;
49
+ }
50
+ catch (err) {
51
+ // Not an error condition — "no email provider registered yet" is the
52
+ // expected signal on the Functions runtime, and the fallback below builds
53
+ // one from credentials. Normalized anyway so the catch is never bare.
54
+ void normalizeError(err);
55
+ const [creds, settings] = await Promise.all([
56
+ siteSettingsRepository.getDecryptedCredentials().catch((err) => {
57
+ void normalizeError(err);
58
+ return {};
59
+ }),
60
+ siteSettingsRepository.getSingleton().catch((err) => {
61
+ void normalizeError(err);
62
+ return null;
63
+ }),
64
+ ]);
65
+ // A seeded placeholder ("re_PLACEHOLDER") is truthy but not a real key and
66
+ // must never win over a real env var — same guard as providers.config.ts.
67
+ const seeded = creds.resendApiKey?.trim() ?? "";
68
+ const apiKey = seeded && !seeded.includes("PLACEHOLDER")
69
+ ? seeded
70
+ : (process.env.RESEND_API_KEY?.trim() ?? "");
71
+ return createResendProvider({
72
+ apiKey,
73
+ fromEmail: settings?.emailSettings?.fromEmail?.trim() ||
74
+ process.env.EMAIL_FROM?.trim() ||
75
+ "",
76
+ fromName: settings?.emailSettings?.fromName?.trim() ||
77
+ process.env.EMAIL_FROM_NAME?.trim() ||
78
+ "App",
79
+ });
80
+ }
81
+ }
28
82
  export async function sendEmail(opts) {
29
83
  try {
30
- const data = await getProviders().email.send({
84
+ const provider = await resolveEmailProvider();
85
+ const data = await provider.send({
31
86
  to: opts.to,
32
87
  subject: opts.subject,
33
88
  html: typeof opts.html === "string" ? opts.html : "",
@@ -16,10 +16,11 @@ import { CustomSectionTabContent } from "../../products/components/CustomSection
16
16
  import { ShareButton } from "../../products/components/ShareButton";
17
17
  import { HistoryTracker } from "../../history/components/HistoryTracker";
18
18
  import { RelatedItemsSection } from "../../products/components/RelatedItemsSection";
19
- import { computeRelatedItems, getReviewItemsForProduct } from "../../../_internal/server/features/products/data";
19
+ import { computeRelatedItems, getReviewPageForProduct } from "../../../_internal/server/features/products/data";
20
20
  import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
21
21
  import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
22
- import { ReviewsList } from "../../reviews/components/ReviewsList";
22
+ import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPanel";
23
+ import { ListingBottomActions } from "../../products/components/ListingBottomActions";
23
24
  function toDescriptionHtml(raw) {
24
25
  if (!raw)
25
26
  return "";
@@ -50,10 +51,10 @@ export async function DigitalCodeDetailPageView({ slug, initialProduct, renderPr
50
51
  const specs = Array.isArray(p.specifications) ? p.specifications : [];
51
52
  const customSections = Array.isArray(p.customSections) ? p.customSections : [];
52
53
  const descriptionHtml = toDescriptionHtml(p.description);
53
- const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups, reviews] = await Promise.all([
54
+ const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups, initialReviews] = await Promise.all([
54
55
  computeRelatedItems(product),
55
56
  getGroupsWithItemsForProduct(product.id),
56
- getReviewItemsForProduct(product.id),
57
+ getReviewPageForProduct(product.id),
57
58
  ]);
58
59
  return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "product", snapshot: {
59
60
  title,
@@ -61,19 +62,19 @@ export async function DigitalCodeDetailPageView({ slug, initialProduct, renderPr
61
62
  price: price ?? undefined,
62
63
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
63
64
  storeName: storeName ?? undefined,
64
- } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.DIGITAL_CODES), className: CLS_BREADCRUMB_LINK, children: "Digital Codes" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_DIGITALCODE_BADGE, children: "Digital Code" }), codesLeft > 0 ? (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: [codesLeft, " available"] })) : (_jsx(Span, { size: "xs", weight: "semibold", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "danger-surface", color: "error", children: "Sold out" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) })), meta?.redemptionInstructions && (_jsx(Text, { size: "xs", color: "muted", children: meta.redemptionInstructions })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), 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: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.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, reviewsContent: _jsx(ReviewsList, { reviews: reviews, context: "listing", emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
65
+ } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.DIGITAL_CODES), className: CLS_BREADCRUMB_LINK, children: "Digital Codes" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_DIGITALCODE_BADGE, children: "Digital Code" }), codesLeft > 0 ? (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: [codesLeft, " available"] })) : (_jsx(Span, { size: "xs", weight: "semibold", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "danger-surface", color: "error", children: "Sold out" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) })), meta?.redemptionInstructions && (_jsx(Text, { size: "xs", color: "muted", children: meta.redemptionInstructions })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), 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: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.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, 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) => ({
65
66
  id: s.id,
66
67
  label: s.title,
67
68
  content: _jsx(CustomSectionTabContent, { section: s }),
68
- })) })), renderBuyBar: () => renderPrimaryActions?.({
69
- productId: String(product.id),
70
- productSlug: String(p.slug ?? product.id),
71
- productTitle: title,
72
- productImage: images[0],
73
- price,
74
- currency,
75
- storeId: typeof p.storeId === "string" ? p.storeId : undefined,
76
- storeName: storeName ?? undefined,
77
- inStock: codesLeft > 0,
78
- }), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand, storeLabel: storeName ?? undefined })] })) })] })] }));
69
+ })) })), renderBuyBar: () => (_jsx(Div, { id: "digital-code-buy-bar", children: renderPrimaryActions?.({
70
+ productId: String(product.id),
71
+ productSlug: String(p.slug ?? product.id),
72
+ productTitle: title,
73
+ productImage: images[0],
74
+ price,
75
+ currency,
76
+ storeId: typeof p.storeId === "string" ? p.storeId : undefined,
77
+ storeName: storeName ?? undefined,
78
+ inStock: codesLeft > 0,
79
+ }) })), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand, storeLabel: storeName ?? undefined })] })) }), _jsx(ListingBottomActions, { listingType: "digital-code", anchorId: "digital-code-buy-bar", price: price, currency: currency, unavailable: codesLeft <= 0 })] })] }));
79
80
  }
@@ -13,5 +13,5 @@ export function FeaturedResultsSection({ title, subtitle, pillLabel, ornamentLab
13
13
  afterImage: item.afterImage,
14
14
  caption: item.caption ?? "",
15
15
  sortOrder: item.sortOrder ?? i,
16
- } }, item.id ?? `result-${i}`) })), keyExtractor: (item, i) => item.id ?? `result-${i}`, perView: CAROUSEL_PER_VIEW.cards, gap: 24, minItemWidth: 260, snapToItems: true, showArrows: true, showFadeEdges: true, loop: true }) })] }));
16
+ } }, item.id ?? `result-${i}`) })), keyExtractor: (item, i) => item.id ?? `result-${i}`, perView: CAROUSEL_PER_VIEW.cards, gap: 24, minItemWidth: 260, snapToItems: true, showArrows: true, loop: true }) })] }));
17
17
  }
@@ -48,5 +48,5 @@ export function SectionCarousel({ title, description, headingVariant = "editoria
48
48
  // Double-row grids (e.g. the homepage Auctions section's
49
49
  // rows:2 config) get the full-height sliver arrows — much
50
50
  // easier to hit than a small circle when the track is tall.
51
- arrowStyle: rows > 1 ? "full-height" : "circle", snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: hand === "left" ? "end" : "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, shadow: useLightText ? "lg" : "none", className: "inline-flex items-[center] gap-[0.375rem] transition-colors bg-primary text-white hover:bg-primary-600", size: "sm", weight: "semibold", children: viewMoreLabel }) }))] })] }));
51
+ arrowStyle: rows > 1 ? "full-height" : "circle", snapToItems: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: hand === "left" ? "end" : "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, shadow: useLightText ? "lg" : "none", className: "inline-flex items-[center] gap-[0.375rem] transition-colors bg-primary text-white hover:bg-primary-600", size: "sm", weight: "semibold", children: viewMoreLabel }) }))] })] }));
52
52
  }
@@ -69,5 +69,5 @@ export function ShopByCategorySection({ title = "Shop by Category", subtitle, li
69
69
  if (!cat)
70
70
  return null;
71
71
  return (_jsx(FilterChip, { label: cat.name, active: activeFilter === id, onClick: () => setActiveFilter(activeFilter === id ? "all" : id) }, id));
72
- })] })), isLoading ? (_jsx(Div, { layout: "flex", gap: "3", className: `${__O.hidden} px-[0.25rem]`, children: Array.from({ length: 6 }).map((_, i) => (_jsx(Div, { className: "flex-none h-[104px] w-[108px] animate-pulse", surface: "subtle", rounded: "xl" }, i))) })) : (_jsx(HorizontalScroller, { items: categories, renderItem: (cat) => _jsx(CategoryChip, { category: cat }), perView: CAROUSEL_PER_VIEW.standard, gap: 16, keyExtractor: (cat) => cat.id, autoScroll: autoScroll, autoScrollInterval: scrollInterval, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, loop: loop, pauseOnHover: true })), cta && !isLoading && (_jsx(Div, { className: "mt-6 text-left", children: _jsx(Link, { href: cta.href, className: CTA_CLASSES[cta.variant], children: cta.label }) })), !cta && viewMoreHref && !isLoading && (_jsx(Div, { className: "mt-6 text-left", children: _jsx(Link, { href: viewMoreHref, className: "inline-flex items-center gap-[var(--appkit-space-1)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:opacity-80", children: viewMoreLabel }) }))] }) }));
72
+ })] })), isLoading ? (_jsx(Div, { layout: "flex", gap: "3", className: `${__O.hidden} px-[0.25rem]`, children: Array.from({ length: 6 }).map((_, i) => (_jsx(Div, { className: "flex-none h-[104px] w-[108px] animate-pulse", surface: "subtle", rounded: "xl" }, i))) })) : (_jsx(HorizontalScroller, { items: categories, renderItem: (cat) => _jsx(CategoryChip, { category: cat }), perView: CAROUSEL_PER_VIEW.standard, gap: 16, keyExtractor: (cat) => cat.id, autoScroll: autoScroll, autoScrollInterval: scrollInterval, showArrows: true, snapToItems: true, showScrollbar: false, loop: loop, pauseOnHover: true })), cta && !isLoading && (_jsx(Div, { className: "mt-6 text-left", children: _jsx(Link, { href: cta.href, className: CTA_CLASSES[cta.variant], children: cta.label }) })), !cta && viewMoreHref && !isLoading && (_jsx(Div, { className: "mt-6 text-left", children: _jsx(Link, { href: viewMoreHref, className: "inline-flex items-center gap-[var(--appkit-space-1)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:opacity-80", children: viewMoreLabel }) }))] }) }));
73
73
  }
@@ -170,9 +170,16 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
170
170
  const handleBeforeDashboardNavToggle = useCallback(() => {
171
171
  setSidebarOpen(false);
172
172
  }, []);
173
+ // Must stay in sync with BottomActions' own `isVisible` — `secondaryLabel` was missing
174
+ // here, so an auction bar shown by its countdown alone got no bottom clearance.
173
175
  const hasBottomActions = bottomActionsState.actions.length > 0 ||
174
176
  !!(bottomActionsState.bulk && bottomActionsState.bulk.selectedCount > 0) ||
175
- !!bottomActionsState.infoLabel;
177
+ !!bottomActionsState.infoLabel ||
178
+ !!bottomActionsState.secondaryLabel;
179
+ /** The bar overlays desktop content too once a page opts in, so reserve space there. */
180
+ const hasDesktopBottomActions = hasBottomActions &&
181
+ (bottomActionsState.desktop === "always" ||
182
+ bottomActionsState.desktop === "after-scroll");
176
183
  const sidebarContent = (_jsx(SidebarContent, { sidebarItems: sidebarItems, sidebarSections: sidebarSections, sidebarPrimaryActions: sidebarPrimaryActions, user: user, activeHref: pathname ?? "", profileHref: profileHref, userOrdersHref: userOrdersHref, userWishlistHref: userWishlistHref, userSettingsHref: userSettingsHref, adminHref: adminHref, storeHref: storeHref, sellerHref: sellerHref, sidebarLocaleSlot: sidebarLocaleSlot, showThemeToggleInSidebar: showThemeToggleInSidebar, sidebarProfileLabels: sidebarProfileLabels, theme: theme, activeThemeName: activeTheme.name, toggleTheme: toggleTheme, onLogout: onLogout, onAfterLogout: () => setSidebarOpen(false) }));
177
184
  const normalizedLightBackground = {
178
185
  type: lightBackground.type,
@@ -192,5 +199,5 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
192
199
  opacity: darkBackground.overlay?.opacity ?? 0,
193
200
  },
194
201
  };
195
- return (_jsx(_Fragment, { children: _jsxs(Stack, { className: "min-h-screen w-full overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onTourStart: onTourStart, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot), eventBannerSlot] }), _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { layout: "flex", className: "relative w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false) })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content", className: `w-full flex-1 flex flex-col ${hasBottomActions ? "mb-28" : "mb-16"} lg:mb-0`, children: _jsx(Div, { padding: "y-lg", className: `flex-1 ${contentClassName ?? "mx-auto w-full max-w-screen-xl px-[var(--appkit-space-5)] md:px-[var(--appkit-space-6)] lg:px-[var(--appkit-space-8)]"}`, children: children }) })] }), _jsx(BackToTop, {}), _jsx(FooterLayout, { ...footer }), _jsx(BottomActions, {}), _jsx(BottomNavbar, { user: user, userId: userId, homeHref: homeHref, shopHref: shopHref, cartHref: cartHref, wishlistHref: wishlistHref, profileHref: profileHref, loginHref: loginHref, onSearchToggle: () => setSearchOpen((prev) => !prev), navItems: navItems, onMoreToggle: hasDashboardNav ? toggleDashboardNav : handleTogglePublicSidebar }), _jsx(UnsavedChangesModal, {})] }) }));
202
+ return (_jsx(_Fragment, { children: _jsxs(Stack, { className: "min-h-screen w-full overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onTourStart: onTourStart, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot), eventBannerSlot] }), _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { layout: "flex", className: "relative w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false) })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content", className: `w-full flex-1 flex flex-col ${hasBottomActions ? "mb-28" : "mb-16"} ${hasDesktopBottomActions ? "lg:mb-24" : "lg:mb-0"}`, children: _jsx(Div, { padding: "y-lg", className: `flex-1 ${contentClassName ?? "mx-auto w-full max-w-screen-xl px-[var(--appkit-space-5)] md:px-[var(--appkit-space-6)] lg:px-[var(--appkit-space-8)]"}`, children: children }) })] }), _jsx(BackToTop, {}), _jsx(FooterLayout, { ...footer }), _jsx(BottomActions, {}), _jsx(BottomNavbar, { user: user, userId: userId, homeHref: homeHref, shopHref: shopHref, cartHref: cartHref, wishlistHref: wishlistHref, profileHref: profileHref, loginHref: loginHref, onSearchToggle: () => setSearchOpen((prev) => !prev), navItems: navItems, onMoreToggle: hasDashboardNav ? toggleDashboardNav : handleTogglePublicSidebar }), _jsx(UnsavedChangesModal, {})] }) }));
196
203
  }
@@ -18,7 +18,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
18
18
  * styled with the selected action's variant (danger = red, etc.).
19
19
  *
20
20
  * Layout rules:
21
- * - Hidden on lg+ screens (`lg:hidden`) — desktop shows inline action panels.
21
+ * - Hidden on lg+ screens by default — desktop shows inline action panels. A page can
22
+ * opt in by passing `desktop: "after-scroll" | "always"` to `useBottomActions`;
23
+ * "after-scroll" brings the bar back once the primary CTA has scrolled out of view.
22
24
  * - The bar slides up with a 300 ms ease-out transition; `pointer-events-none`
23
25
  * while off-screen.
24
26
  *
@@ -38,6 +40,8 @@ const Z_BOTTOM_ACTIONS = "z-[var(--appkit-z-bottom-nav)]";
38
40
  const BOTTOM_NAV_HEIGHT = "h-14";
39
41
  const FLEX_CENTER = "flex items-center justify-center";
40
42
  const CLS_COUNT_BADGE = "bg-error-surface text-white";
43
+ /** How far the user must scroll before the desktop bar reveals itself. */
44
+ const DESKTOP_REVEAL_SCROLL_PX = 400;
41
45
  // ─── Sub-components ───────────────────────────────────────────────────────────
42
46
  function BulkPickerPanel({ bulkActions, selectedActionId, pickerOpen, onSelect, }) {
43
47
  return (_jsx(Div, { role: "listbox", "aria-label": "Bulk actions", className: [
@@ -85,7 +89,23 @@ export default function BottomActions() {
85
89
  const [selectedActionId, setSelectedActionId] = useState(null);
86
90
  const containerRef = useRef(null);
87
91
  useClickOutside(containerRef, () => setPickerOpen(false));
88
- const { actions, bulk, infoLabel, secondaryLabel } = state;
92
+ const { actions, bulk, infoLabel, secondaryLabel, desktop } = state;
93
+ // Desktop reveal-on-scroll. Kept as a plain passive listener to match BackToTop —
94
+ // there is no shared scroll-position hook in the codebase and one consumer doesn't
95
+ // justify inventing one. Only armed when a page actually opts into "after-scroll".
96
+ const [scrolledPastFold, setScrolledPastFold] = useState(false);
97
+ const wantsScrollGate = desktop === "after-scroll";
98
+ useEffect(() => {
99
+ if (!wantsScrollGate) {
100
+ setScrolledPastFold(false);
101
+ return;
102
+ }
103
+ const onScroll = () => setScrolledPastFold(window.scrollY > DESKTOP_REVEAL_SCROLL_PX);
104
+ onScroll();
105
+ window.addEventListener("scroll", onScroll, { passive: true });
106
+ return () => window.removeEventListener("scroll", onScroll);
107
+ }, [wantsScrollGate]);
108
+ const showOnDesktop = desktop === "always" || (wantsScrollGate && scrolledPastFold);
89
109
  const isBulkMode = !!(bulk && bulk.selectedCount > 0);
90
110
  const bulkActions = bulk?.actions ?? [];
91
111
  const pageActions = actions;
@@ -120,7 +140,10 @@ export default function BottomActions() {
120
140
  setPickerOpen(false);
121
141
  };
122
142
  return (_jsxs(Div, { ref: containerRef, role: "toolbar", "aria-label": isBulkMode ? "Bulk actions" : "Page actions", "aria-hidden": !isVisible, className: [
123
- "fixed bottom-[calc(var(--keyboard-inset-height,0px)+var(--bottom-nav-height,4rem))] left-0 right-0 lg:hidden",
143
+ "fixed bottom-[calc(var(--keyboard-inset-height,0px)+var(--bottom-nav-height,4rem))] left-0 right-0",
144
+ // `--bottom-nav-height` resolves to 0px above lg, so the same offset lands the
145
+ // bar on the viewport bottom on desktop with no extra positioning.
146
+ showOnDesktop ? "" : "lg:hidden",
124
147
  Z_BOTTOM_ACTIONS,
125
148
  BOTTOM_NAV_BG,
126
149
  "shadow-[0_-4px_20px_rgba(0,0,0,0.08)] dark:shadow-[0_-4px_20px_rgba(0,0,0,0.30)]",
@@ -69,9 +69,21 @@ export interface BottomBulkConfig {
69
69
  /** Bulk action buttons, rendered right of the selection pill. */
70
70
  actions: BottomAction[];
71
71
  }
72
+ /**
73
+ * Whether the bar is allowed to show on desktop (>= lg), where it has always been
74
+ * `lg:hidden`.
75
+ *
76
+ * - `"hidden"` (default) — mobile/tablet only, the historical behaviour.
77
+ * - `"after-scroll"` — appears once the user has scrolled past the top of the page,
78
+ * so a detail page's primary CTA comes back after the hero scrolls away.
79
+ * - `"always"` — pinned on desktop from first paint.
80
+ */
81
+ export type BottomActionsDesktopMode = "hidden" | "after-scroll" | "always";
72
82
  export interface BottomActionsState {
73
83
  /** Page-level primary actions. */
74
84
  actions: BottomAction[];
85
+ /** Desktop visibility policy. Defaults to "hidden" — opt in per page. */
86
+ desktop?: BottomActionsDesktopMode;
75
87
  /** Bulk-selection config. Bulk mode activates when selectedCount > 0. */
76
88
  bulk?: BottomBulkConfig;
77
89
  /**
@@ -107,6 +119,8 @@ interface BottomActionsContextValue {
107
119
  setInfoLabel: (label: string | undefined) => void;
108
120
  /** Set or clear the secondary label (rendered above infoLabel). */
109
121
  setSecondaryLabel: (label: string | undefined) => void;
122
+ /** Set the desktop visibility policy. */
123
+ setDesktopMode: (mode: BottomActionsDesktopMode | undefined) => void;
110
124
  /** Clear all state (called on feature unmount). */
111
125
  clearAll: () => void;
112
126
  }
@@ -87,6 +87,9 @@ export function BottomActionsProvider({ children, }) {
87
87
  const setSecondaryLabel = useCallback((secondaryLabel) => {
88
88
  setState((prev) => ({ ...prev, secondaryLabel }));
89
89
  }, []);
90
+ const setDesktopMode = useCallback((desktop) => {
91
+ setState((prev) => (prev.desktop === desktop ? prev : { ...prev, desktop }));
92
+ }, []);
90
93
  const clearAll = useCallback(() => {
91
94
  actionCallbacksRef.current = new Map();
92
95
  bulkCallbacksRef.current = new Map();
@@ -102,8 +105,17 @@ export function BottomActionsProvider({ children, }) {
102
105
  bulkClearRef,
103
106
  setInfoLabel,
104
107
  setSecondaryLabel,
108
+ setDesktopMode,
109
+ clearAll,
110
+ }), [
111
+ state,
112
+ setActions,
113
+ setBulkConfig,
114
+ setInfoLabel,
115
+ setSecondaryLabel,
116
+ setDesktopMode,
105
117
  clearAll,
106
- }), [state, setActions, setBulkConfig, setInfoLabel, setSecondaryLabel, clearAll]);
118
+ ]);
107
119
  return (_jsx(BottomActionsContext.Provider, { value: value, children: children }));
108
120
  }
109
121
  // --- Hook ---------------------------------------------------------------------
@@ -1,9 +1,14 @@
1
- import { type BottomAction, type BottomBulkConfig } from "../BottomActionsContext";
1
+ import { type BottomAction, type BottomActionsDesktopMode, type BottomBulkConfig } from "../BottomActionsContext";
2
2
  export interface UseBottomActionsOptions {
3
3
  actions?: BottomAction[];
4
4
  bulk?: BottomBulkConfig;
5
5
  infoLabel?: string;
6
6
  /** Rendered as its own row above infoLabel. See BottomActionsState.secondaryLabel. */
7
7
  secondaryLabel?: string;
8
+ /**
9
+ * Whether the bar may also show on desktop. Defaults to "hidden" (mobile/tablet only),
10
+ * so pages that don't opt in behave exactly as before.
11
+ */
12
+ desktop?: BottomActionsDesktopMode;
8
13
  }
9
14
  export declare function useBottomActions(options?: UseBottomActionsOptions): void;
@@ -2,7 +2,7 @@
2
2
  import { useEffect, useRef } from "react";
3
3
  import { useBottomActionsContext, } from "../BottomActionsContext";
4
4
  export function useBottomActions(options = {}) {
5
- const { setActions, setBulkConfig, setInfoLabel, setSecondaryLabel, clearAll } = useBottomActionsContext();
5
+ const { setActions, setBulkConfig, setInfoLabel, setSecondaryLabel, setDesktopMode, clearAll, } = useBottomActionsContext();
6
6
  const actionsRef = useRef(options.actions ?? []);
7
7
  actionsRef.current = options.actions ?? [];
8
8
  const bulkRef = useRef(options.bulk);
@@ -25,6 +25,9 @@ export function useBottomActions(options = {}) {
25
25
  useEffect(() => {
26
26
  setSecondaryLabel(options.secondaryLabel);
27
27
  }, [setSecondaryLabel, options.secondaryLabel]);
28
+ useEffect(() => {
29
+ setDesktopMode(options.desktop);
30
+ }, [setDesktopMode, options.desktop]);
28
31
  useEffect(() => {
29
32
  return () => clearAll();
30
33
  }, [clearAll]);
@@ -27,7 +27,7 @@ export type { LayoutClientProps, LayoutProvider } from "./LayoutClient";
27
27
  export { AppLayoutShell } from "./AppLayoutShell";
28
28
  export type { AppLayoutShellProps, AppLayoutShellSidebarLink, AppLayoutShellSidebarSection } from "./AppLayoutShell";
29
29
  export { BottomActionsProvider, useBottomActionsContext, } from "./BottomActionsContext";
30
- export type { BottomAction, BottomBulkConfig, BottomActionsState, } from "./BottomActionsContext";
30
+ export type { BottomAction, BottomBulkConfig, BottomActionsState, BottomActionsDesktopMode, } from "./BottomActionsContext";
31
31
  export { useBottomActions } from "./hooks/useBottomActions";
32
32
  export type { UseBottomActionsOptions } from "./hooks/useBottomActions";
33
33
  export { default as BottomActions } from "./BottomActions";
@@ -19,10 +19,11 @@ import { CustomSectionTabContent } from "../../products/components/CustomSection
19
19
  import { ShareButton } from "../../products/components/ShareButton";
20
20
  import { HistoryTracker } from "../../history/components/HistoryTracker";
21
21
  import { RelatedItemsSection } from "../../products/components/RelatedItemsSection";
22
- import { computeRelatedItems, getReviewItemsForProduct } from "../../../_internal/server/features/products/data";
22
+ import { computeRelatedItems, getReviewPageForProduct } from "../../../_internal/server/features/products/data";
23
23
  import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
24
24
  import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
25
- import { ReviewsList } from "../../reviews/components/ReviewsList";
25
+ import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPanel";
26
+ import { ListingBottomActions } from "../../products/components/ListingBottomActions";
26
27
  function toDescriptionHtml(raw) {
27
28
  if (!raw)
28
29
  return "";
@@ -55,10 +56,10 @@ export async function LiveItemDetailPageView({ slug, initialProduct, renderActio
55
56
  const specs = Array.isArray(p.specifications) ? p.specifications : [];
56
57
  const customSections = Array.isArray(p.customSections) ? p.customSections : [];
57
58
  const descriptionHtml = toDescriptionHtml(p.description);
58
- const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups, reviews] = await Promise.all([
59
+ const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups, initialReviews] = await Promise.all([
59
60
  computeRelatedItems(product),
60
61
  getGroupsWithItemsForProduct(product.id),
61
- getReviewItemsForProduct(product.id),
62
+ getReviewPageForProduct(product.id),
62
63
  ]);
63
64
  return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "product", snapshot: {
64
65
  title,
@@ -66,9 +67,9 @@ export async function LiveItemDetailPageView({ slug, initialProduct, renderActio
66
67
  price: price ?? undefined,
67
68
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
68
69
  storeName: storeName ?? undefined,
69
- } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.LIVE), className: CLS_BREADCRUMB_LINK, children: "Live" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_ITEM_BADGE, children: "Live Item" }), meta?.vendorVerified && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Verified Seller" })), meta?.cites && (_jsxs(Span, { color: "warning", surface: "warning-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: ["CITES: ", meta.cites] }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title }), meta?.species && (_jsxs(Text, { className: "italic mt-1", color: "muted", size: "sm", children: [meta.species, meta.sex && meta.sex !== "n/a" && ` · ${meta.sex}`, meta.ageMonths !== undefined && ` · ${meta.ageMonths}mo`] }))] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) })), jurisdictions.length > 0 && (_jsxs(Div, { className: CLS_WARN_BOX, children: [_jsx(Text, { className: CLS_WARN_TITLE, children: "Delivery restrictions" }), _jsxs(Text, { className: CLS_WARN_BODY, children: ["This item can only be shipped to: ", jurisdictions.join(", ")] })] })), transport && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Transport" }), _jsxs(Text, { className: "text-muted-foreground", children: ["Method: ", transport.method, transport.handlingFee !== undefined && ` · Handling: ${formatCurrency(transport.handlingFee, "INR")}`, transport.insuranceIncluded && " · Insurance included"] })] })), meta?.careInfo && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Care information" }), _jsx(Text, { className: "mt-1 text-muted-foreground", children: meta.careInfo })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), 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: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.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, reviewsContent: _jsx(ReviewsList, { reviews: reviews, context: "listing", emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
70
+ } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.LIVE), className: CLS_BREADCRUMB_LINK, children: "Live" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_ITEM_BADGE, children: "Live Item" }), meta?.vendorVerified && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Verified Seller" })), meta?.cites && (_jsxs(Span, { color: "warning", surface: "warning-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: ["CITES: ", meta.cites] }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title }), meta?.species && (_jsxs(Text, { className: "italic mt-1", color: "muted", size: "sm", children: [meta.species, meta.sex && meta.sex !== "n/a" && ` · ${meta.sex}`, meta.ageMonths !== undefined && ` · ${meta.ageMonths}mo`] }))] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) })), jurisdictions.length > 0 && (_jsxs(Div, { className: CLS_WARN_BOX, children: [_jsx(Text, { className: CLS_WARN_TITLE, children: "Delivery restrictions" }), _jsxs(Text, { className: CLS_WARN_BODY, children: ["This item can only be shipped to: ", jurisdictions.join(", ")] })] })), transport && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Transport" }), _jsxs(Text, { className: "text-muted-foreground", children: ["Method: ", transport.method, transport.handlingFee !== undefined && ` · Handling: ${formatCurrency(transport.handlingFee, "INR")}`, transport.insuranceIncluded && " · Insurance included"] })] })), meta?.careInfo && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Care information" }), _jsx(Text, { className: "mt-1 text-muted-foreground", children: meta.careInfo })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), 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: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.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, 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) => ({
70
71
  id: s.id,
71
72
  label: s.title,
72
73
  content: _jsx(CustomSectionTabContent, { section: s }),
73
- })) })), renderBuyBar: () => renderActions?.(product), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand, storeLabel: storeName ?? undefined })] })) })] })] }));
74
+ })) })), renderBuyBar: () => _jsx(Div, { id: "live-item-buy-bar", children: renderActions?.(product) }), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand, storeLabel: storeName ?? undefined })] })) }), _jsx(ListingBottomActions, { listingType: "live", anchorId: "live-item-buy-bar", price: price, currency: currency })] })] }));
74
75
  }
@@ -175,6 +175,10 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
175
175
  canFilter: boolean;
176
176
  canSort: boolean;
177
177
  };
178
+ orderType: {
179
+ canFilter: boolean;
180
+ canSort: boolean;
181
+ };
178
182
  };
179
183
  listAll(model: SieveModel): Promise<FirebaseSieveResult<OrderDocument>>;
180
184
  /**
@@ -293,21 +297,6 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
293
297
  * Cloud Functions: stage a cancellation update into a caller-owned WriteBatch.
294
298
  */
295
299
  cancelInBatch(batch: WriteBatch, ref: DocumentReference): void;
296
- /**
297
- * Cloud Functions: create an auction-won order inside a caller-owned WriteBatch.
298
- * Returns the new DocumentReference synchronously so the caller can chain further batch ops.
299
- */
300
- createFromAuction(batch: WriteBatch, input: {
301
- productId: string;
302
- productTitle: string;
303
- userId: string;
304
- userName: string;
305
- userEmail: string;
306
- storeId?: string;
307
- amount: number;
308
- currency: string;
309
- auctionProductId: string;
310
- }): DocumentReference;
311
300
  }
312
301
  declare const orderRepository: OrderRepository;
313
302
  export { OrderRepository, orderRepository };