@mohasinac/appkit 4.2.0 → 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 (243) 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.js +7 -78
  8. package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
  9. package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
  10. package/dist/_internal/server/features/orders/adapters.js +1 -14
  11. package/dist/_internal/server/features/products/actions.js +8 -3
  12. package/dist/_internal/server/features/products/service.d.ts +15 -0
  13. package/dist/_internal/server/features/products/service.js +41 -0
  14. package/dist/_internal/server/functions/firestore.d.ts +3 -1
  15. package/dist/_internal/server/functions/firestore.js +17 -1
  16. package/dist/_internal/server/functions/scheduled.d.ts +2 -5
  17. package/dist/_internal/server/functions/scheduled.js +6 -30
  18. package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
  19. package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
  20. package/dist/_internal/server/jobs/core/index.d.ts +4 -4
  21. package/dist/_internal/server/jobs/core/index.js +4 -4
  22. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
  23. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
  24. package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
  25. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +2 -2
  26. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
  27. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
  28. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
  29. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
  30. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
  31. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
  32. package/dist/_internal/server/jobs/handlers/index.d.ts +3 -4
  33. package/dist/_internal/server/jobs/handlers/index.js +8 -6
  34. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
  35. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
  36. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
  37. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
  38. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
  39. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
  40. package/dist/_internal/shared/actions/action-registry.js +40 -10
  41. package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
  42. package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
  43. package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
  44. package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
  45. package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
  46. package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
  47. package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
  48. package/dist/_internal/shared/checkout/rules/index.js +1 -1
  49. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
  50. package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
  51. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
  52. package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
  53. package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
  54. package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
  55. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
  56. package/dist/constants/api-endpoints.d.ts +6 -9
  57. package/dist/constants/api-endpoints.js +5 -5
  58. package/dist/constants/field-names.d.ts +17 -1
  59. package/dist/constants/field-names.js +17 -1
  60. package/dist/features/account/hooks/useNotifications.js +5 -0
  61. package/dist/features/admin/actions/admin-actions.js +6 -0
  62. package/dist/features/admin/actions/notification-actions.js +1 -2
  63. package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
  64. package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
  65. package/dist/features/admin/components/AdminSiteSettingsView.js +33 -6
  66. package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
  67. package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
  68. package/dist/features/admin/hooks/useAdminListing.js +4 -2
  69. package/dist/features/admin/schemas/firestore.d.ts +13 -5
  70. package/dist/features/admin/schemas/firestore.js +1 -2
  71. package/dist/features/auctions/actions/bid-actions.js +3 -1
  72. package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
  73. package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
  74. package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
  75. package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
  76. package/dist/features/auctions/components/CollapsibleBidHistory.js +21 -3
  77. package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
  78. package/dist/features/auctions/components/LiveBidPrice.js +17 -0
  79. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
  80. package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
  81. package/dist/features/auctions/hooks/useBids.d.ts +33 -0
  82. package/dist/features/auctions/hooks/useBids.js +32 -0
  83. package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
  84. package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
  85. package/dist/features/auth/checkout-value-otp.d.ts +7 -10
  86. package/dist/features/auth/checkout-value-otp.js +14 -10
  87. package/dist/features/auth/server.d.ts +0 -1
  88. package/dist/features/auth/server.js +0 -1
  89. package/dist/features/blog/api/[slug]/route.d.ts +5 -0
  90. package/dist/features/blog/api/[slug]/route.js +12 -3
  91. package/dist/features/blog/components/BlogPostView.d.ts +2 -0
  92. package/dist/features/blog/components/BlogPostView.js +5 -4
  93. package/dist/features/blog/hooks/useBlog.d.ts +2 -0
  94. package/dist/features/blog/hooks/useBlog.js +2 -0
  95. package/dist/features/blog/repository/blog.repository.d.ts +7 -0
  96. package/dist/features/blog/repository/blog.repository.js +44 -0
  97. package/dist/features/blog/schemas/firestore.d.ts +3 -0
  98. package/dist/features/blog/schemas/firestore.js +1 -0
  99. package/dist/features/blog/types/index.d.ts +2 -0
  100. package/dist/features/cart/components/index.d.ts +0 -2
  101. package/dist/features/cart/components/index.js +0 -1
  102. package/dist/features/cart/hooks/useCartCount.js +22 -5
  103. package/dist/features/cart/hooks/useGuestCart.js +11 -1
  104. package/dist/features/cart/utils/guest-cart.d.ts +4 -0
  105. package/dist/features/cart/utils/guest-cart.js +10 -0
  106. package/dist/features/cart/utils/pending-ops.d.ts +17 -0
  107. package/dist/features/cart/utils/pending-ops.js +46 -5
  108. package/dist/features/categories/components/BrandDetailPageView.js +6 -2
  109. package/dist/features/categories/components/BundleDetailView.js +4 -2
  110. package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
  111. package/dist/features/categories/schemas/firestore.d.ts +7 -0
  112. package/dist/features/checkout/actions/index.d.ts +0 -1
  113. package/dist/features/checkout/actions/index.js +0 -1
  114. package/dist/features/checkout/schemas/firestore.d.ts +2 -2
  115. package/dist/features/events/actions/event-actions.d.ts +2 -0
  116. package/dist/features/events/actions/event-actions.js +5 -0
  117. package/dist/features/events/components/EventCard.js +1 -2
  118. package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
  119. package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
  120. package/dist/features/events/components/index.d.ts +2 -0
  121. package/dist/features/events/components/index.js +1 -0
  122. package/dist/features/events/repository/events.repository.d.ts +6 -0
  123. package/dist/features/events/repository/events.repository.js +21 -0
  124. package/dist/features/history/repository/user-history.repository.d.ts +1 -1
  125. package/dist/features/history/utils/guest-history.d.ts +1 -1
  126. package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
  127. package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
  128. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  129. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  130. package/dist/features/layout/AppLayoutShell.js +1 -1
  131. package/dist/features/layout/NavbarLayout.js +1 -1
  132. package/dist/features/layout/TitleBarLayout.js +2 -2
  133. package/dist/features/media/MediaVideo.d.ts +7 -0
  134. package/dist/features/media/MediaVideo.js +36 -5
  135. package/dist/features/orders/components/OrdersList.js +1 -5
  136. package/dist/features/orders/schemas/firestore.d.ts +2 -8
  137. package/dist/features/orders/schemas/index.d.ts +3 -32
  138. package/dist/features/orders/schemas/index.js +1 -4
  139. package/dist/features/orders/types/index.d.ts +6 -9
  140. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  141. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
  142. package/dist/features/products/actions/product-actions.js +13 -10
  143. package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
  144. package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
  145. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  146. package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
  147. package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
  148. package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
  149. package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
  150. package/dist/features/products/components/PrizeRevealModal.js +18 -147
  151. package/dist/features/products/components/ProductDetailPageView.js +72 -30
  152. package/dist/features/products/components/ProductForm.js +7 -14
  153. package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
  154. package/dist/features/products/components/ProductGalleryClient.js +29 -10
  155. package/dist/features/products/components/ProductsIndexListing.js +2 -1
  156. package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
  157. package/dist/features/products/components/ShowGroupSection.js +2 -2
  158. package/dist/features/products/components/SublistingCarouselSection.js +1 -1
  159. package/dist/features/products/components/index.d.ts +4 -2
  160. package/dist/features/products/components/index.js +1 -0
  161. package/dist/features/products/constants/action-defs.d.ts +0 -1
  162. package/dist/features/products/constants/action-defs.js +0 -2
  163. package/dist/features/products/repository/products.repository.d.ts +6 -0
  164. package/dist/features/products/repository/products.repository.js +20 -1
  165. package/dist/features/products/schemas/firestore.d.ts +26 -2
  166. package/dist/features/products/schemas/index.d.ts +11 -3
  167. package/dist/features/products/schemas/index.js +10 -2
  168. package/dist/features/products/types/index.d.ts +2 -1
  169. package/dist/features/reviews/actions/review-actions.js +1 -0
  170. package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
  171. package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
  172. package/dist/features/reviews/components/ReviewsList.js +2 -1
  173. package/dist/features/reviews/schemas/firestore.d.ts +3 -0
  174. package/dist/features/reviews/schemas/index.d.ts +6 -0
  175. package/dist/features/reviews/schemas/index.js +2 -0
  176. package/dist/features/reviews/types/index.d.ts +3 -0
  177. package/dist/features/scams/actions/scam-actions.d.ts +3 -0
  178. package/dist/features/scams/actions/scam-actions.js +3 -2
  179. package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
  180. package/dist/features/scams/components/ScamProfileView.js +2 -2
  181. package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
  182. package/dist/features/scams/repository/scammer.repository.js +24 -0
  183. package/dist/features/seller/actions/seller-actions.js +8 -0
  184. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
  185. package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
  186. package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
  187. package/dist/features/stores/components/StoreNavTabs.js +21 -9
  188. package/dist/features/tester/actions/index.d.ts +1 -0
  189. package/dist/features/tester/actions/index.js +1 -0
  190. package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
  191. package/dist/features/tester/actions/leaderboard-actions.js +5 -0
  192. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
  193. package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
  194. package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
  195. package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
  196. package/dist/features/tester/components/index.d.ts +2 -0
  197. package/dist/features/tester/components/index.js +1 -0
  198. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
  199. package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
  200. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
  201. package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
  202. package/dist/features/tester/schemas/firestore.d.ts +22 -1
  203. package/dist/features/tester/schemas/firestore.js +8 -0
  204. package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
  205. package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
  206. package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
  207. package/dist/features/tester/seed-data/products-tester-seed-data.js +152 -8
  208. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +249 -14
  209. package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
  210. package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
  211. package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
  212. package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
  213. package/dist/index.d.ts +19 -22
  214. package/dist/index.js +25 -58
  215. package/dist/jobs.d.ts +1 -1
  216. package/dist/jobs.js +2 -2
  217. package/dist/next/routing/route-map.d.ts +2 -0
  218. package/dist/next/routing/route-map.js +1 -0
  219. package/dist/schemas/registry.d.ts +6 -26
  220. package/dist/seed/bids-seed-data.js +14 -2
  221. package/dist/seed/blog-posts-seed-data.js +2 -1
  222. package/dist/seed/categories-seed-data.js +11 -2
  223. package/dist/seed/events-seed-data.js +11 -11
  224. package/dist/seed/faq-seed-data.js +5 -1
  225. package/dist/seed/products-auctions-seed-data.js +2 -2
  226. package/dist/seed/products-preorders-seed-data.d.ts +2 -1
  227. package/dist/seed/products-prize-draws-seed-data.js +56 -10
  228. package/dist/seed/products-standard-seed-data.js +82 -3
  229. package/dist/seed/scammers-seed-data.js +75 -2
  230. package/dist/seed/site-settings-seed-data.js +5 -2
  231. package/dist/server-entry.d.ts +0 -1
  232. package/dist/server-entry.js +0 -1
  233. package/dist/server.d.ts +4 -20
  234. package/dist/server.js +10 -57
  235. package/dist/styles.css +1 -1
  236. package/dist/tailwind-utilities.css +1 -1
  237. package/dist/ui/components/BaseListingCard.js +1 -1
  238. package/dist/ui/components/ImageLightbox.d.ts +9 -0
  239. package/dist/ui/components/ImageLightbox.js +6 -3
  240. package/dist/ui/components/ListingToolbar.d.ts +18 -1
  241. package/dist/ui/components/ListingToolbar.js +5 -2
  242. package/dist/ui/components/StickyToolbar.js +13 -8
  243. package/package.json +1 -1
@@ -7,6 +7,12 @@ declare class EventRepository extends BaseRepository<EventDocument> {
7
7
  list(model: SieveModel): Promise<FirebaseSieveResult<EventDocument>>;
8
8
  findBySlug(slug: string): Promise<EventDocument | null>;
9
9
  findByIdOrSlug(idOrSlug: string): Promise<EventDocument | null>;
10
+ /**
11
+ * Active events sharing at least one tag with the given list — "related
12
+ * events" discovery. `array-contains-any` caps at 10 values, so only the
13
+ * first 10 tags are used.
14
+ */
15
+ findByTagsOverlap(tags: string[], limit?: number): Promise<EventDocument[]>;
10
16
  listActive(): Promise<EventDocument[]>;
11
17
  createEvent(input: EventCreateInput): Promise<EventDocument>;
12
18
  updateEvent(id: string, input: EventUpdateInput): Promise<EventDocument>;
@@ -21,6 +21,27 @@ class EventRepository extends BaseRepository {
21
21
  return bySlug;
22
22
  return this.findById(idOrSlug);
23
23
  }
24
+ /**
25
+ * Active events sharing at least one tag with the given list — "related
26
+ * events" discovery. `array-contains-any` caps at 10 values, so only the
27
+ * first 10 tags are used.
28
+ */
29
+ async findByTagsOverlap(tags, limit = 8) {
30
+ if (tags.length === 0)
31
+ return [];
32
+ try {
33
+ const snap = await this.getCollection()
34
+ .where(EVENT_FIELDS.STATUS, "==", EVENT_FIELDS.STATUS_VALUES.ACTIVE)
35
+ .where(EVENT_FIELDS.TAGS, "array-contains-any", tags.slice(0, 10))
36
+ .limit(limit)
37
+ .get();
38
+ return snap.docs.map((doc) => this.mapDoc(doc));
39
+ }
40
+ catch (error) {
41
+ void normalizeError(error);
42
+ throw new DatabaseError("Failed to find events by tag overlap", error);
43
+ }
44
+ }
24
45
  async listActive() {
25
46
  try {
26
47
  const now = new Date();
@@ -9,7 +9,7 @@
9
9
  * - re-visit semantics: removes existing entry for productId and unshifts new at position 0
10
10
  * - all mutations run inside a Firestore transaction (concurrent-write safe)
11
11
  */
12
- export type HistoryProductType = "product" | "auction" | "preorder";
12
+ export type HistoryProductType = "product" | "auction" | "preorder" | "prize-draw";
13
13
  export interface HistoryItemSnapshot {
14
14
  title?: string;
15
15
  thumb?: string;
@@ -1,4 +1,4 @@
1
- export type GuestHistoryType = "product" | "auction" | "preorder";
1
+ export type GuestHistoryType = "product" | "auction" | "preorder" | "prize-draw";
2
2
  export interface GuestHistoryItem {
3
3
  productId: string;
4
4
  productType: GuestHistoryType;
@@ -5,6 +5,7 @@ import { MediaImage } from "../../media/MediaImage";
5
5
  import { ROUTES } from "../../../next/routing/route-map";
6
6
  import { formatCurrency } from "../../../utils/number.formatter";
7
7
  import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
8
+ import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing";
8
9
  import { BundleBuyNowCta } from "../../categories/components/BundleBuyNowCta";
9
10
  const __P = {
10
11
  p3: "p-[var(--appkit-space-3)]",
@@ -24,7 +25,8 @@ function FeaturedBundleCard({ bundle, onBuyNow }) {
24
25
  const memberCount = bundle.bundleProductIds?.length ?? 0;
25
26
  const cover = bundle.display?.coverImage;
26
27
  const href = String(ROUTES.PUBLIC.BUNDLE_DETAIL?.(bundle.slug) ?? "#");
27
- return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-[var(--appkit-space-3)] hover:no-underline", children: [_jsx(Div, { className: `relative mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: cover ? (_jsx(MediaImage, { src: cover, alt: bundle.name, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })) }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePrice
28
+ const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
29
+ return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-[var(--appkit-space-3)] hover:no-underline", children: [_jsxs(Div, { className: `relative mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: [cover ? (_jsx(MediaImage, { src: cover, alt: bundle.name, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), discount && (_jsx(Badge, { variant: "success", className: "absolute left-2 top-2", children: BUNDLE_COPY.detail.discountBadge(discount.percent) }))] }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePrice
28
30
  ? formatCurrency(bundle.bundlePrice, "INR")
29
- : BUNDLE_COPY.featured.priceFallback }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }), onBuyNow && (_jsx(Div, { border: "subtle", className: `border-t ${__P.p3}`, padding: "t-xs", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
31
+ : BUNDLE_COPY.featured.priceFallback }), discount && (_jsx(Text, { size: "xs", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") })), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }), onBuyNow && (_jsx(Div, { border: "subtle", className: `border-t ${__P.p3}`, padding: "t-xs", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
30
32
  }
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { SectionCarousel } from "./SectionCarousel";
4
4
  import { useFeaturedProducts } from "../hooks/useFeaturedProducts";
5
5
  import { InteractiveProductCard } from "../../products/components/InteractiveProductCard";
6
- import { ROUTES } from "../../../next";
6
+ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
7
7
  import { CAROUSEL_PER_VIEW } from "../constants/carousel-per-view";
8
8
  export function FeaturedProductsSection({ title = "Featured Products", description, viewMoreHref, viewMoreLabel = "View all products →", className = "", filterByBrand, initialItems, rows = 1, autoScroll = false, scrollInterval = 5000, loop = true, }) {
9
9
  const { data, isLoading } = useFeaturedProducts({
@@ -13,5 +13,5 @@ export function FeaturedProductsSection({ title = "Featured Products", descripti
13
13
  : undefined,
14
14
  });
15
15
  const items = data?.items ?? [];
16
- return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "Featured Products", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: CAROUSEL_PER_VIEW.standard, gap: 16, rows: Math.min(Math.max(rows, 1), 4), autoScroll: autoScroll, autoScrollInterval: scrollInterval, loop: loop, keyExtractor: (product) => product.id, renderItem: (product) => (_jsx(InteractiveProductCard, { product: product, href: String(ROUTES.PUBLIC.PRODUCT_DETAIL(product.slug ?? product.id ?? "")) })), className: className }));
16
+ return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "Featured Products", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: CAROUSEL_PER_VIEW.standard, gap: 16, rows: Math.min(Math.max(rows, 1), 4), autoScroll: autoScroll, autoScrollInterval: scrollInterval, loop: loop, keyExtractor: (product) => product.id, renderItem: (product) => (_jsx(InteractiveProductCard, { product: product, href: pluginFor(product.listingType ?? "standard").detailRoute(product.slug ?? product.id ?? "") })), className: className }));
17
17
  }
@@ -41,5 +41,5 @@ export function SectionCarousel({ title, description, headingVariant = "editoria
41
41
  headingClass,
42
42
  ]
43
43
  .filter(Boolean)
44
- .join(" "), children: title }), headingVariant === "editorial" && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", textSize: "xs", color: "faint", className: "mt-1 select-none", "aria-hidden": "true", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { size: "xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `${descVariant} mt-2`, size: "base", children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop: loop, keyExtractor: keyExtractor, rows: rows, minItemWidth: minItemWidth, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, className: `inline-flex items-[center] gap-[0.375rem] border transition-colors ${useLightText ? "border-white/40 text-white hover:bg-[rgba(255,255,255,0.1)]" : "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)]"}`, size: "sm", weight: "medium", children: viewMoreLabel }) }))] })] }));
44
+ .join(" "), children: title }), headingVariant === "editorial" && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", textSize: "xs", color: "faint", className: "mt-1 select-none", "aria-hidden": "true", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { size: "xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `${descVariant} mt-2`, size: "base", children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop: loop, keyExtractor: keyExtractor, rows: rows, minItemWidth: minItemWidth, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, className: `inline-flex items-[center] gap-[0.375rem] transition-colors bg-primary text-white hover:bg-primary-600 ${useLightText ? "shadow-lg" : ""}`, size: "sm", weight: "semibold", children: viewMoreLabel }) }))] })] }));
45
45
  }
@@ -9,5 +9,5 @@ export function WelcomeSection({ title, subtitle, pillLabel, showCTA = true, cta
9
9
  if (isLoading) {
10
10
  return (_jsx(Section, { className: `relative overflow-hidden md:py-[6rem] ${className}`, paddingY: "y-4xl", paddingX: "x-md", children: _jsxs(Div, { className: "animate-pulse max-w-4xl mx-auto text-left", children: [_jsx(Div, { className: "h-6 w-52 mb-6", surface: "subtle", rounded: "full" }), _jsx(Div, { className: "h-20 mb-4 max-w-2xl", surface: "subtle", rounded: "lg" }), _jsx(Div, { className: "h-6 mb-8 max-w-lg", surface: "subtle", rounded: "lg" }), _jsxs(Row, { justify: "start", gap: "md", children: [_jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" }), _jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" })] })] }) }));
11
11
  }
12
- return (_jsxs(Section, { className: `relative overflow-hidden md:py-[7rem] ${className}`, paddingY: "y-5xl", paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-cobalt-50 dark:hover:bg-cobalt-900/20 transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", className: "border border-zinc-[200] dark:border-slate-[700] px-[0.875rem] py-[0.375rem]", rounded: "full", surface: "subtle", color: "muted", children: [chip.emoji, " ", chip.label] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsxs(Div, { className: `relative overflow-hidden aspect-[4/3] bg-[image:var(--appkit-gradient-section-mesh)]`, shadow: "2xl", border: "default", rounded: "3xl", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsxs(Stack, { centered: true, gap: "sm", className: `absolute inset-0 ${flex.center} px-[2.5rem]`, children: [_jsx(SiteMark, { title: brandLogoText || "LetItRip.in", size: "hero" }), _jsx(SiteLogo, { title: brandLogoText || "LetItRip.in", size: "hero" })] })] }) })] }) })] }));
12
+ return (_jsxs(Section, { className: `relative overflow-hidden md:py-[7rem] ${className}`, paddingY: "y-5xl", paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-cobalt-50 dark:hover:bg-cobalt-900/20 transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", className: "border border-zinc-[200] dark:border-slate-[700] px-[0.875rem] py-[0.375rem]", rounded: "full", surface: "subtle", color: "muted", children: [chip.emoji, " ", chip.label] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsxs(Div, { className: `relative overflow-hidden aspect-[4/3] bg-[image:var(--appkit-gradient-section-mesh)]`, shadow: "2xl", border: "default", rounded: "3xl", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsxs(Stack, { centered: true, gap: "sm", className: `absolute inset-0 ${flex.center} p-[2.5rem]`, children: [_jsx(SiteMark, { title: brandLogoText || "LetItRip.in", size: "xl" }), _jsx(SiteLogo, { title: brandLogoText || "LetItRip.in", size: "xl" })] })] }) })] }) })] }));
13
13
  }
@@ -79,7 +79,7 @@ function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions,
79
79
  storeDashboard: sidebarProfileLabels?.storeDashboard ?? sidebarProfileLabels?.sellerDashboard ?? "Store Dashboard",
80
80
  logout: sidebarProfileLabels?.logout ?? "Logout",
81
81
  };
82
- const navItemClass = "flex w-full items-center justify-end gap-[var(--appkit-space-2)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-zinc-700 transition-colors hover:bg-primary-50 hover:text-primary-800 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-300";
82
+ const navItemClass = "flex w-full items-center justify-end gap-[var(--appkit-space-2)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-zinc-700 transition-colors hover:bg-primary-50 hover:text-primary-800 text-[var(--appkit-color-text-muted)] dark:hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-300";
83
83
  const normalizedSections = hasSections
84
84
  ? sidebarSections
85
85
  : hasLegacyItems
@@ -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" }) }));
@@ -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;
@@ -236,12 +232,10 @@ export interface OrderDocument extends BaseDocument {
236
232
  images: string[];
237
233
  wonAt: Date;
238
234
  };
239
- /** Deadline for the buyer to claim the won prize (typically 7 days). */
240
- prizeRevealDeadline?: Date;
241
- /** True once `prizeRevealDeadline` passes without a claim â€" auto-forfeit. */
242
- prizeRevealExpired?: boolean;
243
235
  /** Source product id when the order came from a prize-draw entry. */
244
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";
245
239
  /** True for prize-draw entries and bundle purchases that bypass refund. */
246
240
  isNonRefundable?: boolean;
247
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;