@mohasinac/appkit 4.26.0 → 4.28.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 (277) 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/bundles/data.d.ts +19 -0
  5. package/dist/_internal/server/features/bundles/data.js +32 -0
  6. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  7. package/dist/_internal/server/features/bundles/index.js +1 -1
  8. package/dist/_internal/server/features/checkout/actions.js +6 -4
  9. package/dist/_internal/server/features/classified/metadata.js +3 -1
  10. package/dist/_internal/server/features/digital-code/metadata.js +3 -1
  11. package/dist/_internal/server/features/live/metadata.js +3 -1
  12. package/dist/_internal/server/features/refunds/actions.js +3 -2
  13. package/dist/_internal/server/features/reviews/actions.js +3 -2
  14. package/dist/_internal/server/features/seo/sitemap.js +189 -40
  15. package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
  16. package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
  17. package/dist/_internal/shared/actions/action-registry.js +14 -0
  18. package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
  19. package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
  20. package/dist/_internal/shared/features/checkout/config.js +6 -3
  21. package/dist/client.d.ts +32 -2
  22. package/dist/client.js +37 -2
  23. package/dist/constants/api-endpoints.d.ts +21 -3
  24. package/dist/constants/api-endpoints.js +14 -1
  25. package/dist/constants/field-names.d.ts +2 -2
  26. package/dist/constants/field-names.js +2 -2
  27. package/dist/core/newsletter.repository.js +14 -1
  28. package/dist/features/about/components/FAQPageView.js +1 -1
  29. package/dist/features/about/components/FeesView.js +1 -1
  30. package/dist/features/account/components/AddressForm.d.ts +2 -0
  31. package/dist/features/account/components/AddressForm.js +5 -1
  32. package/dist/features/account/components/UserBidsView.js +1 -1
  33. package/dist/features/account/components/UserSidebar.js +1 -1
  34. package/dist/features/account/components/UserSupportView.js +24 -2
  35. package/dist/features/account/hooks/useAddresses.d.ts +29 -1
  36. package/dist/features/account/hooks/useAddresses.js +4 -1
  37. package/dist/features/account/hooks/useProfile.d.ts +1 -1
  38. package/dist/features/account/hooks/useProfile.js +1 -1
  39. package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
  40. package/dist/features/addresses/repository/addresses.repository.js +19 -0
  41. package/dist/features/admin/actions/chat-actions.js +5 -1
  42. package/dist/features/admin/components/AdminAdsView.js +1 -1
  43. package/dist/features/admin/components/AdminBidsView.js +6 -1
  44. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
  45. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
  46. package/dist/features/admin/components/AdminCarouselView.js +1 -1
  47. package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
  48. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
  49. package/dist/features/admin/components/AdminSidebar.js +1 -1
  50. package/dist/features/admin/components/AdminViewCards.js +2 -2
  51. package/dist/features/admin/components/DataTable.js +2 -2
  52. package/dist/features/admin/schemas/firestore.js +9 -3
  53. package/dist/features/auctions/actions/bid-actions.js +45 -4
  54. package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
  55. package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
  56. package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
  57. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  58. package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
  59. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
  60. package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
  61. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  62. package/dist/features/auth/hooks/useAuth.d.ts +23 -1
  63. package/dist/features/auth/hooks/useAuth.js +27 -1
  64. package/dist/features/auth/permissions/constants.d.ts +1 -1
  65. package/dist/features/auth/permissions/constants.js +3 -0
  66. package/dist/features/auth/repository/session.repository.d.ts +16 -1
  67. package/dist/features/auth/repository/session.repository.js +23 -0
  68. package/dist/features/auth/repository/user.repository.js +13 -2
  69. package/dist/features/blog/components/BlogListView.js +2 -2
  70. package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
  71. package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
  72. package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
  73. package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
  74. package/dist/features/cart/components/index.d.ts +1 -1
  75. package/dist/features/cart/components/index.js +1 -1
  76. package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
  77. package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
  78. package/dist/features/cart/utils/line-total.d.ts +25 -0
  79. package/dist/features/cart/utils/line-total.js +26 -0
  80. package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
  81. package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
  82. package/dist/features/categories/components/CategoryTree.js +2 -2
  83. package/dist/features/contact/components/ContactForm.js +1 -1
  84. package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
  85. package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
  86. package/dist/features/events/components/AdminEventEditorView.js +16 -9
  87. package/dist/features/events/components/AdminEventEntriesView.js +7 -13
  88. package/dist/features/events/components/EventRafflesSection.js +14 -2
  89. package/dist/features/events/components/EventStatusBadge.js +1 -1
  90. package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
  91. package/dist/features/events/components/event-entry-detail-fields.js +13 -0
  92. package/dist/features/faq/components/ContactCTA.js +1 -1
  93. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  94. package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
  95. package/dist/features/faq/repository/faqs.repository.js +64 -54
  96. package/dist/features/faq/schemas/firestore.d.ts +3 -2
  97. package/dist/features/faq/schemas/firestore.js +2 -2
  98. package/dist/features/faq/schemas/index.d.ts +3 -3
  99. package/dist/features/faq/schemas/index.js +1 -1
  100. package/dist/features/filters/FilterFacetSection.js +2 -2
  101. package/dist/features/forms/Toggle.js +1 -1
  102. package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
  103. package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
  104. package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
  105. package/dist/features/homepage/components/BrandsSection.js +1 -1
  106. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  107. package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
  108. package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
  109. package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
  110. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  111. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  112. package/dist/features/homepage/lib/section-renderer.js +2 -1
  113. package/dist/features/layout/AppLayoutShell.js +5 -5
  114. package/dist/features/layout/BottomActions.js +5 -5
  115. package/dist/features/layout/BottomSheet.js +1 -1
  116. package/dist/features/layout/MainNavbar.js +1 -1
  117. package/dist/features/layout/NavbarLayout.js +1 -1
  118. package/dist/features/layout/TitleBarLayout.js +3 -3
  119. package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
  120. package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
  121. package/dist/features/lottery/schemas/config-write.d.ts +141 -0
  122. package/dist/features/lottery/schemas/config-write.js +139 -0
  123. package/dist/features/media/MediaPickerModal.js +3 -1
  124. package/dist/features/media/MediaVideo.js +4 -2
  125. package/dist/features/media/components/MediaSlider.js +1 -1
  126. package/dist/features/orders/components/OrdersList.js +2 -2
  127. package/dist/features/payments/repository/payout.repository.d.ts +11 -1
  128. package/dist/features/payments/repository/payout.repository.js +14 -6
  129. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
  130. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  131. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  132. package/dist/features/products/columns/productTableColumns.js +3 -3
  133. package/dist/features/products/components/CompareOverlay.js +1 -1
  134. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  135. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  136. package/dist/features/products/components/PrizeDrawsSection.js +5 -1
  137. package/dist/features/products/components/ProductDetailActions.js +11 -7
  138. package/dist/features/products/components/ProductDetailPageView.js +3 -3
  139. package/dist/features/products/components/ProductFeatureBadges.js +2 -2
  140. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  141. package/dist/features/products/components/ProductFilters.js +15 -8
  142. package/dist/features/products/components/ProductGrid.js +8 -17
  143. package/dist/features/products/components/ProductTabs.js +7 -4
  144. package/dist/features/products/components/ProductTabsShell.js +2 -7
  145. package/dist/features/products/constants/action-defs.d.ts +21 -4
  146. package/dist/features/products/constants/action-defs.js +7 -3
  147. package/dist/features/products/hooks/useRelatedProducts.js +2 -1
  148. package/dist/features/products/repository/products.repository.d.ts +1 -1
  149. package/dist/features/products/repository/products.repository.js +27 -13
  150. package/dist/features/products/schemas/firestore.d.ts +3 -2
  151. package/dist/features/products/schemas/firestore.js +1 -1
  152. package/dist/features/reviews/actions/review-actions.d.ts +8 -1
  153. package/dist/features/reviews/actions/review-actions.js +14 -3
  154. package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
  155. package/dist/features/reviews/components/ReviewModal.js +1 -1
  156. package/dist/features/reviews/repository/reviews.repository.d.ts +25 -1
  157. package/dist/features/reviews/repository/reviews.repository.js +53 -13
  158. package/dist/features/reviews/schemas/firestore.d.ts +6 -0
  159. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  160. package/dist/features/search/components/Search.js +3 -3
  161. package/dist/features/search/components/SearchResultsSection.js +1 -1
  162. package/dist/features/seller/components/SellerAddressesView.js +3 -1
  163. package/dist/features/seller/components/SellerBidsView.js +6 -1
  164. package/dist/features/seller/components/SellerSidebar.js +1 -1
  165. package/dist/features/shell/SectionForm.d.ts +21 -1
  166. package/dist/features/shell/SectionForm.js +15 -3
  167. package/dist/features/shell/build-sections.js +10 -1
  168. package/dist/features/shell/field-ui-meta.d.ts +21 -0
  169. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
  170. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  171. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
  172. package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
  173. package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
  174. package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
  175. package/dist/features/stores/actions/store-query-actions.js +25 -1
  176. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  177. package/dist/features/stores/components/StoreHeader.js +1 -1
  178. package/dist/features/stores/components/StoreNavTabs.js +20 -8
  179. package/dist/features/support/repository/support.repository.d.ts +12 -0
  180. package/dist/features/support/repository/support.repository.js +15 -0
  181. package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
  182. package/dist/features/support/schemas/ticket-create-form.js +82 -0
  183. package/dist/features/tester/seed-data/products-tester-seed-data.js +108 -2
  184. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +235 -13
  185. package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
  186. package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
  187. package/dist/features/wishlist/components/index.d.ts +2 -1
  188. package/dist/features/wishlist/components/index.js +1 -1
  189. package/dist/index.d.ts +10 -3
  190. package/dist/index.js +19 -4
  191. package/dist/monitoring/error-tracking.js +39 -5
  192. package/dist/monitoring/server-logger.js +70 -32
  193. package/dist/next/components/ErrorView.js +1 -1
  194. package/dist/next/routing/route-map.d.ts +39 -7
  195. package/dist/next/routing/route-map.js +34 -4
  196. package/dist/providers/db-firebase/base.repository.d.ts +30 -0
  197. package/dist/providers/db-firebase/base.repository.js +56 -9
  198. package/dist/react/ErrorBoundary.js +9 -1
  199. package/dist/react/contexts/SessionContext.js +6 -2
  200. package/dist/react/hooks/useAuthGate.d.ts +22 -0
  201. package/dist/react/hooks/useAuthGate.js +86 -7
  202. package/dist/schemas/registry.d.ts +3 -3
  203. package/dist/security/index.d.ts +1 -1
  204. package/dist/security/index.js +1 -1
  205. package/dist/security/pii-encrypt.d.ts +13 -5
  206. package/dist/security/pii-encrypt.js +132 -6
  207. package/dist/security/pii-schemas.d.ts +20 -4
  208. package/dist/security/pii-schemas.js +23 -5
  209. package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
  210. package/dist/seed/_helpers/product-search-txt.js +33 -0
  211. package/dist/seed/faq-seed-data.d.ts +16 -0
  212. package/dist/seed/faq-seed-data.js +22 -1
  213. package/dist/seed/products-art-seed-data.js +49 -7
  214. package/dist/seed/products-auctions-seed-data.js +12 -4
  215. package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
  216. package/dist/seed/products-classifieds-seed-data.js +65 -9
  217. package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
  218. package/dist/seed/products-digital-codes-seed-data.js +65 -9
  219. package/dist/seed/products-live-items-seed-data.d.ts +5 -0
  220. package/dist/seed/products-live-items-seed-data.js +33 -5
  221. package/dist/seed/products-preorders-seed-data.d.ts +1 -1
  222. package/dist/seed/products-preorders-seed-data.js +6 -2
  223. package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
  224. package/dist/seed/products-prize-draws-seed-data.js +57 -8
  225. package/dist/seed/products-standard-seed-data.js +6 -2
  226. package/dist/seed/products-stickers-seed-data.js +49 -7
  227. package/dist/seed/site-settings-seed-data.js +5 -3
  228. package/dist/seo/json-ld.d.ts +44 -2
  229. package/dist/seo/json-ld.js +57 -11
  230. package/dist/server.d.ts +1 -1
  231. package/dist/server.js +1 -1
  232. package/dist/styles.css +368 -90
  233. package/dist/tailwind-utilities.css +1 -1
  234. package/dist/tokens/index.d.ts +1 -1
  235. package/dist/tokens/index.js +1 -1
  236. package/dist/tokens/themes/default-dark.js +6 -0
  237. package/dist/tokens/themes/default-light.js +5 -0
  238. package/dist/tokens/tokens.css +84 -0
  239. package/dist/ui/DataTable.js +1 -1
  240. package/dist/ui/DataTable.style.css +3 -3
  241. package/dist/ui/components/BulkActionBar.style.css +1 -3
  242. package/dist/ui/components/Button.js +15 -6
  243. package/dist/ui/components/Dropdown.style.css +1 -4
  244. package/dist/ui/components/FilterChipGroup.js +1 -1
  245. package/dist/ui/components/FilterDrawer.style.css +1 -1
  246. package/dist/ui/components/Icon.d.ts +53 -0
  247. package/dist/ui/components/Icon.js +41 -0
  248. package/dist/ui/components/IconButton.d.ts +13 -0
  249. package/dist/ui/components/IconButton.js +13 -0
  250. package/dist/ui/components/IconButton.style.css +18 -10
  251. package/dist/ui/components/ListingLayout.style.css +2 -10
  252. package/dist/ui/components/ListingToolbar.js +6 -6
  253. package/dist/ui/components/Modal.js +1 -1
  254. package/dist/ui/components/PaginatedSelect.style.css +3 -3
  255. package/dist/ui/components/Pagination.style.css +1 -6
  256. package/dist/ui/components/RichTextEditor.js +1 -1
  257. package/dist/ui/components/RowActionMenu.style.css +1 -1
  258. package/dist/ui/components/SideDrawer.style.css +1 -1
  259. package/dist/ui/components/Slider.js +1 -1
  260. package/dist/ui/components/TabBarShell.d.ts +78 -0
  261. package/dist/ui/components/TabBarShell.js +95 -0
  262. package/dist/ui/components/Tabs.js +12 -8
  263. package/dist/ui/components/Tabs.style.css +249 -41
  264. package/dist/ui/components/ViewToggle.style.css +1 -5
  265. package/dist/ui/components/tabs-overflow.d.ts +17 -0
  266. package/dist/ui/components/tabs-overflow.js +142 -0
  267. package/dist/ui/icons/icon-registry.d.ts +38 -0
  268. package/dist/ui/icons/icon-registry.js +37 -0
  269. package/dist/ui/index.d.ts +6 -0
  270. package/dist/ui/index.js +3 -0
  271. package/dist/utils/index.d.ts +1 -1
  272. package/dist/utils/index.js +1 -1
  273. package/dist/utils/media-url.d.ts +27 -4
  274. package/dist/utils/media-url.js +37 -6
  275. package/dist/utils/search-txt.d.ts +71 -0
  276. package/dist/utils/search-txt.js +118 -0
  277. package/package.json +1 -1
@@ -12,6 +12,6 @@ export function MainNavbar({ navItems, hiddenNavItems = [], inline = false, hasD
12
12
  icon: item.icon,
13
13
  highlighted: item.highlighted,
14
14
  }));
15
- const dashboardToggle = hasDashboardNav && onToggleDashboardNav ? (_jsx("button", { type: "button", onClick: onToggleDashboardNav, "aria-label": "Toggle dashboard navigation", className: "flex items-center justify-center w-8 h-8 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-elevated)] hover:text-zinc-800 dark:hover:text-zinc-100 transition-colors", children: _jsxs("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 5a2 2 0 012-2h14a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V5z" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 3v18" })] }) })) : undefined;
15
+ const dashboardToggle = hasDashboardNav && onToggleDashboardNav ? (_jsx("button", { type: "button", onClick: onToggleDashboardNav, "aria-label": "Toggle dashboard navigation", className: "flex items-center justify-center w-8 h-8 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-zinc-800 dark:hover:text-zinc-100 transition-colors", children: _jsxs("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 5a2 2 0 012-2h14a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V5z" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 3v18" })] }) })) : undefined;
16
16
  return (_jsx(NavbarLayout, { items: items, activeHref: pathname, inline: inline, rightSlot: dashboardToggle }));
17
17
  }
@@ -12,7 +12,7 @@ function DefaultNavItem({ item, isActive, }) {
12
12
  ? "border border-primary-400/40 dark:border-secondary-400/30 text-primary-700 dark:text-secondary-400 bg-primary-50/80 dark:bg-secondary-900/30 px-[var(--appkit-space-3)] transition-colors duration-150"
13
13
  : isActive
14
14
  ? "bg-primary-50 dark:bg-[var(--appkit-color-surface-elevated)] text-primary-800 dark:text-white font-semibold px-[var(--appkit-space-3)] border-b-2 border-primary-500 dark:border-secondary-400 rounded-none pb-[6px] transition-colors duration-150"
15
- : "text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-elevated)] hover:text-zinc-950 dark:hover:text-white transition-colors duration-150 px-[var(--appkit-space-3)]";
15
+ : "text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-zinc-950 dark:hover:text-white transition-colors duration-150 px-[var(--appkit-space-3)]";
16
16
  return (_jsxs(Link, { href: item.href, "aria-current": isActive ? "page" : undefined, className: `flex items-center gap-[var(--appkit-space-1-5)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] rounded-lg font-medium transition-colors duration-150 whitespace-nowrap ${activeClasses}`, children: [item.icon, _jsx(Span, { children: item.label })] }));
17
17
  }
18
18
  /**
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
3
  import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, SiteMark, Span } from "../../ui";
4
4
  /** Shared icon-button class for all action buttons in the title bar. */
5
- const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 dark:hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
5
+ const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-surface hover:text-primary-700 dark:hover:text-secondary-400 transition-colors";
6
6
  /** Badge counter class for wishlist/cart counts.
7
7
  *
8
8
  * The horizontal offset lives in `.appkit-hand-badge` (HandMode.style.css), NOT
@@ -29,7 +29,7 @@ const countBadge = "appkit-hand-badge absolute -top-0.5 flex items-center justif
29
29
  */
30
30
  export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
31
31
  // ── Element builders ────────────────────────────────────────────────────────
32
- const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] rounded-full text-[length:var(--appkit-text-xs)] font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-200 dark:hover:bg-secondary-900/60 transition-colors border border-primary-200/60 dark:border-secondary-700/40", children: [_jsx("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.37.86.58 1.41.58.55 0 1.05-.21 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z" }) }), _jsx(Span, { className: "hidden sm:inline", children: "Today's Deals" })] })) : null;
32
+ const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] rounded-full text-[length:var(--appkit-text-xs)] font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-surface transition-colors border border-primary-200/60 dark:border-secondary-700/40", children: [_jsx("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.37.86.58 1.41.58.55 0 1.05-.21 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z" }) }), _jsx(Span, { className: "hidden sm:inline", children: "Today's Deals" })] })) : null;
33
33
  const themeBtn = onToggleTheme ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", onClick: onToggleTheme, className: iconBtn, children: isDark ? (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m8.66-9h-1M4.34 12h-1m15.07-6.07-.71.71M6.34 17.66l-.71.71m12.73 0-.71-.71M6.34 6.34l-.71-.71M12 5a7 7 0 1 0 0 14A7 7 0 0 0 12 5z" }) })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12.79A9 9 0 1 1 11.21 3a7 7 0 1 0 9.79 9.79z" }) })) })) : null;
34
34
  const tourBtn = onTourStart ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Start product tour", onClick: onTourStart, className: iconBtn, children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }) })) : null;
35
35
  const hamburgerBtn = !hideSidebarToggle ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": sidebarOpen ? "Close menu" : hasDashboardNav ? "Open dashboard navigation" : "Open menu", "aria-expanded": sidebarOpen, "aria-controls": "secondary-sidebar", onClick: hasDashboardNav && onToggleDashboardNav ? onToggleDashboardNav : onToggleSidebar, className: iconBtn, children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: sidebarOpen ? (_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" })) : (_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" })) }) })) : null;
@@ -46,7 +46,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
46
46
  const authButtonsEl = !user && (loginHref || registerHref) ? (
47
47
  // Mirrors with TB1 so Register (the primary CTA) keeps the outer, more
48
48
  // reachable edge instead of ending up tucked inside after the flip.
49
- _jsxs(Row, { gap: "xs", reverse: "hand", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
49
+ _jsxs(Row, { gap: "xs", reverse: "hand", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-surface", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
50
50
  const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
51
51
  // ── Render ───────────────────────────────────────────────────────────────────
52
52
  return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", reverse: "hand", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsxs(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center gap-[var(--appkit-space-2)] transition-opacity hover:opacity-80", children: [_jsx(Div, { className: "flex md:hidden", children: _jsx(SiteMark, { title: brandShortName ?? brandName, size: "sm" }) }), _jsx(SiteLogo, { title: brandName, size: "md" })] }) }), _jsx(Row, { className: "hidden md:flex absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteMark, { src: siteLogoUrl, title: brandName, size: "lg" }) }) }), _jsxs(Row, { gap: "xs", reverse: "hand", children: [devSlot, navSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
@@ -16,6 +16,16 @@ export interface UseFormBottomActionsOptions {
16
16
  * `Form` itself renders, so the ambient lookup would find the parent's
17
17
  * context or none at all.
18
18
  */
19
+ /**
20
+ * Destructive action for this record, mirrored from `SectionForm`. Pushed
21
+ * to the FRONT of the bar so it is furthest from where the thumb rests on
22
+ * the primary action.
23
+ */
24
+ destructiveAction?: {
25
+ label: string;
26
+ onClick: () => void;
27
+ disabled?: boolean;
28
+ };
19
29
  ctx?: FormShellContextValue;
20
30
  }
21
31
  /**
@@ -53,5 +63,5 @@ export interface UseFormBottomActionsOptions {
53
63
  * overlay tells us, because neither portal leaves a detectable trace in
54
64
  * the DOM at the form's position.
55
65
  */
56
- export declare function useFormBottomActions({ onSubmit, onCancel, submitLabel, cancelLabel, isLoading, disabled, enabled, ctx: ctxProp, }: UseFormBottomActionsOptions): void;
66
+ export declare function useFormBottomActions({ onSubmit, onCancel, submitLabel, cancelLabel, isLoading, disabled, enabled, destructiveAction, ctx: ctxProp, }: UseFormBottomActionsOptions): void;
57
67
  export default useFormBottomActions;
@@ -40,7 +40,7 @@ import { useBottomActions } from "./useBottomActions";
40
40
  * overlay tells us, because neither portal leaves a detectable trace in
41
41
  * the DOM at the form's position.
42
42
  */
43
- export function useFormBottomActions({ onSubmit, onCancel, submitLabel = "Save", cancelLabel = "Cancel", isLoading = false, disabled = false, enabled = true, ctx: ctxProp, }) {
43
+ export function useFormBottomActions({ onSubmit, onCancel, submitLabel = "Save", cancelLabel = "Cancel", isLoading = false, disabled = false, enabled = true, destructiveAction, ctx: ctxProp, }) {
44
44
  const ambient = useContext(FormShellContext);
45
45
  const ctx = ctxProp ?? ambient;
46
46
  const insideOverlay = useIsInsideOverlay();
@@ -51,6 +51,20 @@ export function useFormBottomActions({ onSubmit, onCancel, submitLabel = "Save",
51
51
  if (!active)
52
52
  return [];
53
53
  const list = [];
54
+ /*
55
+ * Destructive FIRST, so it is furthest from the thumb's resting position
56
+ * on the primary action. It never grows, so Save keeps the width.
57
+ */
58
+ if (destructiveAction) {
59
+ list.push({
60
+ id: "form-destructive",
61
+ label: destructiveAction.label,
62
+ variant: "danger",
63
+ grow: false,
64
+ onClick: destructiveAction.onClick,
65
+ disabled: destructiveAction.disabled || isLoading,
66
+ });
67
+ }
54
68
  if (onCancel) {
55
69
  list.push({
56
70
  id: "form-cancel",
@@ -71,7 +85,7 @@ export function useFormBottomActions({ onSubmit, onCancel, submitLabel = "Save",
71
85
  onClick: () => void onSubmit(),
72
86
  });
73
87
  return list;
74
- }, [active, onCancel, cancelLabel, submitLabel, isLoading, disabled, onSubmit]);
88
+ }, [active, onCancel, cancelLabel, submitLabel, isLoading, disabled, onSubmit, destructiveAction]);
75
89
  useBottomActions({
76
90
  actions,
77
91
  // Encodes the count on purpose — see (1) in the header.
@@ -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
  ]
@@ -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
  },