@mohasinac/appkit 2.7.49 → 2.7.52

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 (232) hide show
  1. package/dist/_internal/client/features/layout/SidebarCollapseToggle.d.ts +19 -0
  2. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +15 -0
  3. package/dist/_internal/client/features/layout/index.d.ts +2 -0
  4. package/dist/_internal/client/features/layout/index.js +1 -0
  5. package/dist/_internal/server/features/checkout/actions.js +25 -2
  6. package/dist/_internal/server/features/promotions/actions.js +3 -1
  7. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
  8. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
  9. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
  10. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
  11. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
  12. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
  13. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
  14. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
  15. package/dist/_internal/shared/actions/action-registry.js +149 -0
  16. package/dist/client.d.ts +45 -6
  17. package/dist/client.js +27 -4
  18. package/dist/constants/api-endpoints.d.ts +42 -0
  19. package/dist/constants/api-endpoints.js +14 -0
  20. package/dist/features/_guide-cls.d.ts +13 -0
  21. package/dist/features/_guide-cls.js +14 -0
  22. package/dist/features/about/components/HelpPageView.js +29 -26
  23. package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
  24. package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
  25. package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
  26. package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
  27. package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
  28. package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
  29. package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
  30. package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
  31. package/dist/features/account/components/UserSidebar.js +2 -1
  32. package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
  33. package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
  34. package/dist/features/admin/components/AdminBlogEditorView.js +102 -75
  35. package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
  36. package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
  37. package/dist/features/admin/components/AdminBundlesView.js +173 -52
  38. package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
  39. package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
  40. package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
  41. package/dist/features/admin/components/AdminContentGuideView.js +36 -0
  42. package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
  43. package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
  44. package/dist/features/admin/components/AdminGuideHubView.js +79 -0
  45. package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
  46. package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
  47. package/dist/features/admin/components/AdminProductsView.js +8 -3
  48. package/dist/features/admin/components/AdminSidebar.js +2 -1
  49. package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
  50. package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
  51. package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
  52. package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
  53. package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
  54. package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
  55. package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
  56. package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
  57. package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
  58. package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
  59. package/dist/features/auth/repository/session.repository.js +16 -1
  60. package/dist/features/cart/hooks/useAddToCart.d.ts +2 -0
  61. package/dist/features/cart/hooks/useAddToCart.js +2 -0
  62. package/dist/features/cart/hooks/useGuestCart.d.ts +2 -0
  63. package/dist/features/cart/utils/guest-cart.d.ts +7 -0
  64. package/dist/features/cart/utils/pending-ops.d.ts +6 -0
  65. package/dist/features/categories/components/CategoryBundlesListing.js +3 -25
  66. package/dist/features/categories/components/CategoryProductsListing.js +6 -10
  67. package/dist/features/categories/components/index.d.ts +0 -2
  68. package/dist/features/categories/components/index.js +0 -1
  69. package/dist/features/categories/schemas/firestore.d.ts +9 -0
  70. package/dist/features/events/components/AdminEventEditorView.js +223 -269
  71. package/dist/features/events/components/EventRaffleWinnerView.d.ts +9 -1
  72. package/dist/features/events/components/EventRaffleWinnerView.js +3 -3
  73. package/dist/features/events/components/SpinWheelView.js +3 -3
  74. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
  75. package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
  76. package/dist/features/layout/BottomNavLayout.d.ts +5 -0
  77. package/dist/features/layout/BottomNavLayout.js +21 -0
  78. package/dist/features/layout/TitleBar.js +7 -1
  79. package/dist/features/layout/TitleBarLayout.d.ts +8 -2
  80. package/dist/features/layout/TitleBarLayout.js +11 -7
  81. package/dist/features/media/upload/MediaUploadField.js +1 -1
  82. package/dist/features/orders/schemas/index.d.ts +2 -2
  83. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +12 -6
  84. package/dist/features/products/components/AuctionsIndexListing.js +3 -1
  85. package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
  86. package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
  87. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  88. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +3 -1
  89. package/dist/features/products/components/PrizeDrawEntryActions.js +8 -12
  90. package/dist/features/products/components/ProductDetailActions.d.ts +5 -1
  91. package/dist/features/products/components/ProductDetailActions.js +3 -1
  92. package/dist/features/products/components/ProductDetailPageView.d.ts +2 -0
  93. package/dist/features/products/components/ProductDetailPageView.js +4 -0
  94. package/dist/features/products/components/ProductGrid.js +3 -3
  95. package/dist/features/products/components/ProductsIndexListing.js +18 -9
  96. package/dist/features/products/components/index.d.ts +2 -4
  97. package/dist/features/products/components/index.js +1 -2
  98. package/dist/features/promotions/components/CouponCard.d.ts +11 -1
  99. package/dist/features/promotions/components/CouponCard.js +36 -2
  100. package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +27 -0
  101. package/dist/features/promotions/repository/claimed-coupons.repository.js +115 -0
  102. package/dist/features/promotions/schemas/firestore.d.ts +34 -0
  103. package/dist/features/promotions/schemas/firestore.js +14 -1
  104. package/dist/features/reviews/components/index.d.ts +0 -2
  105. package/dist/features/reviews/components/index.js +0 -1
  106. package/dist/features/seller/components/BrandInlineSelect.js +23 -3
  107. package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
  108. package/dist/features/seller/components/SellerAddressesView.js +3 -2
  109. package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
  110. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
  111. package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
  112. package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
  113. package/dist/features/seller/components/SellerBidsView.js +33 -3
  114. package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
  115. package/dist/features/seller/components/SellerBundlesView.js +104 -0
  116. package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
  117. package/dist/features/seller/components/SellerClassifiedView.js +113 -0
  118. package/dist/features/seller/components/SellerDashboardView.js +19 -7
  119. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
  120. package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
  121. package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
  122. package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
  123. package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
  124. package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
  125. package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
  126. package/dist/features/seller/components/SellerLiveView.js +113 -0
  127. package/dist/features/seller/components/SellerOrdersView.js +53 -2
  128. package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
  129. package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
  130. package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
  131. package/dist/features/seller/components/SellerProductShell.d.ts +3 -1
  132. package/dist/features/seller/components/SellerProductShell.js +35 -11
  133. package/dist/features/seller/components/SellerProductsCards.d.ts +19 -0
  134. package/dist/features/seller/components/SellerProductsCards.js +19 -0
  135. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +14 -0
  136. package/dist/features/seller/components/SellerProductsFilterDrawer.js +22 -0
  137. package/dist/features/seller/components/SellerProductsView.d.ts +3 -1
  138. package/dist/features/seller/components/SellerProductsView.js +92 -49
  139. package/dist/features/seller/components/SellerReviewsView.js +67 -4
  140. package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
  141. package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
  142. package/dist/features/seller/components/SellerShippingView.js +37 -10
  143. package/dist/features/seller/components/SellerSidebar.js +2 -1
  144. package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
  145. package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
  146. package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
  147. package/dist/features/seller/components/SellerStorefrontView.js +26 -29
  148. package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
  149. package/dist/features/seller/components/SellerTemplatesView.js +265 -0
  150. package/dist/features/seller/components/index.d.ts +26 -0
  151. package/dist/features/seller/components/index.js +13 -0
  152. package/dist/features/seller/components/seller-products-styles.d.ts +7 -0
  153. package/dist/features/seller/components/seller-products-styles.js +14 -0
  154. package/dist/features/shell/FormShell.d.ts +7 -1
  155. package/dist/features/shell/FormShell.js +5 -2
  156. package/dist/features/store-extensions/index.d.ts +4 -0
  157. package/dist/features/store-extensions/index.js +4 -0
  158. package/dist/features/store-extensions/repository/rbac.repositories.d.ts +23 -0
  159. package/dist/features/store-extensions/repository/rbac.repositories.js +32 -0
  160. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +91 -0
  161. package/dist/features/store-extensions/repository/store-extensions.repositories.js +127 -0
  162. package/dist/features/store-extensions/schemas/firestore.d.ts +244 -0
  163. package/dist/features/store-extensions/schemas/firestore.js +158 -0
  164. package/dist/features/store-extensions/schemas/rbac.d.ts +65 -0
  165. package/dist/features/store-extensions/schemas/rbac.js +43 -0
  166. package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
  167. package/dist/features/stores/components/InteractiveStoreCard.js +7 -3
  168. package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
  169. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
  170. package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
  171. package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
  172. package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
  173. package/dist/features/stores/components/StoreGuideHubView.js +89 -0
  174. package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
  175. package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
  176. package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
  177. package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
  178. package/dist/features/stores/components/StoreProductsListing.js +6 -10
  179. package/dist/features/stores/components/StoreReviewsListing.js +100 -23
  180. package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
  181. package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
  182. package/dist/features/stores/components/index.d.ts +12 -1
  183. package/dist/features/stores/components/index.js +6 -1
  184. package/dist/features/stores/hooks/useStores.d.ts +5 -0
  185. package/dist/features/stores/hooks/useStores.js +10 -0
  186. package/dist/features/stores/manifest.js +1 -1
  187. package/dist/features/stores/schemas/firestore.d.ts +2 -0
  188. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
  189. package/dist/index.d.ts +60 -9
  190. package/dist/index.js +66 -15
  191. package/dist/next/routing/route-map.d.ts +134 -0
  192. package/dist/next/routing/route-map.js +59 -0
  193. package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
  194. package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
  195. package/dist/providers/db-firebase/sieve.d.ts +49 -0
  196. package/dist/providers/db-firebase/sieve.js +61 -8
  197. package/dist/react/hooks/useFormStatePreservation.d.ts +17 -0
  198. package/dist/react/hooks/useFormStatePreservation.js +62 -0
  199. package/dist/react/hooks/useInlineRowEdit.d.ts +24 -0
  200. package/dist/react/hooks/useInlineRowEdit.js +68 -0
  201. package/dist/react/index.d.ts +4 -0
  202. package/dist/react/index.js +4 -0
  203. package/dist/repositories/index.d.ts +5 -0
  204. package/dist/repositories/index.js +7 -0
  205. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  206. package/dist/seed/claimed-coupons-seed-data.d.ts +13 -0
  207. package/dist/seed/claimed-coupons-seed-data.js +79 -0
  208. package/dist/seed/index.d.ts +2 -0
  209. package/dist/seed/index.js +3 -0
  210. package/dist/seed/manifest.js +17 -0
  211. package/dist/seed/store-extensions-seed-data.d.ts +19 -0
  212. package/dist/seed/store-extensions-seed-data.js +421 -0
  213. package/dist/tailwind-utilities.css +1 -1
  214. package/dist/ui/components/Button.style.css +20 -12
  215. package/dist/ui/components/ClaimCouponButton.d.ts +28 -0
  216. package/dist/ui/components/ClaimCouponButton.js +89 -0
  217. package/dist/ui/components/ListingToolbar.d.ts +8 -1
  218. package/dist/ui/components/ListingToolbar.js +4 -2
  219. package/dist/ui/components/QuickCreateModal.d.ts +15 -0
  220. package/dist/ui/components/QuickCreateModal.js +48 -0
  221. package/dist/ui/components/QuickCreateModal.style.css +84 -0
  222. package/dist/ui/components/StickyBottomBar.d.ts +22 -0
  223. package/dist/ui/components/StickyBottomBar.js +20 -0
  224. package/dist/ui/components/VacationBanner.d.ts +11 -0
  225. package/dist/ui/components/VacationBanner.js +16 -0
  226. package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
  227. package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
  228. package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
  229. package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
  230. package/dist/ui/index.d.ts +9 -1
  231. package/dist/ui/index.js +4 -0
  232. package/package.json +8 -2
@@ -5,9 +5,17 @@ export interface RaffleWinnerEvent {
5
5
  raffleEntryCount?: number;
6
6
  raffleTriggeredAt?: string | Date;
7
7
  raffleGithubFunctionUrl?: string;
8
+ /**
9
+ * Plan §10 — when the prize is a coupon, the issuing code so the winner
10
+ * can claim it directly via the ClaimCouponButton. Pair with
11
+ * `currentUserIsWinner` so the CTA only renders for the actual winner.
12
+ */
13
+ rafflePrizeCouponCode?: string;
8
14
  }
9
15
  export interface EventRaffleWinnerViewProps {
10
16
  event: RaffleWinnerEvent;
17
+ /** When true (= page resolver matched the viewer to raffleWinnerUserId), show the Claim CTA. */
18
+ currentUserIsWinner?: boolean;
11
19
  labels?: {
12
20
  heading?: string;
13
21
  winnerLabel?: string;
@@ -18,4 +26,4 @@ export interface EventRaffleWinnerViewProps {
18
26
  anonymous?: string;
19
27
  };
20
28
  }
21
- export declare function EventRaffleWinnerView({ event, labels }: EventRaffleWinnerViewProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare function EventRaffleWinnerView({ event, currentUserIsWinner, labels }: EventRaffleWinnerViewProps): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Article, Div, Heading, Span, Text, TextLink } from "../../../ui";
2
+ import { Article, ClaimCouponButton, Div, Heading, Span, Text, TextLink } from "../../../ui";
3
3
  const DEFAULT_LABELS = {
4
4
  heading: "Raffle Winner",
5
5
  winnerLabel: "Winner",
@@ -9,10 +9,10 @@ const DEFAULT_LABELS = {
9
9
  notDrawnYet: "The raffle has not been drawn yet.",
10
10
  anonymous: "Anonymous participant",
11
11
  };
12
- export function EventRaffleWinnerView({ event, labels }) {
12
+ export function EventRaffleWinnerView({ event, currentUserIsWinner, labels }) {
13
13
  const l = { ...DEFAULT_LABELS, ...(labels ?? {}) };
14
14
  if (!event.raffleWinnerUserId) {
15
15
  return (_jsx(Div, { className: "rounded-xl border border-dashed border-zinc-300 dark:border-zinc-700 px-6 py-10 text-center", children: _jsx(Text, { className: "text-zinc-500 dark:text-zinc-400", children: l.notDrawnYet }) }));
16
16
  }
17
- return (_jsxs(Article, { className: "space-y-4", children: [_jsxs(Heading, { level: 2, className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100", children: ["\uD83C\uDF9F\uFE0F ", l.heading] }), _jsxs(Div, { className: "rounded-2xl border border-amber-200 bg-amber-50/60 dark:border-amber-700 dark:bg-amber-900/20 p-6 space-y-3", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-xs uppercase tracking-wide text-amber-700 dark:text-amber-300", children: l.winnerLabel }), _jsx(Text, { className: "mt-1 text-2xl font-bold text-amber-900 dark:text-amber-100", children: event.raffleWinnerDisplayName?.trim() || l.anonymous })] }), event.rafflePrize ? (_jsxs(Div, { children: [_jsx(Text, { className: "text-xs uppercase tracking-wide text-amber-700 dark:text-amber-300", children: l.prizeLabel }), _jsx(Text, { className: "mt-1 text-base text-zinc-800 dark:text-zinc-100", children: event.rafflePrize })] })) : null, _jsxs(Div, { className: "flex flex-wrap gap-x-6 gap-y-2 pt-2 text-sm text-zinc-600 dark:text-zinc-300", children: [typeof event.raffleEntryCount === "number" ? (_jsxs(Span, { children: [l.poolLabel, ":", " ", _jsx(Span, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: event.raffleEntryCount })] })) : null, event.raffleTriggeredAt ? (_jsx(Span, { children: new Date(event.raffleTriggeredAt).toLocaleString() })) : null] })] }), event.raffleGithubFunctionUrl ? (_jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 p-4 text-sm", children: [_jsx(Text, { className: "font-medium text-zinc-700 dark:text-zinc-200", children: l.fairnessLabel }), _jsx(TextLink, { href: event.raffleGithubFunctionUrl, className: "mt-1 block break-all text-primary-600 dark:text-primary-400 hover:underline", children: event.raffleGithubFunctionUrl })] })) : null] }));
17
+ return (_jsxs(Article, { className: "space-y-4", children: [_jsxs(Heading, { level: 2, className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100", children: ["\uD83C\uDF9F\uFE0F ", l.heading] }), _jsxs(Div, { className: "rounded-2xl border border-amber-200 bg-amber-50/60 dark:border-amber-700 dark:bg-amber-900/20 p-6 space-y-3", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-xs uppercase tracking-wide text-amber-700 dark:text-amber-300", children: l.winnerLabel }), _jsx(Text, { className: "mt-1 text-2xl font-bold text-amber-900 dark:text-amber-100", children: event.raffleWinnerDisplayName?.trim() || l.anonymous })] }), event.rafflePrize ? (_jsxs(Div, { children: [_jsx(Text, { className: "text-xs uppercase tracking-wide text-amber-700 dark:text-amber-300", children: l.prizeLabel }), _jsx(Text, { className: "mt-1 text-base text-zinc-800 dark:text-zinc-100", children: event.rafflePrize })] })) : null, currentUserIsWinner && event.rafflePrizeCouponCode ? (_jsx(Div, { className: "pt-2", children: _jsx(ClaimCouponButton, { couponCode: event.rafflePrizeCouponCode, source: "raffle", size: "sm" }) })) : null, _jsxs(Div, { className: "flex flex-wrap gap-x-6 gap-y-2 pt-2 text-sm text-zinc-600 dark:text-zinc-300", children: [typeof event.raffleEntryCount === "number" ? (_jsxs(Span, { children: [l.poolLabel, ":", " ", _jsx(Span, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: event.raffleEntryCount })] })) : null, event.raffleTriggeredAt ? (_jsx(Span, { children: new Date(event.raffleTriggeredAt).toLocaleString() })) : null] })] }), event.raffleGithubFunctionUrl ? (_jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 p-4 text-sm", children: [_jsx(Text, { className: "font-medium text-zinc-700 dark:text-zinc-200", children: l.fairnessLabel }), _jsx(TextLink, { href: event.raffleGithubFunctionUrl, className: "mt-1 block break-all text-primary-600 dark:text-primary-400 hover:underline", children: event.raffleGithubFunctionUrl })] })) : null] }));
18
18
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useCallback, useMemo, useState } from "react";
4
- import { Button, Div, Heading, LoginRequiredModal, Span, Text } from "../../../ui";
4
+ import { Button, ClaimCouponButton, Div, Heading, LoginRequiredModal, Span, Text } from "../../../ui";
5
5
  import { isAuthError } from "../../../utils/auth-error";
6
6
  const DEFAULT_LABELS = {
7
7
  heading: "Spin the Wheel",
@@ -67,5 +67,5 @@ export function SpinWheelView({ eventId, prizes, alreadyUsed, initialPrizeId, in
67
67
  0% { transform: rotate(0deg); }
68
68
  100% { transform: rotate(2160deg); }
69
69
  }
70
- ` })] }), wonPrize ? (_jsxs(Div, { className: "rounded-xl border border-emerald-300 bg-emerald-50 dark:border-emerald-700 dark:bg-emerald-900/30 p-4 text-center", children: [_jsx(Text, { className: "text-sm uppercase tracking-wide text-emerald-700 dark:text-emerald-300", children: l.wonHeadline }), _jsx(Text, { className: "mt-1 text-lg font-bold text-emerald-900 dark:text-emerald-100", children: wonPrize.label }), resultCoupon ? (_jsxs(Text, { className: "mt-2 text-sm text-emerald-800 dark:text-emerald-200", children: [l.couponHint, " ", _jsx(Span, { className: "font-mono font-semibold", children: resultCoupon })] })) : null] })) : null, error ? (_jsx(Text, { className: "text-sm text-rose-600 dark:text-rose-400 text-center", children: error })) : null, _jsx(Div, { className: "flex justify-center", children: _jsx(Button, { variant: "primary", size: "lg", onClick: handleSpin, disabled: disabled, children: spinning ? l.spinningButton : l.spinButton }) }), alreadyUsed ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.alreadySpun })) : !inWindow ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.outsideWindow })) : null] }));
70
+ ` })] }), wonPrize ? (_jsxs(Div, { className: "rounded-xl border border-emerald-300 bg-emerald-50 dark:border-emerald-700 dark:bg-emerald-900/30 p-4 text-center", children: [_jsx(Text, { className: "text-sm uppercase tracking-wide text-emerald-700 dark:text-emerald-300", children: l.wonHeadline }), _jsx(Text, { className: "mt-1 text-lg font-bold text-emerald-900 dark:text-emerald-100", children: wonPrize.label }), resultCoupon ? (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "mt-2 text-sm text-emerald-800 dark:text-emerald-200", children: [l.couponHint, " ", _jsx(Span, { className: "font-mono font-semibold", children: resultCoupon })] }), _jsx(Div, { className: "mt-3 flex justify-center", children: _jsx(ClaimCouponButton, { couponCode: resultCoupon, size: "sm", source: "spin" }) })] })) : null] })) : null, error ? (_jsx(Text, { className: "text-sm text-rose-600 dark:text-rose-400 text-center", children: error })) : null, _jsx(Div, { className: "flex justify-center", children: _jsx(Button, { variant: "primary", size: "lg", onClick: handleSpin, disabled: disabled, children: spinning ? l.spinningButton : l.spinButton }) }), alreadyUsed ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.alreadySpun })) : !inWindow ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.outsideWindow })) : null] }));
71
71
  }
@@ -0,0 +1,9 @@
1
+ import { BaseRepository } from "../../../providers/db-firebase";
2
+ import { type GroupedListingDocument } from "../schemas/firestore";
3
+ export declare class GroupedListingsRepository extends BaseRepository<GroupedListingDocument> {
4
+ constructor();
5
+ listByStore(storeId: string): Promise<{
6
+ items: GroupedListingDocument[];
7
+ }>;
8
+ }
9
+ export declare const groupedListingsRepository: GroupedListingsRepository;
@@ -0,0 +1,12 @@
1
+ import { BaseRepository } from "../../../providers/db-firebase";
2
+ import { GROUPED_LISTINGS_COLLECTION, } from "../schemas/firestore";
3
+ export class GroupedListingsRepository extends BaseRepository {
4
+ constructor() {
5
+ super(GROUPED_LISTINGS_COLLECTION);
6
+ }
7
+ async listByStore(storeId) {
8
+ const items = await this.findBy("storeId", storeId);
9
+ return { items };
10
+ }
11
+ }
12
+ export const groupedListingsRepository = new GroupedListingsRepository();
@@ -10,5 +10,10 @@ export interface BottomNavLayoutProps {
10
10
  *
11
11
  * Provides the `nav + ul` container with correct z-index, background, height,
12
12
  * and safe-area inset. Pass `li`-wrapped items as children.
13
+ *
14
+ * Mirrors `AppLayoutShell`'s `--header-height` contract: while mounted, writes
15
+ * `--bottom-nav-height: 4rem` to `:root` (sub-`lg` only — the nav itself is
16
+ * `lg:hidden`). Sticky CTAs read `var(--bottom-nav-height, 0px)` to position
17
+ * themselves above the navbar so they don't get clipped.
13
18
  */
14
19
  export declare function BottomNavLayout({ ariaLabel, children, id, className, }: BottomNavLayoutProps): import("react/jsx-runtime").JSX.Element;
@@ -1,11 +1,32 @@
1
+ "use client";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect } from "react";
2
4
  import { Nav, Ul } from "../../ui";
3
5
  /**
4
6
  * BottomNavLayout — generic fixed-bottom navigation shell.
5
7
  *
6
8
  * Provides the `nav + ul` container with correct z-index, background, height,
7
9
  * and safe-area inset. Pass `li`-wrapped items as children.
10
+ *
11
+ * Mirrors `AppLayoutShell`'s `--header-height` contract: while mounted, writes
12
+ * `--bottom-nav-height: 4rem` to `:root` (sub-`lg` only — the nav itself is
13
+ * `lg:hidden`). Sticky CTAs read `var(--bottom-nav-height, 0px)` to position
14
+ * themselves above the navbar so they don't get clipped.
8
15
  */
9
16
  export function BottomNavLayout({ ariaLabel, children, id = "bottom-navbar", className, }) {
17
+ useEffect(() => {
18
+ const root = document.documentElement;
19
+ const mql = typeof window !== "undefined" ? window.matchMedia("(max-width: 1023.98px)") : null;
20
+ const sync = () => {
21
+ // Only expose the var when the navbar is actually rendered (below lg).
22
+ root.style.setProperty("--bottom-nav-height", mql && mql.matches ? "4rem" : "0px");
23
+ };
24
+ sync();
25
+ mql?.addEventListener("change", sync);
26
+ return () => {
27
+ mql?.removeEventListener("change", sync);
28
+ root.style.setProperty("--bottom-nav-height", "0px");
29
+ };
30
+ }, []);
10
31
  return (_jsx(Nav, { id: id, "aria-label": ariaLabel, className: `fixed bottom-0 left-0 right-0 lg:hidden z-40 bg-white/90 dark:bg-slate-950/90 backdrop-blur-md border-t border-zinc-200/80 dark:border-slate-800/80 shadow-2xl pb-safe${className ? ` ${className}` : ""}`, children: _jsx(Ul, { className: "flex items-stretch h-16", children: children }) }));
11
32
  }
@@ -4,6 +4,7 @@ import { useWishlistCount } from "../wishlist/hooks/useWishlistCount";
4
4
  import { useNotifications } from "../account/hooks/useNotifications";
5
5
  import { useDashboardNav } from "./DashboardNavContext";
6
6
  import { TitleBarLayout } from "./TitleBarLayout";
7
+ import { ROUTES } from "../../next/routing/route-map";
7
8
  /**
8
9
  * TitleBar � domain shell over TitleBarLayout.
9
10
  *
@@ -18,7 +19,12 @@ export function TitleBar({ suppressDashboardNav, onBeforeToggleDashboardNav, use
18
19
  const wishlistCount = useWishlistCount(userId);
19
20
  const { unreadCount } = useNotifications(1);
20
21
  const { hasNav: hasDashboardNav, toggleNav: toggleDashboardNav } = useDashboardNav();
21
- return (_jsx(TitleBarLayout, { ...rest, cartCount: cartCount, wishlistCount: wishlistCount, unreadNotificationCount: rest.user ? unreadCount : 0, hasDashboardNav: suppressDashboardNav ? false : hasDashboardNav, onToggleDashboardNav: suppressDashboardNav
22
+ return (_jsx(TitleBarLayout, { ...rest, cartCount: cartCount, wishlistCount: wishlistCount, unreadNotificationCount: rest.user ? unreadCount : 0,
23
+ // Default the bell to the user notifications page when the user is
24
+ // authenticated — consumers can override by passing their own
25
+ // `notificationsHref` through `...rest`.
26
+ notificationsHref: rest.notificationsHref ??
27
+ (rest.user ? String(ROUTES.USER.NOTIFICATIONS) : undefined), hasDashboardNav: suppressDashboardNav ? false : hasDashboardNav, onToggleDashboardNav: suppressDashboardNav
22
28
  ? undefined
23
29
  : () => {
24
30
  onBeforeToggleDashboardNav?.();
@@ -25,8 +25,14 @@ export interface TitleBarLayoutProps {
25
25
  cartHref?: string;
26
26
  cartCount?: number;
27
27
  profileHref?: string;
28
- /** Unread notification count to show as a badge on the profile icon. */
28
+ /** Unread notification count shown on the dedicated bell icon only. */
29
29
  unreadNotificationCount?: number;
30
+ /**
31
+ * Href for the dedicated notification bell icon rendered to the LEFT of the
32
+ * wishlist icon. When provided, the bell is rendered (with count badge) and
33
+ * navigates to the user notifications page by default.
34
+ */
35
+ notificationsHref?: string;
30
36
  loginHref?: string;
31
37
  registerHref?: string;
32
38
  user?: TitleBarUser | null;
@@ -58,4 +64,4 @@ export interface TitleBarLayoutProps {
58
64
  *
59
65
  * Receives all domain data as props — zero domain imports.
60
66
  */
61
- export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): import("react/jsx-runtime").JSX.Element;
67
+ export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): import("react/jsx-runtime").JSX.Element;
@@ -14,7 +14,7 @@ const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-cente
14
14
  *
15
15
  * Receives all domain data as props — zero domain imports.
16
16
  */
17
- export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
17
+ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
18
18
  // ── Element builders ────────────────────────────────────────────────────────
19
19
  const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-1 px-3 py-1 rounded-full text-xs font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-200 dark:hover:bg-secondary-900/60 transition-colors border border-primary-200/60 dark:border-secondary-700/40", children: [_jsx("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.37.86.58 1.41.58.55 0 1.05-.21 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z" }) }), _jsx(Span, { className: "hidden sm:inline", children: "Today's Deals" })] })) : null;
20
20
  const themeBtn = onToggleTheme ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", onClick: onToggleTheme, className: iconBtn, children: isDark ? (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m8.66-9h-1M4.34 12h-1m15.07-6.07-.71.71M6.34 17.66l-.71.71m12.73 0-.71-.71M6.34 6.34l-.71-.71M12 5a7 7 0 1 0 0 14A7 7 0 0 0 12 5z" }) })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12.79A9 9 0 1 1 11.21 3a7 7 0 1 0 9.79 9.79z" }) })) })) : null;
@@ -22,15 +22,19 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
22
22
  // Compare is always lg+ (desktop-only feature, less critical on mobile)
23
23
  const compareEl = compareHref ? (_jsx(Link, { href: compareHref, "aria-label": "Compare items", className: `${iconBtn} hidden lg:flex`, children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" }) }) })) : null;
24
24
  const searchBtn = (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Search", onClick: onSearchToggle, className: iconBtn, children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z" }) }) }));
25
+ // Dedicated notification bell — rendered immediately before the wishlist
26
+ // icon (per layout request). Falls back to invisible when no href is set;
27
+ // the count badge mirrors the wishlist/cart pattern.
28
+ const notificationsEl = notificationsHref ? (_jsxs(Link, { href: notificationsHref, "aria-label": `Notifications${unreadNotificationCount > 0 ? `, ${unreadNotificationCount} unread` : ""}`, className: `relative ${iconBtn}`, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" }) }), unreadNotificationCount > 0 && (_jsx(Span, { className: countBadge, children: unreadNotificationCount > 99 ? "99+" : unreadNotificationCount }))] })) : null;
25
29
  const wishlistEl = wishlistHref ? (_jsxs(Link, { href: wishlistHref, "aria-label": `Wishlist${wishlistCount > 0 ? `, ${wishlistCount} items` : ""}`, className: `relative ${iconBtn}`, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4.318 6.318a4.5 4.5 0 0 0 0 6.364L12 20.364l7.682-7.682a4.5 4.5 0 0 0-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 0 0-6.364 0z" }) }), wishlistCount > 0 && (_jsx(Span, { className: countBadge, children: wishlistCount > 9 ? "9+" : wishlistCount }))] })) : null;
26
30
  const cartEl = cartHref ? (_jsxs(Link, { href: cartHref, "aria-label": `Cart${cartCount > 0 ? `, ${cartCount} items` : ""}`, className: `relative ${iconBtn}`, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0z" }) }), cartCount > 0 && (_jsx(Span, { className: countBadge, children: cartCount > 9 ? "9+" : cartCount }))] })) : null;
27
- const profileEl = profileHref ? (_jsxs(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}${unreadNotificationCount > 0 ? `, ${unreadNotificationCount} unread alerts` : ""}` : "Sign in", className: `relative ${iconBtn}`, children: [user?.photoURL ? (
28
- // eslint-disable-next-line @next/next/no-img-element
29
- _jsx("img", { src: user.photoURL, alt: user.displayName ?? "Profile", width: 28, height: 28, loading: "lazy", className: "w-7 h-7 rounded-full object-cover" })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0zM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632z" }) })), unreadNotificationCount > 0 && (_jsx(Span, { className: countBadge, children: unreadNotificationCount > 99 ? "99+" : unreadNotificationCount }))] })) : null;
31
+ const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: `relative ${iconBtn}`, children: user?.photoURL ? (
32
+ // eslint-disable-next-line @next/next/no-img-element
33
+ _jsx("img", { src: user.photoURL, alt: user.displayName ?? "Profile", width: 28, height: 28, loading: "lazy", className: "w-7 h-7 rounded-full object-cover" })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0zM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632z" }) })) })) : null;
30
34
  const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex items-center", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-3 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-300 hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-slate-800", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-3 py-1.5 text-sm font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
31
- const hasTb2 = !!(wishlistEl || cartEl || profileEl);
35
+ const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
32
36
  // ── Render ───────────────────────────────────────────────────────────────────
33
- return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-white/95 dark:bg-slate-950/95 backdrop-blur-md border-b border-zinc-100 dark:border-slate-800 shadow-sm ${className}`, children: [promoStripText && (_jsx(Div, { className: "bg-gradient-to-r from-primary-700 to-secondary-600 dark:from-primary-700 dark:to-cobalt-700 text-white text-xs py-1 text-center font-medium", children: promoStripText })), _jsxs(Div, { className: "container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) }), siteLogoUrl ? (_jsx(Div, { className: "absolute inset-y-0 left-1/2 -translate-x-1/2 flex items-center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { src: siteLogoUrl, title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) })) : (navSlot && _jsx(Div, { className: "hidden md:flex", children: navSlot })), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
37
+ return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-white/95 dark:bg-slate-950/95 backdrop-blur-md border-b border-zinc-100 dark:border-slate-800 shadow-sm ${className}`, children: [promoStripText && (_jsx(Div, { className: "bg-gradient-to-r from-primary-700 to-secondary-600 dark:from-primary-700 dark:to-cobalt-700 text-white text-xs py-1 text-center font-medium", children: promoStripText })), _jsxs(Div, { className: "container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) }), siteLogoUrl ? (_jsx(Div, { className: "absolute inset-y-0 left-1/2 -translate-x-1/2 flex items-center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { src: siteLogoUrl, title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) })) : (navSlot && _jsx(Div, { className: "hidden md:flex", children: navSlot })), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
34
38
  ? profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })
35
- : authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, promotionsEl, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t border-zinc-100 dark:border-slate-800 px-1", children: [wishlistEl, cartEl, profileEl] }))] })] }));
39
+ : authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, promotionsEl, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t border-zinc-100 dark:border-slate-800 px-1", children: [notificationsEl, wishlistEl, cartEl, profileEl] }))] })] }));
36
40
  }
@@ -78,7 +78,7 @@ export function MediaUploadField({ label, value, onChange, onChangeField, onUplo
78
78
  // SB-UNI-Z5 2026-05-13 — `kind` derives accept/maxSizeMB when not set
79
79
  // explicitly; explicit props still win. "*" + 50 stay as the floor for
80
80
  // back-compat with callers that pass neither.
81
- kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText, captureSource = "both", captureMode = "photo", enableTrim = true, enableThumbnail = true, onThumbnailChange, showYoutube = false, showExternal = false, onAbort, onStagedUrlsChange, isPersisted = false, }) {
81
+ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText, captureSource = "both", captureMode = "photo", enableTrim = true, enableThumbnail = true, onThumbnailChange, showYoutube = true, showExternal = true, onAbort, onStagedUrlsChange, isPersisted = false, }) {
82
82
  const [isLoading, setIsLoading] = useState(false);
83
83
  const [error, setError] = useState(null);
84
84
  const [pendingVideoUrl, setPendingVideoUrl] = useState(null);
@@ -143,9 +143,9 @@ export declare const orderSchema: z.ZodObject<{
143
143
  createdAt?: string | undefined;
144
144
  updatedAt?: string | undefined;
145
145
  notes?: string | undefined;
146
+ couponCode?: string | undefined;
146
147
  tax?: number | undefined;
147
148
  trackingNumber?: string | undefined;
148
- couponCode?: string | undefined;
149
149
  shippingCarrier?: string | undefined;
150
150
  discount?: number | undefined;
151
151
  paymentGateway?: string | undefined;
@@ -178,9 +178,9 @@ export declare const orderSchema: z.ZodObject<{
178
178
  createdAt?: string | undefined;
179
179
  updatedAt?: string | undefined;
180
180
  notes?: string | undefined;
181
+ couponCode?: string | undefined;
181
182
  tax?: number | undefined;
182
183
  trackingNumber?: string | undefined;
183
- couponCode?: string | undefined;
184
184
  shippingCarrier?: string | undefined;
185
185
  discount?: number | undefined;
186
186
  paymentGateway?: string | undefined;
@@ -116,17 +116,22 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
116
116
  isWishlisted: (productId) => wishlistedIds.has(productId),
117
117
  };
118
118
  const handleAddToCart = useCallback((product) => {
119
- localCart.add(product.id, 1, {
119
+ const snapshot = {
120
120
  productTitle: product.title,
121
121
  productImage: product.mainImage,
122
122
  price: product.price,
123
- });
124
- pushCartOp({ op: "add", productId: product.id, quantity: 1, productTitle: product.title, productImage: product.mainImage, price: product.price });
123
+ storeId: product.storeId,
124
+ storeName: product.storeName,
125
+ };
126
+ localCart.add(product.id, 1, snapshot);
127
+ pushCartOp({ op: "add", productId: product.id, quantity: 1, ...snapshot });
125
128
  showToast("Added to cart", "success");
126
129
  }, [localCart, showToast]);
127
130
  const selection = useBulkSelection({ items: preOrders, keyExtractor: (p) => p.id });
128
131
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
129
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show closed" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showClosed, onClick: () => table.set(TABLE_KEYS.SHOW_CLOSED, showClosed ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showClosed ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showClosed ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
132
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
133
+ { label: "Show closed", active: showClosed, onChange: (next) => table.set(TABLE_KEYS.SHOW_CLOSED, next ? "true" : "") },
134
+ ] }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
130
135
  {
131
136
  id: ACTION_ID.ADD_TO_CART,
132
137
  label: ACTION_META[ACTION_ID.ADD_TO_CART].label,
@@ -135,8 +140,9 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
135
140
  onClick: () => {
136
141
  const selected = preOrders.filter((p) => selection.selectedIdSet.has(p.id));
137
142
  selected.forEach((p) => {
138
- localCart.add(p.id, 1, { productTitle: p.title, productImage: p.mainImage, price: p.price });
139
- pushCartOp({ op: "add", productId: p.id, quantity: 1, productTitle: p.title, productImage: p.mainImage, price: p.price });
143
+ const snapshot = { productTitle: p.title, productImage: p.mainImage, price: p.price, storeId: p.storeId, storeName: p.storeName };
144
+ localCart.add(p.id, 1, snapshot);
145
+ pushCartOp({ op: "add", productId: p.id, quantity: 1, ...snapshot });
140
146
  });
141
147
  showToast(`${selected.length} items added to cart`, "success");
142
148
  selection.clearSelection();
@@ -111,7 +111,9 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
111
111
  isWishlisted: (productId) => wishlistedIds.has(productId),
112
112
  };
113
113
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-3 gap-4";
114
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show ended" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showEnded, onClick: () => table.set(TABLE_KEYS.SHOW_ENDED, showEnded ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showEnded ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showEnded ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
114
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
115
+ { label: "Show ended", active: showEnded, onChange: (next) => table.set(TABLE_KEYS.SHOW_ENDED, next ? "true" : "") },
116
+ ] }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
115
117
  {
116
118
  id: ACTION_ID.WATCH_AUCTION,
117
119
  label: "Add to Watchlist",
@@ -0,0 +1,21 @@
1
+ import type { CategoryDocument } from "../../categories/schemas/firestore";
2
+ export type MarketplaceBundleCardData = Pick<CategoryDocument, "id" | "name" | "slug" | "bundleItemDetails" | "bundleProductIds" | "bundlePriceInPaise" | "bundleStockStatus" | "display">;
3
+ export interface MarketplaceBundleCardLabels {
4
+ bundleBadge?: string;
5
+ inStockBadge?: string;
6
+ outOfStockBadge?: string;
7
+ itemsLabel?: (count: number) => string;
8
+ }
9
+ export interface MarketplaceBundleCardProps {
10
+ bundle: MarketplaceBundleCardData;
11
+ className?: string;
12
+ variant?: "grid" | "list";
13
+ selectable?: boolean;
14
+ isSelected?: boolean;
15
+ onSelect?: (id: string, selected: boolean) => void;
16
+ href?: string;
17
+ hrefBuilder?: (bundle: MarketplaceBundleCardData) => string;
18
+ onNavigate?: (href: string) => void;
19
+ labels?: MarketplaceBundleCardLabels;
20
+ }
21
+ export declare function MarketplaceBundleCard({ bundle, className, variant, selectable, isSelected, onSelect, href, hrefBuilder, onNavigate, labels, }: MarketplaceBundleCardProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,56 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { useLongPress } from "../../../react/hooks/useLongPress";
6
+ import { ROUTES } from "../../../next";
7
+ import { formatCurrency } from "../../../utils";
8
+ import { getDefaultCurrency } from "../../../core/baseline-resolver";
9
+ import { BaseListingCard, Div, Row, Span, Text, TextLink, } from "../../../ui";
10
+ import { THEME_CONSTANTS } from "../../../tokens";
11
+ const DEFAULT_LABELS = {
12
+ bundleBadge: "Bundle",
13
+ inStockBadge: "In stock",
14
+ outOfStockBadge: "Out of stock",
15
+ itemsLabel: (count) => `${count} item${count !== 1 ? "s" : ""}`,
16
+ };
17
+ function resolveHref(bundle, href, hrefBuilder) {
18
+ if (href)
19
+ return href;
20
+ if (hrefBuilder)
21
+ return hrefBuilder(bundle);
22
+ return ROUTES.PUBLIC.BUNDLE_DETAIL(bundle.slug);
23
+ }
24
+ export function MarketplaceBundleCard({ bundle, className = "", variant = "grid", selectable = false, isSelected = false, onSelect, href, hrefBuilder, onNavigate, labels, }) {
25
+ const router = useRouter();
26
+ const mergedLabels = { ...DEFAULT_LABELS, ...labels };
27
+ const detailHref = resolveHref(bundle, href, hrefBuilder);
28
+ const longPress = useLongPress(() => onSelect?.(bundle.id, !isSelected));
29
+ const memberCount = bundle.bundleProductIds?.length ?? 0;
30
+ const stock = bundle.bundleStockStatus ?? "in_stock";
31
+ const cover = bundle.display?.coverImage;
32
+ const price = bundle.bundlePriceInPaise;
33
+ const collageTiles = (bundle.bundleItemDetails ?? [])
34
+ .filter((d) => Boolean(d.imageURL))
35
+ .slice(0, 4);
36
+ const showCollage = collageTiles.length >= 2;
37
+ const overflow = memberCount - collageTiles.length;
38
+ const handleNavigate = useCallback(() => {
39
+ if (onNavigate) {
40
+ onNavigate(String(detailHref));
41
+ return;
42
+ }
43
+ router.push(String(detailHref));
44
+ }, [detailHref, onNavigate, router]);
45
+ return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { className: `grid h-full w-full gap-0.5 bg-[var(--appkit-color-surface-muted)] ${collageTiles.length === 2 ? "grid-cols-2 grid-rows-1" : "grid-cols-2 grid-rows-2"}`, children: collageTiles.map((tile, i) => (_jsxs(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: [_jsx("img", { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, loading: "lazy", className: "absolute inset-0 h-full w-full object-cover" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Div, { className: "absolute inset-0 flex items-center justify-center bg-black/55 text-sm font-semibold text-white", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx("img", { src: cover, alt: bundle.name, loading: "lazy", className: "h-full w-full object-cover" }) })) : (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-[var(--appkit-color-surface-muted)]", children: _jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "No image" }) })) }), _jsxs(Div, { className: "absolute right-2 top-2 flex flex-col items-end gap-1", children: [_jsx(Span, { className: "inline-flex items-center rounded-full bg-violet-600 px-2 py-0.5 text-xs font-medium text-white", children: mergedLabels.bundleBadge }), _jsx(Span, { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${stock === "out_of_stock"
46
+ ? "bg-zinc-500 text-white"
47
+ : "bg-emerald-600 text-white"}`, children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-black/40", children: _jsx(Span, { className: "rounded bg-zinc-900/80 px-3 py-1 text-xs font-bold uppercase tracking-wider text-white", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
48
+ event.stopPropagation();
49
+ onSelect(bundle.id, !isSelected);
50
+ }, className: selectable || isSelected
51
+ ? "opacity-100"
52
+ : "opacity-0 group-hover:opacity-100 transition-opacity" }))] }), _jsxs(BaseListingCard.Info, { variant: variant, children: [_jsx(TextLink, { href: String(detailHref), children: _jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-medium text-zinc-900 dark:text-zinc-100`, children: bundle.name }) }), _jsxs(Row, { justify: "between", className: "mt-1 gap-2", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: price
53
+ ? formatCurrency(price / 100, getDefaultCurrency())
54
+ : "—" }), _jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: mergedLabels.itemsLabel(memberCount) })] }), _jsx("div", { className: "mt-2 w-full cursor-pointer rounded-md bg-violet-600 py-1.5 text-center text-xs font-semibold text-white transition-colors hover:bg-violet-700 active:scale-[0.98]", role: "button", tabIndex: 0, onClick: handleNavigate, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ")
55
+ handleNavigate(); }, children: "View Bundle" })] })] }));
56
+ }
@@ -115,5 +115,5 @@ export async function PrizeDrawDetailPageView({ id, initialPrizeDraw, currentUse
115
115
  price: pricePerEntry,
116
116
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
117
117
  storeName: storeName ?? undefined,
118
- } }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 transition-colors", children: "Prize Draws" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "text-zinc-700 dark:text-zinc-300 truncate max-w-[200px]", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", className: "mb-2 flex-wrap", children: [_jsx(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: "Prize Draw" }), _jsx(Span, { className: `inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold ${statusClass(revealStatus)}`, children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-0.5 text-xs font-medium text-zinc-700 dark:text-zinc-300", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title })] }), _jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Entries" }), _jsxs(Text, { className: "text-sm font-semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window opens" }), _jsx(Text, { className: "text-sm font-medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window closes" }), _jsx(Text, { className: "text-sm font-medium", children: revealEnd.toLocaleString() })] }))] }) }), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-500 mb-0.5", children: "Sold by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 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-zinc-700 dark:text-zinc-300" })) : undefined })), renderBuyBar: () => (_jsx(Div, { id: "prize-draw-buy-bar", className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, thumb: thumb, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl }) })) }), _jsxs(BuyBar, { children: [_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(pricePerEntry, currency) }), _jsx("a", { href: "#prize-draw-buy-bar", className: "appkit-button appkit-button--primary appkit-button--sm flex-1", children: _jsx("span", { className: "appkit-button__content", children: ACTIONS.PRIZE_DRAW["enter-draw"].label }) })] })] })] }));
118
+ } }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 transition-colors", children: "Prize Draws" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "text-zinc-700 dark:text-zinc-300 truncate max-w-[200px]", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", className: "mb-2 flex-wrap", children: [_jsx(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: "Prize Draw" }), _jsx(Span, { className: `inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold ${statusClass(revealStatus)}`, children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-0.5 text-xs font-medium text-zinc-700 dark:text-zinc-300", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title })] }), _jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Entries" }), _jsxs(Text, { className: "text-sm font-semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window opens" }), _jsx(Text, { className: "text-sm font-medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window closes" }), _jsx(Text, { className: "text-sm font-medium", children: revealEnd.toLocaleString() })] }))] }) }), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-500 mb-0.5", children: "Sold by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 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-zinc-700 dark:text-zinc-300" })) : undefined })), renderBuyBar: () => (_jsx(Div, { id: "prize-draw-buy-bar", className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, thumb: thumb, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl, storeId: typeof p.storeId === "string" ? p.storeId : undefined, storeName: storeName ?? undefined }) })) }), _jsxs(BuyBar, { children: [_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(pricePerEntry, currency) }), _jsx("a", { href: "#prize-draw-buy-bar", className: "appkit-button appkit-button--primary appkit-button--sm flex-1", children: _jsx("span", { className: "appkit-button__content", children: ACTIONS.PRIZE_DRAW["enter-draw"].label }) })] })] })] }));
119
119
  }
@@ -8,6 +8,8 @@ export interface PrizeDrawEntryActionsProps {
8
8
  remainingEntries: number;
9
9
  revealStatus: "pending" | "open" | "closed" | undefined;
10
10
  prizeGithubFileUrl?: string;
11
+ storeId?: string;
12
+ storeName?: string;
11
13
  }
12
14
  /**
13
15
  * Client buy panel for a prize-draw detail page (SB4-G).
@@ -16,4 +18,4 @@ export interface PrizeDrawEntryActionsProps {
16
18
  * - On confirm → add 1 entry to guest cart and route to /user/cart
17
19
  * - "View RNG source" link → prizeGithubFileUrl
18
20
  */
19
- export declare function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }: PrizeDrawEntryActionsProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, storeId, storeName, }: PrizeDrawEntryActionsProps): import("react/jsx-runtime").JSX.Element;
@@ -18,7 +18,7 @@ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
18
18
  * - On confirm → add 1 entry to guest cart and route to /user/cart
19
19
  * - "View RNG source" link → prizeGithubFileUrl
20
20
  */
21
- export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }) {
21
+ export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, storeId, storeName, }) {
22
22
  const router = useRouter();
23
23
  const cart = useGuestCart();
24
24
  const { showToast } = useToast();
@@ -31,22 +31,18 @@ export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pr
31
31
  requireAuth(ACTION_ID.ENTER_PRIZE_DRAW, () => setConsentOpen(true));
32
32
  }, [closed, requireAuth]);
33
33
  const handleConfirm = useCallback(() => {
34
- cart.add(productId, 1, {
34
+ const snapshot = {
35
35
  productTitle: title,
36
36
  productImage: thumb,
37
37
  price: pricePerEntry,
38
- });
39
- pushCartOp({
40
- op: "add",
41
- productId,
42
- quantity: 1,
43
- productTitle: title,
44
- productImage: thumb,
45
- price: pricePerEntry,
46
- });
38
+ storeId,
39
+ storeName,
40
+ };
41
+ cart.add(productId, 1, snapshot);
42
+ pushCartOp({ op: "add", productId, quantity: 1, ...snapshot });
47
43
  setConsentOpen(false);
48
44
  showToast("Entry added to cart", "success");
49
45
  router.push(String(ROUTES.USER.CART));
50
- }, [cart, productId, title, thumb, pricePerEntry, router, showToast]);
46
+ }, [cart, productId, title, thumb, pricePerEntry, storeId, storeName, router, showToast]);
51
47
  return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-xs font-normal text-[var(--appkit-color-text-muted)]", children: "per entry" })] }), _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["enter-draw"], variant: "primary", size: "md", className: "w-full", disabled: closed, onClick: handleEnter, children: closed ? "Draw closed" : undefined }), prizeGithubFileUrl ? (_jsx("a", { href: prizeGithubFileUrl, target: "_blank", rel: "noopener noreferrer", className: "text-center text-xs font-medium text-primary-600 underline-offset-4 hover:underline dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-center text-xs text-[var(--appkit-color-text-muted)]", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
52
48
  }
@@ -5,6 +5,10 @@ export interface ProductDetailActionsProps {
5
5
  productImage?: string;
6
6
  price?: number;
7
7
  currency?: string;
8
+ /** Store identifier (storeSlug) — propagated so guest cart lines carry it. */
9
+ storeId?: string;
10
+ /** Denormalised store display name — used by the cart group header. */
11
+ storeName?: string;
8
12
  inStock: boolean;
9
13
  variant?: "desktop" | "mobile";
10
14
  }
@@ -13,4 +17,4 @@ export interface ProductDetailActionsProps {
13
17
  * to the server APIs with guest-fallback. Replaces the previously-unwired
14
18
  * placeholder buttons inside ProductDetailPageView.
15
19
  */
16
- export declare function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, inStock, variant, }: ProductDetailActionsProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, storeId, storeName, inStock, variant, }: ProductDetailActionsProps): import("react/jsx-runtime").JSX.Element;
@@ -15,7 +15,7 @@ import { ACTION_ID } from "../constants/action-defs";
15
15
  * to the server APIs with guest-fallback. Replaces the previously-unwired
16
16
  * placeholder buttons inside ProductDetailPageView.
17
17
  */
18
- export function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, inStock, variant = "desktop", }) {
18
+ export function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, storeId, storeName, inStock, variant = "desktop", }) {
19
19
  const router = useRouter();
20
20
  const { showToast } = useToast();
21
21
  const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
@@ -34,6 +34,8 @@ export function ProductDetailActions({ productId, productSlug, productTitle, pro
34
34
  productTitle,
35
35
  productImage,
36
36
  price,
37
+ storeId,
38
+ storeName,
37
39
  });
38
40
  then?.();
39
41
  }
@@ -30,6 +30,8 @@ export interface ProductDetailPageViewProps {
30
30
  productImage?: string;
31
31
  price: number | null;
32
32
  currency: string;
33
+ storeId?: string;
34
+ storeName?: string;
33
35
  inStock: boolean;
34
36
  variant: "desktop" | "mobile";
35
37
  }) => React.ReactNode;
@@ -270,6 +270,8 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
270
270
  productImage: product.mainImage,
271
271
  price,
272
272
  currency,
273
+ storeId: typeof p.storeId === "string" ? p.storeId : undefined,
274
+ storeName: typeof p.storeName === "string" ? p.storeName : undefined,
273
275
  inStock,
274
276
  variant: "desktop",
275
277
  })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsx(Button, { variant: "ghost", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: "\u2661 Add to Wishlist" })] })), allowOffers && productType === "simple" && price !== null && renderOfferAction?.({
@@ -298,6 +300,8 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
298
300
  productImage: product.mainImage,
299
301
  price,
300
302
  currency,
303
+ storeId: typeof p.storeId === "string" ? p.storeId : undefined,
304
+ storeName: typeof p.storeName === "string" ? p.storeName : undefined,
301
305
  inStock,
302
306
  variant: "mobile",
303
307
  })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "secondary", size: "sm", className: "shrink-0", disabled: true, title: ACTION_NOT_WIRED, children: "Add to Cart" }), _jsx(Button, { variant: "primary", size: "sm", className: "flex-1", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" })] }))] })] })] }));