@mohasinac/appkit 4.1.3 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/dist/_internal/server/features/auth/actions.d.ts +0 -1
  2. package/dist/_internal/server/features/auth/actions.js +0 -1
  3. package/dist/_internal/server/features/bundles/data.d.ts +22 -4
  4. package/dist/_internal/server/features/bundles/data.js +41 -4
  5. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  6. package/dist/_internal/server/features/bundles/index.js +1 -1
  7. package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
  8. package/dist/_internal/server/features/checkout/actions.js +249 -257
  9. package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
  10. package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
  11. package/dist/_internal/server/features/orders/adapters.js +1 -14
  12. package/dist/_internal/server/features/products/actions.js +8 -3
  13. package/dist/_internal/server/features/products/service.d.ts +15 -0
  14. package/dist/_internal/server/features/products/service.js +41 -0
  15. package/dist/_internal/server/functions/firestore.d.ts +3 -1
  16. package/dist/_internal/server/functions/firestore.js +17 -1
  17. package/dist/_internal/server/functions/scheduled.d.ts +3 -5
  18. package/dist/_internal/server/functions/scheduled.js +14 -30
  19. package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
  20. package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
  21. package/dist/_internal/server/jobs/core/index.d.ts +4 -4
  22. package/dist/_internal/server/jobs/core/index.js +4 -4
  23. package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
  24. package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
  25. package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
  26. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  27. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  28. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
  29. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
  30. package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
  31. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +3 -2
  32. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  33. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
  34. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
  35. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
  36. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
  37. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
  38. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
  39. package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
  40. package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
  41. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  42. package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
  43. package/dist/_internal/server/jobs/handlers/index.d.ts +4 -4
  44. package/dist/_internal/server/jobs/handlers/index.js +9 -6
  45. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
  46. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
  47. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
  48. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
  49. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
  50. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
  51. package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
  52. package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
  53. package/dist/_internal/shared/actions/action-registry.js +40 -10
  54. package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
  55. package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
  56. package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
  57. package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
  58. package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
  59. package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
  60. package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
  61. package/dist/_internal/shared/checkout/rules/index.js +1 -1
  62. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
  63. package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
  64. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
  65. package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
  66. package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
  67. package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
  68. package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
  69. package/dist/_internal/shared/features/checkout/config.js +7 -0
  70. package/dist/_internal/shared/fees/calculator.d.ts +26 -0
  71. package/dist/_internal/shared/fees/calculator.js +25 -0
  72. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
  73. package/dist/constants/api-endpoints.d.ts +6 -9
  74. package/dist/constants/api-endpoints.js +5 -5
  75. package/dist/constants/field-names.d.ts +17 -1
  76. package/dist/constants/field-names.js +17 -1
  77. package/dist/features/about/components/FeesView.js +18 -0
  78. package/dist/features/account/hooks/useNotifications.js +5 -0
  79. package/dist/features/admin/actions/admin-actions.js +6 -0
  80. package/dist/features/admin/actions/notification-actions.d.ts +4 -1
  81. package/dist/features/admin/actions/notification-actions.js +57 -12
  82. package/dist/features/admin/components/AdminAdsView.js +1 -1
  83. package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
  84. package/dist/features/admin/components/AdminNewsletterView.js +42 -11
  85. package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
  86. package/dist/features/admin/components/AdminSiteSettingsView.js +115 -11
  87. package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
  88. package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
  89. package/dist/features/admin/hooks/useAdminListing.js +4 -2
  90. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
  91. package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
  92. package/dist/features/admin/repository/site-settings.repository.js +8 -1
  93. package/dist/features/admin/schemas/firestore.d.ts +54 -5
  94. package/dist/features/admin/schemas/firestore.js +63 -26
  95. package/dist/features/auctions/actions/bid-actions.js +3 -1
  96. package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
  97. package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
  98. package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
  99. package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
  100. package/dist/features/auctions/components/CollapsibleBidHistory.js +21 -3
  101. package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
  102. package/dist/features/auctions/components/LiveBidPrice.js +17 -0
  103. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
  104. package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
  105. package/dist/features/auctions/hooks/useBids.d.ts +33 -0
  106. package/dist/features/auctions/hooks/useBids.js +32 -0
  107. package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
  108. package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
  109. package/dist/features/auth/checkout-value-otp.d.ts +7 -10
  110. package/dist/features/auth/checkout-value-otp.js +14 -10
  111. package/dist/features/auth/server.d.ts +0 -1
  112. package/dist/features/auth/server.js +0 -1
  113. package/dist/features/blog/api/[slug]/route.d.ts +5 -0
  114. package/dist/features/blog/api/[slug]/route.js +12 -3
  115. package/dist/features/blog/components/BlogPostView.d.ts +2 -0
  116. package/dist/features/blog/components/BlogPostView.js +5 -4
  117. package/dist/features/blog/hooks/useBlog.d.ts +2 -0
  118. package/dist/features/blog/hooks/useBlog.js +2 -0
  119. package/dist/features/blog/repository/blog.repository.d.ts +7 -0
  120. package/dist/features/blog/repository/blog.repository.js +44 -0
  121. package/dist/features/blog/schemas/firestore.d.ts +3 -0
  122. package/dist/features/blog/schemas/firestore.js +1 -0
  123. package/dist/features/blog/types/index.d.ts +2 -0
  124. package/dist/features/cart/components/index.d.ts +0 -2
  125. package/dist/features/cart/components/index.js +0 -1
  126. package/dist/features/cart/hooks/useCartCount.js +22 -5
  127. package/dist/features/cart/hooks/useGuestCart.js +11 -1
  128. package/dist/features/cart/utils/guest-cart.d.ts +4 -0
  129. package/dist/features/cart/utils/guest-cart.js +10 -0
  130. package/dist/features/cart/utils/pending-ops.d.ts +17 -0
  131. package/dist/features/cart/utils/pending-ops.js +46 -5
  132. package/dist/features/categories/components/BrandDetailPageView.js +6 -2
  133. package/dist/features/categories/components/BundleDetailView.js +4 -2
  134. package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
  135. package/dist/features/categories/schemas/firestore.d.ts +7 -0
  136. package/dist/features/checkout/actions/index.d.ts +0 -1
  137. package/dist/features/checkout/actions/index.js +0 -1
  138. package/dist/features/checkout/schemas/firestore.d.ts +2 -2
  139. package/dist/features/events/actions/event-actions.d.ts +2 -0
  140. package/dist/features/events/actions/event-actions.js +5 -0
  141. package/dist/features/events/components/EventCard.js +1 -2
  142. package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
  143. package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
  144. package/dist/features/events/components/index.d.ts +2 -0
  145. package/dist/features/events/components/index.js +1 -0
  146. package/dist/features/events/repository/events.repository.d.ts +6 -0
  147. package/dist/features/events/repository/events.repository.js +21 -0
  148. package/dist/features/history/repository/user-history.repository.d.ts +1 -1
  149. package/dist/features/history/utils/guest-history.d.ts +1 -1
  150. package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
  151. package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
  152. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  153. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  154. package/dist/features/layout/AppLayoutShell.js +1 -1
  155. package/dist/features/layout/BackToTop.d.ts +9 -5
  156. package/dist/features/layout/BackToTop.js +17 -21
  157. package/dist/features/layout/NavbarLayout.js +1 -1
  158. package/dist/features/layout/TitleBarLayout.js +2 -2
  159. package/dist/features/media/MediaVideo.d.ts +7 -0
  160. package/dist/features/media/MediaVideo.js +36 -5
  161. package/dist/features/orders/actions/refund-actions.js +1 -0
  162. package/dist/features/orders/components/OrdersList.js +1 -5
  163. package/dist/features/orders/schemas/firestore.d.ts +16 -8
  164. package/dist/features/orders/schemas/index.d.ts +3 -32
  165. package/dist/features/orders/schemas/index.js +1 -4
  166. package/dist/features/orders/types/index.d.ts +6 -9
  167. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  168. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
  169. package/dist/features/products/actions/product-actions.js +13 -10
  170. package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
  171. package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
  172. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  173. package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
  174. package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
  175. package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
  176. package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
  177. package/dist/features/products/components/PrizeRevealModal.js +18 -147
  178. package/dist/features/products/components/ProductDetailPageView.js +72 -30
  179. package/dist/features/products/components/ProductForm.js +7 -14
  180. package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
  181. package/dist/features/products/components/ProductGalleryClient.js +29 -10
  182. package/dist/features/products/components/ProductsIndexListing.js +2 -1
  183. package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
  184. package/dist/features/products/components/ShowGroupSection.js +2 -2
  185. package/dist/features/products/components/SublistingCarouselSection.js +1 -1
  186. package/dist/features/products/components/index.d.ts +4 -2
  187. package/dist/features/products/components/index.js +1 -0
  188. package/dist/features/products/constants/action-defs.d.ts +28 -4
  189. package/dist/features/products/constants/action-defs.js +76 -3
  190. package/dist/features/products/repository/products.repository.d.ts +6 -0
  191. package/dist/features/products/repository/products.repository.js +20 -1
  192. package/dist/features/products/schemas/firestore.d.ts +26 -2
  193. package/dist/features/products/schemas/index.d.ts +11 -3
  194. package/dist/features/products/schemas/index.js +10 -2
  195. package/dist/features/products/types/index.d.ts +2 -1
  196. package/dist/features/reviews/actions/review-actions.js +1 -0
  197. package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
  198. package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
  199. package/dist/features/reviews/components/ReviewsList.js +2 -1
  200. package/dist/features/reviews/schemas/firestore.d.ts +3 -0
  201. package/dist/features/reviews/schemas/index.d.ts +6 -0
  202. package/dist/features/reviews/schemas/index.js +2 -0
  203. package/dist/features/reviews/types/index.d.ts +3 -0
  204. package/dist/features/scams/actions/scam-actions.d.ts +3 -0
  205. package/dist/features/scams/actions/scam-actions.js +3 -2
  206. package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
  207. package/dist/features/scams/components/ScamProfileView.js +2 -2
  208. package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
  209. package/dist/features/scams/repository/scammer.repository.js +24 -0
  210. package/dist/features/seller/actions/seller-actions.js +8 -0
  211. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  212. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
  213. package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
  214. package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
  215. package/dist/features/stores/components/StoreNavTabs.js +21 -9
  216. package/dist/features/tester/actions/index.d.ts +1 -0
  217. package/dist/features/tester/actions/index.js +1 -0
  218. package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
  219. package/dist/features/tester/actions/leaderboard-actions.js +5 -0
  220. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
  221. package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
  222. package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
  223. package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
  224. package/dist/features/tester/components/index.d.ts +2 -0
  225. package/dist/features/tester/components/index.js +1 -0
  226. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
  227. package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
  228. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
  229. package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
  230. package/dist/features/tester/schemas/firestore.d.ts +22 -1
  231. package/dist/features/tester/schemas/firestore.js +8 -0
  232. package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
  233. package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
  234. package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
  235. package/dist/features/tester/seed-data/products-tester-seed-data.js +152 -8
  236. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +249 -14
  237. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
  238. package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
  239. package/dist/features/whatsapp-bot/types/index.d.ts +19 -0
  240. package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
  241. package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
  242. package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
  243. package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
  244. package/dist/index.d.ts +23 -24
  245. package/dist/index.js +29 -60
  246. package/dist/jobs.d.ts +1 -1
  247. package/dist/jobs.js +2 -2
  248. package/dist/next/routing/route-map.d.ts +4 -0
  249. package/dist/next/routing/route-map.js +2 -0
  250. package/dist/repositories/index.d.ts +2 -0
  251. package/dist/repositories/index.js +1 -0
  252. package/dist/schemas/registry.d.ts +6 -26
  253. package/dist/seed/bids-seed-data.js +14 -2
  254. package/dist/seed/blog-posts-seed-data.js +2 -1
  255. package/dist/seed/categories-seed-data.js +11 -2
  256. package/dist/seed/events-seed-data.js +11 -11
  257. package/dist/seed/faq-seed-data.js +5 -1
  258. package/dist/seed/products-auctions-seed-data.js +2 -2
  259. package/dist/seed/products-preorders-seed-data.d.ts +2 -1
  260. package/dist/seed/products-prize-draws-seed-data.js +56 -10
  261. package/dist/seed/products-standard-seed-data.js +82 -3
  262. package/dist/seed/scammers-seed-data.js +75 -2
  263. package/dist/seed/site-settings-seed-data.js +12 -2
  264. package/dist/server-entry.d.ts +0 -1
  265. package/dist/server-entry.js +0 -1
  266. package/dist/server.d.ts +6 -22
  267. package/dist/server.js +12 -59
  268. package/dist/styles.css +1 -1
  269. package/dist/tailwind-utilities.css +1 -1
  270. package/dist/ui/components/BaseListingCard.js +1 -1
  271. package/dist/ui/components/ImageLightbox.d.ts +9 -0
  272. package/dist/ui/components/ImageLightbox.js +6 -3
  273. package/dist/ui/components/ListingToolbar.d.ts +18 -1
  274. package/dist/ui/components/ListingToolbar.js +5 -2
  275. package/dist/ui/components/StickyToolbar.js +13 -8
  276. package/package.json +1 -1
@@ -1,46 +1,42 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
4
5
  import { useEffect, useState } from "react";
5
6
  import { ChevronUp, X } from "lucide-react";
6
7
  import { Button, Div } from "../../ui";
7
- import { normalizeError } from "../../errors/normalize";
8
- const DISMISSED_KEY = "appkit:back-to-top-dismissed";
9
8
  /**
10
9
  * BackToTop — floating scroll-to-top button.
11
10
  * Appears after the page has scrolled past `threshold` pixels. A small
12
- * dismiss control lets the user hide it for the rest of the browser
13
- * session (sessionStorage) on narrow viewports it can sit close enough
14
- * to the bottom nav / other floating controls to feel like clutter, so it
15
- * needs the same "get out of my way" escape hatch as StickyToolbar.
16
- * Re-appears on the next page load.
11
+ * dismiss control hides it for the current page view only — dismissal is
12
+ * plain in-memory component state, reset on every route change (via
13
+ * `usePathname()`) so navigating anywhere brings it back. Deliberately NOT
14
+ * persisted to storage: an earlier version wrote the dismissal to
15
+ * `sessionStorage`, and because this component is mounted once inside the
16
+ * persistent `AppLayoutShell` (it never remounts on client-side navigation),
17
+ * that meant one click hid the button for the rest of the browser tab
18
+ * session with no way to bring it back — reported as "the button is gone
19
+ * forever." See CLAUDE.md Recurrent Root Cause Patterns.
17
20
  */
18
21
  export function BackToTop({ threshold = 400, ariaLabel = "Back to top", className = "", }) {
22
+ const pathname = usePathname();
19
23
  const [visible, setVisible] = useState(false);
20
24
  const [dismissed, setDismissed] = useState(false);
21
25
  useEffect(() => {
22
- try {
23
- setDismissed(window.sessionStorage.getItem(DISMISSED_KEY) === "1");
24
- }
25
- catch (_err) {
26
- void normalizeError(_err);
27
- // sessionStorage unavailable — never dismissed for this render.
28
- }
29
26
  const handleScroll = () => setVisible(window.scrollY > threshold);
30
27
  window.addEventListener("scroll", handleScroll, { passive: true });
31
28
  return () => window.removeEventListener("scroll", handleScroll);
32
29
  }, [threshold]);
30
+ // Reset dismissal on every route change — the component itself never
31
+ // unmounts (it lives inside the persistent app shell), so without this
32
+ // a single dismiss would otherwise hide the button for the whole session.
33
+ useEffect(() => {
34
+ setDismissed(false);
35
+ }, [pathname]);
33
36
  if (!visible || dismissed)
34
37
  return null;
35
38
  const handleDismiss = () => {
36
39
  setDismissed(true);
37
- try {
38
- window.sessionStorage.setItem(DISMISSED_KEY, "1");
39
- }
40
- catch (_err) {
41
- void normalizeError(_err);
42
- // sessionStorage unavailable — dismissal still applies for this render.
43
- }
44
40
  };
45
41
  return (_jsxs(Div, { layout: "flex", align: "center", gap: "1", className: `fixed bottom-[calc(var(--keyboard-inset-height,0px)+var(--bottom-nav-height,4rem))] right-4 z-[var(--appkit-z-bottom-nav)] ${className}`, children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", "aria-label": ariaLabel, onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }), className: "flex items-center justify-center w-10 h-10 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] shadow-md text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] dark:hover:bg-slate-700 transition-colors", children: _jsx(ChevronUp, { className: "w-5 h-5", "aria-hidden": "true" }) }), _jsx(Button, { type: "button", variant: "outline", size: "sm", "aria-label": "Hide back-to-top button", onClick: handleDismiss, className: "flex items-center justify-center w-6 h-6 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] shadow-md text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text)] transition-colors", children: _jsx(X, { className: "w-3 h-3", "aria-hidden": "true" }) })] }));
46
42
  }
@@ -11,7 +11,7 @@ function DefaultNavItem({ item, isActive, }) {
11
11
  const activeClasses = item.highlighted
12
12
  ? "border border-primary-400/40 dark:border-secondary-400/30 text-primary-700 dark:text-secondary-400 bg-primary-50/80 dark:bg-secondary-900/30 px-[var(--appkit-space-3)] transition-colors duration-150"
13
13
  : isActive
14
- ? "bg-primary-50 bg-[var(--appkit-color-surface-elevated)] text-primary-800 dark:text-white font-semibold px-[var(--appkit-space-3)] border-b-2 border-primary-500 dark:border-secondary-400 rounded-none pb-[6px] transition-colors duration-150"
14
+ ? "bg-primary-50 dark:bg-[var(--appkit-color-surface-elevated)] text-primary-800 dark:text-white font-semibold px-[var(--appkit-space-3)] border-b-2 border-primary-500 dark:border-secondary-400 rounded-none pb-[6px] transition-colors duration-150"
15
15
  : "text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-elevated)] hover:text-zinc-950 dark:hover:text-white transition-colors duration-150 px-[var(--appkit-space-3)]";
16
16
  return (_jsxs(Link, { href: item.href, "aria-current": isActive ? "page" : undefined, className: `flex items-center gap-[var(--appkit-space-1-5)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] rounded-lg font-medium transition-colors duration-150 whitespace-nowrap ${activeClasses}`, children: [item.icon, _jsx(Span, { children: item.label })] }));
17
17
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
3
  import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, SiteMark, Span } from "../../ui";
4
4
  /** Shared icon-button class for all action buttons in the title bar. */
5
- const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
5
+ const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 dark:hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
6
6
  /** Badge counter class for wishlist/cart counts. */
7
7
  const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-center w-4 h-4 rounded-full bg-error-surface text-white text-[10px] font-bold leading-none";
8
8
  /**
@@ -30,7 +30,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
30
30
  const wishlistEl = wishlistHref ? (_jsxs(Link, { href: wishlistHref, "aria-label": `Wishlist${wishlistCount > 0 ? `, ${wishlistCount} items` : ""}`, className: `relative ${iconBtn}`, "data-tour": "nav-wishlist", 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;
31
31
  const cartEl = cartHref ? (_jsxs(Link, { href: cartHref, "aria-label": `Cart${cartCount > 0 ? `, ${cartCount} items` : ""}`, className: `relative ${iconBtn}`, "data-tour": "nav-cart", 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;
32
32
  const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: `relative ${iconBtn}`, "data-tour": "nav-profile", children: _jsx(Avatar, { src: user?.photoURL ?? undefined, name: user?.displayName ?? user?.email ?? undefined, alt: user?.displayName ?? "Profile", size: "sm" }) })) : null;
33
- const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
33
+ const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
34
34
  const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
35
35
  // ── Render ───────────────────────────────────────────────────────────────────
36
36
  return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsxs(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center gap-[var(--appkit-space-2)] transition-opacity hover:opacity-80", children: [_jsx(Div, { className: "flex md:hidden", children: _jsx(SiteMark, { title: brandName, size: "sm" }) }), _jsx(SiteLogo, { title: brandName, size: "md" })] }) }), _jsx(Row, { className: "hidden md:flex absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteMark, { src: siteLogoUrl, title: brandName, size: "lg" }) }) }), _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
@@ -5,6 +5,7 @@
5
5
  * client-side overlay covers `<video>` playback because there is no
6
6
  * Vercel-Hobby-budget ffmpeg pipeline to bake the watermark into the frames.
7
7
  */
8
+ export type MediaVideoWatermarkPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center" | "custom";
8
9
  export interface MediaVideoWatermark {
9
10
  /** `"text"` renders a `<Span>` overlay; `"image"` renders a `<MediaImage>` (proxied). */
10
11
  type: "text" | "image";
@@ -16,6 +17,12 @@ export interface MediaVideoWatermark {
16
17
  size?: number;
17
18
  /** Percentage opacity (0–100). Default 20. */
18
19
  opacity?: number;
20
+ /** Anchor preset — mirrors the server-side Sharp pipeline. Default `"center"`. */
21
+ position?: MediaVideoWatermarkPosition;
22
+ /** `position === "custom"` only: % of container width, from center. +right / -left. */
23
+ offsetX?: number;
24
+ /** `position === "custom"` only: % of container height, from center. +down / -up. */
25
+ offsetY?: number;
19
26
  }
20
27
  export interface MediaVideoProps {
21
28
  /** Video URL. When undefined the fallback placeholder is rendered. */
@@ -48,6 +48,39 @@ export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true,
48
48
  }
49
49
  return (_jsxs(_Fragment, { children: [_jsx("video", { ref: videoRef, src: resolvedSrc, poster: resolvedPoster, controls: controls, autoPlay: autoPlayMuted, muted: autoPlayMuted, loop: loop, playsInline: true, "aria-label": alt, className: `absolute inset-0 w-full h-full ${fitClass}` }), effectiveWatermark ? _jsx(MediaVideoWatermarkLayer, { config: effectiveWatermark }) : null] }));
50
50
  }
51
+ /**
52
+ * Mirrors the server-side Sharp pipeline's 5 anchor presets + `"custom"`
53
+ * %-offset-from-center (`resolveCompositePlacement` in
54
+ * `src/app/api/media/_watermark.ts`) as CSS insets, so a video's live
55
+ * overlay and its poster-frame-as-image watermark land in the same spot.
56
+ * Default `"center"`, 0/0 offset — matches the server-side default too.
57
+ */
58
+ function resolveWatermarkPlacementStyle(config) {
59
+ const position = config.position ?? "center";
60
+ const inset = "2%";
61
+ switch (position) {
62
+ case "top-left":
63
+ return { top: inset, left: inset };
64
+ case "top-right":
65
+ return { top: inset, right: inset };
66
+ case "bottom-left":
67
+ return { bottom: inset, left: inset };
68
+ case "bottom-right":
69
+ return { bottom: inset, right: inset };
70
+ case "custom": {
71
+ const offsetX = Math.max(-45, Math.min(45, config.offsetX ?? 0));
72
+ const offsetY = Math.max(-45, Math.min(45, config.offsetY ?? 0));
73
+ return {
74
+ top: `calc(50% + ${offsetY}%)`,
75
+ left: `calc(50% + ${offsetX}%)`,
76
+ transform: "translate(-50%, -50%)",
77
+ };
78
+ }
79
+ case "center":
80
+ default:
81
+ return { top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
82
+ }
83
+ }
51
84
  /**
52
85
  * Visual watermark overlay rendered on top of the playing `<video>`. Native
53
86
  * video controls render in a higher layer (the browser shadow DOM), so this
@@ -62,16 +95,14 @@ function MediaVideoWatermarkLayer({ config }) {
62
95
  const opacity = Math.max(0, Math.min(100, config.opacity ?? 20)) / 100;
63
96
  if (widthPct === 0 || opacity === 0)
64
97
  return null;
65
- // Anchor: bottom-right, with a small inset matching the image watermark
66
- // position. Width is a percentage of the container so the watermark scales
67
- // with the video, just like the server-side Sharp pipeline scales images.
98
+ // Width is a percentage of the container so the watermark scales with the
99
+ // video, just like the server-side Sharp pipeline scales images.
68
100
  const containerStyle = {
69
101
  position: "absolute",
70
- right: "2%",
71
- bottom: "2%",
72
102
  width: `${widthPct}%`,
73
103
  pointerEvents: "none",
74
104
  opacity,
105
+ ...resolveWatermarkPlacementStyle(config),
75
106
  };
76
107
  if (config.type === "image" && config.imageUrl) {
77
108
  return (_jsx(Div, { className: "z-10", style: containerStyle, "aria-hidden": "true", children: _jsx("img", { src: resolveMediaUrl(config.imageUrl), alt: "", className: "block w-full h-auto" }) }));
@@ -49,6 +49,7 @@ export async function issuePartialRefund(adminUid, orderId, deductFees, refundNo
49
49
  : `Your full refund of ₹${grossRefund} is being processed.`,
50
50
  relatedId: orderId,
51
51
  relatedType: "order",
52
+ orderWhatsappAddonPaid: order.whatsappNotifyAddon === true,
52
53
  });
53
54
  serverLogger.info("Admin partial refund initiated", {
54
55
  adminUid,
@@ -29,13 +29,9 @@ export function OrderCard({ order, onClick, labels = {}, renderActions }) {
29
29
  it.prizeRevealStatus !== undefined &&
30
30
  it.prizeRevealStatus !== "revealed");
31
31
  const revealsRemaining = unrevealedPrizeDraws.length;
32
- const earliestDeadline = unrevealedPrizeDraws
33
- .map((it) => it.prizeRevealDeadline)
34
- .filter((d) => !!d)
35
- .sort()[0];
36
32
  return (_jsxs(Div, { padding: "5", role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
37
33
  ? (e) => (e.key === "Enter" || e.key === " ") && onClick(order)
38
- : undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsxs(Row, { gap: "sm", className: "mt-2", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }), earliestDeadline && (_jsxs(Span, { size: "xs", color: "muted", children: ["before ", new Date(earliestDeadline).toLocaleDateString(getDefaultLocale(), { month: "short", day: "numeric" })] }))] })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { gap: "sm", children: [item.image && (_jsx(Div, { className: "relative h-10 w-10 flex-shrink-0", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) })), _jsxs(Div, { children: [_jsx(Text, { truncate: true, size: "sm", weight: "medium", color: "primary", children: item.title }), _jsxs(Text, { size: "xs", color: "faint", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { size: "xs", className: "self-center", color: "faint", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100", padding: "t-sm", children: [_jsxs(Span, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [order.currency ?? "", " Total"] }), _jsx(Span, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(order.total, order.currency) })] }), renderActions && (_jsx(Row, { justify: "end", gap: "sm", className: "mt-3", onClick: (e) => e.stopPropagation(), children: renderActions(order) }))] }));
34
+ : undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsx(Row, { gap: "sm", className: "mt-2", children: _jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }) })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { gap: "sm", children: [item.image && (_jsx(Div, { className: "relative h-10 w-10 flex-shrink-0", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) })), _jsxs(Div, { children: [_jsx(Text, { truncate: true, size: "sm", weight: "medium", color: "primary", children: item.title }), _jsxs(Text, { size: "xs", color: "faint", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { size: "xs", className: "self-center", color: "faint", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100", padding: "t-sm", children: [_jsxs(Span, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [order.currency ?? "", " Total"] }), _jsx(Span, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(order.total, order.currency) })] }), renderActions && (_jsx(Row, { justify: "end", gap: "sm", className: "mt-3", onClick: (e) => e.stopPropagation(), children: renderActions(order) }))] }));
39
35
  }
40
36
  export function OrdersList({ orders, isLoading, onOrderClick, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No orders found", renderActions, }) {
41
37
  if (isLoading) {
@@ -92,10 +92,6 @@ export interface OrderDocumentItem {
92
92
  listingType?: ListingType;
93
93
  /** SB8-F â€" per-item reveal status; flips through pending â†' open â†' revealed/closed. */
94
94
  prizeRevealStatus?: "pending" | "open" | "closed" | "revealed";
95
- /** SB8-F â€" ISO timestamp; deadline by which the buyer must claim the prize. */
96
- prizeRevealDeadline?: string;
97
- /** SB8-F â€" set after the reveal API picks a winner. */
98
- revealedItemNumber?: number;
99
95
  /** Set when the buyer cancels this specific line item post-order (partial cancellation). */
100
96
  cancelledQuantity?: number;
101
97
  cancelledAt?: Date;
@@ -183,6 +179,20 @@ export interface OrderDocument extends BaseDocument {
183
179
  codRemainingAmount?: number;
184
180
  /** COD handling fee charged to the buyer: max(codHandlingFeeMin, subtotal × codHandlingFeePercent / 100). Only set when paymentMethod === "cod". */
185
181
  codHandlingFee?: number;
182
+ /** Buyer opted into WhatsApp order-update messages at checkout (unchecked by default). */
183
+ whatsappNotifyAddon?: boolean;
184
+ /** Fee charged for the WhatsApp addon, decimal rupees — snapshot of siteSettings.commissions.whatsappNotifyFee at order time. */
185
+ whatsappNotifyFee?: number;
186
+ /** Buyer opted into gift wrapping at checkout (unchecked by default). */
187
+ giftWrapAddon?: boolean;
188
+ /** Fee charged for gift wrap, decimal rupees — snapshot of siteSettings.commissions.giftWrapFee at order time. */
189
+ giftWrapFee?: number;
190
+ /** Optional buyer-written gift message, shown to the seller for fulfilment. Capped length. */
191
+ giftWrapMessage?: string;
192
+ /** Buyer opted into shipment protection (loss/damage insurance) at checkout (unchecked by default). */
193
+ shipmentProtectionAddon?: boolean;
194
+ /** Fee charged for shipment protection, decimal rupees — snapshot of siteSettings.commissions.shipmentProtectionFeePercent/Min at order time. */
195
+ shipmentProtectionFee?: number;
186
196
  /** Order subtotal before GST — the amount GST is computed on. */
187
197
  taxableAmount?: number;
188
198
  /** Total GST charged (cgst + sgst, or igst — never both pairs at once). */
@@ -222,12 +232,10 @@ export interface OrderDocument extends BaseDocument {
222
232
  images: string[];
223
233
  wonAt: Date;
224
234
  };
225
- /** Deadline for the buyer to claim the won prize (typically 7 days). */
226
- prizeRevealDeadline?: Date;
227
- /** True once `prizeRevealDeadline` passes without a claim â€" auto-forfeit. */
228
- prizeRevealExpired?: boolean;
229
235
  /** Source product id when the order came from a prize-draw entry. */
230
236
  prizeDrawProductId?: string;
237
+ /** Denormalized from the product at order-creation time — drives reveal copy on the order-detail page without an extra product fetch. */
238
+ prizeRevealMode?: "instant" | "scheduled";
231
239
  /** True for prize-draw entries and bundle purchases that bypass refund. */
232
240
  isNonRefundable?: boolean;
233
241
  /** Set when the order came from a bundle â€" points back to `bundles/{id}`. */
@@ -90,8 +90,6 @@ export declare const orderDocumentItemSchema: z.ZodObject<{
90
90
  totalPrice: z.ZodEffects<z.ZodNumber, number, number>;
91
91
  listingType: z.ZodOptional<z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live"]>>;
92
92
  prizeRevealStatus: z.ZodOptional<z.ZodEnum<["pending", "open", "closed", "revealed"]>>;
93
- prizeRevealDeadline: z.ZodOptional<z.ZodString>;
94
- revealedItemNumber: z.ZodOptional<z.ZodNumber>;
95
93
  }, "strip", z.ZodTypeAny, {
96
94
  productId: string;
97
95
  quantity: number;
@@ -100,8 +98,6 @@ export declare const orderDocumentItemSchema: z.ZodObject<{
100
98
  totalPrice: number;
101
99
  listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
102
100
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
103
- prizeRevealDeadline?: string | undefined;
104
- revealedItemNumber?: number | undefined;
105
101
  }, {
106
102
  productId: string;
107
103
  quantity: number;
@@ -110,8 +106,6 @@ export declare const orderDocumentItemSchema: z.ZodObject<{
110
106
  totalPrice: number;
111
107
  listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
112
108
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
113
- prizeRevealDeadline?: string | undefined;
114
- revealedItemNumber?: number | undefined;
115
109
  }>;
116
110
  export declare const orderRefundEventSchema: z.ZodObject<{
117
111
  refundId: z.ZodString;
@@ -255,8 +249,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
255
249
  totalPrice: z.ZodEffects<z.ZodNumber, number, number>;
256
250
  listingType: z.ZodOptional<z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live"]>>;
257
251
  prizeRevealStatus: z.ZodOptional<z.ZodEnum<["pending", "open", "closed", "revealed"]>>;
258
- prizeRevealDeadline: z.ZodOptional<z.ZodString>;
259
- revealedItemNumber: z.ZodOptional<z.ZodNumber>;
260
252
  }, "strip", z.ZodTypeAny, {
261
253
  productId: string;
262
254
  quantity: number;
@@ -265,8 +257,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
265
257
  totalPrice: number;
266
258
  listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
267
259
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
268
- prizeRevealDeadline?: string | undefined;
269
- revealedItemNumber?: number | undefined;
270
260
  }, {
271
261
  productId: string;
272
262
  quantity: number;
@@ -275,8 +265,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
275
265
  totalPrice: number;
276
266
  listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
277
267
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
278
- prizeRevealDeadline?: string | undefined;
279
- revealedItemNumber?: number | undefined;
280
268
  }>, "many">>;
281
269
  orderType: z.ZodOptional<z.ZodString>;
282
270
  imageUrls: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -450,15 +438,8 @@ export declare const orderFirestoreSchema: z.ZodObject<{
450
438
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
451
439
  }, z.ZodTypeAny, "passthrough">;
452
440
  }>>;
453
- prizeRevealDeadline: z.ZodOptional<z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
454
- toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
455
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
456
- toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
457
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
458
- toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
459
- }, z.ZodTypeAny, "passthrough">>]>>;
460
- prizeRevealExpired: z.ZodOptional<z.ZodBoolean>;
461
441
  prizeDrawProductId: z.ZodOptional<z.ZodString>;
442
+ prizeRevealMode: z.ZodOptional<z.ZodEnum<["instant", "scheduled"]>>;
462
443
  isNonRefundable: z.ZodOptional<z.ZodBoolean>;
463
444
  bundleId: z.ZodOptional<z.ZodString>;
464
445
  paymentBatchId: z.ZodOptional<z.ZodString>;
@@ -582,6 +563,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
582
563
  paymentMethod?: string | undefined;
583
564
  notes?: string | undefined;
584
565
  storeName?: string | undefined;
566
+ prizeRevealMode?: "instant" | "scheduled" | undefined;
585
567
  physicalLocation?: {
586
568
  zone: string;
587
569
  shelf: string;
@@ -595,8 +577,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
595
577
  totalPrice: number;
596
578
  listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
597
579
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
598
- prizeRevealDeadline?: string | undefined;
599
- revealedItemNumber?: number | undefined;
600
580
  }[] | undefined;
601
581
  payoutStatus?: "paid" | "eligible" | "requested" | undefined;
602
582
  couponCode?: string | undefined;
@@ -668,10 +648,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
668
648
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
669
649
  }, z.ZodTypeAny, "passthrough">;
670
650
  } | undefined;
671
- prizeRevealDeadline?: string | Date | z.objectOutputType<{
672
- toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
673
- }, z.ZodTypeAny, "passthrough"> | undefined;
674
- prizeRevealExpired?: boolean | undefined;
675
651
  prizeDrawProductId?: string | undefined;
676
652
  isNonRefundable?: boolean | undefined;
677
653
  bundleId?: string | undefined;
@@ -735,6 +711,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
735
711
  paymentMethod?: string | undefined;
736
712
  notes?: string | undefined;
737
713
  storeName?: string | undefined;
714
+ prizeRevealMode?: "instant" | "scheduled" | undefined;
738
715
  physicalLocation?: {
739
716
  zone: string;
740
717
  shelf: string;
@@ -748,8 +725,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
748
725
  totalPrice: number;
749
726
  listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
750
727
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
751
- prizeRevealDeadline?: string | undefined;
752
- revealedItemNumber?: number | undefined;
753
728
  }[] | undefined;
754
729
  payoutStatus?: "paid" | "eligible" | "requested" | undefined;
755
730
  couponCode?: string | undefined;
@@ -821,10 +796,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
821
796
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
822
797
  }, z.ZodTypeAny, "passthrough">;
823
798
  } | undefined;
824
- prizeRevealDeadline?: string | Date | z.objectInputType<{
825
- toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
826
- }, z.ZodTypeAny, "passthrough"> | undefined;
827
- prizeRevealExpired?: boolean | undefined;
828
799
  prizeDrawProductId?: string | undefined;
829
800
  isNonRefundable?: boolean | undefined;
830
801
  bundleId?: string | undefined;
@@ -51,8 +51,6 @@ export const orderDocumentItemSchema = z.object({
51
51
  totalPrice: rupeesSchema,
52
52
  listingType: orderListingTypeSchema.optional(),
53
53
  prizeRevealStatus: z.enum(["pending", "open", "closed", "revealed"]).optional(),
54
- prizeRevealDeadline: z.string().optional(),
55
- revealedItemNumber: z.number().int().optional(),
56
54
  });
57
55
  export const orderRefundEventSchema = z.object({
58
56
  refundId: z.string(),
@@ -152,9 +150,8 @@ export const orderFirestoreSchema = z.object({
152
150
  payoutId: z.string().optional(),
153
151
  offerId: z.string().optional(),
154
152
  prizeWon: prizeWonSchema.optional(),
155
- prizeRevealDeadline: firestoreDateSchema.optional(),
156
- prizeRevealExpired: z.boolean().optional(),
157
153
  prizeDrawProductId: z.string().optional(),
154
+ prizeRevealMode: z.enum(["instant", "scheduled"]).optional(),
158
155
  isNonRefundable: z.boolean().optional(),
159
156
  bundleId: z.string().optional(),
160
157
  paymentBatchId: z.string().optional(),
@@ -32,10 +32,6 @@ export interface OrderItem {
32
32
  * "X reveals pending" badge on user orders.
33
33
  */
34
34
  prizeRevealStatus?: "pending" | "open" | "closed" | "revealed";
35
- /** ISO timestamp — when the user must claim their reveal before forfeit. */
36
- prizeRevealDeadline?: string;
37
- /** Set after the reveal endpoint picks a winner — the prize item index. */
38
- revealedItemNumber?: number;
39
35
  /** Set when the buyer cancels this specific line item post-order (partial cancellation). */
40
36
  cancelledQuantity?: number;
41
37
  /**
@@ -85,9 +81,10 @@ export interface Order {
85
81
  disputeRaised?: boolean;
86
82
  disputeStatus?: "open" | "resolved";
87
83
  /**
88
- * Reveal-mode prize-draw fields (SB4-H/SB8-C). Set once the reveal
89
- * endpoint has assigned a prize to this order; used to drive the
90
- * "Reveal my prize" CTA on the order-detail page.
84
+ * Reveal-mode prize-draw fields (SB4-H/SB8-C). Set once the winner has
85
+ * been automatically assigned to this order (on payment confirmation,
86
+ * draw expiry, or sellout see assignPrizeDrawWinner); drives the
87
+ * PrizeRevealModal's "won" display on the order-detail page.
91
88
  */
92
89
  prizeWon?: {
93
90
  itemNumber: number;
@@ -95,10 +92,10 @@ export interface Order {
95
92
  images: string[];
96
93
  wonAt: string;
97
94
  };
98
- prizeRevealDeadline?: string;
99
- prizeRevealExpired?: boolean;
100
95
  /** Source prize-draw product id, when this order came from one. */
101
96
  prizeDrawProductId?: string;
97
+ /** Denormalized from the product at order-creation time — drives PrizeRevealModal copy. */
98
+ prizeRevealMode?: "instant" | "scheduled";
102
99
  }
103
100
  export interface OrderListResponse {
104
101
  items: Order[];
@@ -64,5 +64,5 @@ export function MarketplacePreorderCard({ product, className = "", variant = "gr
64
64
  /* ── Compact list layout ── */
65
65
  _jsxs(_Fragment, { children: [_jsxs(Row, { className: "min-w-0", align: "start", justify: "between", gap: "sm", children: [_jsx(TextLink, { href: detailHref, className: "min-w-0 flex-1", children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), wishlistActions && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: `shrink-0 p-[0.25rem] leading-none ${inWishlist ? "text-primary" : "text-[var(--appkit-color-text-faint)]"}`, onClick: handleWishlist, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, children: inWishlist ? "♥" : "♡" }))] }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-primary", size: "sm", weight: "bold", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "self-start mt-0.5", onClick: handleNavigate, children: mergedLabels.reserveNow })] })) : (
66
66
  /* ── Full grid layout ── */
67
- _jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-2", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: `${inWishlist ? "text-primary" : "text-[var(--appkit-color-text-muted)]"}`, onClick: handleWishlist, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, children: inWishlist ? "♥" : "♡" })) : null] })] })) })] }));
67
+ _jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-auto pt-2", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: `${inWishlist ? "text-primary" : "text-[var(--appkit-color-text-muted)]"}`, onClick: handleWishlist, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, children: inWishlist ? "♥" : "♡" })) : null] })] })) })] }));
68
68
  }
@@ -82,6 +82,7 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
82
82
  : typeof p.mainImage === "string"
83
83
  ? [p.mainImage]
84
84
  : [];
85
+ const productVideo = p.video;
85
86
  const reservedCount = typeof p.preOrderCurrentCount === "number"
86
87
  ? p.preOrderCurrentCount
87
88
  : typeof p.reservedCount === "number"
@@ -150,7 +151,7 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
150
151
  price: typeof p.price === "number" ? p.price : undefined,
151
152
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
152
153
  storeName: typeof p.storeName === "string" ? p.storeName : undefined,
153
- } }), _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.PRE_ORDERS), className: CLS_BREADCRUMB_LINK, children: "Pre-Orders" }), 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: () => (_jsx(PreOrderInfoSection, { title: title, productionStatus: productionStatus, maxPerUser: maxPerUser, deliveryDate: deliveryDate, featured: featured, freeShipping: freeShipping, condition: condition, isCancellable: isCancellable, category: category, categoryName: categoryName, brand: brand, brandSlug: brandSlug, productFeatures: productFeatures, features: features, descriptionHtml: descriptionHtml, safeSeller: safeSeller, storeHref: storeHref })), renderSublistingSection: sublistingCategoryId
154
+ } }), _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.PRE_ORDERS), className: CLS_BREADCRUMB_LINK, children: "Pre-Orders" }), 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: () => (_jsx(PreOrderInfoSection, { title: title, productionStatus: productionStatus, maxPerUser: maxPerUser, deliveryDate: deliveryDate, featured: featured, freeShipping: freeShipping, condition: condition, isCancellable: isCancellable, category: category, categoryName: categoryName, brand: brand, brandSlug: brandSlug, productFeatures: productFeatures, features: features, descriptionHtml: descriptionHtml, safeSeller: safeSeller, storeHref: storeHref })), renderSublistingSection: sublistingCategoryId
154
155
  ? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
155
156
  : undefined, renderGroupSection: groupId
156
157
  ? () => (_jsx(ShowGroupSection, { groupId: groupId, currentSlug: String(p.slug ?? product.id), isParent: isGroupParent, groupTitle: groupTitle }))
@@ -101,16 +101,19 @@ export async function listPreOrders(params = {}) {
101
101
  });
102
102
  }
103
103
  export async function getRelatedProducts(categoryId, excludeId, limit = 6) {
104
- const result = await productRepository.list({
105
- filters: sieveAnd(sieveFilter("categoryId", SIEVE_OP.EQ, categoryId), PUBLISHED_CLAUSE),
106
- sorts: sortBy("createdAt", "DESC"),
107
- page: 1,
108
- pageSize: limit + 1,
109
- });
110
- return {
111
- ...result,
112
- items: result.items.filter((p) => p.id !== excludeId).slice(0, limit),
113
- };
104
+ // `categoryId` here is really a category slug — products store their
105
+ // category FK in categorySlugs[] (array-contains), not a scalar
106
+ // "categoryId" field, which never existed on ProductDocument.
107
+ const docs = await productRepository.findByCategory(categoryId);
108
+ const items = docs
109
+ .filter((p) => p.id !== excludeId && p.status === ProductStatusValues.PUBLISHED)
110
+ .sort((a, b) => {
111
+ const aTime = a.createdAt instanceof Date ? a.createdAt.getTime() : 0;
112
+ const bTime = b.createdAt instanceof Date ? b.createdAt.getTime() : 0;
113
+ return bTime - aTime;
114
+ })
115
+ .slice(0, limit);
116
+ return { items, total: items.length, page: 1, pageSize: limit, totalPages: 1, hasMore: false };
114
117
  }
115
118
  export async function getStoreStorefrontProducts(storeId) {
116
119
  const products = await productRepository.findByStore(storeId);
@@ -1,5 +1,5 @@
1
1
  import type { CategoryDocument } from "../../categories/schemas/firestore";
2
- export type MarketplaceBundleCardData = Pick<CategoryDocument, "id" | "name" | "slug" | "bundleItemDetails" | "bundleProductIds" | "bundlePrice" | "bundleStockStatus" | "display">;
2
+ export type MarketplaceBundleCardData = Pick<CategoryDocument, "id" | "name" | "slug" | "bundleItemDetails" | "bundleProductIds" | "bundlePrice" | "bundleOriginalTotal" | "bundleStockStatus" | "display">;
3
3
  export interface MarketplaceBundleCardLabels {
4
4
  bundleBadge?: string;
5
5
  inStockBadge?: string;
@@ -12,6 +12,8 @@ const CLS_STOCK_OK = "bg-success-surface text-white";
12
12
  const CLS_VIEW_BTN = "mt-2 w-full cursor-pointer rounded-md bg-violet-600 py-[var(--appkit-space-1-5)] text-center text-[length:var(--appkit-text-xs)] font-semibold text-white transition-colors hover:bg-violet-700 active:scale-[0.98]";
13
13
  import { BaseListingCard, Div, Row, Span, Stack, Text, TextLink } from "../../../ui";
14
14
  import { MediaImage } from "../../media/MediaImage";
15
+ import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing";
16
+ import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy";
15
17
  const DEFAULT_LABELS = {
16
18
  bundleBadge: "Bundle",
17
19
  inStockBadge: "In stock",
@@ -34,6 +36,7 @@ export function MarketplaceBundleCard({ bundle, className = "", variant = "grid"
34
36
  const stock = bundle.bundleStockStatus ?? "in_stock";
35
37
  const cover = bundle.display?.coverImage;
36
38
  const price = bundle.bundlePrice;
39
+ const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
37
40
  const collageTiles = (bundle.bundleItemDetails ?? [])
38
41
  .filter((d) => Boolean(d.imageURL))
39
42
  .slice(0, 4);
@@ -46,13 +49,13 @@ export function MarketplaceBundleCard({ bundle, className = "", variant = "grid"
46
49
  }
47
50
  router.push(String(detailHref));
48
51
  }, [detailHref, onNavigate, router]);
49
- 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, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { layout: "grid", className: `h-full w-full gap-[0.125rem] 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(MediaImage, { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, size: "card" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Row, { textWeight: "semibold", textSize: "sm", surface: "overlay-md", className: "absolute inset-0 text-white", align: "center", justify: "center", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "card" }) })) : (_jsx(Row, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", align: "center", justify: "center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "No image" }) })) }), _jsxs(Stack, { className: "absolute right-2 top-2", align: "end", gap: "xs", children: [_jsx(Span, { size: "xs", weight: "medium", className: CLS_BUNDLE_PILL, children: mergedLabels.bundleBadge }), _jsx(Span, { layout: "inline-flex", color: "inverse", className: `${stock === "out_of_stock" ? "bg-neutral-500 " : CLS_STOCK_OK}`, rounded: "full", padding: "pill-xs", size: "xs", weight: "medium", children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Row, { surface: "overlay-xs", className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { color: "inverse", size: "xs", weight: "bold", className: "bg-neutral-900/80 tracking-wider", rounded: "default", padding: "pill-md", transform: "uppercase", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
52
+ 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, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { layout: "grid", className: `h-full w-full gap-[0.125rem] 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(MediaImage, { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, size: "card" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Row, { textWeight: "semibold", textSize: "sm", surface: "overlay-md", className: "absolute inset-0 text-white", align: "center", justify: "center", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "card" }) })) : (_jsx(Row, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", align: "center", justify: "center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "No image" }) })) }), discount && (_jsx(Span, { size: "xs", weight: "bold", color: "inverse", className: "absolute left-2 top-2 bg-success-surface", rounded: "full", padding: "pill-xs", children: BUNDLE_COPY.detail.discountBadge(discount.percent) })), _jsxs(Stack, { className: "absolute right-2 top-2", align: "end", gap: "xs", children: [_jsx(Span, { size: "xs", weight: "medium", className: CLS_BUNDLE_PILL, children: mergedLabels.bundleBadge }), _jsx(Span, { layout: "inline-flex", color: "inverse", className: `${stock === "out_of_stock" ? "bg-neutral-500 " : CLS_STOCK_OK}`, rounded: "full", padding: "pill-xs", size: "xs", weight: "medium", children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Row, { surface: "overlay-xs", className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { color: "inverse", size: "xs", weight: "bold", className: "bg-neutral-900/80 tracking-wider", rounded: "default", padding: "pill-md", transform: "uppercase", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
50
53
  event.stopPropagation();
51
54
  onSelect(bundle.id, !isSelected);
52
55
  }, className: selectable || isSelected
53
56
  ? "opacity-100"
54
- : "opacity-0 group-hover:opacity-100 transition-opacity" }))] }), _jsxs(BaseListingCard.Info, { variant: variant, children: [_jsx(TextLink, { href: String(detailHref), children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: bundle.name }) }), _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: price
55
- ? formatCurrency(price, getDefaultCurrency())
56
- : "—" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: mergedLabels.itemsLabel(memberCount) })] }), _jsx(Div, { className: CLS_VIEW_BTN, role: "button", tabIndex: 0, onClick: handleNavigate, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ")
57
+ : "opacity-0 group-hover:opacity-100 transition-opacity" }))] }), _jsxs(BaseListingCard.Info, { variant: variant, children: [_jsx(TextLink, { href: String(detailHref), children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: bundle.name }) }), _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", wrap: true, children: [_jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: price
58
+ ? formatCurrency(price, getDefaultCurrency())
59
+ : "—" }), discount && (_jsx(Text, { size: "xs", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, getDefaultCurrency()) }))] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: mergedLabels.itemsLabel(memberCount) })] }), _jsx(Div, { className: CLS_VIEW_BTN, role: "button", tabIndex: 0, onClick: handleNavigate, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ")
57
60
  handleNavigate(); }, children: "View Bundle" })] })] }));
58
61
  }
@@ -112,7 +112,7 @@ export async function PrizeDrawDetailPageView({ id, initialPrizeDraw, currentUse
112
112
  ? String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug))
113
113
  : null;
114
114
  const descriptionHtml = toDescriptionHtml(p.description);
115
- return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "product", snapshot: {
115
+ return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "prize-draw", snapshot: {
116
116
  title,
117
117
  thumb,
118
118
  price: pricePerEntry,