@mohasinac/appkit 4.27.0 → 4.29.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 (298) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +16 -16
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +29 -6
  3. package/dist/_internal/client/features/lottery/LotteryListView.js +13 -1
  4. package/dist/_internal/server/features/checkout/actions.js +6 -4
  5. package/dist/_internal/server/features/classified/metadata.js +3 -1
  6. package/dist/_internal/server/features/digital-code/metadata.js +3 -1
  7. package/dist/_internal/server/features/live/metadata.js +3 -1
  8. package/dist/_internal/server/features/refunds/actions.js +3 -2
  9. package/dist/_internal/server/features/reviews/actions.js +3 -2
  10. package/dist/_internal/server/features/seo/sitemap.js +189 -40
  11. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  12. package/dist/_internal/server/features/stores/adapters.js +5 -0
  13. package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
  14. package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
  15. package/dist/_internal/shared/actions/action-registry.js +14 -0
  16. package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
  17. package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
  18. package/dist/_internal/shared/features/checkout/config.js +6 -3
  19. package/dist/client.d.ts +32 -2
  20. package/dist/client.js +37 -2
  21. package/dist/constants/api-endpoints.d.ts +21 -3
  22. package/dist/constants/api-endpoints.js +14 -1
  23. package/dist/constants/field-names.d.ts +2 -2
  24. package/dist/constants/field-names.js +2 -2
  25. package/dist/core/newsletter.repository.js +14 -1
  26. package/dist/features/about/components/FAQPageView.js +1 -1
  27. package/dist/features/about/components/FeesView.js +1 -1
  28. package/dist/features/account/components/AddressForm.d.ts +2 -0
  29. package/dist/features/account/components/AddressForm.js +5 -1
  30. package/dist/features/account/components/UserBidsView.js +1 -1
  31. package/dist/features/account/components/UserSidebar.js +1 -1
  32. package/dist/features/account/components/UserSupportView.js +24 -2
  33. package/dist/features/account/hooks/useAddresses.d.ts +29 -1
  34. package/dist/features/account/hooks/useAddresses.js +4 -1
  35. package/dist/features/account/hooks/useProfile.d.ts +1 -1
  36. package/dist/features/account/hooks/useProfile.js +1 -1
  37. package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
  38. package/dist/features/addresses/repository/addresses.repository.js +19 -0
  39. package/dist/features/admin/actions/chat-actions.js +5 -1
  40. package/dist/features/admin/components/AdminAdsView.js +1 -1
  41. package/dist/features/admin/components/AdminBidsView.js +6 -1
  42. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
  43. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
  44. package/dist/features/admin/components/AdminCarouselView.js +1 -1
  45. package/dist/features/admin/components/AdminContentGuideView.js +1 -1
  46. package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
  47. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
  48. package/dist/features/admin/components/AdminSidebar.js +1 -1
  49. package/dist/features/admin/components/AdminViewCards.js +2 -2
  50. package/dist/features/admin/components/DataTable.js +2 -2
  51. package/dist/features/admin/schemas/firestore.js +9 -3
  52. package/dist/features/auctions/actions/bid-actions.js +45 -4
  53. package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
  54. package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
  55. package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
  56. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  57. package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
  58. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
  59. package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
  60. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  61. package/dist/features/auth/hooks/useAuth.d.ts +23 -1
  62. package/dist/features/auth/hooks/useAuth.js +27 -1
  63. package/dist/features/auth/permissions/constants.d.ts +1 -1
  64. package/dist/features/auth/permissions/constants.js +3 -0
  65. package/dist/features/auth/repository/session.repository.d.ts +16 -1
  66. package/dist/features/auth/repository/session.repository.js +23 -0
  67. package/dist/features/auth/repository/user.repository.js +13 -2
  68. package/dist/features/blog/components/BlogListView.js +2 -2
  69. package/dist/features/blog/repository/blog.repository.d.ts +4 -0
  70. package/dist/features/blog/repository/blog.repository.js +1 -0
  71. package/dist/features/blog/schemas/firestore.d.ts +3 -1
  72. package/dist/features/blog/schemas/firestore.js +1 -0
  73. package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
  74. package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
  75. package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
  76. package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
  77. package/dist/features/cart/components/index.d.ts +1 -1
  78. package/dist/features/cart/components/index.js +1 -1
  79. package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
  80. package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
  81. package/dist/features/cart/utils/line-total.d.ts +25 -0
  82. package/dist/features/cart/utils/line-total.js +26 -0
  83. package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
  84. package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
  85. package/dist/features/categories/components/CategoryTree.js +2 -2
  86. package/dist/features/contact/components/ContactForm.js +1 -1
  87. package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
  88. package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
  89. package/dist/features/events/components/AdminEventEditorView.js +16 -9
  90. package/dist/features/events/components/AdminEventEntriesView.js +7 -13
  91. package/dist/features/events/components/EventRafflesSection.js +14 -2
  92. package/dist/features/events/components/EventStatusBadge.js +1 -1
  93. package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
  94. package/dist/features/events/components/event-entry-detail-fields.js +13 -0
  95. package/dist/features/events/repository/events.repository.js +1 -0
  96. package/dist/features/events/schemas/firestore.d.ts +3 -1
  97. package/dist/features/events/schemas/firestore.js +1 -0
  98. package/dist/features/faq/components/ContactCTA.js +1 -1
  99. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  100. package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
  101. package/dist/features/faq/repository/faqs.repository.js +64 -54
  102. package/dist/features/faq/schemas/firestore.d.ts +3 -2
  103. package/dist/features/faq/schemas/firestore.js +2 -2
  104. package/dist/features/faq/schemas/index.d.ts +3 -3
  105. package/dist/features/faq/schemas/index.js +1 -1
  106. package/dist/features/filters/FilterFacetSection.js +2 -2
  107. package/dist/features/forms/Toggle.js +1 -1
  108. package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
  109. package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
  110. package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
  111. package/dist/features/homepage/components/BrandsSection.js +1 -1
  112. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  113. package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
  114. package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
  115. package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
  116. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  117. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  118. package/dist/features/homepage/lib/section-renderer.js +2 -1
  119. package/dist/features/layout/AppLayoutShell.js +5 -5
  120. package/dist/features/layout/BottomActions.js +5 -5
  121. package/dist/features/layout/BottomSheet.js +1 -1
  122. package/dist/features/layout/MainNavbar.js +1 -1
  123. package/dist/features/layout/NavbarLayout.js +1 -1
  124. package/dist/features/layout/TitleBarLayout.js +3 -3
  125. package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
  126. package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
  127. package/dist/features/lottery/schemas/config-write.d.ts +141 -0
  128. package/dist/features/lottery/schemas/config-write.js +139 -0
  129. package/dist/features/media/MediaPickerModal.js +3 -1
  130. package/dist/features/media/MediaVideo.js +4 -2
  131. package/dist/features/media/components/MediaSlider.js +1 -1
  132. package/dist/features/messages/repository/conversations.repository.js +15 -5
  133. package/dist/features/orders/components/OrdersList.js +2 -2
  134. package/dist/features/payments/repository/payout.repository.d.ts +11 -1
  135. package/dist/features/payments/repository/payout.repository.js +14 -6
  136. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
  137. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  138. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  139. package/dist/features/products/columns/productTableColumns.js +3 -3
  140. package/dist/features/products/components/CompareOverlay.js +1 -1
  141. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  142. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  143. package/dist/features/products/components/PrizeDrawsSection.js +5 -1
  144. package/dist/features/products/components/ProductDetailActions.js +11 -7
  145. package/dist/features/products/components/ProductDetailPageView.js +3 -3
  146. package/dist/features/products/components/ProductFeatureBadges.js +2 -2
  147. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  148. package/dist/features/products/components/ProductFilters.js +15 -8
  149. package/dist/features/products/components/ProductGrid.js +8 -17
  150. package/dist/features/products/components/ProductTabs.js +7 -4
  151. package/dist/features/products/components/ProductTabsShell.js +2 -7
  152. package/dist/features/products/constants/action-defs.d.ts +21 -4
  153. package/dist/features/products/constants/action-defs.js +7 -3
  154. package/dist/features/products/hooks/useRelatedProducts.js +2 -1
  155. package/dist/features/products/repository/products.repository.d.ts +1 -1
  156. package/dist/features/products/repository/products.repository.js +38 -16
  157. package/dist/features/products/schemas/firestore.d.ts +3 -2
  158. package/dist/features/products/schemas/firestore.js +1 -1
  159. package/dist/features/reviews/actions/review-actions.d.ts +8 -1
  160. package/dist/features/reviews/actions/review-actions.js +14 -3
  161. package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
  162. package/dist/features/reviews/components/ReviewModal.js +1 -1
  163. package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
  164. package/dist/features/reviews/repository/reviews.repository.js +54 -13
  165. package/dist/features/reviews/schemas/firestore.d.ts +9 -1
  166. package/dist/features/reviews/schemas/firestore.js +1 -0
  167. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  168. package/dist/features/search/components/Search.js +3 -3
  169. package/dist/features/search/components/SearchResultsSection.js +1 -1
  170. package/dist/features/seller/components/SellerAddressesView.js +3 -1
  171. package/dist/features/seller/components/SellerBidsView.js +6 -1
  172. package/dist/features/seller/components/SellerSidebar.js +1 -1
  173. package/dist/features/shell/SectionForm.d.ts +21 -1
  174. package/dist/features/shell/SectionForm.js +15 -3
  175. package/dist/features/shell/build-sections.js +10 -1
  176. package/dist/features/shell/field-ui-meta.d.ts +21 -0
  177. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
  178. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  179. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
  180. package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
  181. package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
  182. package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
  183. package/dist/features/stores/actions/store-query-actions.js +25 -1
  184. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  185. package/dist/features/stores/components/StoreHeader.js +1 -1
  186. package/dist/features/stores/components/StoreNavTabs.js +20 -8
  187. package/dist/features/stores/repository/store.repository.js +1 -0
  188. package/dist/features/stores/schemas/firestore.d.ts +2 -0
  189. package/dist/features/support/repository/support.repository.d.ts +12 -0
  190. package/dist/features/support/repository/support.repository.js +15 -0
  191. package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
  192. package/dist/features/support/schemas/ticket-create-form.js +82 -0
  193. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +0 -1
  194. package/dist/features/tester/repository/tester-checklist-item.repository.js +6 -15
  195. package/dist/features/tester/schemas/firestore.d.ts +5 -4
  196. package/dist/features/tester/schemas/firestore.js +3 -3
  197. package/dist/features/tester/seed-data/products-tester-seed-data.js +2 -2
  198. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +138 -1
  199. package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
  200. package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
  201. package/dist/features/wishlist/components/index.d.ts +2 -1
  202. package/dist/features/wishlist/components/index.js +1 -1
  203. package/dist/index.d.ts +10 -3
  204. package/dist/index.js +19 -4
  205. package/dist/monitoring/error-tracking.js +39 -5
  206. package/dist/monitoring/server-logger.js +70 -32
  207. package/dist/next/components/ErrorView.js +1 -1
  208. package/dist/next/routing/route-map.d.ts +39 -7
  209. package/dist/next/routing/route-map.js +34 -4
  210. package/dist/providers/db-firebase/base.repository.d.ts +30 -0
  211. package/dist/providers/db-firebase/base.repository.js +56 -9
  212. package/dist/react/ErrorBoundary.js +9 -1
  213. package/dist/react/contexts/SessionContext.js +6 -2
  214. package/dist/react/hooks/useAuthGate.d.ts +22 -0
  215. package/dist/react/hooks/useAuthGate.js +86 -7
  216. package/dist/schemas/registry.d.ts +3 -3
  217. package/dist/security/index.d.ts +1 -1
  218. package/dist/security/index.js +1 -1
  219. package/dist/security/pii-encrypt.d.ts +13 -5
  220. package/dist/security/pii-encrypt.js +132 -6
  221. package/dist/security/pii-schemas.d.ts +32 -4
  222. package/dist/security/pii-schemas.js +38 -5
  223. package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
  224. package/dist/seed/_helpers/product-search-txt.js +33 -0
  225. package/dist/seed/_helpers/search-txt-wrappers.d.ts +20 -0
  226. package/dist/seed/_helpers/search-txt-wrappers.js +73 -0
  227. package/dist/seed/blog-posts-seed-data.d.ts +4 -0
  228. package/dist/seed/blog-posts-seed-data.js +7 -1
  229. package/dist/seed/events-seed-data.d.ts +5 -1
  230. package/dist/seed/events-seed-data.js +7 -1
  231. package/dist/seed/faq-seed-data.d.ts +16 -0
  232. package/dist/seed/faq-seed-data.js +22 -1
  233. package/dist/seed/products-art-seed-data.js +49 -7
  234. package/dist/seed/products-auctions-seed-data.js +12 -4
  235. package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
  236. package/dist/seed/products-classifieds-seed-data.js +65 -9
  237. package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
  238. package/dist/seed/products-digital-codes-seed-data.js +65 -9
  239. package/dist/seed/products-live-items-seed-data.d.ts +5 -0
  240. package/dist/seed/products-live-items-seed-data.js +33 -5
  241. package/dist/seed/products-preorders-seed-data.d.ts +1 -1
  242. package/dist/seed/products-preorders-seed-data.js +6 -2
  243. package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
  244. package/dist/seed/products-prize-draws-seed-data.js +57 -8
  245. package/dist/seed/products-standard-seed-data.js +6 -2
  246. package/dist/seed/products-stickers-seed-data.js +49 -7
  247. package/dist/seed/reviews-seed-data.js +2 -1
  248. package/dist/seed/site-settings-seed-data.js +5 -3
  249. package/dist/seed/stores-seed-data.d.ts +4 -0
  250. package/dist/seed/stores-seed-data.js +7 -1
  251. package/dist/seo/json-ld.d.ts +44 -2
  252. package/dist/seo/json-ld.js +57 -11
  253. package/dist/styles.css +368 -90
  254. package/dist/tailwind-utilities.css +1 -1
  255. package/dist/tokens/index.d.ts +1 -1
  256. package/dist/tokens/index.js +1 -1
  257. package/dist/tokens/themes/default-dark.js +6 -0
  258. package/dist/tokens/themes/default-light.js +5 -0
  259. package/dist/tokens/tokens.css +84 -0
  260. package/dist/ui/DataTable.js +1 -1
  261. package/dist/ui/DataTable.style.css +3 -3
  262. package/dist/ui/components/BulkActionBar.style.css +1 -3
  263. package/dist/ui/components/Button.js +15 -6
  264. package/dist/ui/components/Dropdown.style.css +1 -4
  265. package/dist/ui/components/FilterChipGroup.js +1 -1
  266. package/dist/ui/components/FilterDrawer.style.css +1 -1
  267. package/dist/ui/components/Icon.d.ts +53 -0
  268. package/dist/ui/components/Icon.js +41 -0
  269. package/dist/ui/components/IconButton.d.ts +13 -0
  270. package/dist/ui/components/IconButton.js +13 -0
  271. package/dist/ui/components/IconButton.style.css +18 -10
  272. package/dist/ui/components/ListingLayout.style.css +2 -10
  273. package/dist/ui/components/ListingToolbar.js +6 -6
  274. package/dist/ui/components/Modal.js +1 -1
  275. package/dist/ui/components/PaginatedSelect.style.css +3 -3
  276. package/dist/ui/components/Pagination.style.css +1 -6
  277. package/dist/ui/components/RichTextEditor.js +1 -1
  278. package/dist/ui/components/RowActionMenu.style.css +1 -1
  279. package/dist/ui/components/SideDrawer.style.css +1 -1
  280. package/dist/ui/components/Slider.js +1 -1
  281. package/dist/ui/components/TabBarShell.d.ts +78 -0
  282. package/dist/ui/components/TabBarShell.js +95 -0
  283. package/dist/ui/components/Tabs.js +12 -8
  284. package/dist/ui/components/Tabs.style.css +249 -41
  285. package/dist/ui/components/ViewToggle.style.css +1 -5
  286. package/dist/ui/components/tabs-overflow.d.ts +17 -0
  287. package/dist/ui/components/tabs-overflow.js +142 -0
  288. package/dist/ui/icons/icon-registry.d.ts +38 -0
  289. package/dist/ui/icons/icon-registry.js +37 -0
  290. package/dist/ui/index.d.ts +6 -0
  291. package/dist/ui/index.js +3 -0
  292. package/dist/utils/index.d.ts +1 -1
  293. package/dist/utils/index.js +1 -1
  294. package/dist/utils/media-url.d.ts +27 -4
  295. package/dist/utils/media-url.js +37 -6
  296. package/dist/utils/search-txt.d.ts +71 -0
  297. package/dist/utils/search-txt.js +118 -0
  298. package/package.json +1 -1
@@ -0,0 +1,141 @@
1
+ import { z } from "zod";
2
+ import type { LotteryConfig } from "../types/index";
3
+ /**
4
+ * What an admin may say about one slot. Booking state is deliberately absent
5
+ * — see the header. `.strict()`, so `isBooked` is a 400.
6
+ */
7
+ export declare const lotterySlotWriteSchema: z.ZodObject<{
8
+ slotNumber: z.ZodNumber;
9
+ name: z.ZodString;
10
+ image: z.ZodOptional<z.ZodString>;
11
+ price: z.ZodNumber;
12
+ }, "strict", z.ZodTypeAny, {
13
+ name: string;
14
+ price: number;
15
+ slotNumber: number;
16
+ image?: string | undefined;
17
+ }, {
18
+ name: string;
19
+ price: number;
20
+ slotNumber: number;
21
+ image?: string | undefined;
22
+ }>;
23
+ export declare const lotteryConfigWriteSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
24
+ slots: z.ZodArray<z.ZodObject<{
25
+ slotNumber: z.ZodNumber;
26
+ name: z.ZodString;
27
+ image: z.ZodOptional<z.ZodString>;
28
+ price: z.ZodNumber;
29
+ }, "strict", z.ZodTypeAny, {
30
+ name: string;
31
+ price: number;
32
+ slotNumber: number;
33
+ image?: string | undefined;
34
+ }, {
35
+ name: string;
36
+ price: number;
37
+ slotNumber: number;
38
+ image?: string | undefined;
39
+ }>, "many">;
40
+ pricingMode: z.ZodEnum<["uniform", "variable"]>;
41
+ uniformPrice: z.ZodOptional<z.ZodNumber>;
42
+ drawWindowDurationMinutes: z.ZodNumber;
43
+ maxPullsPerTransaction: z.ZodNumber;
44
+ maxPullsPerUser: z.ZodNumber;
45
+ }, "strict", z.ZodTypeAny, {
46
+ slots: {
47
+ name: string;
48
+ price: number;
49
+ slotNumber: number;
50
+ image?: string | undefined;
51
+ }[];
52
+ pricingMode: "uniform" | "variable";
53
+ drawWindowDurationMinutes: number;
54
+ maxPullsPerTransaction: number;
55
+ maxPullsPerUser: number;
56
+ uniformPrice?: number | undefined;
57
+ }, {
58
+ slots: {
59
+ name: string;
60
+ price: number;
61
+ slotNumber: number;
62
+ image?: string | undefined;
63
+ }[];
64
+ pricingMode: "uniform" | "variable";
65
+ drawWindowDurationMinutes: number;
66
+ maxPullsPerTransaction: number;
67
+ maxPullsPerUser: number;
68
+ uniformPrice?: number | undefined;
69
+ }>, {
70
+ slots: {
71
+ name: string;
72
+ price: number;
73
+ slotNumber: number;
74
+ image?: string | undefined;
75
+ }[];
76
+ pricingMode: "uniform" | "variable";
77
+ drawWindowDurationMinutes: number;
78
+ maxPullsPerTransaction: number;
79
+ maxPullsPerUser: number;
80
+ uniformPrice?: number | undefined;
81
+ }, {
82
+ slots: {
83
+ name: string;
84
+ price: number;
85
+ slotNumber: number;
86
+ image?: string | undefined;
87
+ }[];
88
+ pricingMode: "uniform" | "variable";
89
+ drawWindowDurationMinutes: number;
90
+ maxPullsPerTransaction: number;
91
+ maxPullsPerUser: number;
92
+ uniformPrice?: number | undefined;
93
+ }>, {
94
+ slots: {
95
+ name: string;
96
+ price: number;
97
+ slotNumber: number;
98
+ image?: string | undefined;
99
+ }[];
100
+ pricingMode: "uniform" | "variable";
101
+ drawWindowDurationMinutes: number;
102
+ maxPullsPerTransaction: number;
103
+ maxPullsPerUser: number;
104
+ uniformPrice?: number | undefined;
105
+ }, {
106
+ slots: {
107
+ name: string;
108
+ price: number;
109
+ slotNumber: number;
110
+ image?: string | undefined;
111
+ }[];
112
+ pricingMode: "uniform" | "variable";
113
+ drawWindowDurationMinutes: number;
114
+ maxPullsPerTransaction: number;
115
+ maxPullsPerUser: number;
116
+ uniformPrice?: number | undefined;
117
+ }>;
118
+ export type LotteryConfigWriteInput = z.infer<typeof lotteryConfigWriteSchema>;
119
+ /** Why a merge was refused. Distinct from a validation failure. */
120
+ export interface LotteryMergeRejection {
121
+ ok: false;
122
+ code: "BOOKED_SLOT_REMOVED";
123
+ message: string;
124
+ slotNumbers: number[];
125
+ }
126
+ export type LotteryMergeResult = {
127
+ ok: true;
128
+ config: LotteryConfig;
129
+ } | LotteryMergeRejection;
130
+ /**
131
+ * Apply an admin's edit to the stored config, carrying every booking across.
132
+ *
133
+ * `existing` is undefined when the lottery is being created, in which case
134
+ * there is nothing to preserve and every slot starts unbooked.
135
+ *
136
+ * `totalSlots` is DERIVED from `slots.length` and never accepted from input —
137
+ * a caller-supplied count that disagrees with the array is exactly the
138
+ * mirror-drift trap (Root Cause #42), and `totalSlots` is what the fullness
139
+ * check reads.
140
+ */
141
+ export declare function mergeLotteryConfig(input: LotteryConfigWriteInput, existing?: LotteryConfig): LotteryMergeResult;
@@ -0,0 +1,139 @@
1
+ /*
2
+ * WHY: `lotteryConfig` had NO write schema. The only authoring UI
3
+ * (`LotteryAdminEditView`) was orphaned, and the route it would have
4
+ * posted to — `PATCH /api/admin/events/[id]` — is `.passthrough()`, so
5
+ * the whole config wrote through completely unvalidated.
6
+ * WHAT: The write contract, plus `mergeLotteryConfig` — the one function that
7
+ * is allowed to turn an admin's edit into a stored config.
8
+ *
9
+ * ## 🛑 The author-side slot shape CANNOT carry booking state
10
+ *
11
+ * `LotteryAdminEditView.handleSubmit` sent `isBooked: false` and `weight: 0`
12
+ * for every slot, and dropped `bookedByUserId` / `bookedByDisplayName` /
13
+ * `bookedByUserLotteryNumber` entirely. Against a `.passthrough()` PATCH that
14
+ * is not a validation gap — it is a **destructive write**: the first save of
15
+ * a live lottery marks every purchased slot as available again, and the
16
+ * buyers who paid for them are simply gone from the record.
17
+ *
18
+ * So the input type deliberately has NO booking fields at all. An admin
19
+ * editing a lottery is describing PRIZES, not attendance, and the schema
20
+ * refuses to express the latter — a `.strict()` object, so sending
21
+ * `isBooked` is a 400 rather than something a merge has to defend against.
22
+ *
23
+ * `weight` is absent for the same reason: it is derived from price
24
+ * server-side and is never client input.
25
+ *
26
+ * ## Merging is BY `slotNumber`, not by index
27
+ *
28
+ * Array position is not identity. An admin who deletes slot 3 shifts every
29
+ * later slot's index by one, and an index-wise merge would hand slot 7's
30
+ * buyer the prize that used to be slot 8. `slotNumber` is the stable key
31
+ * users actually see and pull against.
32
+ *
33
+ * A slot that is booked cannot be REMOVED either. Dropping it would erase a
34
+ * paid entry with no trace; the merge rejects that rather than silently
35
+ * carrying the slot along, because an admin who thinks they deleted a prize
36
+ * and did not is a worse outcome than an error.
37
+ *
38
+ * EXPORTS: lotteryConfigWriteSchema, lotterySlotWriteSchema,
39
+ * mergeLotteryConfig, type LotteryConfigWriteInput
40
+ *
41
+ * @tag domain:lottery
42
+ * @tag layer:schema
43
+ * @tag pattern:none
44
+ * @tag access:isomorphic
45
+ * @tag consumers:/api/admin/events/[id]/lottery-config,LotteryAdminEditView
46
+ * @tag sideEffects:none
47
+ */
48
+ import { z } from "zod";
49
+ /** Mirrors `LotteryPricingMode`. */
50
+ const pricingModeSchema = z.enum(["uniform", "variable"]);
51
+ /**
52
+ * What an admin may say about one slot. Booking state is deliberately absent
53
+ * — see the header. `.strict()`, so `isBooked` is a 400.
54
+ */
55
+ export const lotterySlotWriteSchema = z
56
+ .object({
57
+ slotNumber: z.coerce.number().int().min(1).max(200),
58
+ name: z.string().min(1, "Every slot needs a prize name.").max(200),
59
+ image: z.string().max(2000).optional(),
60
+ price: z.coerce.number().min(0, "A price cannot be negative."),
61
+ })
62
+ .strict();
63
+ export const lotteryConfigWriteSchema = z
64
+ .object({
65
+ slots: z
66
+ .array(lotterySlotWriteSchema)
67
+ .min(1, "A lottery needs at least one slot.")
68
+ .max(200, "A lottery cannot exceed 200 slots."),
69
+ pricingMode: pricingModeSchema,
70
+ uniformPrice: z.coerce.number().min(0).optional(),
71
+ drawWindowDurationMinutes: z.coerce.number().int().min(1).max(1440),
72
+ maxPullsPerTransaction: z.coerce.number().int().min(1).max(200),
73
+ maxPullsPerUser: z.coerce.number().int().min(1).max(200),
74
+ })
75
+ .strict()
76
+ .refine((c) => new Set(c.slots.map((s) => s.slotNumber)).size === c.slots.length, { message: "Two slots share a slot number.", path: ["slots"] })
77
+ .refine((c) => c.pricingMode !== "uniform" || typeof c.uniformPrice === "number", {
78
+ message: "Uniform pricing needs a price.",
79
+ path: ["uniformPrice"],
80
+ });
81
+ /**
82
+ * Apply an admin's edit to the stored config, carrying every booking across.
83
+ *
84
+ * `existing` is undefined when the lottery is being created, in which case
85
+ * there is nothing to preserve and every slot starts unbooked.
86
+ *
87
+ * `totalSlots` is DERIVED from `slots.length` and never accepted from input —
88
+ * a caller-supplied count that disagrees with the array is exactly the
89
+ * mirror-drift trap (Root Cause #42), and `totalSlots` is what the fullness
90
+ * check reads.
91
+ */
92
+ export function mergeLotteryConfig(input, existing) {
93
+ const priorBySlot = new Map();
94
+ for (const slot of existing?.slots ?? [])
95
+ priorBySlot.set(slot.slotNumber, slot);
96
+ const incoming = new Set(input.slots.map((s) => s.slotNumber));
97
+ const droppedBooked = [...priorBySlot.values()]
98
+ .filter((s) => s.isBooked && !incoming.has(s.slotNumber))
99
+ .map((s) => s.slotNumber);
100
+ if (droppedBooked.length > 0) {
101
+ return {
102
+ ok: false,
103
+ code: "BOOKED_SLOT_REMOVED",
104
+ message: `Slot ${droppedBooked.join(", ")} has already been pulled and cannot be ` +
105
+ `removed. Reopen it first if the pull needs to be undone.`,
106
+ slotNumbers: droppedBooked,
107
+ };
108
+ }
109
+ const slots = input.slots.map((s) => {
110
+ const prior = priorBySlot.get(s.slotNumber);
111
+ return {
112
+ slotNumber: s.slotNumber,
113
+ name: s.name,
114
+ image: s.image?.trim() || undefined,
115
+ price: input.pricingMode === "uniform" ? (input.uniformPrice ?? 0) : s.price,
116
+ // Server-derived, never input. Recomputed by the pricing pass; carried
117
+ // from the prior slot so an edit does not zero a live weighting.
118
+ weight: prior?.weight ?? 0,
119
+ // 🛑 THE WHOLE POINT: booking state comes from the STORED slot, never
120
+ // from the request. A slot the admin renames keeps its buyer.
121
+ isBooked: prior?.isBooked ?? false,
122
+ bookedByUserId: prior?.bookedByUserId,
123
+ bookedByDisplayName: prior?.bookedByDisplayName,
124
+ bookedByUserLotteryNumber: prior?.bookedByUserLotteryNumber,
125
+ };
126
+ });
127
+ return {
128
+ ok: true,
129
+ config: {
130
+ slots,
131
+ totalSlots: slots.length,
132
+ pricingMode: input.pricingMode,
133
+ uniformPrice: input.pricingMode === "uniform" ? input.uniformPrice : undefined,
134
+ drawWindowDurationMinutes: input.drawWindowDurationMinutes,
135
+ maxPullsPerTransaction: input.maxPullsPerTransaction,
136
+ maxPullsPerUser: input.maxPullsPerUser,
137
+ },
138
+ };
139
+ }
@@ -57,8 +57,10 @@ export function MediaPickerModal({ isOpen, onClose, onSelect, accept = "image/*"
57
57
  setExistingFiles(data.data.files);
58
58
  })
59
59
  .catch((err) => {
60
+ // normalizeError covers Zod/Api/Firebase shapes an `instanceof Error`
61
+ // check misses, so those stop collapsing to the generic fallback.
60
62
  if (!cancelled)
61
- setExistingError(err instanceof Error ? err.message : "Failed to load");
63
+ setExistingError(normalizeError(err).message || "Failed to load");
62
64
  })
63
65
  .finally(() => {
64
66
  if (!cancelled)
@@ -2,12 +2,14 @@
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useRef, useEffect } from "react";
4
4
  import { Div, Row, Span } from "../../ui";
5
- import { resolveMediaUrl, getYouTubeVideoId } from "../../utils/media-url";
5
+ import { resolveMediaUrl, resolveVideoUrl, getYouTubeVideoId } from "../../utils/media-url";
6
6
  import { useSiteSettings } from "../../core/hooks/useSiteSettings";
7
7
  export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true, autoPlayMuted = false, loop = false, trimStart, trimEnd, objectFit = "cover", watermark, }) {
8
8
  const videoRef = useRef(null);
9
9
  const fitClass = objectFit === "contain" ? "object-contain" : "object-cover";
10
- const resolvedSrc = resolveMediaUrl(src);
10
+ // Video src must NOT fall through to /api/media/ext (image-only, 400s on
11
+ // video/mp4). The poster is an image and stays on the normal image path.
12
+ const resolvedSrc = resolveVideoUrl(src);
11
13
  const resolvedPoster = resolveMediaUrl(thumbnailUrl);
12
14
  const youtubeId = getYouTubeVideoId(src);
13
15
  // Apply trimStart on load
@@ -5,7 +5,7 @@ export function MediaSlider({ value, min = 0, max = 100, step = 1, onChange, onC
5
5
  // drag/cancel state; nothing here persists across a cancelled touch.
6
6
  onTouchEnd: (e) => onChangeEnd?.(parseFloat(e.target.value)), className: [
7
7
  "w-full h-2 appearance-none cursor-pointer rounded-full",
8
- "bg-zinc-200 bg-[var(--appkit-color-border)]",
8
+ " bg-[var(--appkit-color-border)]",
9
9
  "accent-primary-600 dark:accent-secondary-400",
10
10
  className,
11
11
  ]
@@ -9,6 +9,8 @@
9
9
  * or unread counters.
10
10
  */
11
11
  import { getAdminDb } from "../../../providers/db-firebase";
12
+ import { encryptPiiFields, decryptPiiFields } from "../../../security/pii-encrypt";
13
+ import { CONVERSATION_PII_FIELDS } from "../../../security/pii-schemas";
12
14
  import { serverLogger } from "../../../monitoring";
13
15
  import { CONVERSATION_FIELDS } from "../../../constants/field-names";
14
16
  import { CONVERSATIONS_COLLECTION, } from "../schemas/firestore";
@@ -37,7 +39,11 @@ function normaliseMessage(raw) {
37
39
  };
38
40
  }
39
41
  function normaliseDoc(id, raw) {
40
- const r = raw;
42
+ // Decrypt here rather than at the six call sites: this is the one function
43
+ // every read path already goes through, so a new reader cannot forget it.
44
+ // `markRead` deliberately does NOT decrypt — it reads raw messages, flips
45
+ // isRead and writes them straight back, so ciphertext round-trips untouched.
46
+ const r = decryptPiiFields(raw, [...CONVERSATION_PII_FIELDS]);
41
47
  return {
42
48
  id,
43
49
  buyerId: String(r.buyerId ?? ""),
@@ -102,8 +108,9 @@ export class ConversationsRepository {
102
108
  createdAt: now,
103
109
  updatedAt: now,
104
110
  };
105
- tx.set(ref, newDoc);
106
- return normaliseDoc(stableId, newDoc);
111
+ const encryptedDoc = encryptPiiFields(newDoc, [...CONVERSATION_PII_FIELDS]);
112
+ tx.set(ref, encryptedDoc);
113
+ return normaliseDoc(stableId, encryptedDoc);
107
114
  });
108
115
  }
109
116
  async findById(id) {
@@ -168,8 +175,11 @@ export class ConversationsRepository {
168
175
  unreadSeller,
169
176
  updatedAt: now,
170
177
  };
171
- tx.update(ref, patch);
172
- return normaliseDoc(conversationId, { ...data, ...patch });
178
+ // Encrypt before the write; normaliseDoc decrypts on the way back out,
179
+ // so the caller still receives plaintext.
180
+ const encryptedPatch = encryptPiiFields(patch, [...CONVERSATION_PII_FIELDS]);
181
+ tx.update(ref, encryptedPatch);
182
+ return normaliseDoc(conversationId, { ...data, ...encryptedPatch });
173
183
  });
174
184
  }
175
185
  catch (error) {
@@ -23,14 +23,14 @@ export function OrderCard({ order, onClick, labels = {}, renderActions }) {
23
23
  day: "numeric",
24
24
  })
25
25
  : "";
26
- const statusColor = STATUS_COLORS[order.orderStatus] ?? "bg-[var(--appkit-color-surface)] text-neutral-700 bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text-muted)]";
26
+ const statusColor = STATUS_COLORS[order.orderStatus] ?? "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text-muted)]";
27
27
  // SB8-F — count unrevealed prize-draw entries to surface a "reveals pending" badge.
28
28
  const unrevealedPrizeDraws = order.items.filter((it) => it.listingType === "prize-draw" &&
29
29
  it.prizeRevealStatus !== undefined &&
30
30
  it.prizeRevealStatus !== "revealed");
31
31
  const revealsRemaining = unrevealedPrizeDraws.length;
32
32
  return (_jsxs(Div, { padding: "5", role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
33
- ? (e) => (e.key === "Enter" || e.key === " ") && onClick(order)
33
+ ? (e) => (e.key === "Enter" || e.key === "") && onClick(order)
34
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) }))] }));
35
35
  }
36
36
  export function OrdersList({ orders, isLoading, onOrderClick, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No orders found", renderActions, }) {
@@ -19,7 +19,17 @@ export declare class PayoutRepository extends BaseRepository<PayoutDocument> {
19
19
  constructor();
20
20
  /** Decrypt PII fields on a payout document after Firestore read */
21
21
  private decryptPayout;
22
- /** Encrypt PII fields on payout data before Firestore write */
22
+ /**
23
+ * Encrypt PII, then attach blind indices derived from the plaintext.
24
+ *
25
+ * Previously this reassigned `encrypted = addPiiIndices(data, …)` — which
26
+ * re-reads the ORIGINAL plaintext and returns `{...data, ...indices}` — and
27
+ * then spread that over the ciphertext, so plaintext won and `sellerEmail`
28
+ * and `upiId` were written to Firestore in the clear beside a valid index.
29
+ * `piiIndicesFor` returns the index fields alone, so the ciphertext cannot be
30
+ * overwritten. The identical bug was fixed in UserRepository and never
31
+ * propagated here.
32
+ */
23
33
  private encryptPayoutData;
24
34
  /** Override mapDoc to auto-decrypt PII on every Firestore read */
25
35
  protected mapDoc<D = PayoutDocument>(snap: import("../../../providers/db-firebase").DocumentSnapshot): D;
@@ -7,7 +7,7 @@
7
7
  import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase";
8
8
  import { PAYOUT_COLLECTION, PAYOUT_FIELDS, PAYOUT_TRACKED_FIELDS, createPayoutId } from "../schemas";
9
9
  import { withHistory } from "../../../_internal/shared/history/index";
10
- import { encryptPiiFields, decryptPiiFields, addPiiIndices, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, encryptPayoutBankAccount, decryptPayoutBankAccount, } from "../../../security";
10
+ import { encryptPiiFields, decryptPiiFields, piiIndicesFor, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, encryptPayoutBankAccount, decryptPayoutBankAccount, } from "../../../security";
11
11
  export class PayoutRepository extends BaseRepository {
12
12
  constructor() {
13
13
  super(PAYOUT_COLLECTION);
@@ -20,13 +20,21 @@ export class PayoutRepository extends BaseRepository {
20
20
  }
21
21
  return decrypted;
22
22
  }
23
- /** Encrypt PII fields on payout data before Firestore write */
23
+ /**
24
+ * Encrypt PII, then attach blind indices derived from the plaintext.
25
+ *
26
+ * Previously this reassigned `encrypted = addPiiIndices(data, …)` — which
27
+ * re-reads the ORIGINAL plaintext and returns `{...data, ...indices}` — and
28
+ * then spread that over the ciphertext, so plaintext won and `sellerEmail`
29
+ * and `upiId` were written to Firestore in the clear beside a valid index.
30
+ * `piiIndicesFor` returns the index fields alone, so the ciphertext cannot be
31
+ * overwritten. The identical bug was fixed in UserRepository and never
32
+ * propagated here.
33
+ */
24
34
  encryptPayoutData(data) {
25
- let encrypted = encryptPiiFields(data, [...PAYOUT_PII_FIELDS]);
26
- encrypted = addPiiIndices(data, PAYOUT_PII_INDEX_MAP);
27
- encrypted = {
35
+ const encrypted = {
28
36
  ...encryptPiiFields(data, [...PAYOUT_PII_FIELDS]),
29
- ...encrypted,
37
+ ...piiIndicesFor(data, PAYOUT_PII_INDEX_MAP),
30
38
  };
31
39
  const access = encrypted;
32
40
  if (access.bankAccount) {
@@ -17,6 +17,7 @@ const CLS_LIMITED_BADGE = "rounded-full bg-info-solid px-[var(--appkit-space-2)]
17
17
  import { BaseListingCard, Button, RichText, Row, Span, Stack, Text, TextLink } from "../../../ui";
18
18
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
19
19
  import { PreorderBadge } from "./PreorderCard";
20
+ import { WishlistHeartButton } from "../../wishlist/components/WishlistHeartButton";
20
21
  const DEFAULT_LABELS = {
21
22
  preOrderBadge: "Pre-order",
22
23
  featuredBadge: "Featured",
@@ -62,7 +63,7 @@ export function MarketplacePreorderCard({ product, className = "", variant = "gr
62
63
  onSelect(product.id, !isSelected);
63
64
  }, className: selectable || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" }))] }), _jsx(BaseListingCard.Info, { variant: variant, children: variant === "list" ? (
64
65
  /* ── Compact list layout ── */
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
+ _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(WishlistHeartButton, { size: "inline", inWishlist: inWishlist, addLabel: mergedLabels.addToWishlist, removeLabel: mergedLabels.removeFromWishlist, onToggle: handleWishlist }))] }), _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
67
  /* ── 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-auto pt-[var(--appkit-space-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
+ _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-[var(--appkit-space-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(WishlistHeartButton, { size: "inline", inWishlist: inWishlist, addLabel: mergedLabels.addToWishlist, removeLabel: mergedLabels.removeFromWishlist, onToggle: handleWishlist })) : null] })] })) })] }));
68
69
  }
@@ -5,7 +5,7 @@ const STATUS_MAP = {
5
5
  pending: { label: "Pending", color: "bg-warning-surface text-warning" },
6
6
  confirmed: { label: "Confirmed", color: "bg-info-surface text-info" },
7
7
  ready: { label: "Ready", color: "bg-success-surface text-success" },
8
- fulfilled: { label: "Fulfilled", color: "bg-[var(--appkit-color-surface)] text-gray-700 bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text-muted)]" },
8
+ fulfilled: { label: "Fulfilled", color: "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text-muted)]" },
9
9
  cancelled: { label: "Cancelled", color: "bg-error-surface text-error" },
10
10
  };
11
11
  export function PreOrderBadge({ status, className = "" }) {
@@ -61,7 +61,7 @@ function PreOrderInfoSection({ title, productionStatus, maxPerUser, deliveryDate
61
61
  emiAvailable: "EMI Available",
62
62
  wishlistCount: (n) => `${n} wishlisted`,
63
63
  categoryProductCount: (n, cat) => `${n} in ${cat}`,
64
- } }), (categoryName || category || 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-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] 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 })), !category && categoryName && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] 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, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this product" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] }));
64
+ } }), (categoryName || category || 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-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryName || category })), !category && categoryName && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this product" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] }));
65
65
  }
66
66
  function PreOrderBuyBarPanel({ reserveTarget, reservedCount, progressPct, productId, price, currency, depositAmount, depositPercent, isCancellable, tags, onReserveNow, }) {
67
67
  return (_jsxs(Stack, { id: "pre-order-buy-bar", className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [reserveTarget > 0 && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", color: "muted", children: [reservedCount, " of ", reserveTarget, " reserved"] }), _jsxs(Span, { size: "xs", weight: "semibold", className: "text-primary-600 dark:text-primary-400", children: [progressPct, "%"] })] }), _jsx(Div, { className: `h-2 w-full ${__O.hidden}`, rounded: "full", surface: "subtle", children: _jsx(ProgressBarFill, { pct: progressPct, className: "h-full bg-primary transition-all rounded-full" }) })] })), onReserveNow ? (_jsx(PreOrderActionsClient, { productId: productId, price: price, currency: currency, depositAmount: depositAmount, depositPercent: depositPercent, isCancellable: isCancellable, tags: tags, onReserveNow: onReserveNow })) : (_jsxs(_Fragment, { children: [price !== null && (_jsxs(Div, { children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) }), depositAmount !== null && (_jsxs(Text, { className: "mt-0.5", color: "muted", size: "xs", children: ["Reserve with ", formatCurrency(depositAmount, currency), depositPercent !== null ? ` (${depositPercent}% deposit)` : ""] }))] })), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Reserve Now" }), isCancellable && (_jsx(Text, { size: "xs", align: "start", color: "muted", children: "\u2713 Free cancellation before production" }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) })), _jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "sm", justify: "center", className: "text-center", children: [
@@ -7,7 +7,7 @@ const __O = {
7
7
  };
8
8
  const STATUS_STYLES = {
9
9
  published: "bg-success-surface text-success dark:bg-success-surface dark:text-success",
10
- draft: "bg-[var(--appkit-color-surface)] text-zinc-700 bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]",
10
+ draft: "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]",
11
11
  out_of_stock: "bg-warning-surface text-warning dark:bg-warning-surface dark:text-warning",
12
12
  discontinued: "bg-error-surface text-error dark:bg-error-surface dark:text-error",
13
13
  sold: "bg-primary/10 dark:bg-primary/20 text-primary",
@@ -54,7 +54,7 @@ export function getProductTableColumns({ labels, onEdit, onDelete, currencyCode,
54
54
  return (_jsx(Span, { className: [
55
55
  "rounded px-[var(--appkit-space-2)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium",
56
56
  STATUS_STYLES[status] ??
57
- "bg-[var(--appkit-color-surface)] text-zinc-700 bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]",
57
+ "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]",
58
58
  ].join(" "), children: status.replace("_", " ") }));
59
59
  },
60
60
  },
@@ -76,7 +76,7 @@ export function getProductTableColumns({ labels, onEdit, onDelete, currencyCode,
76
76
  "rounded px-[var(--appkit-space-2)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium",
77
77
  featured
78
78
  ? CLS_LOW_STOCK
79
- : "bg-[var(--appkit-color-surface)] text-zinc-700 bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]",
79
+ : "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]",
80
80
  ].join(" "), children: featured ? labels.yes : labels.no }));
81
81
  },
82
82
  },
@@ -9,7 +9,7 @@ const __O = {
9
9
  hidden: "overflow-hidden",
10
10
  };
11
11
  const REL_NOOPENER = "noopener noreferrer";
12
- const CLS_REMOVE_BTN = "shrink-0 text-zinc-400 hover:bg-[var(--appkit-color-surface)] hover:text-error hover:bg-[var(--appkit-color-surface-elevated)]";
12
+ const CLS_REMOVE_BTN = "shrink-0 text-zinc-400 hover:text-error hover:bg-surface-hover";
13
13
  import Link from "next/link";
14
14
  import { X } from "lucide-react";
15
15
  import { Button, Div, Heading, IconButton, Row, Span, Stack, Text } from "../../../ui";
@@ -44,7 +44,7 @@ function statusClass(s) {
44
44
  case "open":
45
45
  return "bg-success-surface text-success";
46
46
  case "closed":
47
- return "bg-zinc-200 text-zinc-700 bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]";
47
+ return " bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]";
48
48
  case "pending":
49
49
  default:
50
50
  return "bg-warning-surface text-warning";
@@ -18,7 +18,7 @@ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
18
18
  */
19
19
  export function PrizeDrawEntryActions({ productId, productSlug, title, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }) {
20
20
  const router = useRouter();
21
- const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
21
+ const { requireAuth, isAuthResolving, modalOpen, modalMessage, closeModal } = useAuthGate();
22
22
  const [consentOpen, setConsentOpen] = useState(false);
23
23
  const closed = revealStatus === "closed" || remainingEntries === 0;
24
24
  const handleEnter = useCallback(() => {
@@ -31,5 +31,5 @@ export function PrizeDrawEntryActions({ productId, productSlug, title, pricePerE
31
31
  const slug = productSlug ?? productId;
32
32
  router.push(`${String(ROUTES.USER.CHECKOUT)}?directItem=${encodeURIComponent(slug)}&type=prize-draw`);
33
33
  }, [productSlug, productId, router]);
34
- return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "2xl", weight: "bold", color: "primary", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-[var(--appkit-color-text-muted)]", size: "xs", weight: "normal", children: "per entry" })] }), _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["buy-now"], variant: "primary", size: "md", className: "w-full", disabled: closed, onClick: handleEnter, children: closed ? "Draw closed" : undefined }), prizeGithubFileUrl ? (_jsx(Anchor, { href: prizeGithubFileUrl, tone: "none", underline: "hover", size: "xs", weight: "medium", className: "text-left text-primary-600 underline-offset-4 dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", align: "start", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
34
+ return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "2xl", weight: "bold", color: "primary", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-[var(--appkit-color-text-muted)]", size: "xs", weight: "normal", children: "per entry" })] }), _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["buy-now"], variant: "primary", size: "md", className: "w-full", disabled: closed || isAuthResolving, onClick: handleEnter, children: closed ? "Draw closed" : isAuthResolving ? "Checking…" : undefined }), prizeGithubFileUrl ? (_jsx(Anchor, { href: prizeGithubFileUrl, tone: "none", underline: "hover", size: "xs", weight: "medium", className: "text-left text-primary-600 underline-offset-4 dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", align: "start", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
35
35
  }
@@ -1,5 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { sortBy } from "@mohasinac/appkit/client";
2
+ // Import from the DEFINING module, never `@mohasinac/appkit/client` — that entry
3
+ // is `"use client"`, so in this async Server Component `sortBy` would resolve to
4
+ // a client-reference proxy and calling it throws during the server render
5
+ // (Root Cause #18). That is exactly what took `section-prize-draws` down.
6
+ import { sortBy } from "../../../constants/sort";
3
7
  import { Container, Div, Heading, Section, Stack, Text, } from "../../../ui";
4
8
  import { ROUTES } from "../../../next";
5
9
  import { listPublicProducts } from "../../../_internal/server/features/products/list-public";