@mohasinac/appkit 2.6.1 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +38 -0
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +75 -0
  3. package/dist/_internal/client/features/layout/RoleGuard.d.ts +15 -0
  4. package/dist/_internal/client/features/layout/RoleGuard.js +25 -0
  5. package/dist/_internal/client/features/layout/index.d.ts +5 -0
  6. package/dist/_internal/client/features/layout/index.js +4 -0
  7. package/dist/_internal/server/features/brands/actions.d.ts +3 -3
  8. package/dist/_internal/server/features/brands/actions.js +72 -5
  9. package/dist/_internal/server/features/brands/data.d.ts +8 -8
  10. package/dist/_internal/server/features/brands/data.js +10 -11
  11. package/dist/_internal/server/features/brands/service.d.ts +2 -2
  12. package/dist/_internal/server/features/brands/service.js +5 -5
  13. package/dist/_internal/server/features/categories/og.d.ts +33 -0
  14. package/dist/_internal/server/features/categories/og.js +75 -0
  15. package/dist/_internal/server/features/checkout/actions.d.ts +24 -0
  16. package/dist/_internal/server/features/checkout/actions.js +442 -13
  17. package/dist/_internal/server/features/checkout/index.d.ts +1 -1
  18. package/dist/_internal/server/features/checkout/index.js +1 -1
  19. package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +59 -0
  20. package/dist/_internal/server/features/checkout/prize-bundle-gates.js +99 -0
  21. package/dist/_internal/server/features/grouped/data.js +12 -5
  22. package/dist/_internal/server/features/homepage/data.d.ts +1 -1
  23. package/dist/_internal/server/features/homepage/data.js +2 -2
  24. package/dist/_internal/server/features/media/contextGuards.d.ts +52 -0
  25. package/dist/_internal/server/features/media/contextGuards.js +198 -0
  26. package/dist/_internal/server/features/orders/adapters.js +12 -0
  27. package/dist/_internal/server/features/products/data.d.ts +1 -1
  28. package/dist/_internal/server/features/sublisting-categories/data.d.ts +1 -1
  29. package/dist/_internal/server/features/sublisting-categories/data.js +2 -2
  30. package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +24 -0
  31. package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +86 -0
  32. package/dist/_internal/server/jobs/handlers/bundleStockSync.d.ts +18 -0
  33. package/dist/_internal/server/jobs/handlers/bundleStockSync.js +80 -0
  34. package/dist/_internal/server/jobs/handlers/index.d.ts +8 -0
  35. package/dist/_internal/server/jobs/handlers/index.js +13 -0
  36. package/dist/_internal/server/jobs/handlers/listingProcessor.js +13 -3
  37. package/dist/_internal/server/jobs/handlers/onProductStockChange.d.ts +17 -0
  38. package/dist/_internal/server/jobs/handlers/onProductStockChange.js +136 -0
  39. package/dist/_internal/server/jobs/handlers/onProductWrite.js +17 -1
  40. package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +9 -0
  41. package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +29 -0
  42. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +10 -0
  43. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +58 -0
  44. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +10 -0
  45. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +65 -0
  46. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +9 -0
  47. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +45 -0
  48. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +30 -0
  49. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +94 -0
  50. package/dist/_internal/shared/features/brands/schema.d.ts +3 -3
  51. package/dist/_internal/shared/features/cart/schema.d.ts +8 -8
  52. package/dist/_internal/shared/features/cart/schema.js +1 -1
  53. package/dist/_internal/shared/features/categories/bundle-config.d.ts +17 -0
  54. package/dist/_internal/shared/features/categories/bundle-config.js +17 -0
  55. package/dist/_internal/shared/features/layout/config.d.ts +35 -0
  56. package/dist/_internal/shared/features/layout/config.js +58 -0
  57. package/dist/_internal/shared/features/layout/index.d.ts +3 -0
  58. package/dist/_internal/shared/features/layout/index.js +2 -0
  59. package/dist/_internal/shared/features/layout/types.d.ts +137 -0
  60. package/dist/_internal/shared/features/layout/types.js +13 -0
  61. package/dist/_internal/shared/features/products/types.d.ts +1 -1
  62. package/dist/_internal/shared/listing-types/_registry.d.ts +57 -0
  63. package/dist/_internal/shared/listing-types/_registry.js +28 -0
  64. package/dist/_internal/shared/listing-types/auction/config.d.ts +7 -0
  65. package/dist/_internal/shared/listing-types/auction/config.js +8 -0
  66. package/dist/_internal/shared/listing-types/auction/ctas.d.ts +1 -0
  67. package/dist/_internal/shared/listing-types/auction/ctas.js +2 -0
  68. package/dist/_internal/shared/listing-types/auction/og.d.ts +1 -0
  69. package/dist/_internal/shared/listing-types/auction/og.js +1 -0
  70. package/dist/_internal/shared/listing-types/auction/schema.d.ts +1 -0
  71. package/dist/_internal/shared/listing-types/auction/schema.js +1 -0
  72. package/dist/_internal/shared/listing-types/auction/seed-factory.d.ts +1 -0
  73. package/dist/_internal/shared/listing-types/auction/seed-factory.js +1 -0
  74. package/dist/_internal/shared/listing-types/capabilities.d.ts +41 -0
  75. package/dist/_internal/shared/listing-types/capabilities.js +75 -0
  76. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +7 -0
  77. package/dist/_internal/shared/listing-types/pre-order/config.js +8 -0
  78. package/dist/_internal/shared/listing-types/pre-order/ctas.d.ts +1 -0
  79. package/dist/_internal/shared/listing-types/pre-order/ctas.js +2 -0
  80. package/dist/_internal/shared/listing-types/pre-order/og.d.ts +1 -0
  81. package/dist/_internal/shared/listing-types/pre-order/og.js +1 -0
  82. package/dist/_internal/shared/listing-types/pre-order/schema.d.ts +1 -0
  83. package/dist/_internal/shared/listing-types/pre-order/schema.js +1 -0
  84. package/dist/_internal/shared/listing-types/pre-order/seed-factory.d.ts +1 -0
  85. package/dist/_internal/shared/listing-types/pre-order/seed-factory.js +1 -0
  86. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +7 -0
  87. package/dist/_internal/shared/listing-types/prize-draw/config.js +8 -0
  88. package/dist/_internal/shared/listing-types/prize-draw/ctas.d.ts +1 -0
  89. package/dist/_internal/shared/listing-types/prize-draw/ctas.js +2 -0
  90. package/dist/_internal/shared/listing-types/prize-draw/og.d.ts +1 -0
  91. package/dist/_internal/shared/listing-types/prize-draw/og.js +1 -0
  92. package/dist/_internal/shared/listing-types/prize-draw/schema.d.ts +1 -0
  93. package/dist/_internal/shared/listing-types/prize-draw/schema.js +1 -0
  94. package/dist/_internal/shared/listing-types/prize-draw/seed-factory.d.ts +1 -0
  95. package/dist/_internal/shared/listing-types/prize-draw/seed-factory.js +1 -0
  96. package/dist/_internal/shared/listing-types/standard/config.d.ts +7 -0
  97. package/dist/_internal/shared/listing-types/standard/config.js +8 -0
  98. package/dist/_internal/shared/listing-types/standard/ctas.d.ts +1 -0
  99. package/dist/_internal/shared/listing-types/standard/ctas.js +3 -0
  100. package/dist/_internal/shared/listing-types/standard/og.d.ts +1 -0
  101. package/dist/_internal/shared/listing-types/standard/og.js +1 -0
  102. package/dist/_internal/shared/listing-types/standard/schema.d.ts +1 -0
  103. package/dist/_internal/shared/listing-types/standard/schema.js +1 -0
  104. package/dist/_internal/shared/listing-types/standard/seed-factory.d.ts +1 -0
  105. package/dist/_internal/shared/listing-types/standard/seed-factory.js +1 -0
  106. package/dist/_internal/shared/media/limits.d.ts +33 -0
  107. package/dist/_internal/shared/media/limits.js +97 -0
  108. package/dist/_internal/shared/schema-versions.d.ts +76 -0
  109. package/dist/_internal/shared/schema-versions.js +82 -0
  110. package/dist/client.d.ts +9 -0
  111. package/dist/client.js +7 -0
  112. package/dist/constants/api-endpoints.d.ts +6 -3
  113. package/dist/constants/api-endpoints.js +2 -1
  114. package/dist/errors/messages.d.ts +1 -1
  115. package/dist/errors/messages.js +1 -1
  116. package/dist/features/account/migrations.d.ts +2 -0
  117. package/dist/features/account/migrations.js +10 -0
  118. package/dist/features/admin/components/AdminMediaView.js +1 -1
  119. package/dist/features/admin/components/AdminProductsView.js +7 -3
  120. package/dist/features/admin/migrations.d.ts +2 -0
  121. package/dist/features/admin/migrations.js +10 -0
  122. package/dist/features/admin/types/product.types.d.ts +1 -1
  123. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +1 -1
  124. package/dist/features/auctions/migrations.d.ts +2 -0
  125. package/dist/features/auctions/migrations.js +10 -0
  126. package/dist/features/auctions/schemas/index.d.ts +3 -3
  127. package/dist/features/auctions/schemas/index.js +1 -1
  128. package/dist/features/auth/migrations.d.ts +2 -0
  129. package/dist/features/auth/migrations.js +10 -0
  130. package/dist/features/blog/migrations.d.ts +2 -0
  131. package/dist/features/blog/migrations.js +10 -0
  132. package/dist/features/brands/migrations.d.ts +2 -0
  133. package/dist/features/brands/migrations.js +10 -0
  134. package/dist/features/bundles/components/BundlesByCategoryListing.d.ts +6 -0
  135. package/dist/features/bundles/components/BundlesByCategoryListing.js +50 -0
  136. package/dist/features/bundles/components/index.d.ts +2 -0
  137. package/dist/features/bundles/components/index.js +1 -0
  138. package/dist/features/bundles/migrations.d.ts +2 -0
  139. package/dist/features/bundles/migrations.js +10 -0
  140. package/dist/features/bundles/schemas/index.d.ts +1 -0
  141. package/dist/features/bundles/schemas/index.js +1 -0
  142. package/dist/features/bundles/schemas/zod.d.ts +377 -0
  143. package/dist/features/bundles/schemas/zod.js +71 -0
  144. package/dist/features/cart/migrations.d.ts +2 -0
  145. package/dist/features/cart/migrations.js +10 -0
  146. package/dist/features/cart/schemas/firestore.d.ts +2 -2
  147. package/dist/features/categories/components/BrandDetailPageView.js +35 -4
  148. package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
  149. package/dist/features/categories/components/BrandDetailTabs.js +22 -8
  150. package/dist/features/categories/components/CategoryBundlesListing.d.ts +6 -0
  151. package/dist/features/categories/components/CategoryBundlesListing.js +74 -0
  152. package/dist/features/categories/components/CategoryDetailPageView.js +29 -4
  153. package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
  154. package/dist/features/categories/components/CategoryDetailTabs.js +22 -8
  155. package/dist/features/categories/migrations.d.ts +2 -0
  156. package/dist/features/categories/migrations.js +10 -0
  157. package/dist/features/categories/repository/categories.repository.d.ts +29 -0
  158. package/dist/features/categories/repository/categories.repository.js +83 -0
  159. package/dist/features/categories/schemas/firestore.d.ts +59 -2
  160. package/dist/features/categories/schemas/firestore.js +6 -0
  161. package/dist/features/categories/types/index.d.ts +11 -3
  162. package/dist/features/events/migrations.d.ts +2 -0
  163. package/dist/features/events/migrations.js +10 -0
  164. package/dist/features/faq/migrations.d.ts +2 -0
  165. package/dist/features/faq/migrations.js +10 -0
  166. package/dist/features/grouped/migrations.d.ts +2 -0
  167. package/dist/features/grouped/migrations.js +10 -0
  168. package/dist/features/grouped/schemas/firestore.d.ts +29 -10
  169. package/dist/features/grouped/schemas/firestore.js +10 -5
  170. package/dist/features/history/migrations.d.ts +2 -0
  171. package/dist/features/history/migrations.js +10 -0
  172. package/dist/features/homepage/hooks/useFeaturedAuctions.js +2 -2
  173. package/dist/features/homepage/hooks/useFeaturedPreOrders.js +2 -2
  174. package/dist/features/homepage/lib/section-renderer.js +5 -3
  175. package/dist/features/media/AvatarUpload.js +6 -28
  176. package/dist/features/media/hooks/useMedia.d.ts +31 -15
  177. package/dist/features/media/hooks/useMedia.js +48 -13
  178. package/dist/features/media/upload/ImageUpload.js +1 -1
  179. package/dist/features/media/upload/MediaUploadField.js +1 -1
  180. package/dist/features/messages/migrations.d.ts +2 -0
  181. package/dist/features/messages/migrations.js +10 -0
  182. package/dist/features/orders/components/OrdersList.js +10 -1
  183. package/dist/features/orders/migrations.d.ts +2 -0
  184. package/dist/features/orders/migrations.js +10 -0
  185. package/dist/features/orders/repository/orders.repository.d.ts +16 -0
  186. package/dist/features/orders/repository/orders.repository.js +49 -0
  187. package/dist/features/orders/schemas/firestore.d.ts +8 -0
  188. package/dist/features/orders/types/index.d.ts +12 -0
  189. package/dist/features/orders/utils/order-splitter.d.ts +2 -2
  190. package/dist/features/orders/utils/order-splitter.js +5 -0
  191. package/dist/features/payments/migrations.d.ts +2 -0
  192. package/dist/features/payments/migrations.js +10 -0
  193. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -1
  194. package/dist/features/products/actions/product-actions.d.ts +1 -1
  195. package/dist/features/products/api/[id]/route.js +34 -0
  196. package/dist/features/products/api/route.js +1 -19
  197. package/dist/features/products/components/CompareOverlay.d.ts +1 -1
  198. package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +24 -0
  199. package/dist/features/products/components/MarketplacePrizeDrawCard.js +102 -0
  200. package/dist/features/products/components/PrizeDrawCollage.d.ts +32 -0
  201. package/dist/features/products/components/PrizeDrawCollage.js +22 -0
  202. package/dist/features/products/components/PrizeDrawDetailPageView.d.ts +27 -0
  203. package/dist/features/products/components/PrizeDrawDetailPageView.js +118 -0
  204. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +19 -0
  205. package/dist/features/products/components/PrizeDrawEntryActions.js +48 -0
  206. package/dist/features/products/components/PrizeDrawItemsEditor.d.ts +13 -0
  207. package/dist/features/products/components/PrizeDrawItemsEditor.js +97 -0
  208. package/dist/features/products/components/PrizeDrawsIndexListing.d.ts +8 -0
  209. package/dist/features/products/components/PrizeDrawsIndexListing.js +128 -0
  210. package/dist/features/products/components/PrizeDrawsListingView.d.ts +15 -0
  211. package/dist/features/products/components/PrizeDrawsListingView.js +49 -0
  212. package/dist/features/products/components/PrizeRevealModal.d.ts +34 -0
  213. package/dist/features/products/components/PrizeRevealModal.js +124 -0
  214. package/dist/features/products/components/ProductDetailPageView.js +13 -1
  215. package/dist/features/products/components/ProductForm.js +35 -2
  216. package/dist/features/products/components/ProductGrid.js +3 -1
  217. package/dist/features/products/components/index.d.ts +16 -0
  218. package/dist/features/products/components/index.js +8 -0
  219. package/dist/features/products/constants/listing-tabs.d.ts +113 -0
  220. package/dist/features/products/constants/listing-tabs.js +43 -0
  221. package/dist/features/products/index.d.ts +1 -0
  222. package/dist/features/products/index.js +1 -0
  223. package/dist/features/products/migrations.d.ts +2 -0
  224. package/dist/features/products/migrations.js +10 -0
  225. package/dist/features/products/repository/products.repository.d.ts +11 -7
  226. package/dist/features/products/repository/products.repository.js +49 -24
  227. package/dist/features/products/schemas/firestore.d.ts +3 -3
  228. package/dist/features/products/schemas/firestore.js +2 -2
  229. package/dist/features/products/schemas/index.d.ts +5 -5
  230. package/dist/features/products/schemas/index.js +3 -1
  231. package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
  232. package/dist/features/products/types/index.d.ts +17 -1
  233. package/dist/features/products/utils/listing-type.d.ts +7 -4
  234. package/dist/features/products/utils/listing-type.js +8 -4
  235. package/dist/features/promotions/actions/coupon-actions.d.ts +1 -1
  236. package/dist/features/promotions/hooks/useCouponValidate.d.ts +1 -1
  237. package/dist/features/promotions/migrations.d.ts +2 -0
  238. package/dist/features/promotions/migrations.js +10 -0
  239. package/dist/features/promotions/repository/coupons.repository.d.ts +1 -1
  240. package/dist/features/promotions/schemas/index.d.ts +2 -2
  241. package/dist/features/reviews/migrations.d.ts +2 -0
  242. package/dist/features/reviews/migrations.js +10 -0
  243. package/dist/features/scams/migrations.d.ts +2 -0
  244. package/dist/features/scams/migrations.js +10 -0
  245. package/dist/features/search/api/route.d.ts +1 -1
  246. package/dist/features/search/api/route.js +3 -3
  247. package/dist/features/search/components/Search.d.ts +1 -1
  248. package/dist/features/search/schemas/index.d.ts +3 -3
  249. package/dist/features/search/schemas/index.js +3 -1
  250. package/dist/features/search/types/index.d.ts +2 -2
  251. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  252. package/dist/features/seller/components/SellerProductsView.js +20 -6
  253. package/dist/features/seller/migrations.d.ts +2 -0
  254. package/dist/features/seller/migrations.js +10 -0
  255. package/dist/features/seller/schemas/index.d.ts +2 -2
  256. package/dist/features/stores/components/StoreBundlesPageView.d.ts +12 -0
  257. package/dist/features/stores/components/StoreBundlesPageView.js +24 -0
  258. package/dist/features/stores/components/StoreDetailLayoutView.js +15 -3
  259. package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +11 -0
  260. package/dist/features/stores/components/StorePrizeDrawsPageView.js +27 -0
  261. package/dist/features/stores/components/index.d.ts +2 -0
  262. package/dist/features/stores/migrations.d.ts +2 -0
  263. package/dist/features/stores/migrations.js +10 -0
  264. package/dist/features/stores/schemas/index.d.ts +2 -2
  265. package/dist/features/stores/types/index.d.ts +1 -1
  266. package/dist/features/sublisting/migrations.d.ts +2 -0
  267. package/dist/features/sublisting/migrations.js +10 -0
  268. package/dist/features/sublisting/schemas/firestore.d.ts +2 -0
  269. package/dist/features/support/migrations.d.ts +2 -0
  270. package/dist/features/support/migrations.js +10 -0
  271. package/dist/features/wishlist/migrations.d.ts +2 -0
  272. package/dist/features/wishlist/migrations.js +10 -0
  273. package/dist/features/wishlist/types/index.d.ts +1 -1
  274. package/dist/index.d.ts +26 -18
  275. package/dist/index.js +41 -24
  276. package/dist/jobs.d.ts +1 -1
  277. package/dist/jobs.js +4 -0
  278. package/dist/next/routing/route-map.d.ts +4 -0
  279. package/dist/next/routing/route-map.js +2 -0
  280. package/dist/providers/db-firebase/admin-app-lite.js +21 -0
  281. package/dist/providers/db-firebase/filter-aliases.d.ts +2 -2
  282. package/dist/repositories/index.d.ts +0 -5
  283. package/dist/repositories/index.js +5 -4
  284. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  285. package/dist/seed/categories-seed-data.js +1105 -6
  286. package/dist/seed/faq-seed-data.js +160 -0
  287. package/dist/seed/grouped-listings-seed-data.js +32 -32
  288. package/dist/seed/homepage-sections-seed-data.js +52 -6
  289. package/dist/seed/index.d.ts +1 -3
  290. package/dist/seed/index.js +4 -3
  291. package/dist/seed/manifest.js +8 -13
  292. package/dist/seed/products-prize-draws-seed-data.d.ts +17 -0
  293. package/dist/seed/products-prize-draws-seed-data.js +313 -0
  294. package/dist/seo/json-ld.d.ts +1 -1
  295. package/dist/server-entry.d.ts +2 -2
  296. package/dist/server-entry.js +5 -3
  297. package/dist/server.d.ts +9 -2
  298. package/dist/server.js +11 -5
  299. package/dist/tailwind-utilities.css +1 -1
  300. package/dist/validation/schemas.d.ts +8 -8
  301. package/package.json +1 -1
  302. package/scripts/seed-cli.mjs +2 -4
@@ -0,0 +1,124 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * PrizeRevealModal (SB4-I)
5
+ *
6
+ * Theatrical reveal UI for prize-draw orders. When the buyer clicks "Reveal
7
+ * my prize" we call the reveal API (which has already picked the winner via
8
+ * crypto.randomInt server-side) and then run a 3-second highlight-cycling
9
+ * animation across the collage — fast at first, decelerating, finally
10
+ * landing on the winning tile. This is pure showmanship: the winner was
11
+ * locked in by the server before the first frame rendered.
12
+ *
13
+ * A permanent disclaimer below the collage explains exactly that — the RNG
14
+ * is `crypto.randomInt`, the source code is on GitHub, and neither the store
15
+ * nor LetItRip admins can influence the outcome.
16
+ */
17
+ import { useCallback, useEffect, useRef, useState } from "react";
18
+ import { Button, Div, Heading, Modal, Stack, Text } from "../../../ui";
19
+ import { PrizeDrawCollage } from "./PrizeDrawCollage";
20
+ const REVEAL_DURATION_MS = 3200; // total animation length
21
+ const CYCLE_INTERVAL_FAST = 80;
22
+ const CYCLE_INTERVAL_SLOW = 360;
23
+ export function PrizeRevealModal({ open, onClose, items, orderId, productId, onReveal, initialPrizeWon, rngSourceUrl, }) {
24
+ const [phase, setPhase] = useState(initialPrizeWon ? "won" : "idle");
25
+ const [highlight, setHighlight] = useState(undefined);
26
+ const [winner, setWinner] = useState(initialPrizeWon);
27
+ const [errorMessage, setErrorMessage] = useState("");
28
+ const [effectiveRngUrl, setEffectiveRngUrl] = useState(rngSourceUrl);
29
+ const cycleTimerRef = useRef(null);
30
+ const endTimerRef = useRef(null);
31
+ // Clean up timers on close/unmount so we don't leak between sessions.
32
+ useEffect(() => {
33
+ if (!open) {
34
+ if (cycleTimerRef.current)
35
+ clearTimeout(cycleTimerRef.current);
36
+ if (endTimerRef.current)
37
+ clearTimeout(endTimerRef.current);
38
+ cycleTimerRef.current = null;
39
+ endTimerRef.current = null;
40
+ }
41
+ return () => {
42
+ if (cycleTimerRef.current)
43
+ clearTimeout(cycleTimerRef.current);
44
+ if (endTimerRef.current)
45
+ clearTimeout(endTimerRef.current);
46
+ };
47
+ }, [open]);
48
+ const startAnimation = useCallback((winningItemNumber) => {
49
+ const startedAt = Date.now();
50
+ const tile = (n) => items[n % items.length]?.itemNumber ?? winningItemNumber;
51
+ let step = 0;
52
+ const tick = () => {
53
+ const elapsed = Date.now() - startedAt;
54
+ const remaining = REVEAL_DURATION_MS - elapsed;
55
+ if (remaining <= 0) {
56
+ setHighlight(winningItemNumber);
57
+ return;
58
+ }
59
+ // Decelerate: as we approach the end, the interval stretches.
60
+ const progress = elapsed / REVEAL_DURATION_MS;
61
+ const interval = CYCLE_INTERVAL_FAST +
62
+ (CYCLE_INTERVAL_SLOW - CYCLE_INTERVAL_FAST) * progress;
63
+ step += 1;
64
+ setHighlight(tile(step));
65
+ cycleTimerRef.current = setTimeout(tick, interval);
66
+ };
67
+ tick();
68
+ }, [items]);
69
+ const handleRevealClick = useCallback(async () => {
70
+ if (phase !== "idle")
71
+ return;
72
+ setPhase("revealing");
73
+ setErrorMessage("");
74
+ let response;
75
+ try {
76
+ if (onReveal) {
77
+ response = await onReveal({ orderId, productId });
78
+ }
79
+ else {
80
+ const res = await fetch(`/api/prize-draws/${productId}/reveal`, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ orderId }),
84
+ });
85
+ const json = (await res.json());
86
+ if (!res.ok)
87
+ throw new Error("Reveal request failed");
88
+ response = json.data ?? {};
89
+ }
90
+ }
91
+ catch (err) {
92
+ setPhase("error");
93
+ setErrorMessage(err instanceof Error ? err.message : "Reveal request failed");
94
+ return;
95
+ }
96
+ if (response.refunded) {
97
+ setPhase("refunded");
98
+ return;
99
+ }
100
+ if (!response.prizeWon) {
101
+ setPhase("error");
102
+ setErrorMessage("Unexpected reveal response");
103
+ return;
104
+ }
105
+ if (response.rngSourceUrl)
106
+ setEffectiveRngUrl(response.rngSourceUrl);
107
+ setWinner(response.prizeWon);
108
+ if (response.alreadyRevealed) {
109
+ // No theatrics for re-opens — just show the prize.
110
+ setHighlight(response.prizeWon.itemNumber);
111
+ setPhase("won");
112
+ return;
113
+ }
114
+ startAnimation(response.prizeWon.itemNumber);
115
+ endTimerRef.current = setTimeout(() => {
116
+ setPhase("won");
117
+ }, REVEAL_DURATION_MS);
118
+ }, [onReveal, orderId, phase, productId, startAnimation]);
119
+ const winnerImg = winner?.images?.[0];
120
+ return (_jsx(Modal, { isOpen: open, onClose: onClose, title: "Prize Reveal", size: "lg", children: _jsxs(Stack, { gap: "md", children: [phase === "refunded" ? (_jsxs(Div, { className: "rounded border border-yellow-400/40 bg-yellow-50 px-4 py-3 text-yellow-900 dark:bg-yellow-900/30 dark:text-yellow-100", children: [_jsx(Heading, { level: 3, className: "mb-1", children: "Pool exhausted \u2014 you've been refunded" }), _jsx(Text, { className: "text-sm", children: "Every prize in this draw was already claimed by the time your entry rolled. Your order has been marked refunded automatically." })] })) : null, phase === "error" ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-red-50 px-4 py-3 text-red-900 dark:bg-red-900/30 dark:text-red-100", children: [_jsx(Text, { className: "text-sm font-semibold", children: "Something went wrong" }), _jsx(Text, { className: "text-sm", children: errorMessage })] })) : null, _jsx(PrizeDrawCollage, { items: items, hideWonState: true, highlightItemNumber: highlight }), phase === "idle" ? (_jsx(Button, { type: "button", variant: "primary", size: "lg", onClick: handleRevealClick, children: "\u2728 Reveal my prize" })) : null, phase === "revealing" ? (_jsxs(Div, { className: "rounded bg-[var(--appkit-color-surface-muted)] p-4 text-center", children: [_jsx(Text, { className: "text-lg font-semibold", children: "Rolling\u2026" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "The winner was locked by the server before this animation started. Hang tight \u2014 we're just making it look pretty." })] })) : null, phase === "won" && winner ? (_jsxs(Div, { className: "rounded-lg border-2 border-[var(--appkit-color-primary)] bg-[var(--appkit-color-surface)] p-4 text-center", children: [_jsx(Text, { className: "text-xs uppercase tracking-wider text-[var(--appkit-color-text-muted)]", children: "You won" }), _jsxs(Heading, { level: 2, className: "my-2", children: ["#", winner.itemNumber, " \u2014 ", winner.title] }), winnerImg ? (
121
+ /* eslint-disable-next-line @next/next/no-img-element */
122
+ _jsx("img", { src: winnerImg, alt: winner.title, className: "mx-auto max-h-64 rounded" })) : null, winner.estimatedValue != null ? (_jsxs(Text, { className: "mt-2 text-sm text-[var(--appkit-color-text-muted)]", children: ["Estimated value: \u20B9", (winner.estimatedValue / 100).toLocaleString("en-IN")] })) : null] })) : null, _jsxs(Div, { className: "rounded border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-muted)] px-3 py-2 text-xs text-[var(--appkit-color-text-muted)]", children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Fairness guarantee:" }), " ", "Winners are picked by ", _jsx("code", { children: "crypto.randomInt" }), " running on LetItRip's server before the animation starts. The animation is theatrical \u2014 neither the store nor LetItRip staff can influence the outcome.", " ", effectiveRngUrl ? (_jsx("a", { href: effectiveRngUrl, target: "_blank", rel: "noreferrer noopener", className: "underline", children: "View RNG source code \u2192" })) : null] })] }) }));
123
+ }
124
+ export default PrizeRevealModal;
@@ -125,6 +125,18 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
125
125
  const brand = typeof p.brand === "string" ? p.brand : null;
126
126
  const brandSlug = typeof p.brandSlug === "string" ? p.brandSlug : null;
127
127
  const condition = typeof p.condition === "string" ? p.condition : null;
128
+ // SB7-B — bundle reverse refs. partOfBundleIds + partOfBundleTitles are
129
+ // parallel arrays maintained by the bundles repository's syncReverseRefs.
130
+ const partOfBundleIds = Array.isArray(p.partOfBundleIds)
131
+ ? p.partOfBundleIds
132
+ : [];
133
+ const partOfBundleTitles = Array.isArray(p.partOfBundleTitles)
134
+ ? p.partOfBundleTitles
135
+ : [];
136
+ const bundleMemberships = partOfBundleIds.map((id, i) => ({
137
+ id,
138
+ title: partOfBundleTitles[i] ?? id,
139
+ }));
128
140
  const tags = Array.isArray(p.tags) ? p.tags : [];
129
141
  const features = Array.isArray(p.features)
130
142
  ? p.features
@@ -198,7 +210,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
198
210
  ? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
199
211
  : "bg-red-50 text-red-700 dark:bg-red-900/30 dark:text-red-400"}`, children: [inStock ? "✓ In Stock" : "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
200
212
  ? ` — only ${effectiveStock} left`
201
- : ""] })), (categoryName || category || subcategory || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category })), subcategory && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400 capitalize", children: subcategory })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: brand }))] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, returnable: returnPolicy != null && returnPolicy.length > 0, labels: {
213
+ : ""] })), (categoryName || category || subcategory || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category })), subcategory && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400 capitalize", children: subcategory })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: brand }))] })), bundleMemberships.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, children: bundleMemberships.map((b) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: "inline-flex items-center gap-1 rounded-full border border-teal-300 bg-teal-50 px-2.5 py-1 text-xs font-semibold text-teal-700 transition-colors hover:border-teal-500 hover:bg-teal-100 dark:border-teal-800/60 dark:bg-teal-900/30 dark:text-teal-300 dark:hover:border-teal-600 dark:hover:bg-teal-900/50", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), "In bundle: ", b.title] }, b.id))) })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, returnable: returnPolicy != null && returnPolicy.length > 0, labels: {
202
214
  featured: "Featured",
203
215
  fasterDelivery: "Faster Delivery",
204
216
  ratedSeller: "Rated Seller",
@@ -10,7 +10,8 @@ import { ImageUpload, MediaUploadField, MediaUploadList, } from "../../media";
10
10
  import { useMediaUpload } from "../../media";
11
11
  import { resolveDate } from "../../../utils/date.formatter";
12
12
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
13
- import { isAuctionListing, isPreOrderListing, } from "../utils/listing-type";
13
+ import { isAuctionListing, isPreOrderListing, isPrizeDrawListing, } from "../utils/listing-type";
14
+ import { PrizeDrawItemsEditor } from "./PrizeDrawItemsEditor";
14
15
  export const PRODUCT_STATUS_OPTIONS = [
15
16
  { value: "draft", label: "Draft" },
16
17
  { value: "published", label: "Published" },
@@ -117,7 +118,39 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
117
118
  }), disabled: isReadonly, options: [
118
119
  { value: "winner", label: t("formAuctionShippingPaidByWinner") },
119
120
  { value: "seller", label: t("formAuctionShippingPaidBySeller") },
120
- ] }), _jsx(Text, { variant: "secondary", weight: "semibold", className: "mt-2", children: t("sectionAuctionAdvanced") }), _jsx(Checkbox, { label: t("formAutoExtendable"), checked: !!product.autoExtendable, onChange: (e) => update({ autoExtendable: e.target.checked }), disabled: isReadonly }), product.autoExtendable && (_jsxs(_Fragment, { children: [_jsx(Alert, { variant: "info", title: t("formAutoExtendableHelp"), children: t("formAutoExtendableHelp") }), _jsx(FormField, { name: "auctionExtensionMinutes", label: t("formAuctionExtensionMinutes"), type: "number", value: String(product.auctionExtensionMinutes ?? 5), onChange: (value) => update({ auctionExtensionMinutes: Number(value) || 5 }), disabled: isReadonly, placeholder: "5", helpText: t("formAuctionExtensionMinutesHelp") })] }))] })), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionPreOrderSettings") }), _jsx(Checkbox, { label: t("formIsPreOrder"), checked: isPreOrderListing(product), onChange: (e) => update({ listingType: e.target.checked ? "pre-order" : "standard" }), disabled: isReadonly }), isPreOrderListing(product) && (_jsxs(_Fragment, { children: [_jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "preOrderDeliveryDate", label: t("formPreOrderDeliveryDate"), type: "datetime-local", value: (() => {
121
+ ] }), _jsx(Text, { variant: "secondary", weight: "semibold", className: "mt-2", children: t("sectionAuctionAdvanced") }), _jsx(Checkbox, { label: t("formAutoExtendable"), checked: !!product.autoExtendable, onChange: (e) => update({ autoExtendable: e.target.checked }), disabled: isReadonly }), product.autoExtendable && (_jsxs(_Fragment, { children: [_jsx(Alert, { variant: "info", title: t("formAutoExtendableHelp"), children: t("formAutoExtendableHelp") }), _jsx(FormField, { name: "auctionExtensionMinutes", label: t("formAuctionExtensionMinutes"), type: "number", value: String(product.auctionExtensionMinutes ?? 5), onChange: (value) => update({ auctionExtensionMinutes: Number(value) || 5 }), disabled: isReadonly, placeholder: "5", helpText: t("formAuctionExtensionMinutesHelp") })] }))] })), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionPreOrderSettings") }), _jsx(Checkbox, { label: t("formIsPreOrder"), checked: isPreOrderListing(product), onChange: (e) => update({ listingType: e.target.checked ? "pre-order" : "standard" }), disabled: isReadonly }), _jsx(Heading, { level: 4, className: "mt-4", children: "Prize Draw Settings" }), _jsx(Checkbox, { label: "This is a prize-draw listing", checked: isPrizeDrawListing(product), onChange: (e) => update({ listingType: e.target.checked ? "prize-draw" : "standard" }), disabled: isReadonly ||
122
+ (product.prizeDrawItems ?? []).some((it) => it.isWon) }), isPrizeDrawListing(product) && ((() => {
123
+ const prizeItems = (product.prizeDrawItems ?? []);
124
+ const anyWon = prizeItems.some((it) => it.isWon);
125
+ const lockedForReveal = anyWon;
126
+ const fieldDisabled = isReadonly || lockedForReveal;
127
+ return (_jsxs(_Fragment, { children: [lockedForReveal ? (_jsx(Alert, { variant: "error", title: "Listing locked", children: "At least one prize has been revealed for this draw \u2014 the listing and its fields are now frozen. To rerun a similar draw, clone it into a new prize-draw listing." })) : null, _jsx(Alert, { variant: "warning", title: "Non-refundable", children: "Prize-draw entries are non-refundable once the reveal window opens. Buyers see and must accept this notice before paying." }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "pricePerEntry", label: "Price per entry (\u20B9)", type: "number", value: product.pricePerEntry != null
128
+ ? String(Math.round(product.pricePerEntry / 100))
129
+ : "", onChange: (value) => update({
130
+ pricePerEntry: Math.round((parseFloat(value) || 0) * 100),
131
+ }), disabled: fieldDisabled, placeholder: "299" }), _jsx(FormField, { name: "prizeMaxEntries", label: "Max entries (pool size)", type: "number", value: String(product.prizeMaxEntries ?? ""), onChange: (value) => update({ prizeMaxEntries: Number(value) || undefined }), disabled: fieldDisabled, placeholder: "100" })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "prizeRevealWindowStart", label: "Reveal window start", type: "datetime-local", value: (() => {
132
+ const d = resolveDate(product.prizeRevealWindowStart);
133
+ if (!d)
134
+ return "";
135
+ return d.toISOString().slice(0, 16);
136
+ })(), onChange: (value) => update({ prizeRevealWindowStart: value }), disabled: fieldDisabled }), _jsx(FormField, { name: "prizeRevealWindowEnd", label: "Reveal window end", type: "datetime-local", value: (() => {
137
+ const d = resolveDate(product.prizeRevealWindowEnd);
138
+ if (!d)
139
+ return "";
140
+ return d.toISOString().slice(0, 16);
141
+ })(), onChange: (value) => update({ prizeRevealWindowEnd: value }), disabled: fieldDisabled })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "prizeRevealDeadlineDays", label: "Reveal deadline (days)", type: "number", value: String(product.prizeRevealDeadlineDays ?? 3), onChange: (value) => update({
142
+ prizeRevealDeadlineDays: Number(value) || 3,
143
+ }), disabled: fieldDisabled, placeholder: "3", helpText: "After window opens, buyers have this many days to claim." }), _jsx(FormField, { name: "maxPerUser", label: "Max entries per customer (blank = unlimited)", type: "number", value: String(product.maxPerUser ?? ""), onChange: (value) => update({ maxPerUser: Number(value) || undefined }), disabled: fieldDisabled, placeholder: "" })] }), product.prizeGithubFileUrl ? (_jsx(FormField, { name: "prizeGithubFileUrl", label: "RNG Source Code URL (read-only)", type: "text", value: product.prizeGithubFileUrl, onChange: () => { }, disabled: true, helpText: "Auto-set on first save. Public proof-of-fairness link." })) : null, _jsx(PrizeDrawItemsEditor, { items: (product.prizeDrawItems ?? []), onChange: (items) => update({ prizeDrawItems: items }), onUploadImage: async (file) => {
144
+ // Caller is expected to wire a real uploader by overriding
145
+ // renderPrizeDrawImageUpload — fall back to data URL preview
146
+ // so the editor still functions in unwired admin contexts.
147
+ return new Promise((resolve) => {
148
+ const reader = new FileReader();
149
+ reader.onload = () => resolve(String(reader.result ?? ""));
150
+ reader.readAsDataURL(file);
151
+ });
152
+ } })] }));
153
+ })()), isPreOrderListing(product) && (_jsxs(_Fragment, { children: [_jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "preOrderDeliveryDate", label: t("formPreOrderDeliveryDate"), type: "datetime-local", value: (() => {
121
154
  const d = resolveDate(product.preOrderDeliveryDate);
122
155
  if (!d)
123
156
  return "";
@@ -53,7 +53,9 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
53
53
  ? "bg-primary border-primary text-white opacity-100"
54
54
  : "bg-white/90 dark:bg-slate-800/90 border-zinc-300 dark:border-slate-500",
55
55
  selectionMode || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100",
56
- ].join(" "), children: isSelected && (_jsx("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 2.5, "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2 6l3 3 5-5" }) })) })), !selectionMode && !isSelected && (_jsxs(_Fragment, { children: [discount && (_jsxs(Span, { className: "rounded-full bg-rose-500 px-2 py-0.5 text-[10px] font-bold text-white shadow-sm", children: ["-", discount, "%"] })), typeBadge && (_jsx(Span, { className: `rounded-full px-2 py-0.5 text-[10px] font-bold shadow-sm ${typeBadge.cls}`, children: typeBadge.label }))] }))] }), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
56
+ ].join(" "), children: isSelected && (_jsx("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 2.5, "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2 6l3 3 5-5" }) })) })), !selectionMode && !isSelected && (_jsxs(_Fragment, { children: [discount && (_jsxs(Span, { className: "rounded-full bg-rose-500 px-2 py-0.5 text-[10px] font-bold text-white shadow-sm", children: ["-", discount, "%"] })), typeBadge && (_jsx(Span, { className: `rounded-full px-2 py-0.5 text-[10px] font-bold shadow-sm ${typeBadge.cls}`, children: typeBadge.label })), product.partOfBundleIds && product.partOfBundleIds.length > 0 && (_jsx(Span, { className: "rounded-full bg-teal-600 px-2 py-0.5 text-[10px] font-bold text-white shadow-sm", title: product.partOfBundleTitles && product.partOfBundleTitles.length > 0
57
+ ? `In bundle: ${product.partOfBundleTitles[0]}`
58
+ : "In a bundle", children: "Bundled" }))] }))] }), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
57
59
  e.stopPropagation();
58
60
  e.preventDefault();
59
61
  onAddToWishlist(product.id);
@@ -7,6 +7,22 @@ export { ProductForm, PRODUCT_STATUS_OPTIONS } from "./ProductForm";
7
7
  export type { ProductFormProps, ProductFormValue } from "./ProductForm";
8
8
  export { NonRefundableConsentModal } from "./NonRefundableConsentModal";
9
9
  export type { NonRefundableConsentModalProps, NonRefundableListingType, } from "./NonRefundableConsentModal";
10
+ export { PrizeDrawItemsEditor } from "./PrizeDrawItemsEditor";
11
+ export type { PrizeDrawItemsEditorProps } from "./PrizeDrawItemsEditor";
12
+ export { PrizeDrawCollage } from "./PrizeDrawCollage";
13
+ export type { PrizeDrawCollageProps } from "./PrizeDrawCollage";
14
+ export { PrizeRevealModal } from "./PrizeRevealModal";
15
+ export type { PrizeRevealModalProps, PrizeRevealResponse, } from "./PrizeRevealModal";
16
+ export { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
17
+ export type { MarketplacePrizeDrawCardProps, MarketplacePrizeDrawCardData, MarketplacePrizeDrawCardLabels, } from "./MarketplacePrizeDrawCard";
18
+ export { PrizeDrawsIndexListing } from "./PrizeDrawsIndexListing";
19
+ export type { PrizeDrawsIndexListingProps } from "./PrizeDrawsIndexListing";
20
+ export { PrizeDrawsListingView } from "./PrizeDrawsListingView";
21
+ export type { PrizeDrawsListingViewProps } from "./PrizeDrawsListingView";
22
+ export { PrizeDrawDetailPageView } from "./PrizeDrawDetailPageView";
23
+ export type { PrizeDrawDetailPageViewProps } from "./PrizeDrawDetailPageView";
24
+ export { PrizeDrawEntryActions } from "./PrizeDrawEntryActions";
25
+ export type { PrizeDrawEntryActionsProps } from "./PrizeDrawEntryActions";
10
26
  export { ProductFeaturesSelector } from "./ProductFeaturesSelector";
11
27
  export type { ProductFeaturesSelectorProps } from "./ProductFeaturesSelector";
12
28
  export { ProductFeatureBadges } from "./ProductFeatureBadges";
@@ -3,6 +3,14 @@ export { ProductFilters, PRODUCT_FILTER_KEYS, PRODUCT_ADMIN_SORT_OPTIONS, PRODUC
3
3
  export { InteractiveProductCard } from "./InteractiveProductCard";
4
4
  export { ProductForm, PRODUCT_STATUS_OPTIONS } from "./ProductForm";
5
5
  export { NonRefundableConsentModal } from "./NonRefundableConsentModal";
6
+ export { PrizeDrawItemsEditor } from "./PrizeDrawItemsEditor";
7
+ export { PrizeDrawCollage } from "./PrizeDrawCollage";
8
+ export { PrizeRevealModal } from "./PrizeRevealModal";
9
+ export { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
10
+ export { PrizeDrawsIndexListing } from "./PrizeDrawsIndexListing";
11
+ export { PrizeDrawsListingView } from "./PrizeDrawsListingView";
12
+ export { PrizeDrawDetailPageView } from "./PrizeDrawDetailPageView";
13
+ export { PrizeDrawEntryActions } from "./PrizeDrawEntryActions";
6
14
  export { ProductFeaturesSelector } from "./ProductFeaturesSelector";
7
15
  export { ProductFeatureBadges } from "./ProductFeatureBadges";
8
16
  export { FeatureBadge, FeatureBadgeList } from "./FeatureBadge";
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Listing-type tab constants (SB10-A).
3
+ *
4
+ * Single source of truth for the tab bars shown on category, brand, store,
5
+ * admin, and search surfaces. Each entry maps to either a `listingType`
6
+ * filter on the `products` collection or to a separate collection
7
+ * (currently only `bundles`).
8
+ *
9
+ * Add new tab variants here — never inline the tab list inside the view.
10
+ */
11
+ import type { ListingType } from "../types";
12
+ export interface ListingTab {
13
+ id: string;
14
+ label: string;
15
+ /** Filter against `products.listingType` when set. */
16
+ listingType?: ListingType;
17
+ /** Set when the tab queries a different collection (e.g. `bundles`). */
18
+ collection?: "bundles";
19
+ }
20
+ /** Tabs shown on `/categories/[slug]` and `/brands/[slug]` detail pages. */
21
+ export declare const CATEGORY_PAGE_TABS: readonly [{
22
+ readonly id: "products";
23
+ readonly label: "Products";
24
+ readonly listingType: "standard";
25
+ }, {
26
+ readonly id: "auctions";
27
+ readonly label: "Auctions";
28
+ readonly listingType: "auction";
29
+ }, {
30
+ readonly id: "pre-orders";
31
+ readonly label: "Pre-Orders";
32
+ readonly listingType: "pre-order";
33
+ }, {
34
+ readonly id: "prize-draws";
35
+ readonly label: "Prize Draws";
36
+ readonly listingType: "prize-draw";
37
+ }, {
38
+ readonly id: "bundles";
39
+ readonly label: "Bundles";
40
+ readonly collection: "bundles";
41
+ }];
42
+ export type CategoryTabId = (typeof CATEGORY_PAGE_TABS)[number]["id"];
43
+ /** Tabs shown on the public `/stores/[slug]` nav bar. */
44
+ export declare const STORE_PAGE_TABS: readonly [{
45
+ readonly id: "products";
46
+ readonly label: "Products";
47
+ readonly listingType: "standard";
48
+ }, {
49
+ readonly id: "auctions";
50
+ readonly label: "Auctions";
51
+ readonly listingType: "auction";
52
+ }, {
53
+ readonly id: "pre-orders";
54
+ readonly label: "Pre-Orders";
55
+ readonly listingType: "pre-order";
56
+ }, {
57
+ readonly id: "prize-draws";
58
+ readonly label: "Prize Draws";
59
+ readonly listingType: "prize-draw";
60
+ }, {
61
+ readonly id: "bundles";
62
+ readonly label: "Bundles";
63
+ readonly collection: "bundles";
64
+ }];
65
+ export type StoreTabId = (typeof STORE_PAGE_TABS)[number]["id"];
66
+ /** Tabs shown on the seller-dashboard listings view + admin products list. */
67
+ export declare const SELLER_LISTING_TABS: readonly [{
68
+ readonly id: "all";
69
+ readonly label: "All";
70
+ }, {
71
+ readonly id: "products";
72
+ readonly label: "Products";
73
+ readonly listingType: "standard";
74
+ }, {
75
+ readonly id: "auctions";
76
+ readonly label: "Auctions";
77
+ readonly listingType: "auction";
78
+ }, {
79
+ readonly id: "pre-orders";
80
+ readonly label: "Pre-Orders";
81
+ readonly listingType: "pre-order";
82
+ }, {
83
+ readonly id: "prize-draws";
84
+ readonly label: "Prize Draws";
85
+ readonly listingType: "prize-draw";
86
+ }];
87
+ export type SellerListingTabId = (typeof SELLER_LISTING_TABS)[number]["id"];
88
+ /** Tabs shown on `/search` results. */
89
+ export declare const SEARCH_RESULT_TABS: readonly [{
90
+ readonly id: "all";
91
+ readonly label: "All";
92
+ }, {
93
+ readonly id: "products";
94
+ readonly label: "Products";
95
+ readonly listingType: "standard";
96
+ }, {
97
+ readonly id: "auctions";
98
+ readonly label: "Auctions";
99
+ readonly listingType: "auction";
100
+ }, {
101
+ readonly id: "pre-orders";
102
+ readonly label: "Pre-Orders";
103
+ readonly listingType: "pre-order";
104
+ }, {
105
+ readonly id: "prize-draws";
106
+ readonly label: "Prize Draws";
107
+ readonly listingType: "prize-draw";
108
+ }, {
109
+ readonly id: "bundles";
110
+ readonly label: "Bundles";
111
+ readonly collection: "bundles";
112
+ }];
113
+ export type SearchTabId = (typeof SEARCH_RESULT_TABS)[number]["id"];
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Listing-type tab constants (SB10-A).
3
+ *
4
+ * Single source of truth for the tab bars shown on category, brand, store,
5
+ * admin, and search surfaces. Each entry maps to either a `listingType`
6
+ * filter on the `products` collection or to a separate collection
7
+ * (currently only `bundles`).
8
+ *
9
+ * Add new tab variants here — never inline the tab list inside the view.
10
+ */
11
+ /** Tabs shown on `/categories/[slug]` and `/brands/[slug]` detail pages. */
12
+ export const CATEGORY_PAGE_TABS = [
13
+ { id: "products", label: "Products", listingType: "standard" },
14
+ { id: "auctions", label: "Auctions", listingType: "auction" },
15
+ { id: "pre-orders", label: "Pre-Orders", listingType: "pre-order" },
16
+ { id: "prize-draws", label: "Prize Draws", listingType: "prize-draw" },
17
+ { id: "bundles", label: "Bundles", collection: "bundles" },
18
+ ];
19
+ /** Tabs shown on the public `/stores/[slug]` nav bar. */
20
+ export const STORE_PAGE_TABS = [
21
+ { id: "products", label: "Products", listingType: "standard" },
22
+ { id: "auctions", label: "Auctions", listingType: "auction" },
23
+ { id: "pre-orders", label: "Pre-Orders", listingType: "pre-order" },
24
+ { id: "prize-draws", label: "Prize Draws", listingType: "prize-draw" },
25
+ { id: "bundles", label: "Bundles", collection: "bundles" },
26
+ ];
27
+ /** Tabs shown on the seller-dashboard listings view + admin products list. */
28
+ export const SELLER_LISTING_TABS = [
29
+ { id: "all", label: "All" },
30
+ { id: "products", label: "Products", listingType: "standard" },
31
+ { id: "auctions", label: "Auctions", listingType: "auction" },
32
+ { id: "pre-orders", label: "Pre-Orders", listingType: "pre-order" },
33
+ { id: "prize-draws", label: "Prize Draws", listingType: "prize-draw" },
34
+ ];
35
+ /** Tabs shown on `/search` results. */
36
+ export const SEARCH_RESULT_TABS = [
37
+ { id: "all", label: "All" },
38
+ { id: "products", label: "Products", listingType: "standard" },
39
+ { id: "auctions", label: "Auctions", listingType: "auction" },
40
+ { id: "pre-orders", label: "Pre-Orders", listingType: "pre-order" },
41
+ { id: "prize-draws", label: "Prize Draws", listingType: "prize-draw" },
42
+ { id: "bundles", label: "Bundles", collection: "bundles" },
43
+ ];
@@ -1,5 +1,6 @@
1
1
  export * from "./types";
2
2
  export * from "./constants/action-defs";
3
+ export * from "./constants/listing-tabs";
3
4
  export * from "./hooks/useProducts";
4
5
  export * from "./hooks/useProductDetail";
5
6
  export * from "./hooks/useBrands";
@@ -1,5 +1,6 @@
1
1
  export * from "./types";
2
2
  export * from "./constants/action-defs";
3
+ export * from "./constants/listing-tabs";
3
4
  export * from "./hooks/useProducts";
4
5
  export * from "./hooks/useProductDetail";
5
6
  export * from "./hooks/useBrands";
@@ -0,0 +1,2 @@
1
+ import type { MigrationStep } from "../../_internal/shared/schema-versions";
2
+ export declare const migrations: Record<number, MigrationStep<any>>;
@@ -0,0 +1,10 @@
1
+ // SB-UNI X3 — migrations shell for product/productFeature/productTemplate.
2
+ // v1 schema is current; no migrations needed yet.
3
+ //
4
+ // To add a migration:
5
+ // 1. Bump the relevant SCHEMA_VERSIONS entry in
6
+ // `_internal/shared/schema-versions.ts` (e.g. `products: 2`)
7
+ // 2. Add a row below keyed by the FROM-version, returning the upgraded doc
8
+ // 3. Repository wraps reads with `migrateDocument(doc, current, migrations)`
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ export const migrations = {};
@@ -90,11 +90,7 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
90
90
  canFilter: boolean;
91
91
  canSort: boolean;
92
92
  };
93
- isAuction: {
94
- canFilter: boolean;
95
- canSort: boolean;
96
- };
97
- isPreOrder: {
93
+ listingType: {
98
94
  canFilter: boolean;
99
95
  canSort: boolean;
100
96
  };
@@ -204,8 +200,8 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
204
200
  };
205
201
  };
206
202
  /**
207
- * Virtual filter aliases — callers can pass `f=listingType==auction` instead
208
- * of `f=isAuction==true,isPreOrder==false`. The Sieve adapter expands the
203
+ * Virtual filter aliases — callers can pass `f=listingType==auction` for
204
+ * the canonical listing-kind discriminator. The Sieve adapter expands the
209
205
  * alias into real clauses before reaching Firestore.
210
206
  *
211
207
  * Adding a new alias here makes it usable everywhere `productRepository.list`
@@ -240,6 +236,14 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
240
236
  * Cloud Functions: stage an atomic bid-count increment into a caller-owned WriteBatch.
241
237
  */
242
238
  incrementBidCountInBatch(batch: WriteBatch, productId: string, currentBid: number): void;
239
+ /**
240
+ * SB6-C / SB4-H — atomically bump `prizeCurrentEntries` by `count`. The
241
+ * checkout transaction calls this after pool-cap validation; the reveal
242
+ * code-path uses it as a defence-in-depth check (already incremented at
243
+ * order create, so this is a no-op unless we shift accounting later).
244
+ */
245
+ incrementPrizeEntriesInBatch(batch: WriteBatch, productId: string, count: number): void;
246
+ productRef(productId: string): DocumentReference;
243
247
  }
244
248
  export declare class ProductsRepository extends ProductRepository {
245
249
  }