@mohasinac/appkit 4.3.2 → 4.4.1

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 (183) hide show
  1. package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +1 -1
  2. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +1 -1
  3. package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +1 -1
  4. package/dist/_internal/client/features/maintenance/views/ServerErrorDetailView.js +1 -1
  5. package/dist/_internal/client/features/maintenance/views/ServerErrorsListView.js +1 -1
  6. package/dist/_internal/server/jobs/core/assignSpinPrize.d.ts +14 -3
  7. package/dist/_internal/server/jobs/core/assignSpinPrize.js +95 -56
  8. package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +8 -2
  9. package/dist/_internal/shared/actions/action-registry.js +15 -0
  10. package/dist/_internal/shared/features/brands/schema.d.ts +4 -4
  11. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +4 -4
  12. package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
  13. package/dist/_internal/shared/features/reviews/schema.d.ts +2 -2
  14. package/dist/client.d.ts +36 -0
  15. package/dist/client.js +30 -0
  16. package/dist/constants/api-endpoints.d.ts +6 -0
  17. package/dist/constants/api-endpoints.js +2 -0
  18. package/dist/constants/field-names.d.ts +7 -0
  19. package/dist/constants/field-names.js +8 -0
  20. package/dist/features/about/components/DeveloperView.d.ts +16 -0
  21. package/dist/features/about/components/DeveloperView.js +29 -0
  22. package/dist/features/about/index.d.ts +2 -0
  23. package/dist/features/about/index.js +1 -0
  24. package/dist/features/account/schemas/index.d.ts +16 -16
  25. package/dist/features/addresses/schemas/index.d.ts +8 -8
  26. package/dist/features/admin/components/AdminAddressesView.js +1 -1
  27. package/dist/features/admin/components/AdminAllEventEntriesView.js +2 -2
  28. package/dist/features/admin/components/AdminArtView.js +1 -1
  29. package/dist/features/admin/components/AdminBidsView.js +2 -2
  30. package/dist/features/admin/components/AdminBlogView.js +2 -2
  31. package/dist/features/admin/components/AdminBrandsView.js +2 -2
  32. package/dist/features/admin/components/AdminBundlesView.js +2 -2
  33. package/dist/features/admin/components/AdminCartsView.js +2 -2
  34. package/dist/features/admin/components/AdminCategoriesView.js +2 -2
  35. package/dist/features/admin/components/AdminClassifiedView.js +1 -1
  36. package/dist/features/admin/components/AdminContactView.js +2 -2
  37. package/dist/features/admin/components/AdminCouponsView.js +2 -2
  38. package/dist/features/admin/components/AdminDigitalCodesView.js +1 -1
  39. package/dist/features/admin/components/AdminFaqsView.js +2 -2
  40. package/dist/features/admin/components/AdminFeaturesView.js +1 -1
  41. package/dist/features/admin/components/AdminGroupedListingsView.js +1 -1
  42. package/dist/features/admin/components/AdminHistoryView.js +1 -1
  43. package/dist/features/admin/components/AdminLiveView.js +1 -1
  44. package/dist/features/admin/components/AdminNewsletterView.js +2 -2
  45. package/dist/features/admin/components/AdminNotificationsView.js +2 -2
  46. package/dist/features/admin/components/AdminOrdersView.js +2 -2
  47. package/dist/features/admin/components/AdminPaymentMethodsView.js +1 -1
  48. package/dist/features/admin/components/AdminPayoutsView.js +2 -2
  49. package/dist/features/admin/components/AdminPrizeDrawsView.js +2 -2
  50. package/dist/features/admin/components/AdminProductsView.js +2 -2
  51. package/dist/features/admin/components/AdminReturnRequestsView.js +1 -1
  52. package/dist/features/admin/components/AdminReviewsView.js +2 -2
  53. package/dist/features/admin/components/AdminScammersView.js +2 -2
  54. package/dist/features/admin/components/AdminSessionsView.js +2 -2
  55. package/dist/features/admin/components/AdminStickersView.js +1 -1
  56. package/dist/features/admin/components/AdminStoreAddressesView.js +1 -1
  57. package/dist/features/admin/components/AdminStoresView.js +2 -2
  58. package/dist/features/admin/components/AdminSublistingCategoriesView.js +1 -1
  59. package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
  60. package/dist/features/admin/components/AdminTeamView.js +2 -2
  61. package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
  62. package/dist/features/admin/components/AdminUsersView.js +2 -2
  63. package/dist/features/admin/components/AdminWishlistsView.js +1 -1
  64. package/dist/features/admin/hooks/useAdminListing.d.ts +1 -1
  65. package/dist/features/auth/hooks/useAuth.d.ts +1 -1
  66. package/dist/features/blog/actions/blog-actions.d.ts +14 -14
  67. package/dist/features/blog/schemas/index.d.ts +14 -14
  68. package/dist/features/cart/hooks/useAddToCart.d.ts +6 -2
  69. package/dist/features/cart/hooks/useAddToCart.js +20 -3
  70. package/dist/features/cart/schemas/index.d.ts +3 -3
  71. package/dist/features/cart/utils/pending-ops.d.ts +20 -0
  72. package/dist/features/cart/utils/pending-ops.js +22 -0
  73. package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
  74. package/dist/features/categories/components/CategoryBundlesListing.js +1 -1
  75. package/dist/features/categories/components/CategoryFilters.js +1 -1
  76. package/dist/features/categories/components/CategoryTree.js +1 -1
  77. package/dist/features/categories/schemas/index.d.ts +16 -16
  78. package/dist/features/events/actions/event-actions.d.ts +21 -1
  79. package/dist/features/events/actions/event-actions.js +85 -3
  80. package/dist/features/events/components/AdminEventEditorView.js +11 -2
  81. package/dist/features/events/components/AdminEventEntriesView.js +37 -4
  82. package/dist/features/events/components/AdminEventsView.js +2 -2
  83. package/dist/features/events/components/EventLeaderboard.d.ts +12 -2
  84. package/dist/features/events/components/EventLeaderboard.js +4 -1
  85. package/dist/features/events/components/EventRafflesSection.js +2 -2
  86. package/dist/features/events/repository/event-entry.repository.d.ts +39 -0
  87. package/dist/features/events/repository/event-entry.repository.js +153 -0
  88. package/dist/features/events/schemas/firestore.d.ts +28 -1
  89. package/dist/features/events/schemas/firestore.js +9 -0
  90. package/dist/features/events/schemas/index.d.ts +13 -13
  91. package/dist/features/events/types/index.d.ts +26 -0
  92. package/dist/features/faq/actions/faq-actions.d.ts +8 -8
  93. package/dist/features/faq/schemas/index.d.ts +22 -22
  94. package/dist/features/grouped/schemas/index.d.ts +2 -2
  95. package/dist/features/homepage/components/AnnouncementBar.d.ts +9 -1
  96. package/dist/features/homepage/components/AnnouncementBar.js +6 -3
  97. package/dist/features/homepage/components/MarketplaceHomepageView.js +2 -2
  98. package/dist/features/homepage/components/SectionCarousel.js +5 -1
  99. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  100. package/dist/features/homepage/schemas/index.d.ts +2 -2
  101. package/dist/features/layout/BottomNavbar.js +8 -6
  102. package/dist/features/lottery/schemas/zod.d.ts +10 -10
  103. package/dist/features/media/MediaVideo.js +11 -3
  104. package/dist/features/orders/schemas/index.d.ts +52 -52
  105. package/dist/features/products/components/CompareOverlay.d.ts +9 -0
  106. package/dist/features/products/components/CompareOverlay.js +28 -3
  107. package/dist/features/products/components/InteractiveProductCard.js +9 -3
  108. package/dist/features/products/components/MakeOfferForm.js +1 -1
  109. package/dist/features/products/components/PlaceBidForm.js +1 -1
  110. package/dist/features/products/components/PrizeDrawCollage.js +1 -1
  111. package/dist/features/products/components/PrizeDrawsSection.js +2 -2
  112. package/dist/features/products/components/ProductDetailActions.js +3 -1
  113. package/dist/features/products/components/ProductGrid.js +1 -1
  114. package/dist/features/products/components/ProductInfo.js +1 -1
  115. package/dist/features/products/components/ProductsIndexListing.js +12 -2
  116. package/dist/features/products/hooks/useRelatedProducts.js +1 -1
  117. package/dist/features/products/schemas/index.d.ts +10 -10
  118. package/dist/features/promotions/schemas/index.d.ts +8 -8
  119. package/dist/features/reviews/schemas/index.d.ts +12 -12
  120. package/dist/features/scams/schemas/index.d.ts +6 -6
  121. package/dist/features/seller/components/CouponInlineSelect.js +1 -1
  122. package/dist/features/seller/components/SellerArtView.js +2 -2
  123. package/dist/features/seller/components/SellerAuctionsView.js +2 -2
  124. package/dist/features/seller/components/SellerBidsView.js +2 -2
  125. package/dist/features/seller/components/SellerBundlesView.js +1 -1
  126. package/dist/features/seller/components/SellerClassifiedView.js +2 -2
  127. package/dist/features/seller/components/SellerCouponsView.js +2 -2
  128. package/dist/features/seller/components/SellerDigitalCodesView.js +2 -2
  129. package/dist/features/seller/components/SellerGroupedListingsView.js +1 -1
  130. package/dist/features/seller/components/SellerLiveView.js +2 -2
  131. package/dist/features/seller/components/SellerOffersView.js +2 -2
  132. package/dist/features/seller/components/SellerOrdersView.js +2 -2
  133. package/dist/features/seller/components/SellerPayoutMethodsView.js +2 -2
  134. package/dist/features/seller/components/SellerPayoutsView.js +2 -2
  135. package/dist/features/seller/components/SellerPreOrdersView.js +2 -2
  136. package/dist/features/seller/components/SellerPrizeDrawsView.js +2 -2
  137. package/dist/features/seller/components/SellerProductsView.js +2 -2
  138. package/dist/features/seller/components/SellerShippingConfigsView.js +2 -2
  139. package/dist/features/seller/components/SellerStickersView.js +2 -2
  140. package/dist/features/seller/components/SellerStoreCategoriesView.js +1 -1
  141. package/dist/features/seller/components/SublistingInlineSelect.js +1 -1
  142. package/dist/features/seller/hooks/useSellerListingData.d.ts +1 -1
  143. package/dist/features/seller/schemas/index.d.ts +16 -16
  144. package/dist/features/server-errors/schemas/index.d.ts +4 -4
  145. package/dist/features/shell/StepForm.d.ts +1 -1
  146. package/dist/features/shipments/components/AdminShipmentsView.js +2 -2
  147. package/dist/features/stores/components/StoreProductsView.js +1 -1
  148. package/dist/features/stores/components/StoresIndexListing.js +1 -1
  149. package/dist/features/stores/schemas/index.d.ts +32 -32
  150. package/dist/features/support/schemas/index.d.ts +2 -2
  151. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +68 -1
  152. package/dist/features/whatsapp-bot/helpers/payment-proof-message.d.ts +14 -0
  153. package/dist/features/whatsapp-bot/helpers/payment-proof-message.js +17 -0
  154. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +2 -8
  155. package/dist/features/whatsapp-bot/helpers/whatsapp.js +4 -11
  156. package/dist/features/wishlist/repository/user-wishlist.repository.d.ts +11 -0
  157. package/dist/features/wishlist/repository/user-wishlist.repository.js +33 -0
  158. package/dist/index.d.ts +7 -12
  159. package/dist/index.js +16 -28
  160. package/dist/next/components/ErrorView.js +1 -1
  161. package/dist/next/components/NotFoundView.js +1 -1
  162. package/dist/next/components/UnauthorizedView.js +1 -1
  163. package/dist/next/routing/route-map.d.ts +4 -0
  164. package/dist/next/routing/route-map.js +2 -0
  165. package/dist/react/contexts/SessionContext.d.ts +1 -1
  166. package/dist/react/hooks/use-action-dispatch.d.ts +1 -1
  167. package/dist/react/hooks/useFormStatePreservation.d.ts +1 -1
  168. package/dist/schemas/registry.d.ts +238 -238
  169. package/dist/schemas/webhooks/razorpay.d.ts +100 -100
  170. package/dist/security/index.d.ts +1 -1
  171. package/dist/security/index.js +1 -1
  172. package/dist/security/rate-limit.d.ts +10 -0
  173. package/dist/security/rate-limit.js +13 -1
  174. package/dist/seed/events-seed-data.js +52 -1
  175. package/dist/server.d.ts +6 -0
  176. package/dist/server.js +8 -0
  177. package/dist/styles.css +25 -1
  178. package/dist/tailwind-utilities.css +1 -1
  179. package/dist/ui/components/HorizontalScroller.d.ts +7 -1
  180. package/dist/ui/components/HorizontalScroller.js +2 -2
  181. package/dist/ui/components/HorizontalScroller.style.css +24 -0
  182. package/dist/validation/schemas.d.ts +12 -12
  183. package/package.json +1 -1
@@ -5,6 +5,7 @@ import { AvatarDisplay, Li, Span, TextLink } from "../../ui";
5
5
  import { BottomNavLayout } from "./BottomNavLayout";
6
6
  import { NavItem } from "./NavItem";
7
7
  import { useWishlistCount } from "../wishlist/hooks/useWishlistCount";
8
+ import { useCartCount } from "../cart/hooks/useCartCount";
8
9
  /**
9
10
  * BottomNavbar � mobile bottom navigation bar (5 slots: home, shop, search, cart, profile).
10
11
  *
@@ -13,6 +14,7 @@ import { useWishlistCount } from "../wishlist/hooks/useWishlistCount";
13
14
  export function BottomNavbar({ user, userId, homeHref, shopHref, cartHref, wishlistHref, profileHref, loginHref, onSearchToggle, navItems, onMoreToggle, activeClassName = "text-primary-600 dark:text-primary-400", inactiveClassName = "text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)]", iconClassName, labelClassName = "text-[11px] leading-tight font-medium", getRoleBadgeClass, }) {
14
15
  const pathname = usePathname();
15
16
  const wishlistCount = useWishlistCount(userId);
17
+ const cartCount = useCartCount(!!userId);
16
18
  const labels = {
17
19
  mobileNav: "Mobile navigation",
18
20
  home: "Home",
@@ -26,12 +28,12 @@ export function BottomNavbar({ user, userId, homeHref, shopHref, cartHref, wishl
26
28
  const slotClassName = "relative flex h-full w-full flex-col items-center justify-center gap-[var(--appkit-space-1)] text-center transition-colors duration-200";
27
29
  const defaultIcon = (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "w-5 h-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }));
28
30
  if (navItems && navItems.length > 0) {
29
- // When a wishlist href is available, trade one nav-item slot for a
30
- // dedicated wishlist slot so mobile has the same 1-click wishlist
31
- // access desktop's TitleBar already has (previously: More sidebar
32
- // Shopping group, 2 taps).
33
- const visibleItems = navItems.slice(0, wishlistHref ? 3 : 4);
34
- return (_jsxs(BottomNavLayout, { ariaLabel: labels.mobileNav, children: [visibleItems.map((item) => (_jsx(Li, { className: "flex-1", children: _jsx(NavItem, { href: item.href, label: item.label, icon: item.icon ?? defaultIcon, isActive: pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href + "/")), variant: "vertical", activeClassName: activeClassName, inactiveClassName: inactiveClassName, iconClassName: iconClassName, labelClassName: labelClassName }) }, item.key))), wishlistHref && (_jsx(Li, { className: "flex-1", children: _jsxs(TextLink, { href: wishlistHref, variant: "none", className: `${slotClassName} ${pathname === wishlistHref ? activeClassName : inactiveClassName}`, "aria-label": wishlistCount > 0 ? `${labels.wishlist}, ${wishlistCount} items` : labels.wishlist, children: [_jsxs(Span, { className: "relative", children: [_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4.318 6.318a4.5 4.5 0 0 0 0 6.364L12 20.364l7.682-7.682a4.5 4.5 0 0 0-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 0 0-6.364 0z" }) }), wishlistCount > 0 && (_jsx(Span, { size: "xs", weight: "semibold", className: "absolute -right-2 -top-2 flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--appkit-color-error)] px-[var(--appkit-space-1)] leading-none text-[var(--appkit-color-text-on-primary)]", children: wishlistCount > 9 ? "9+" : wishlistCount }))] }), _jsx(Span, { className: labelClassName, children: labels.wishlist })] }) })), _jsx(Li, { className: "flex-1", children: _jsxs("button", { type: "button", onClick: onMoreToggle, className: `${slotClassName} ${inactiveClassName}`, "aria-label": labels.more, children: [_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "w-5 h-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }), _jsx(Span, { className: labelClassName, children: labels.more })] }) })] }));
31
+ // Trade one nav-item slot for a dedicated Cart slot so mobile has
32
+ // 1-click cart access with a live item-count badge (previously this
33
+ // slot was Wishlist Wishlist remains reachable from the header/app
34
+ // bar, it's just no longer duplicated in the bottom tab bar).
35
+ const visibleItems = navItems.slice(0, cartHref ? 3 : 4);
36
+ return (_jsxs(BottomNavLayout, { ariaLabel: labels.mobileNav, children: [visibleItems.map((item) => (_jsx(Li, { className: "flex-1", children: _jsx(NavItem, { href: item.href, label: item.label, icon: item.icon ?? defaultIcon, isActive: pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href + "/")), variant: "vertical", activeClassName: activeClassName, inactiveClassName: inactiveClassName, iconClassName: iconClassName, labelClassName: labelClassName }) }, item.key))), cartHref && (_jsx(Li, { className: "flex-1", children: _jsxs(TextLink, { href: cartHref, variant: "none", className: `${slotClassName} ${pathname === cartHref ? activeClassName : inactiveClassName}`, "aria-label": cartCount > 0 ? `${labels.cart}, ${cartCount} items` : labels.cart, children: [_jsxs(Span, { className: "relative", children: [_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" }) }), cartCount > 0 && (_jsx(Span, { size: "xs", weight: "semibold", className: "absolute -right-2 -top-2 flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--appkit-color-error)] px-[var(--appkit-space-1)] leading-none text-[var(--appkit-color-text-on-primary)]", children: cartCount > 9 ? "9+" : cartCount }))] }), _jsx(Span, { className: labelClassName, children: labels.cart })] }) })), _jsx(Li, { className: "flex-1", children: _jsxs("button", { type: "button", onClick: onMoreToggle, className: `${slotClassName} ${inactiveClassName}`, "aria-label": labels.more, children: [_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "w-5 h-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }), _jsx(Span, { className: labelClassName, children: labels.more })] }) })] }));
35
37
  }
36
38
  return (_jsxs(BottomNavLayout, { ariaLabel: labels.mobileNav, children: [_jsx(Li, { className: "flex-1 w-1/5", children: _jsx(NavItem, { href: homeHref, label: labels.home, icon: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "w-5 h-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" }) }), isActive: pathname === homeHref, variant: "vertical", activeClassName: activeClassName, inactiveClassName: inactiveClassName, iconClassName: iconClassName, labelClassName: labelClassName }) }), _jsx(Li, { className: "flex-1", children: _jsx(NavItem, { href: shopHref, label: labels.products, icon: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "w-5 h-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" }) }), isActive: pathname === shopHref, variant: "vertical", activeClassName: activeClassName, inactiveClassName: inactiveClassName, iconClassName: iconClassName, labelClassName: labelClassName }) }), _jsx(Li, { className: "flex-1", children: _jsxs("button", { type: "button", onClick: onSearchToggle, className: `${slotClassName} ${inactiveClassName}`, "aria-label": labels.search, children: [_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }), _jsx(Span, { className: labelClassName, children: labels.search })] }) }), _jsx(Li, { className: "flex-1", children: _jsxs(TextLink, { href: cartHref, variant: "none", className: `${slotClassName} ${pathname === cartHref ? activeClassName : inactiveClassName}`, "aria-label": labels.cart, children: [_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" }) }), _jsx(Span, { className: labelClassName, children: labels.cart })] }) }), wishlistHref && (_jsx(Li, { className: "flex-1", children: _jsxs(TextLink, { href: wishlistHref, variant: "none", className: `${slotClassName} ${pathname === wishlistHref ? activeClassName : inactiveClassName}`, "aria-label": wishlistCount > 0 ? `${labels.wishlist}, ${wishlistCount} items` : labels.wishlist, children: [_jsxs(Span, { className: "relative", children: [_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4.318 6.318a4.5 4.5 0 0 0 0 6.364L12 20.364l7.682-7.682a4.5 4.5 0 0 0-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 0 0-6.364 0z" }) }), wishlistCount > 0 && (_jsx(Span, { size: "xs", weight: "semibold", className: "absolute -right-2 -top-2 flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--appkit-color-error)] px-[var(--appkit-space-1)] leading-none text-[var(--appkit-color-text-on-primary)]", children: wishlistCount > 9 ? "9+" : wishlistCount }))] }), _jsx(Span, { className: labelClassName, children: labels.wishlist })] }) })), _jsx(Li, { className: "flex-1", children: user ? (_jsxs(TextLink, { href: profileHref, variant: "none", className: `${slotClassName} ${pathname === profileHref ? activeClassName : inactiveClassName}`, "aria-label": labels.profile, children: [_jsx(AvatarDisplay, { cropData: user.avatarMetadata ||
37
39
  (user.photoURL ? { url: user.photoURL, position: { x: 50, y: 50 }, zoom: 1 } : null), size: "sm", alt: user.displayName || "User", displayName: user.displayName, email: user.email }), user.role && (_jsx(Span, { className: `text-[7px] uppercase leading-none ${getRoleBadgeClass ? getRoleBadgeClass(user.role) : "text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)]"}`, weight: "semibold", children: user.role }))] })) : (_jsx(NavItem, { href: loginHref, label: labels.profile, icon: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" }) }), isActive: pathname === loginHref, variant: "vertical", activeClassName: activeClassName, inactiveClassName: inactiveClassName, iconClassName: iconClassName, labelClassName: labelClassName })) })] }));
@@ -42,12 +42,12 @@ export declare const reopenLotterySlotSchema: z.ZodObject<{
42
42
  adminUserId: z.ZodString;
43
43
  }, "strip", z.ZodTypeAny, {
44
44
  slotNumber: number;
45
- sourceType: "product" | "event";
45
+ sourceType: "event" | "product";
46
46
  sourceId: string;
47
47
  adminUserId: string;
48
48
  }, {
49
49
  slotNumber: number;
50
- sourceType: "product" | "event";
50
+ sourceType: "event" | "product";
51
51
  sourceId: string;
52
52
  adminUserId: string;
53
53
  }>;
@@ -69,45 +69,45 @@ export declare const submitLotteryPullSchema: z.ZodEffects<z.ZodObject<{
69
69
  transactionId: string;
70
70
  userId: string;
71
71
  userPhone: string;
72
- sourceType: "product" | "event";
72
+ sourceType: "event" | "product";
73
73
  paymentTime: string;
74
74
  purchasedItemNumber: number;
75
75
  productId?: string | undefined;
76
76
  userEmail?: string | undefined;
77
- userDisplayName?: string | undefined;
78
77
  eventId?: string | undefined;
78
+ userDisplayName?: string | undefined;
79
79
  }, {
80
80
  transactionId: string;
81
81
  userId: string;
82
82
  userPhone: string;
83
- sourceType: "product" | "event";
83
+ sourceType: "event" | "product";
84
84
  paymentTime: string;
85
85
  purchasedItemNumber: number;
86
86
  productId?: string | undefined;
87
87
  userEmail?: string | undefined;
88
- userDisplayName?: string | undefined;
89
88
  eventId?: string | undefined;
89
+ userDisplayName?: string | undefined;
90
90
  }>, {
91
91
  transactionId: string;
92
92
  userId: string;
93
93
  userPhone: string;
94
- sourceType: "product" | "event";
94
+ sourceType: "event" | "product";
95
95
  paymentTime: string;
96
96
  purchasedItemNumber: number;
97
97
  productId?: string | undefined;
98
98
  userEmail?: string | undefined;
99
- userDisplayName?: string | undefined;
100
99
  eventId?: string | undefined;
100
+ userDisplayName?: string | undefined;
101
101
  }, {
102
102
  transactionId: string;
103
103
  userId: string;
104
104
  userPhone: string;
105
- sourceType: "product" | "event";
105
+ sourceType: "event" | "product";
106
106
  paymentTime: string;
107
107
  purchasedItemNumber: number;
108
108
  productId?: string | undefined;
109
109
  userEmail?: string | undefined;
110
- userDisplayName?: string | undefined;
111
110
  eventId?: string | undefined;
111
+ userDisplayName?: string | undefined;
112
112
  }>;
113
113
  export type SubmitLotteryPullInput = z.infer<typeof submitLotteryPullSchema>;
@@ -91,15 +91,23 @@ function resolveWatermarkPlacementStyle(config) {
91
91
  * play/pause toggling.
92
92
  */
93
93
  function MediaVideoWatermarkLayer({ config }) {
94
- const widthPct = Math.max(0, Math.min(100, config.size ?? 30));
95
- const opacity = Math.max(0, Math.min(100, config.opacity ?? 20)) / 100;
94
+ // Fallbacks match the server-side Sharp pipeline's DEFAULT_SIZE/DEFAULT_OPACITY
95
+ // (resolve-effective-watermark.ts) these only kick in if no config is passed
96
+ // at all; in practice callers read siteSettings.effectiveWatermark, which
97
+ // always carries the real size/opacity.
98
+ const widthPct = Math.max(0, Math.min(100, config.size ?? 10));
99
+ const opacity = Math.max(0, Math.min(100, config.opacity ?? 10)) / 100;
96
100
  if (widthPct === 0 || opacity === 0)
97
101
  return null;
98
102
  // Width is a percentage of the container so the watermark scales with the
99
- // video, just like the server-side Sharp pipeline scales images.
103
+ // video, just like the server-side Sharp pipeline scales images — capped at
104
+ // an absolute max so a large player (hero banner, carousel) doesn't render
105
+ // an oversized mark just because the percentage of a big box is a big
106
+ // number. Mirrors MAX_WATERMARK_PX in src/app/api/media/_watermark.ts.
100
107
  const containerStyle = {
101
108
  position: "absolute",
102
109
  width: `${widthPct}%`,
110
+ maxWidth: "180px",
103
111
  pointerEvents: "none",
104
112
  opacity,
105
113
  ...resolveWatermarkPlacementStyle(config),
@@ -550,14 +550,14 @@ export declare const orderFirestoreSchema: z.ZodObject<{
550
550
  productId: string;
551
551
  quantity: number;
552
552
  productTitle: string;
553
+ userName: string;
553
554
  userEmail: string;
555
+ unitPrice: number;
556
+ totalPrice: number;
554
557
  paymentStatus: "paid" | "failed" | "pending" | "processing" | "refunded" | "partial_refund";
555
558
  orderDate: string | Date | z.objectOutputType<{
556
559
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
557
560
  }, z.ZodTypeAny, "passthrough">;
558
- userName: string;
559
- unitPrice: number;
560
- totalPrice: number;
561
561
  storeId?: string | undefined;
562
562
  platformFee?: number | undefined;
563
563
  paymentMethod?: string | undefined;
@@ -569,6 +569,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
569
569
  shelf: string;
570
570
  bin: string;
571
571
  } | undefined;
572
+ payoutStatus?: "paid" | "eligible" | "requested" | undefined;
572
573
  items?: {
573
574
  productId: string;
574
575
  quantity: number;
@@ -578,11 +579,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
578
579
  listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
579
580
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
580
581
  }[] | undefined;
581
- payoutStatus?: "paid" | "eligible" | "requested" | undefined;
582
- couponCode?: string | undefined;
583
- shippingMethod?: "custom" | undefined;
584
- orderType?: string | undefined;
585
- imageUrls?: string[] | undefined;
586
582
  paymentId?: string | undefined;
587
583
  shippingAddress?: string | undefined;
588
584
  trackingNumber?: string | undefined;
@@ -598,6 +594,26 @@ export declare const orderFirestoreSchema: z.ZodObject<{
598
594
  cancellationReason?: string | undefined;
599
595
  refundAmount?: number | undefined;
600
596
  refundStatus?: "pending" | "processing" | "rejected" | "completed" | undefined;
597
+ depositAmount?: number | undefined;
598
+ codRemainingAmount?: number | undefined;
599
+ shippingFee?: number | undefined;
600
+ payoutId?: string | undefined;
601
+ shippingMethod?: "custom" | undefined;
602
+ shippingCarrier?: string | undefined;
603
+ trackingUrl?: string | undefined;
604
+ bundleId?: string | undefined;
605
+ prizeWon?: {
606
+ title: string;
607
+ images: string[];
608
+ itemNumber: number;
609
+ wonAt: string | Date | z.objectOutputType<{
610
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
611
+ }, z.ZodTypeAny, "passthrough">;
612
+ } | undefined;
613
+ prizeDrawProductId?: string | undefined;
614
+ couponCode?: string | undefined;
615
+ orderType?: string | undefined;
616
+ imageUrls?: string[] | undefined;
601
617
  refundFeeDeducted?: number | undefined;
602
618
  refundNetAmount?: number | undefined;
603
619
  refundNote?: string | undefined;
@@ -610,8 +626,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
610
626
  storeId?: string | undefined;
611
627
  couponId?: string | undefined;
612
628
  }[] | undefined;
613
- depositAmount?: number | undefined;
614
- codRemainingAmount?: number | undefined;
615
629
  codHandlingFee?: number | undefined;
616
630
  emiEnabled?: boolean | undefined;
617
631
  emiTenureMonths?: number | undefined;
@@ -635,22 +649,8 @@ export declare const orderFirestoreSchema: z.ZodObject<{
635
649
  }[] | undefined;
636
650
  emiRemainingBalance?: number | undefined;
637
651
  emiComplete?: boolean | undefined;
638
- shippingFee?: number | undefined;
639
- shippingCarrier?: string | undefined;
640
- trackingUrl?: string | undefined;
641
- payoutId?: string | undefined;
642
652
  offerId?: string | undefined;
643
- prizeWon?: {
644
- title: string;
645
- images: string[];
646
- itemNumber: number;
647
- wonAt: string | Date | z.objectOutputType<{
648
- toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
649
- }, z.ZodTypeAny, "passthrough">;
650
- } | undefined;
651
- prizeDrawProductId?: string | undefined;
652
653
  isNonRefundable?: boolean | undefined;
653
- bundleId?: string | undefined;
654
654
  paymentBatchId?: string | undefined;
655
655
  refunds?: {
656
656
  type: "full" | "partial";
@@ -698,14 +698,14 @@ export declare const orderFirestoreSchema: z.ZodObject<{
698
698
  productId: string;
699
699
  quantity: number;
700
700
  productTitle: string;
701
+ userName: string;
701
702
  userEmail: string;
703
+ unitPrice: number;
704
+ totalPrice: number;
702
705
  paymentStatus: "paid" | "failed" | "pending" | "processing" | "refunded" | "partial_refund";
703
706
  orderDate: string | Date | z.objectInputType<{
704
707
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
705
708
  }, z.ZodTypeAny, "passthrough">;
706
- userName: string;
707
- unitPrice: number;
708
- totalPrice: number;
709
709
  storeId?: string | undefined;
710
710
  platformFee?: number | undefined;
711
711
  paymentMethod?: string | undefined;
@@ -717,6 +717,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
717
717
  shelf: string;
718
718
  bin: string;
719
719
  } | undefined;
720
+ payoutStatus?: "paid" | "eligible" | "requested" | undefined;
720
721
  items?: {
721
722
  productId: string;
722
723
  quantity: number;
@@ -726,11 +727,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
726
727
  listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
727
728
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
728
729
  }[] | undefined;
729
- payoutStatus?: "paid" | "eligible" | "requested" | undefined;
730
- couponCode?: string | undefined;
731
- shippingMethod?: "custom" | undefined;
732
- orderType?: string | undefined;
733
- imageUrls?: string[] | undefined;
734
730
  paymentId?: string | undefined;
735
731
  shippingAddress?: string | undefined;
736
732
  trackingNumber?: string | undefined;
@@ -746,6 +742,26 @@ export declare const orderFirestoreSchema: z.ZodObject<{
746
742
  cancellationReason?: string | undefined;
747
743
  refundAmount?: number | undefined;
748
744
  refundStatus?: "pending" | "processing" | "rejected" | "completed" | undefined;
745
+ depositAmount?: number | undefined;
746
+ codRemainingAmount?: number | undefined;
747
+ shippingFee?: number | undefined;
748
+ payoutId?: string | undefined;
749
+ shippingMethod?: "custom" | undefined;
750
+ shippingCarrier?: string | undefined;
751
+ trackingUrl?: string | undefined;
752
+ bundleId?: string | undefined;
753
+ prizeWon?: {
754
+ title: string;
755
+ images: string[];
756
+ itemNumber: number;
757
+ wonAt: string | Date | z.objectInputType<{
758
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
759
+ }, z.ZodTypeAny, "passthrough">;
760
+ } | undefined;
761
+ prizeDrawProductId?: string | undefined;
762
+ couponCode?: string | undefined;
763
+ orderType?: string | undefined;
764
+ imageUrls?: string[] | undefined;
749
765
  refundFeeDeducted?: number | undefined;
750
766
  refundNetAmount?: number | undefined;
751
767
  refundNote?: string | undefined;
@@ -758,8 +774,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
758
774
  storeId?: string | undefined;
759
775
  couponId?: string | undefined;
760
776
  }[] | undefined;
761
- depositAmount?: number | undefined;
762
- codRemainingAmount?: number | undefined;
763
777
  codHandlingFee?: number | undefined;
764
778
  emiEnabled?: boolean | undefined;
765
779
  emiTenureMonths?: number | undefined;
@@ -783,22 +797,8 @@ export declare const orderFirestoreSchema: z.ZodObject<{
783
797
  }[] | undefined;
784
798
  emiRemainingBalance?: number | undefined;
785
799
  emiComplete?: boolean | undefined;
786
- shippingFee?: number | undefined;
787
- shippingCarrier?: string | undefined;
788
- trackingUrl?: string | undefined;
789
- payoutId?: string | undefined;
790
800
  offerId?: string | undefined;
791
- prizeWon?: {
792
- title: string;
793
- images: string[];
794
- itemNumber: number;
795
- wonAt: string | Date | z.objectInputType<{
796
- toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
797
- }, z.ZodTypeAny, "passthrough">;
798
- } | undefined;
799
- prizeDrawProductId?: string | undefined;
800
801
  isNonRefundable?: boolean | undefined;
801
- bundleId?: string | undefined;
802
802
  paymentBatchId?: string | undefined;
803
803
  refunds?: {
804
804
  type: "full" | "partial";
@@ -979,11 +979,11 @@ export declare const orderSchema: z.ZodObject<{
979
979
  createdAt?: string | undefined;
980
980
  updatedAt?: string | undefined;
981
981
  notes?: string | undefined;
982
- couponCode?: string | undefined;
983
- tax?: number | undefined;
984
982
  trackingNumber?: string | undefined;
985
983
  shippingCarrier?: string | undefined;
984
+ couponCode?: string | undefined;
986
985
  discount?: number | undefined;
986
+ tax?: number | undefined;
987
987
  paymentGateway?: string | undefined;
988
988
  shippingCost?: number | undefined;
989
989
  timeline?: {
@@ -1016,11 +1016,11 @@ export declare const orderSchema: z.ZodObject<{
1016
1016
  createdAt?: string | undefined;
1017
1017
  updatedAt?: string | undefined;
1018
1018
  notes?: string | undefined;
1019
- couponCode?: string | undefined;
1020
- tax?: number | undefined;
1021
1019
  trackingNumber?: string | undefined;
1022
1020
  shippingCarrier?: string | undefined;
1021
+ couponCode?: string | undefined;
1023
1022
  discount?: number | undefined;
1023
+ tax?: number | undefined;
1024
1024
  paymentGateway?: string | undefined;
1025
1025
  shippingCost?: number | undefined;
1026
1026
  timeline?: {
@@ -36,6 +36,15 @@ export interface CompareProductLike {
36
36
  /** Canonical discriminator (SB1-G Phase 4). */
37
37
  listingType?: ListingType;
38
38
  features?: string[];
39
+ /** Seller-defined spec fields (dimensions, material, edition, etc.) — the
40
+ * per-listing-type field *set* varies, so the overlay unions the keys
41
+ * present across all compared items rather than assuming a fixed schema. */
42
+ customFields?: {
43
+ key: string;
44
+ type: string;
45
+ value: string;
46
+ unit?: string;
47
+ }[];
39
48
  }
40
49
  interface CompareFieldLabels {
41
50
  image: string;
@@ -56,10 +56,18 @@ function priceLabel(item) {
56
56
  function FieldRow({ label, children }) {
57
57
  return (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: FIELD_LABEL_CLASS, children: label }), _jsx(Div, { children: children })] }));
58
58
  }
59
- function CompareColumn({ item, productType, labels, onRemove, onClose }) {
59
+ /** Formats a single custom-field value for display, appending its unit when present. */
60
+ function customFieldValue(field) {
61
+ if (!field)
62
+ return "—";
63
+ if (field.type === "boolean")
64
+ return field.value === "true" ? "Yes" : "No";
65
+ return field.unit ? `${field.value} ${field.unit}` : field.value;
66
+ }
67
+ function CompareColumn({ item, productType, labels, onRemove, onClose, customFieldKeys }) {
60
68
  const href = detailHref(item, productType);
61
69
  const img = item.mainImage ?? item.images?.[0];
62
- return (_jsxs(Stack, { gap: "md", className: COLUMN_CARD_CLASS, children: [_jsxs(Row, { justify: "between", align: "start", gap: "sm", children: [_jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, className: "text-[length:var(--appkit-text-sm)] font-semibold text-[var(--appkit-color-text)] hover:underline line-clamp-2", children: item.title ?? item.id }), onRemove && (_jsx(IconButton, { type: "button", onClick: () => onRemove(item.id), "aria-label": `${labels.remove} ${item.title ?? item.id}`, variant: "ghost", rounded: "full", className: CLS_REMOVE_BTN, children: _jsx(X, { className: "h-4 w-4" }) }))] }), _jsx(FieldRow, { label: labels.field.image, children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, children: _jsx(Div, { className: `relative aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: img ? (_jsx(MediaImage, { src: img, alt: item.title ?? item.id, size: "card" })) : null }) }) }), _jsx(FieldRow, { label: labels.field.price, children: _jsx(Text, { size: "base", weight: "semibold", color: "primary", children: priceLabel(item) }) }), _jsx(FieldRow, { label: labels.field.condition, children: _jsx(Text, { children: item.condition ? _jsx(Span, { className: CHIP_CLASS, children: item.condition }) : "—" }) }), _jsx(FieldRow, { label: labels.field.brand, children: _jsx(Text, { children: item.brand ? _jsx(Span, { className: CHIP_CLASS, children: item.brand }) : "—" }) }), _jsx(FieldRow, { label: labels.field.category, children: _jsx(Text, { children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) || (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) ? (_jsx(Span, { className: CHIP_CLASS, children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) ?? (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) })) : ("—") }) }), _jsx(FieldRow, { label: labels.field.store, children: _jsx(Text, { size: "sm", color: "muted", children: item.storeName ?? "—" }) }), _jsx(Button, { asChild: true, variant: "primary", size: "sm", className: "mt-auto", children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, onClick: onClose, children: labels.view }) })] }));
70
+ return (_jsxs(Stack, { gap: "md", className: COLUMN_CARD_CLASS, children: [_jsxs(Row, { justify: "between", align: "start", gap: "sm", children: [_jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, className: "text-[length:var(--appkit-text-sm)] font-semibold text-[var(--appkit-color-text)] hover:underline line-clamp-2", children: item.title ?? item.id }), onRemove && (_jsx(IconButton, { type: "button", onClick: () => onRemove(item.id), "aria-label": `${labels.remove} ${item.title ?? item.id}`, variant: "ghost", rounded: "full", className: CLS_REMOVE_BTN, children: _jsx(X, { className: "h-4 w-4" }) }))] }), _jsx(FieldRow, { label: labels.field.image, children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, children: _jsx(Div, { className: `relative aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: img ? (_jsx(MediaImage, { src: img, alt: item.title ?? item.id, size: "card" })) : null }) }) }), _jsx(FieldRow, { label: labels.field.price, children: _jsx(Text, { size: "base", weight: "semibold", color: "primary", children: priceLabel(item) }) }), _jsx(FieldRow, { label: labels.field.condition, children: _jsx(Text, { children: item.condition ? _jsx(Span, { className: CHIP_CLASS, children: item.condition }) : "—" }) }), _jsx(FieldRow, { label: labels.field.brand, children: _jsx(Text, { children: item.brand ? _jsx(Span, { className: CHIP_CLASS, children: item.brand }) : "—" }) }), _jsx(FieldRow, { label: labels.field.category, children: _jsx(Text, { children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) || (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) ? (_jsx(Span, { className: CHIP_CLASS, children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) ?? (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) })) : ("—") }) }), _jsx(FieldRow, { label: labels.field.store, children: _jsx(Text, { size: "sm", color: "muted", children: item.storeName ?? "—" }) }), customFieldKeys.map((key) => (_jsx(FieldRow, { label: key, children: _jsx(Text, { size: "sm", children: customFieldValue(item.customFields?.find((f) => f.key === key)) }) }, key))), _jsx(Button, { asChild: true, variant: "primary", size: "sm", className: "mt-auto", children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, onClick: onClose, children: labels.view }) })] }));
63
71
  }
64
72
  function useFetchedProducts(productIds, passedItems, isOpen) {
65
73
  const [fetched, setFetched] = useState([]);
@@ -109,6 +117,23 @@ export function CompareOverlay({ isOpen, items: passedItems, productIds, product
109
117
  const { items, isLoading } = useFetchedProducts(productIds, passedItems, isOpen);
110
118
  const trimmed = useMemo(() => items.slice(0, COMPARE_MAX_ITEMS), [items]);
111
119
  const [activeIndex, setActiveIndex] = useState(0);
120
+ // Union (not intersection) of custom-field keys across every compared
121
+ // item, first-seen order — a field only one item has still gets its own
122
+ // row, showing "—" for items that lack it, rather than silently dropping
123
+ // fields that aren't shared by every compared listing type.
124
+ const customFieldKeys = useMemo(() => {
125
+ const seen = new Set();
126
+ const keys = [];
127
+ for (const item of trimmed) {
128
+ for (const field of item.customFields ?? []) {
129
+ if (!seen.has(field.key)) {
130
+ seen.add(field.key);
131
+ keys.push(field.key);
132
+ }
133
+ }
134
+ }
135
+ return keys;
136
+ }, [trimmed]);
112
137
  // Keep activeIndex in range as items shrink (e.g. user removes a column).
113
138
  useEffect(() => {
114
139
  if (activeIndex >= trimmed.length && trimmed.length > 0) {
@@ -146,7 +171,7 @@ export function CompareOverlay({ isOpen, items: passedItems, productIds, product
146
171
  // audit-content-alignment-ok: loading-state text inside a transient overlay panel, not marketing content
147
172
  _jsx(Text, { paddingY: "3xl", variant: "secondary", align: "center", children: "Loading\u00E2\u20AC\u00A6" })) : trimmed.length === 0 ? (
148
173
  // audit-content-alignment-ok: empty-state inside a transient overlay panel, not marketing content
149
- _jsx(Text, { paddingY: "3xl", variant: "secondary", align: "center", children: labels.empty })) : (_jsxs(_Fragment, { children: [_jsx(Div, { ref: gridRef, gap: "4", className: "hidden md:grid", children: trimmed.map((item) => (_jsx(CompareColumn, { item: item, productType: productType, labels: labels, onRemove: onRemove ? handleRemove : undefined, onClose: onClose }, item.id))) }), _jsxs(Div, { className: "md:hidden", children: [_jsx(Div, { ref: swipeRef, className: "touch-pan-y", children: trimmed[activeIndex] && (_jsx(CompareColumn, { item: trimmed[activeIndex], productType: productType, labels: labels, onRemove: onRemove ? handleRemove : undefined, onClose: onClose })) }), trimmed.length > 1 && (_jsx(Row, { justify: "center", gap: "xs", className: "mt-4", children: trimmed.map((it, idx) => (_jsx(Button, { variant: "ghost", type: "button", "aria-label": `Show item ${idx + 1}`, onClick: () => setActiveIndex(idx), rounded: "full", paddingX: "none", paddingY: "none", className: `h-2 w-2 min-w-0 transition-colors ${idx === activeIndex
174
+ _jsx(Text, { paddingY: "3xl", variant: "secondary", align: "center", children: labels.empty })) : (_jsxs(_Fragment, { children: [_jsx(Div, { ref: gridRef, gap: "4", className: "hidden md:grid", children: trimmed.map((item) => (_jsx(CompareColumn, { item: item, productType: productType, labels: labels, onRemove: onRemove ? handleRemove : undefined, onClose: onClose, customFieldKeys: customFieldKeys }, item.id))) }), _jsxs(Div, { className: "md:hidden", children: [_jsx(Div, { ref: swipeRef, className: "touch-pan-y", children: trimmed[activeIndex] && (_jsx(CompareColumn, { item: trimmed[activeIndex], productType: productType, labels: labels, onRemove: onRemove ? handleRemove : undefined, onClose: onClose, customFieldKeys: customFieldKeys })) }), trimmed.length > 1 && (_jsx(Row, { justify: "center", gap: "xs", className: "mt-4", children: trimmed.map((it, idx) => (_jsx(Button, { variant: "ghost", type: "button", "aria-label": `Show item ${idx + 1}`, onClick: () => setActiveIndex(idx), rounded: "full", paddingX: "none", paddingY: "none", className: `h-2 w-2 min-w-0 transition-colors ${idx === activeIndex
150
175
  ? "bg-primary"
151
176
  : "bg-[var(--appkit-color-border)]"}` }, it.id))) }))] })] })) })] }));
152
177
  }
@@ -3,10 +3,16 @@ import Link from "next/link";
3
3
  import { ProductCard } from "./ProductGrid";
4
4
  export function InteractiveProductCard({ product, href, className, selectable, isSelected, onSelect, isWishlisted = false, onToggleWishlist, onAddToCart, onBuyNow, }) {
5
5
  // When selection is enabled, render ProductCard directly so its built-in
6
- // checkbox + long-press (BaseListingCard.Checkbox) work; wrap with Link
7
- // only outside of selection mode.
6
+ // checkbox + long-press (BaseListingCard.Checkbox) work. `href` is still
7
+ // forwarded here (not just in the plain-Link branch below) — ProductCard's
8
+ // own `if (href && !selectionMode) return <Link>...` only wraps in a Link
9
+ // when nothing is actively selected, so passing href unconditionally keeps
10
+ // the card navigable whenever a selection handler is merely wired but not
11
+ // active, instead of silently losing all click/tap navigation the instant
12
+ // `onSelect` is provided at all (see Root Cause Pattern: selectable-card
13
+ // navigation must not depend on a selection callback being present).
8
14
  if (onSelect) {
9
- return (_jsx(ProductCard, { product: product, className: className, isWishlisted: isWishlisted, onAddToWishlist: onToggleWishlist, onAddToCart: onAddToCart, onBuyNow: onBuyNow, selectionMode: selectable, isSelected: isSelected, onSelect: (id) => onSelect(id, !isSelected) }));
15
+ return (_jsx(ProductCard, { product: product, href: href, className: className, isWishlisted: isWishlisted, onAddToWishlist: onToggleWishlist, onAddToCart: onAddToCart, onBuyNow: onBuyNow, selectionMode: selectable, isSelected: isSelected, onSelect: (id) => onSelect(id, !isSelected) }));
10
16
  }
11
17
  return (_jsx(Link, { href: href, className: "block", children: _jsx(ProductCard, { product: product, className: className, isWishlisted: isWishlisted, onAddToWishlist: onToggleWishlist, onAddToCart: onAddToCart, onBuyNow: onBuyNow }) }));
12
18
  }
@@ -1,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Stack } from "@mohasinac/appkit";
2
+ import { Stack } from "@mohasinac/appkit/client";
3
3
  export function MakeOfferForm({ isSubmitted = false, renderInput, renderAction, renderTerms, renderSuccess, renderAuthGate, renderError, className = "", }) {
4
4
  if (renderAuthGate)
5
5
  return _jsx(_Fragment, { children: renderAuthGate() });
@@ -1,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Stack } from "@mohasinac/appkit";
2
+ import { Stack } from "@mohasinac/appkit/client";
3
3
  export function PlaceBidForm({ renderInput, renderAction, renderBidInfo, renderError, renderAuthGate, renderCountdown, className = "", }) {
4
4
  if (renderAuthGate)
5
5
  return _jsx(_Fragment, { children: renderAuthGate() });
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
14
14
  * Optional `highlightItemNumber` (passed by the reveal modal during animation)
15
15
  * adds an emphasis ring to the cell that just won.
16
16
  */
17
- import { Row } from "@mohasinac/appkit";
17
+ import { Row } from "@mohasinac/appkit/client";
18
18
  import { useState } from "react";
19
19
  import { Button, Div, Scrim, Text } from "../../../ui";
20
20
  import { MediaImage } from "../../media/MediaImage";
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { normalizeError } from "../../../errors/normalize";
3
- import { sieveFilter, sieveAnd, SIEVE_OP } from "@mohasinac/appkit";
4
- import { sortBy } from "@mohasinac/appkit";
3
+ import { sieveFilter, sieveAnd, SIEVE_OP } from "@mohasinac/appkit/client";
4
+ import { sortBy } from "@mohasinac/appkit/client";
5
5
  import { Container, Div, Heading, Section, Stack, Text, } from "../../../ui";
6
6
  import { ROUTES } from "../../../next";
7
7
  import { productRepository } from "../repository/products.repository";
@@ -25,8 +25,10 @@ export function ProductDetailActions({ productId, productSlug: _productSlug, pro
25
25
  const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
26
26
  const [busy, setBusy] = useState(null);
27
27
  const [wishlisted, setWishlisted] = useState(false);
28
+ // Success toast (item + updated count/total) is now shown by useAddToCart
29
+ // itself — centralised so every add-to-cart call site gets the same
30
+ // richer toast, not just this one.
28
31
  const addCart = useAddToCart({
29
- onSuccess: () => showToast("Added to cart", "success"),
30
32
  onError: (err) => showToast(err?.message ?? "Could not add to cart", "error"),
31
33
  });
32
34
  async function handleAddToCart(then) {
@@ -23,7 +23,7 @@ const CLS_BADGE_NEW = "rounded-full bg-error-surface px-[var(--appkit-space-2)]
23
23
  const CLS_BADGE_SALE = "rounded-full bg-success-surface px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
24
24
  const CLS_BADGE_TRENDING = "rounded-full bg-indigo-600 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
25
25
  const CLS_BADGE_LIMITED = "rounded-full bg-info-surface px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
26
- const CLS_HEART_ACTIVE = "bg-error-surface text-white hover:bg-error-surface";
26
+ const CLS_HEART_ACTIVE = "bg-[var(--appkit-color-surface-elevated)]/90 text-error hover:bg-[var(--appkit-color-surface-elevated)]";
27
27
  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)]";
28
28
  const CLS_STAR = "text-[11px] text-warning";
29
29
  const CLS_DISCOUNT_TEXT = "mt-1 text-[11px] font-semibold text-error";
@@ -1,5 +1,5 @@
1
1
  import { jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Stack } from "@mohasinac/appkit";
2
+ import { Stack } from "@mohasinac/appkit/client";
3
3
  export function ProductInfo({ isLoading = false, renderTitle, renderPrice, renderBadges, renderDescription, renderVariants, renderSeller, renderRating, className = "", }) {
4
4
  return (_jsxs(Stack, { className: `${className}`, gap: "md", children: [renderTitle?.(isLoading), renderRating?.(), renderPrice?.(isLoading), renderBadges?.(), renderVariants?.(), renderDescription?.(), renderSeller?.()] }));
5
5
  }
@@ -14,8 +14,11 @@ import { ROUTES } from "../../../next";
14
14
  import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
15
15
  import { ProductGrid, ProductFilters, PRODUCT_PUBLIC_SORT_OPTIONS } from ".";
16
16
  import { useGuestCart } from "../../cart/hooks/useGuestCart";
17
+ import { getGuestCartItems } from "../../cart/utils/guest-cart";
17
18
  import { useGuestWishlist } from "../../wishlist/hooks/useGuestWishlist";
18
- import { pushCartOp, pushWishlistOp } from "../../cart/utils/pending-ops";
19
+ import { pushCartOp, pushWishlistOp, dispatchCartUpdated, formatCartAddedMessage } from "../../cart/utils/pending-ops";
20
+ import { formatCurrency } from "../../../utils/number.formatter";
21
+ import { getDefaultCurrency } from "../../../core/baseline-resolver";
19
22
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
20
23
  import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hooks/useCategoryTree";
21
24
  import { useBrands } from "../hooks/useBrands";
@@ -126,7 +129,14 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
126
129
  };
127
130
  localCart.add(product.id, 1, snapshot);
128
131
  pushCartOp({ op: "add", productId: product.id, quantity: 1, ...snapshot });
129
- showToast("Added to cart", "success");
132
+ const items = getGuestCartItems();
133
+ const detail = {
134
+ productTitle: product.title,
135
+ itemCount: items.reduce((sum, it) => sum + it.quantity, 0),
136
+ totalValue: items.reduce((sum, it) => sum + (it.price ?? 0) * it.quantity, 0),
137
+ };
138
+ showToast(formatCartAddedMessage(detail, (amount) => formatCurrency(amount, getDefaultCurrency())), "success");
139
+ dispatchCartUpdated(detail);
130
140
  }, [localCart, showToast]);
131
141
  const handleBuyNow = useCallback((product) => {
132
142
  requireAuth(ACTION_ID.BUY_NOW, () => {
@@ -1,4 +1,4 @@
1
- import { sieveAnd, sieveFilter, SIEVE_OP, sortBy } from "@mohasinac/appkit";
1
+ import { sieveAnd, sieveFilter, SIEVE_OP, sortBy } from "@mohasinac/appkit/client";
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
  import { apiClient } from "../../../http";
4
4
  import { PRODUCT_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -449,16 +449,16 @@ export declare const productItemSchema: z.ZodObject<{
449
449
  inStock?: boolean | undefined;
450
450
  rating?: number | undefined;
451
451
  categorySlug?: string | undefined;
452
- originalPrice?: number | undefined;
453
- sellerAvatar?: string | undefined;
454
- stockCount?: number | undefined;
455
- attributes?: Record<string, string> | undefined;
452
+ publishedAt?: string | undefined;
456
453
  seo?: {
457
454
  title?: string | undefined;
458
455
  description?: string | undefined;
459
456
  keywords?: string[] | undefined;
460
457
  } | undefined;
461
- publishedAt?: string | undefined;
458
+ attributes?: Record<string, string> | undefined;
459
+ originalPrice?: number | undefined;
460
+ sellerAvatar?: string | undefined;
461
+ stockCount?: number | undefined;
462
462
  }, {
463
463
  status: "draft" | "published" | "archived" | "in_review";
464
464
  id: string;
@@ -587,16 +587,16 @@ export declare const productItemSchema: z.ZodObject<{
587
587
  inStock?: boolean | undefined;
588
588
  rating?: number | undefined;
589
589
  categorySlug?: string | undefined;
590
- originalPrice?: number | undefined;
591
- sellerAvatar?: string | undefined;
592
- stockCount?: number | undefined;
593
- attributes?: Record<string, string> | undefined;
590
+ publishedAt?: string | undefined;
594
591
  seo?: {
595
592
  title?: string | undefined;
596
593
  description?: string | undefined;
597
594
  keywords?: string[] | undefined;
598
595
  } | undefined;
599
- publishedAt?: string | undefined;
596
+ attributes?: Record<string, string> | undefined;
597
+ originalPrice?: number | undefined;
598
+ sellerAvatar?: string | undefined;
599
+ stockCount?: number | undefined;
600
600
  }>;
601
601
  /** Base Zod schema for list-query parameters. */
602
602
  export declare const productListParamsSchema: z.ZodObject<{