@mohasinac/appkit 4.27.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 (271) 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/jobs/core/listingProcessor.d.ts +0 -1
  12. package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
  13. package/dist/_internal/shared/actions/action-registry.js +14 -0
  14. package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
  15. package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
  16. package/dist/_internal/shared/features/checkout/config.js +6 -3
  17. package/dist/client.d.ts +32 -2
  18. package/dist/client.js +37 -2
  19. package/dist/constants/api-endpoints.d.ts +21 -3
  20. package/dist/constants/api-endpoints.js +14 -1
  21. package/dist/constants/field-names.d.ts +2 -2
  22. package/dist/constants/field-names.js +2 -2
  23. package/dist/core/newsletter.repository.js +14 -1
  24. package/dist/features/about/components/FAQPageView.js +1 -1
  25. package/dist/features/about/components/FeesView.js +1 -1
  26. package/dist/features/account/components/AddressForm.d.ts +2 -0
  27. package/dist/features/account/components/AddressForm.js +5 -1
  28. package/dist/features/account/components/UserBidsView.js +1 -1
  29. package/dist/features/account/components/UserSidebar.js +1 -1
  30. package/dist/features/account/components/UserSupportView.js +24 -2
  31. package/dist/features/account/hooks/useAddresses.d.ts +29 -1
  32. package/dist/features/account/hooks/useAddresses.js +4 -1
  33. package/dist/features/account/hooks/useProfile.d.ts +1 -1
  34. package/dist/features/account/hooks/useProfile.js +1 -1
  35. package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
  36. package/dist/features/addresses/repository/addresses.repository.js +19 -0
  37. package/dist/features/admin/actions/chat-actions.js +5 -1
  38. package/dist/features/admin/components/AdminAdsView.js +1 -1
  39. package/dist/features/admin/components/AdminBidsView.js +6 -1
  40. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
  41. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
  42. package/dist/features/admin/components/AdminCarouselView.js +1 -1
  43. package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
  44. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
  45. package/dist/features/admin/components/AdminSidebar.js +1 -1
  46. package/dist/features/admin/components/AdminViewCards.js +2 -2
  47. package/dist/features/admin/components/DataTable.js +2 -2
  48. package/dist/features/admin/schemas/firestore.js +9 -3
  49. package/dist/features/auctions/actions/bid-actions.js +45 -4
  50. package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
  51. package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
  52. package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
  53. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  54. package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
  55. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
  56. package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
  57. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  58. package/dist/features/auth/hooks/useAuth.d.ts +23 -1
  59. package/dist/features/auth/hooks/useAuth.js +27 -1
  60. package/dist/features/auth/permissions/constants.d.ts +1 -1
  61. package/dist/features/auth/permissions/constants.js +3 -0
  62. package/dist/features/auth/repository/session.repository.d.ts +16 -1
  63. package/dist/features/auth/repository/session.repository.js +23 -0
  64. package/dist/features/auth/repository/user.repository.js +13 -2
  65. package/dist/features/blog/components/BlogListView.js +2 -2
  66. package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
  67. package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
  68. package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
  69. package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
  70. package/dist/features/cart/components/index.d.ts +1 -1
  71. package/dist/features/cart/components/index.js +1 -1
  72. package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
  73. package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
  74. package/dist/features/cart/utils/line-total.d.ts +25 -0
  75. package/dist/features/cart/utils/line-total.js +26 -0
  76. package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
  77. package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
  78. package/dist/features/categories/components/CategoryTree.js +2 -2
  79. package/dist/features/contact/components/ContactForm.js +1 -1
  80. package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
  81. package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
  82. package/dist/features/events/components/AdminEventEditorView.js +16 -9
  83. package/dist/features/events/components/AdminEventEntriesView.js +7 -13
  84. package/dist/features/events/components/EventRafflesSection.js +14 -2
  85. package/dist/features/events/components/EventStatusBadge.js +1 -1
  86. package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
  87. package/dist/features/events/components/event-entry-detail-fields.js +13 -0
  88. package/dist/features/faq/components/ContactCTA.js +1 -1
  89. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  90. package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
  91. package/dist/features/faq/repository/faqs.repository.js +64 -54
  92. package/dist/features/faq/schemas/firestore.d.ts +3 -2
  93. package/dist/features/faq/schemas/firestore.js +2 -2
  94. package/dist/features/faq/schemas/index.d.ts +3 -3
  95. package/dist/features/faq/schemas/index.js +1 -1
  96. package/dist/features/filters/FilterFacetSection.js +2 -2
  97. package/dist/features/forms/Toggle.js +1 -1
  98. package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
  99. package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
  100. package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
  101. package/dist/features/homepage/components/BrandsSection.js +1 -1
  102. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  103. package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
  104. package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
  105. package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
  106. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  107. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  108. package/dist/features/homepage/lib/section-renderer.js +2 -1
  109. package/dist/features/layout/AppLayoutShell.js +5 -5
  110. package/dist/features/layout/BottomActions.js +5 -5
  111. package/dist/features/layout/BottomSheet.js +1 -1
  112. package/dist/features/layout/MainNavbar.js +1 -1
  113. package/dist/features/layout/NavbarLayout.js +1 -1
  114. package/dist/features/layout/TitleBarLayout.js +3 -3
  115. package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
  116. package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
  117. package/dist/features/lottery/schemas/config-write.d.ts +141 -0
  118. package/dist/features/lottery/schemas/config-write.js +139 -0
  119. package/dist/features/media/MediaPickerModal.js +3 -1
  120. package/dist/features/media/MediaVideo.js +4 -2
  121. package/dist/features/media/components/MediaSlider.js +1 -1
  122. package/dist/features/orders/components/OrdersList.js +2 -2
  123. package/dist/features/payments/repository/payout.repository.d.ts +11 -1
  124. package/dist/features/payments/repository/payout.repository.js +14 -6
  125. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
  126. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  127. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  128. package/dist/features/products/columns/productTableColumns.js +3 -3
  129. package/dist/features/products/components/CompareOverlay.js +1 -1
  130. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  131. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  132. package/dist/features/products/components/PrizeDrawsSection.js +5 -1
  133. package/dist/features/products/components/ProductDetailActions.js +11 -7
  134. package/dist/features/products/components/ProductDetailPageView.js +3 -3
  135. package/dist/features/products/components/ProductFeatureBadges.js +2 -2
  136. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  137. package/dist/features/products/components/ProductFilters.js +15 -8
  138. package/dist/features/products/components/ProductGrid.js +8 -17
  139. package/dist/features/products/components/ProductTabs.js +7 -4
  140. package/dist/features/products/components/ProductTabsShell.js +2 -7
  141. package/dist/features/products/constants/action-defs.d.ts +21 -4
  142. package/dist/features/products/constants/action-defs.js +7 -3
  143. package/dist/features/products/hooks/useRelatedProducts.js +2 -1
  144. package/dist/features/products/repository/products.repository.d.ts +1 -1
  145. package/dist/features/products/repository/products.repository.js +27 -13
  146. package/dist/features/products/schemas/firestore.d.ts +3 -2
  147. package/dist/features/products/schemas/firestore.js +1 -1
  148. package/dist/features/reviews/actions/review-actions.d.ts +8 -1
  149. package/dist/features/reviews/actions/review-actions.js +14 -3
  150. package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
  151. package/dist/features/reviews/components/ReviewModal.js +1 -1
  152. package/dist/features/reviews/repository/reviews.repository.d.ts +25 -1
  153. package/dist/features/reviews/repository/reviews.repository.js +53 -13
  154. package/dist/features/reviews/schemas/firestore.d.ts +6 -0
  155. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  156. package/dist/features/search/components/Search.js +3 -3
  157. package/dist/features/search/components/SearchResultsSection.js +1 -1
  158. package/dist/features/seller/components/SellerAddressesView.js +3 -1
  159. package/dist/features/seller/components/SellerBidsView.js +6 -1
  160. package/dist/features/seller/components/SellerSidebar.js +1 -1
  161. package/dist/features/shell/SectionForm.d.ts +21 -1
  162. package/dist/features/shell/SectionForm.js +15 -3
  163. package/dist/features/shell/build-sections.js +10 -1
  164. package/dist/features/shell/field-ui-meta.d.ts +21 -0
  165. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
  166. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  167. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
  168. package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
  169. package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
  170. package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
  171. package/dist/features/stores/actions/store-query-actions.js +25 -1
  172. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  173. package/dist/features/stores/components/StoreHeader.js +1 -1
  174. package/dist/features/stores/components/StoreNavTabs.js +20 -8
  175. package/dist/features/support/repository/support.repository.d.ts +12 -0
  176. package/dist/features/support/repository/support.repository.js +15 -0
  177. package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
  178. package/dist/features/support/schemas/ticket-create-form.js +82 -0
  179. package/dist/features/tester/seed-data/products-tester-seed-data.js +2 -2
  180. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +138 -1
  181. package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
  182. package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
  183. package/dist/features/wishlist/components/index.d.ts +2 -1
  184. package/dist/features/wishlist/components/index.js +1 -1
  185. package/dist/index.d.ts +10 -3
  186. package/dist/index.js +19 -4
  187. package/dist/monitoring/error-tracking.js +39 -5
  188. package/dist/monitoring/server-logger.js +70 -32
  189. package/dist/next/components/ErrorView.js +1 -1
  190. package/dist/next/routing/route-map.d.ts +39 -7
  191. package/dist/next/routing/route-map.js +34 -4
  192. package/dist/providers/db-firebase/base.repository.d.ts +30 -0
  193. package/dist/providers/db-firebase/base.repository.js +56 -9
  194. package/dist/react/ErrorBoundary.js +9 -1
  195. package/dist/react/contexts/SessionContext.js +6 -2
  196. package/dist/react/hooks/useAuthGate.d.ts +22 -0
  197. package/dist/react/hooks/useAuthGate.js +86 -7
  198. package/dist/schemas/registry.d.ts +3 -3
  199. package/dist/security/index.d.ts +1 -1
  200. package/dist/security/index.js +1 -1
  201. package/dist/security/pii-encrypt.d.ts +13 -5
  202. package/dist/security/pii-encrypt.js +132 -6
  203. package/dist/security/pii-schemas.d.ts +20 -4
  204. package/dist/security/pii-schemas.js +23 -5
  205. package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
  206. package/dist/seed/_helpers/product-search-txt.js +33 -0
  207. package/dist/seed/faq-seed-data.d.ts +16 -0
  208. package/dist/seed/faq-seed-data.js +22 -1
  209. package/dist/seed/products-art-seed-data.js +49 -7
  210. package/dist/seed/products-auctions-seed-data.js +12 -4
  211. package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
  212. package/dist/seed/products-classifieds-seed-data.js +65 -9
  213. package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
  214. package/dist/seed/products-digital-codes-seed-data.js +65 -9
  215. package/dist/seed/products-live-items-seed-data.d.ts +5 -0
  216. package/dist/seed/products-live-items-seed-data.js +33 -5
  217. package/dist/seed/products-preorders-seed-data.d.ts +1 -1
  218. package/dist/seed/products-preorders-seed-data.js +6 -2
  219. package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
  220. package/dist/seed/products-prize-draws-seed-data.js +57 -8
  221. package/dist/seed/products-standard-seed-data.js +6 -2
  222. package/dist/seed/products-stickers-seed-data.js +49 -7
  223. package/dist/seed/site-settings-seed-data.js +5 -3
  224. package/dist/seo/json-ld.d.ts +44 -2
  225. package/dist/seo/json-ld.js +57 -11
  226. package/dist/styles.css +368 -90
  227. package/dist/tailwind-utilities.css +1 -1
  228. package/dist/tokens/index.d.ts +1 -1
  229. package/dist/tokens/index.js +1 -1
  230. package/dist/tokens/themes/default-dark.js +6 -0
  231. package/dist/tokens/themes/default-light.js +5 -0
  232. package/dist/tokens/tokens.css +84 -0
  233. package/dist/ui/DataTable.js +1 -1
  234. package/dist/ui/DataTable.style.css +3 -3
  235. package/dist/ui/components/BulkActionBar.style.css +1 -3
  236. package/dist/ui/components/Button.js +15 -6
  237. package/dist/ui/components/Dropdown.style.css +1 -4
  238. package/dist/ui/components/FilterChipGroup.js +1 -1
  239. package/dist/ui/components/FilterDrawer.style.css +1 -1
  240. package/dist/ui/components/Icon.d.ts +53 -0
  241. package/dist/ui/components/Icon.js +41 -0
  242. package/dist/ui/components/IconButton.d.ts +13 -0
  243. package/dist/ui/components/IconButton.js +13 -0
  244. package/dist/ui/components/IconButton.style.css +18 -10
  245. package/dist/ui/components/ListingLayout.style.css +2 -10
  246. package/dist/ui/components/ListingToolbar.js +6 -6
  247. package/dist/ui/components/Modal.js +1 -1
  248. package/dist/ui/components/PaginatedSelect.style.css +3 -3
  249. package/dist/ui/components/Pagination.style.css +1 -6
  250. package/dist/ui/components/RichTextEditor.js +1 -1
  251. package/dist/ui/components/RowActionMenu.style.css +1 -1
  252. package/dist/ui/components/SideDrawer.style.css +1 -1
  253. package/dist/ui/components/Slider.js +1 -1
  254. package/dist/ui/components/TabBarShell.d.ts +78 -0
  255. package/dist/ui/components/TabBarShell.js +95 -0
  256. package/dist/ui/components/Tabs.js +12 -8
  257. package/dist/ui/components/Tabs.style.css +249 -41
  258. package/dist/ui/components/ViewToggle.style.css +1 -5
  259. package/dist/ui/components/tabs-overflow.d.ts +17 -0
  260. package/dist/ui/components/tabs-overflow.js +142 -0
  261. package/dist/ui/icons/icon-registry.d.ts +38 -0
  262. package/dist/ui/icons/icon-registry.js +37 -0
  263. package/dist/ui/index.d.ts +6 -0
  264. package/dist/ui/index.js +3 -0
  265. package/dist/utils/index.d.ts +1 -1
  266. package/dist/utils/index.js +1 -1
  267. package/dist/utils/media-url.d.ts +27 -4
  268. package/dist/utils/media-url.js +37 -6
  269. package/dist/utils/search-txt.d.ts +71 -0
  270. package/dist/utils/search-txt.js +118 -0
  271. package/package.json +1 -1
@@ -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
  },
@@ -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";
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { useRouter } from "next/navigation";
5
- import { Button, Stack, LoginRequiredModal } from "../../../ui";
5
+ import { Button, Stack, LoginRequiredModal, Icon } from "../../../ui";
6
6
  import { useToast } from "../../../ui/components/Toast";
7
7
  import { useAddToCart } from "../../cart/hooks/useAddToCart";
8
8
  import { apiClient } from "../../../http";
@@ -22,9 +22,13 @@ import { normalizeError } from "../../../errors/normalize";
22
22
  export function ProductDetailActions({ productId, productTitle, productImage, price, currency, storeId, storeName, inStock, variant = "desktop", }) {
23
23
  const router = useRouter();
24
24
  const { showToast } = useToast();
25
- const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
25
+ const { requireAuth, isAuthResolving, modalOpen, modalMessage, closeModal } = useAuthGate();
26
26
  const [busy, setBusy] = useState(null);
27
27
  const [wishlisted, setWishlisted] = useState(false);
28
+ // `isAuthResolving` covers the window where the session has not settled and a
29
+ // click would be parked by useAuthGate rather than acted on. Without it the
30
+ // button looks live, swallows the click, and appears to do nothing.
31
+ const blocked = busy !== null || isAuthResolving;
28
32
  // Success toast (item + updated count/total) is now shown by useAddToCart
29
33
  // itself — centralised so every add-to-cart call site gets the same
30
34
  // richer toast, not just this one.
@@ -98,24 +102,24 @@ export function ProductDetailActions({ productId, productTitle, productImage, pr
98
102
  actions: [
99
103
  {
100
104
  id: ACTION_ID.ADD_TO_WISHLIST,
101
- label: wishlisted ? "♥ Saved" : "♡ Wishlist",
105
+ label: wishlisted ? "Saved" : "Wishlist",
102
106
  variant: "ghost",
103
107
  grow: false,
104
- disabled: busy !== null || wishlisted,
108
+ disabled: blocked || wishlisted,
105
109
  onClick: handleWishlist,
106
110
  },
107
111
  {
108
112
  id: ACTION_ID.ADD_TO_CART,
109
113
  label: busy === "cart" ? "Adding…" : "Add to Cart",
110
114
  variant: "outline",
111
- disabled: !inStock || busy !== null,
115
+ disabled: !inStock || blocked,
112
116
  onClick: () => handleAddToCart(),
113
117
  },
114
118
  {
115
119
  id: ACTION_ID.BUY_NOW,
116
120
  label: !inStock ? "Out of Stock" : busy === "buy" ? "Loading…" : "Buy Now",
117
121
  variant: "primary",
118
- disabled: !inStock || busy !== null,
122
+ disabled: !inStock || blocked,
119
123
  onClick: handleBuyNow,
120
124
  },
121
125
  ],
@@ -132,5 +136,5 @@ export function ProductDetailActions({ productId, productTitle, productImage, pr
132
136
  if (variant === "mobile") {
133
137
  return _jsx(_Fragment, { children: authModal });
134
138
  }
135
- return (_jsxs(_Fragment, { children: [authModal, _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: !inStock || busy !== null, onClick: handleBuyNow, children: !inStock ? "Out of Stock" : busy === "buy" ? "Loading…" : "Buy Now" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: !inStock || busy !== null, onClick: () => handleAddToCart(), children: !inStock ? "Out of Stock" : busy === "cart" ? "Adding…" : "Add to Cart" }), _jsx(Button, { variant: "ghost", size: "md", className: "w-full", disabled: busy !== null || wishlisted, onClick: handleWishlist, children: wishlisted ? "♥ In Wishlist" : "♡ Add to Wishlist" })] })] }));
139
+ return (_jsxs(_Fragment, { children: [authModal, _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: !inStock || blocked, onClick: handleBuyNow, children: !inStock ? "Out of Stock" : busy === "buy" ? "Loading…" : "Buy Now" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: !inStock || blocked, onClick: () => handleAddToCart(), children: !inStock ? "Out of Stock" : busy === "cart" ? "Adding…" : "Add to Cart" }), _jsxs(Button, { variant: "ghost", size: "md", className: "w-full", disabled: blocked || wishlisted, onClick: handleWishlist, children: [_jsx(Icon, { name: "wishlist", size: "md", filled: wishlisted }), wishlisted ? "In Wishlist" : "Add to Wishlist"] })] })] }));
136
140
  }
@@ -22,7 +22,7 @@ const CLS_BUNDLE_LABEL = "text-[length:var(--appkit-text-xs)] text-success dark:
22
22
  const CLS_BUNDLE_CTA = "flex-shrink-0 rounded-lg bg-success-solid hover:brightness-110 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-success-on-solid transition-colors";
23
23
  import { ROUTES } from "../../../next";
24
24
  import { getDefaultCurrency } from "../../../core/baseline-resolver";
25
- import { Button, Container, Div, Heading, Main, Nav, PriceDisplay, RichText, Row, Section, Span, Stack, Text, Ul, Ol, Li, Dl, Dt, Dd, } from "../../../ui";
25
+ import { Button, Container, Div, Heading, Main, Nav, PriceDisplay, RichText, Row, Section, Span, Stack, Text, Ul, Ol, Li, Dl, Dt, Dd, Icon, } from "../../../ui";
26
26
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
27
27
  import { safeDisplayName } from "../../../security";
28
28
  import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPanel";
@@ -204,7 +204,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
204
204
  ? ` — only ${effectiveStock} left`
205
205
  : ""] })] })) : (_jsxs(Span, { layout: "inline-flex", gap: "xs", className: `w-fit .5 ${inStock ? CLS_STOCK_IN : CLS_STOCK_OUT}`, rounded: "full", padding: "pill-md", size: "xs", weight: "medium", children: [inStock ? "✓ In Stock" : "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
206
206
  ? ` — only ${effectiveStock} left`
207
- : ""] })), (categorySlugs.length > 0 || subcategory || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [categorySlugs.map((slug, i) => (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(slug)), 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: categoryNames[i] ?? slug }, slug))), subcategory && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", transform: "capitalize", children: subcategory })), 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 }))] })), bundleMemberships.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, children: bundleMemberships.map((b) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: CLS_BUNDLE_PILL, children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), "In bundle: ", b.title] }, b.id))) })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, codAvailable: codEnabled === true, codHref: String(ROUTES.PUBLIC.HOW_CHECKOUT_WORKS), emiAvailable: emiEnabled === true, emiHref: String(ROUTES.PUBLIC.HOW_EMI_WORKS), condition: condition ?? undefined, returnable: returnPolicy != null && returnPolicy.length > 0, labels: {
207
+ : ""] })), (categorySlugs.length > 0 || subcategory || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [categorySlugs.map((slug, i) => (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(slug)), 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: categoryNames[i] ?? slug }, slug))), subcategory && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", transform: "capitalize", children: subcategory })), 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 }))] })), bundleMemberships.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, children: bundleMemberships.map((b) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: CLS_BUNDLE_PILL, children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), "In bundle: ", b.title] }, b.id))) })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, codAvailable: codEnabled === true, codHref: String(ROUTES.PUBLIC.HOW_CHECKOUT_WORKS), emiAvailable: emiEnabled === true, emiHref: String(ROUTES.PUBLIC.HOW_EMI_WORKS), condition: condition ?? undefined, returnable: returnPolicy != null && returnPolicy.length > 0, labels: {
208
208
  featured: "Featured",
209
209
  fasterDelivery: "Faster Delivery",
210
210
  ratedSeller: "Rated Seller",
@@ -232,7 +232,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
232
232
  storeName: typeof p.storeName === "string" ? p.storeName : undefined,
233
233
  inStock,
234
234
  variant: "desktop",
235
- })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsx(Button, { variant: "ghost", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: "\u2661 Add to Wishlist" })] })), allowOffers && offerableType && offersEnabled && price !== null && renderOfferAction?.({
235
+ })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsxs(Button, { variant: "ghost", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: [_jsx(Icon, { name: "wishlist", size: "md" }), "Add to Wishlist"] })] })), allowOffers && offerableType && offersEnabled && price !== null && renderOfferAction?.({
236
236
  productId: product.id,
237
237
  price,
238
238
  currency,
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Fragment } from "react";
3
- import { Row, Span, TextLink } from "../../../ui";
3
+ import { Row, Span, TextLink, Icon } from "../../../ui";
4
4
  export function ProductFeatureBadges({ featured, fasterDelivery, ratedSeller, condition, returnable, freeShipping, wishlistCount, categoryProductCount, categoryName, codAvailable, codHref, emiAvailable, emiHref, labels, formatCount, categoryBadgeClassName, }) {
5
5
  const badges = [];
6
6
  if (featured) {
@@ -89,7 +89,7 @@ export function ProductFeatureBadges({ featured, fasterDelivery, ratedSeller, co
89
89
  if (wishlistCount && wishlistCount > 0) {
90
90
  badges.push({
91
91
  key: "wishlist",
92
- icon: _jsx(Span, { size: "xs", children: "\u2665" }),
92
+ icon: _jsx(Icon, { name: "wishlist", size: "xs", filled: true }),
93
93
  label: labels.wishlistCount(wishlistCount),
94
94
  colorClass: "text-pink-700 dark:text-pink-300",
95
95
  bgClass: "bg-pink-50 dark:bg-pink-900/30 border-pink-200 dark:border-pink-800",
@@ -11,7 +11,7 @@ const SECTION_LABEL_CLASS = "text-[var(--appkit-font-size-2xs,11px)] font-semibo
11
11
  const ROW_BASE_CLASS = "flex items-start gap-[var(--appkit-space-2)] rounded-lg border px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-xs)] cursor-pointer transition-colors";
12
12
  const ROW_CHECKED_CLASS = "border-primary bg-primary/5 dark:bg-primary/10";
13
13
  const ROW_DISABLED_CLASS = "border-[var(--appkit-color-border)] opacity-50 cursor-not-allowed";
14
- const ROW_DEFAULT_CLASS = "border-[var(--appkit-color-border)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]";
14
+ const ROW_DEFAULT_CLASS = "border-[var(--appkit-color-border)] hover:bg-surface-hover";
15
15
  const OVER_LIMIT_BANNER_CLASS = "rounded-lg border border-error/20 bg-error-surface px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-xs)] text-error";
16
16
  const EMPTY_STATE_CLASS = "rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-3)] py-[var(--appkit-space-4)] text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] text-left";
17
17
  function applyProductType(list, productType) {
@@ -75,12 +75,21 @@ export function ProductFilters({ table, categoryOptions = [], brandOptions = [],
75
75
  { value: PRODUCT_FIELDS.CONDITION_VALUES.REFURBISHED, label: t("conditionRefurbished") },
76
76
  { value: PRODUCT_FIELDS.CONDITION_VALUES.BROKEN, label: t("conditionBroken") },
77
77
  ];
78
- const defaultStatusOptions = [
79
- { value: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED, label: t("statusPublished") },
80
- { value: PRODUCT_FIELDS.STATUS_VALUES.DRAFT, label: t("statusDraft") },
81
- { value: PRODUCT_FIELDS.STATUS_VALUES.IN_REVIEW, label: t("statusInReview") },
82
- { value: PRODUCT_FIELDS.STATUS_VALUES.ARCHIVED, label: t("statusArchived") },
83
- ];
78
+ const resolvedVariant = variant ?? (showStatus ? "admin" : "public");
79
+ const shouldShowStatus = resolvedVariant !== "public" || showStatus;
80
+ // Built ONLY when the status facet actually renders. This used to be
81
+ // constructed unconditionally at the top of the component, so every public
82
+ // listing that never shows a status facet still called t() four times —
83
+ // five such listings mount ProductFilters, which is how one missing key
84
+ // (`statusInReview`) produced a MISSING_MESSAGE storm on every page load.
85
+ const defaultStatusOptions = shouldShowStatus
86
+ ? [
87
+ { value: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED, label: t("statusPublished") },
88
+ { value: PRODUCT_FIELDS.STATUS_VALUES.DRAFT, label: t("statusDraft") },
89
+ { value: PRODUCT_FIELDS.STATUS_VALUES.IN_REVIEW, label: t("statusInReview") },
90
+ { value: PRODUCT_FIELDS.STATUS_VALUES.ARCHIVED, label: t("statusArchived") },
91
+ ]
92
+ : [];
84
93
  const splitPipe = (key) => table.get(key) ? table.get(key).split("|").filter(Boolean) : [];
85
94
  const selectedCategories = splitPipe(TABLE_KEYS.CATEGORY);
86
95
  const selectedBrands = splitPipe(TABLE_KEYS.BRAND);
@@ -90,7 +99,5 @@ export function ProductFilters({ table, categoryOptions = [], brandOptions = [],
90
99
  const selectedStatuses = splitPipe(TABLE_KEYS.STATUS);
91
100
  const selectedSublistings = splitPipe(TABLE_KEYS.SUBLISTING_CATEGORY);
92
101
  const selectedFeatures = splitPipe(TABLE_KEYS.FEATURES);
93
- const resolvedVariant = variant ?? (showStatus ? "admin" : "public");
94
- const shouldShowStatus = resolvedVariant !== "public" || showStatus;
95
102
  return (_jsxs(Div, { children: [loadCategoryOptions ? (_jsx(AsyncFacetSection, { title: t("category"), loadOptions: loadCategoryOptions, selected: selectedCategories, onChange: (vals) => table.set(TABLE_KEYS.CATEGORY, vals.join("|")), defaultCollapsed: false })) : categoryOptions.length > 0 ? (_jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategories, onChange: (vals) => table.set(TABLE_KEYS.CATEGORY, vals.join("|")), searchable: true, defaultCollapsed: categoryOptions.length > 6 })) : null, _jsx(FilterFacetSection, { title: t("condition"), options: conditionOptions, selected: selectedConditions, onChange: (vals) => table.set(TABLE_KEYS.CONDITION, vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(RangeFilter, { title: t("priceRange"), minValue: table.get(TABLE_KEYS.MIN_PRICE), maxValue: table.get(TABLE_KEYS.MAX_PRICE), onMinChange: (v) => table.set(TABLE_KEYS.MIN_PRICE, v), onMaxChange: (v) => table.set(TABLE_KEYS.MAX_PRICE, v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minPrice"), maxPlaceholder: t("maxPrice"), defaultCollapsed: false }), loadBrandOptions ? (_jsx(AsyncFacetSection, { title: t("brand"), loadOptions: loadBrandOptions, selected: selectedBrands, onChange: (vals) => table.set(TABLE_KEYS.BRAND, vals.join("|")), defaultCollapsed: false })) : brandOptions.length > 0 ? (_jsx(FilterFacetSection, { title: t("brand"), options: brandOptions, selected: selectedBrands, onChange: (vals) => table.set(TABLE_KEYS.BRAND, vals.join("|")), searchable: brandOptions.length > 4, defaultCollapsed: brandOptions.length > 6 })) : null, loadStoreOptions ? (_jsx(AsyncFacetSection, { title: t("store"), loadOptions: loadStoreOptions, selected: selectedSellers, onChange: (vals) => table.set(TABLE_KEYS.STORE_ID, vals.join("|")), defaultCollapsed: false })) : resolvedStoreOptions.length > 0 ? (_jsx(FilterFacetSection, { title: t("store"), options: resolvedStoreOptions, selected: selectedSellers, onChange: (vals) => table.set(TABLE_KEYS.STORE_ID, vals.join("|")), searchable: resolvedStoreOptions.length > 4, defaultCollapsed: resolvedStoreOptions.length > 6 })) : null, showShipping && (_jsx(SwitchFilter, { title: t("shipping"), label: t("freeShippingOnly"), checked: table.get(TABLE_KEYS.FREE_SHIPPING) === "true", onChange: (v) => table.set(TABLE_KEYS.FREE_SHIPPING, v ? "true" : ""), defaultCollapsed: false })), tagOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("tags"), options: tagOptions, selected: selectedTags, onChange: (vals) => table.set(TABLE_KEYS.TAGS, vals.join("|")), searchable: tagOptions.length > 4, defaultCollapsed: tagOptions.length > 6 })), shouldShowStatus && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions ?? defaultStatusOptions, selected: selectedStatuses, onChange: (vals) => table.set(TABLE_KEYS.STATUS, vals.join("|")), searchable: false, defaultCollapsed: false })), sublistingCategoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: "Sublisting Type", options: sublistingCategoryOptions, selected: selectedSublistings, onChange: (vals) => table.set(TABLE_KEYS.SUBLISTING_CATEGORY, vals.join("|")), searchable: sublistingCategoryOptions.length > 4, defaultCollapsed: false })), showBundleFilter && (_jsx(SwitchFilter, { title: "Bundles", label: "Part of a bundle only", checked: table.get(TABLE_KEYS.IS_PART_OF_BUNDLE) === "true", onChange: (v) => table.set(TABLE_KEYS.IS_PART_OF_BUNDLE, v ? "true" : ""), defaultCollapsed: false })), featureOptions.length > 0 && (_jsx(FilterFacetSection, { title: "Features", options: featureOptions, selected: selectedFeatures, onChange: (vals) => table.set(TABLE_KEYS.FEATURES, vals.join("|")), searchable: featureOptions.length > 4, defaultCollapsed: featureOptions.length > 4 }))] }));
96
103
  }
@@ -15,6 +15,7 @@ import { useProductFeatures } from "./ProductFeaturesContext";
15
15
  import { PRODUCT_FEATURE_CARD_MAX_VISIBLE } from "../constants/product-features.constants";
16
16
  import { normalizeListingType } from "../utils/listing-type";
17
17
  import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
18
+ import { WishlistHeartButton } from "../../wishlist/components/WishlistHeartButton";
18
19
  const __P = {
19
20
  p3: "p-[var(--appkit-space-3)]",
20
21
  };
@@ -30,15 +31,11 @@ const CLS_BADGE_LIMITED = "rounded-full bg-info-solid px-[var(--appkit-space-2)]
30
31
  // inverts with the theme. `warning` because success/info/error are each already
31
32
  // taken by a sibling pill in this same stack.
32
33
  const CLS_BADGE_OFFERABLE = "rounded-full bg-warning-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-warning-on-solid shadow-sm";
33
- const CLS_HEART_ACTIVE = "bg-[var(--appkit-color-surface-elevated)]/90 text-error hover:bg-[var(--appkit-color-surface-elevated)]";
34
- const CLS_HEART_IDLE = "bg-[var(--appkit-color-surface-elevated)]/90 text-[var(--appkit-color-text-muted)] hover:text-error hover:bg-[var(--appkit-color-surface-elevated)]";
35
34
  const CLS_STAR = "text-[11px] text-warning";
36
35
  const CLS_DISCOUNT_TEXT = "mt-1 text-[11px] font-semibold text-error";
37
36
  const CLS_DISCOUNT_TEXT_BARE = "text-[10px] text-error";
38
37
  const CLS_STAR_BARE = "text-warning";
39
38
  const CLS_BID_TEXT = "text-[11px] text-error";
40
- const CLS_HEART_ROSE_ACTIVE = "text-error";
41
- const CLS_HEART_ROSE_IDLE = "text-zinc-300 dark:text-[var(--appkit-color-text-muted)] hover:text-error";
42
39
  export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlisted, onAddToCart, onBuyNow, className = "", selectionMode = false, isSelected = false, onSelect, }) {
43
40
  const discount = product.originalPrice && product.originalPrice > product.price
44
41
  ? Math.round(((product.originalPrice - product.price) / product.originalPrice) *
@@ -55,9 +52,9 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
55
52
  ? (e) => { e.preventDefault(); e.stopPropagation(); onSelect?.(product.id); }
56
53
  : onClick && !href ? () => onClick(product) : undefined;
57
54
  const cardBody = (_jsxs(Div, { role: selectionMode ? "checkbox" : onClick ? "button" : undefined, "aria-checked": selectionMode ? isSelected : undefined, tabIndex: selectionMode || (onClick && !href) ? 0 : undefined, onKeyDown: selectionMode
58
- ? (e) => (e.key === "Enter" || e.key === " ") && onSelect?.(product.id)
55
+ ? (e) => (e.key === "Enter" || e.key === "") && onSelect?.(product.id)
59
56
  : onClick && !href
60
- ? (e) => (e.key === "Enter" || e.key === " ") && onClick(product)
57
+ ? (e) => (e.key === "Enter" || e.key === "") && onClick(product)
61
58
  : undefined, onClick: handleCardClick, ...(!selectionMode ? longPress : {}), className: [
62
59
  "group relative flex h-full flex-col overflow-hidden",
63
60
  "rounded-2xl border bg-[var(--appkit-color-surface)]",
@@ -73,17 +70,11 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
73
70
  .filter(Boolean)
74
71
  .join(" "), children: [_jsxs(Div, { className: `relative ${__O.hidden} aspect-square`, surface: "subtle", children: [product.mainImage ? (_jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", className: "transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { surface: "muted", className: "h-full w-full", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-30", size: "4xl", children: "\uD83D\uDECD\uFE0F" }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(product.id); }, label: isSelected ? "Deselect" : "Select", position: "top-2 left-2", className: selectionMode || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" })), _jsx(Stack, { className: `absolute top-2 ${onSelect ? "left-10" : "left-2"}`, gap: "xs", children: !selectionMode && !isSelected && (_jsxs(_Fragment, { children: [discount && (_jsxs(Span, { weight: "bold", className: CLS_BADGE_NEW, children: ["-", discount, "%"] })), typeBadge && (_jsx(Span, { weight: "bold", className: `text-[10px] ${typeBadge.cls}`, rounded: "full", padding: "pill-xs", shadow: "sm", children: typeBadge.label })), product.partOfBundleIds && product.partOfBundleIds.length > 0 && (_jsx(Span, { className: CLS_BADGE_SALE, title: product.partOfBundleTitles && product.partOfBundleTitles.length > 0
75
72
  ? `In bundle: ${product.partOfBundleTitles[0]}`
76
- : "In a bundle", children: "Bundled" })), product.groupId && (_jsx(Span, { className: CLS_BADGE_TRENDING, title: product.groupTitle ? `Part of set: ${product.groupTitle}` : "Part of a set", children: product.isGroupParent ? "Set Parent" : "In Set" })), product.allowOffers && (_jsx(Span, { className: CLS_BADGE_OFFERABLE, title: "The seller accepts offers on this listing", children: "Taking Offers" })), product.sublistingCategoryId && (_jsx(Span, { className: CLS_BADGE_LIMITED, title: "Has variants or sub-listings", children: "Has Variants" }))] })) }), _jsx(BaseListingCard.IconOverlay, { groupIcon: product.groupIcon, sublistingIcon: product.sublistingIcon, position: "bottom-2 right-2" }), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
73
+ : "In a bundle", children: "Bundled" })), product.groupId && (_jsx(Span, { className: CLS_BADGE_TRENDING, title: product.groupTitle ? `Part of set: ${product.groupTitle}` : "Part of a set", children: product.isGroupParent ? "Set Parent" : "In Set" })), product.allowOffers && (_jsx(Span, { className: CLS_BADGE_OFFERABLE, title: "The seller accepts offers on this listing", children: "Taking Offers" })), product.sublistingCategoryId && (_jsx(Span, { className: CLS_BADGE_LIMITED, title: "Has variants or sub-listings", children: "Has Variants" }))] })) }), _jsx(BaseListingCard.IconOverlay, { groupIcon: product.groupIcon, sublistingIcon: product.sublistingIcon, position: "bottom-2 right-2" }), onAddToWishlist && (_jsx(WishlistHeartButton, { size: "card", variant: "overlay", inWishlist: !!isWishlisted, addLabel: "Add to wishlist", removeLabel: "Remove from wishlist", className: "absolute right-2 top-2", onToggle: (e) => {
77
74
  e.stopPropagation();
78
75
  e.preventDefault();
79
76
  onAddToWishlist(product.id);
80
- }, "aria-label": isWishlisted ? "Remove from wishlist" : "Add to wishlist", className: [
81
- "absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-full shadow-sm",
82
- "transition-all duration-150",
83
- isWishlisted
84
- ? CLS_HEART_ACTIVE
85
- : CLS_HEART_IDLE,
86
- ].join(" "), children: _jsx("svg", { className: "h-4 w-4", fill: isWishlisted ? "currentColor" : "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" }) }) }))] }), _jsxs(Stack, { className: `flex-1 ${__P.p3} pt-[0.625rem]`, children: [_jsx(Text, { className: `leading-snug`, truncate: 2, size: "sm", weight: "semibold", color: "primary", children: product.title }), (() => {
77
+ } }))] }), _jsxs(Stack, { className: `flex-1 ${__P.p3} pt-[0.625rem]`, children: [_jsx(Text, { className: `leading-snug`, truncate: 2, size: "sm", weight: "semibold", color: "primary", children: product.title }), (() => {
87
78
  const categories = Array.isArray(product.categoryNames)
88
79
  ? product.categoryNames.filter(Boolean)
89
80
  : product.categoryName
@@ -142,7 +133,7 @@ function ProductListRow({ product, onClick, onAddToWishlist, isWishlisted, }) {
142
133
  100)
143
134
  : null;
144
135
  return (_jsxs(Div, { role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
145
- ? (e) => (e.key === "Enter" || e.key === " ") && onClick(product)
136
+ ? (e) => (e.key === "Enter" || e.key === "") && onClick(product)
146
137
  : undefined, onClick: onClick ? () => onClick(product) : undefined, className: [
147
138
  "flex items-center gap-[var(--appkit-space-3)] p-[var(--appkit-space-2)] sm:p-[var(--appkit-space-3)]",
148
139
  "hover:bg-[var(--appkit-color-surface)]/60 transition-colors",
@@ -160,10 +151,10 @@ function ProductListRow({ product, onClick, onAddToWishlist, isWishlisted, }) {
160
151
  return (_jsx(Span, { weight: "medium", className: `text-[11px] ${low
161
152
  ? "text-warning"
162
153
  : "text-[var(--appkit-color-text-faint)]"}`, children: low ? `Only ${stock} left` : `${stock} in stock` }));
163
- })()] })] }), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
154
+ })()] })] }), onAddToWishlist && (_jsx(WishlistHeartButton, { size: "inline", inWishlist: !!isWishlisted, addLabel: "Add to wishlist", removeLabel: "Remove from wishlist", onToggle: (e) => {
164
155
  e.stopPropagation();
165
156
  onAddToWishlist(product.id);
166
- }, "aria-label": isWishlisted ? "Remove from wishlist" : "Add to wishlist", className: `flex-shrink-0 h-8 w-8 flex items-center justify-center rounded-full text-[length:var(--appkit-text-base)] leading-none ${isWishlisted ? CLS_HEART_ROSE_ACTIVE : CLS_HEART_ROSE_IDLE}`, children: isWishlisted ? "♥" : "♡" }))] }));
157
+ } }))] }));
167
158
  }
168
159
  function safeHref(href) {
169
160
  if (!href)
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Button, Row } from "@mohasinac/appkit/ui";
3
+ import { Tabs, TabsList, TabsTrigger } from "@mohasinac/appkit/ui";
4
4
  import React from "react";
5
5
  import { Div } from "../../../ui/components/Div";
6
6
  const DEFAULT_TABS = [
@@ -19,7 +19,10 @@ export function ProductTabs({ renderDescription, renderSpecs, renderReviews, ext
19
19
  ...extraTabs,
20
20
  ];
21
21
  const tabs = allTabs.length > 0 ? allTabs : DEFAULT_TABS;
22
- return (_jsxs(Div, { className: className, children: [renderTabBar ? (renderTabBar(activeTab, setActiveTab, tabs)) : (_jsx(Row, { gap: "md", className: "border-b mb-4", children: tabs.map((t) => (_jsx(Button, { variant: "ghost", type: "button", onClick: () => setActiveTab(t.value), paddingX: "none", paddingY: "xs", textSize: "sm", weight: "medium", rounded: "none", className: `border-b-2 transition-colors ${activeTab === t.value
23
- ? "border-current text-current"
24
- : "border-transparent text-[var(--appkit-color-text-muted)]"}`, children: t.label }, t.value))) })), activeTab === "description" && renderDescription?.(), activeTab === "specs" && renderSpecs?.(), activeTab === "reviews" && renderReviews?.(), renderExtraTab?.(activeTab)] }));
22
+ return (_jsxs(Div, { className: className, children: [renderTabBar ? (renderTabBar(activeTab, setActiveTab, tabs)) : (
23
+ // Was a hand-rolled <Row> of ghost <Button>s whose ACTIVE state was
24
+ // `border-current text-current` — the inherited colour, i.e. visually
25
+ // identical to inactive. It also had no role="tablist"/role="tab"/
26
+ // aria-selected at all; routing through Tabs supplies all three.
27
+ _jsx(Tabs, { value: activeTab, onChange: setActiveTab, className: "mb-4", children: _jsx(TabsList, { children: tabs.map((t) => (_jsx(TabsTrigger, { value: t.value, children: t.label }, t.value))) }) })), activeTab === "description" && renderDescription?.(), activeTab === "specs" && renderSpecs?.(), activeTab === "reviews" && renderReviews?.(), renderExtraTab?.(activeTab)] }));
25
28
  }
@@ -1,11 +1,8 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
- import { Button } from "../../../ui/components/Button";
5
4
  import { Div } from "../../../ui/components/Div";
6
- const __O = {
7
- xAuto: "overflow-x-auto",
8
- };
5
+ import { Tabs, TabsList, TabsTrigger } from "../../../ui/components/Tabs";
9
6
  const STATIC_TABS = [
10
7
  { id: "description", label: "Description" },
11
8
  { id: "specs", label: "Specifications" },
@@ -40,7 +37,5 @@ export function ProductTabsShell({ descriptionContent, specsContent, gradingCont
40
37
  if (allTabs.length === 0)
41
38
  return null;
42
39
  const activeContent = allTabs.find((t) => t.id === activeId)?.content;
43
- return (_jsxs(Div, { className: `mt-8 ${className}`, children: [_jsx(Div, { layout: "flex", gap: "1", border: "default", className: `mb-6 ${__O.xAuto} border-b pb-px`, children: allTabs.map((t) => (_jsx(Button, { variant: "ghost", type: "button", onClick: () => setActiveId(t.id), paddingX: "md", paddingY: "none", textSize: "sm", weight: "medium", rounded: "none", className: `flex-shrink-0 -mb-px pb-[var(--appkit-space-3)] border-b-2 transition-colors ${activeId === t.id
44
- ? "border-[var(--appkit-color-primary)] text-[var(--appkit-color-primary)]"
45
- : "border-transparent text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text-muted)]"}`, children: t.label }, t.id))) }), _jsx("div", { children: activeContent })] }));
40
+ return (_jsxs(Div, { className: `mt-8 ${className}`, children: [_jsx(Tabs, { value: activeId, onChange: setActiveId, className: "mb-6", children: _jsx(TabsList, { children: allTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, children: t.label }, t.id))) }) }), _jsx("div", { children: activeContent })] }));
46
41
  }