@mohasinac/appkit 2.7.49 → 2.7.52

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 (232) hide show
  1. package/dist/_internal/client/features/layout/SidebarCollapseToggle.d.ts +19 -0
  2. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +15 -0
  3. package/dist/_internal/client/features/layout/index.d.ts +2 -0
  4. package/dist/_internal/client/features/layout/index.js +1 -0
  5. package/dist/_internal/server/features/checkout/actions.js +25 -2
  6. package/dist/_internal/server/features/promotions/actions.js +3 -1
  7. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
  8. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
  9. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
  10. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
  11. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
  12. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
  13. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
  14. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
  15. package/dist/_internal/shared/actions/action-registry.js +149 -0
  16. package/dist/client.d.ts +45 -6
  17. package/dist/client.js +27 -4
  18. package/dist/constants/api-endpoints.d.ts +42 -0
  19. package/dist/constants/api-endpoints.js +14 -0
  20. package/dist/features/_guide-cls.d.ts +13 -0
  21. package/dist/features/_guide-cls.js +14 -0
  22. package/dist/features/about/components/HelpPageView.js +29 -26
  23. package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
  24. package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
  25. package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
  26. package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
  27. package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
  28. package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
  29. package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
  30. package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
  31. package/dist/features/account/components/UserSidebar.js +2 -1
  32. package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
  33. package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
  34. package/dist/features/admin/components/AdminBlogEditorView.js +102 -75
  35. package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
  36. package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
  37. package/dist/features/admin/components/AdminBundlesView.js +173 -52
  38. package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
  39. package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
  40. package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
  41. package/dist/features/admin/components/AdminContentGuideView.js +36 -0
  42. package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
  43. package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
  44. package/dist/features/admin/components/AdminGuideHubView.js +79 -0
  45. package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
  46. package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
  47. package/dist/features/admin/components/AdminProductsView.js +8 -3
  48. package/dist/features/admin/components/AdminSidebar.js +2 -1
  49. package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
  50. package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
  51. package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
  52. package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
  53. package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
  54. package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
  55. package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
  56. package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
  57. package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
  58. package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
  59. package/dist/features/auth/repository/session.repository.js +16 -1
  60. package/dist/features/cart/hooks/useAddToCart.d.ts +2 -0
  61. package/dist/features/cart/hooks/useAddToCart.js +2 -0
  62. package/dist/features/cart/hooks/useGuestCart.d.ts +2 -0
  63. package/dist/features/cart/utils/guest-cart.d.ts +7 -0
  64. package/dist/features/cart/utils/pending-ops.d.ts +6 -0
  65. package/dist/features/categories/components/CategoryBundlesListing.js +3 -25
  66. package/dist/features/categories/components/CategoryProductsListing.js +6 -10
  67. package/dist/features/categories/components/index.d.ts +0 -2
  68. package/dist/features/categories/components/index.js +0 -1
  69. package/dist/features/categories/schemas/firestore.d.ts +9 -0
  70. package/dist/features/events/components/AdminEventEditorView.js +223 -269
  71. package/dist/features/events/components/EventRaffleWinnerView.d.ts +9 -1
  72. package/dist/features/events/components/EventRaffleWinnerView.js +3 -3
  73. package/dist/features/events/components/SpinWheelView.js +3 -3
  74. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
  75. package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
  76. package/dist/features/layout/BottomNavLayout.d.ts +5 -0
  77. package/dist/features/layout/BottomNavLayout.js +21 -0
  78. package/dist/features/layout/TitleBar.js +7 -1
  79. package/dist/features/layout/TitleBarLayout.d.ts +8 -2
  80. package/dist/features/layout/TitleBarLayout.js +11 -7
  81. package/dist/features/media/upload/MediaUploadField.js +1 -1
  82. package/dist/features/orders/schemas/index.d.ts +2 -2
  83. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +12 -6
  84. package/dist/features/products/components/AuctionsIndexListing.js +3 -1
  85. package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
  86. package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
  87. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  88. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +3 -1
  89. package/dist/features/products/components/PrizeDrawEntryActions.js +8 -12
  90. package/dist/features/products/components/ProductDetailActions.d.ts +5 -1
  91. package/dist/features/products/components/ProductDetailActions.js +3 -1
  92. package/dist/features/products/components/ProductDetailPageView.d.ts +2 -0
  93. package/dist/features/products/components/ProductDetailPageView.js +4 -0
  94. package/dist/features/products/components/ProductGrid.js +3 -3
  95. package/dist/features/products/components/ProductsIndexListing.js +18 -9
  96. package/dist/features/products/components/index.d.ts +2 -4
  97. package/dist/features/products/components/index.js +1 -2
  98. package/dist/features/promotions/components/CouponCard.d.ts +11 -1
  99. package/dist/features/promotions/components/CouponCard.js +36 -2
  100. package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +27 -0
  101. package/dist/features/promotions/repository/claimed-coupons.repository.js +115 -0
  102. package/dist/features/promotions/schemas/firestore.d.ts +34 -0
  103. package/dist/features/promotions/schemas/firestore.js +14 -1
  104. package/dist/features/reviews/components/index.d.ts +0 -2
  105. package/dist/features/reviews/components/index.js +0 -1
  106. package/dist/features/seller/components/BrandInlineSelect.js +23 -3
  107. package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
  108. package/dist/features/seller/components/SellerAddressesView.js +3 -2
  109. package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
  110. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
  111. package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
  112. package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
  113. package/dist/features/seller/components/SellerBidsView.js +33 -3
  114. package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
  115. package/dist/features/seller/components/SellerBundlesView.js +104 -0
  116. package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
  117. package/dist/features/seller/components/SellerClassifiedView.js +113 -0
  118. package/dist/features/seller/components/SellerDashboardView.js +19 -7
  119. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
  120. package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
  121. package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
  122. package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
  123. package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
  124. package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
  125. package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
  126. package/dist/features/seller/components/SellerLiveView.js +113 -0
  127. package/dist/features/seller/components/SellerOrdersView.js +53 -2
  128. package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
  129. package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
  130. package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
  131. package/dist/features/seller/components/SellerProductShell.d.ts +3 -1
  132. package/dist/features/seller/components/SellerProductShell.js +35 -11
  133. package/dist/features/seller/components/SellerProductsCards.d.ts +19 -0
  134. package/dist/features/seller/components/SellerProductsCards.js +19 -0
  135. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +14 -0
  136. package/dist/features/seller/components/SellerProductsFilterDrawer.js +22 -0
  137. package/dist/features/seller/components/SellerProductsView.d.ts +3 -1
  138. package/dist/features/seller/components/SellerProductsView.js +92 -49
  139. package/dist/features/seller/components/SellerReviewsView.js +67 -4
  140. package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
  141. package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
  142. package/dist/features/seller/components/SellerShippingView.js +37 -10
  143. package/dist/features/seller/components/SellerSidebar.js +2 -1
  144. package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
  145. package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
  146. package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
  147. package/dist/features/seller/components/SellerStorefrontView.js +26 -29
  148. package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
  149. package/dist/features/seller/components/SellerTemplatesView.js +265 -0
  150. package/dist/features/seller/components/index.d.ts +26 -0
  151. package/dist/features/seller/components/index.js +13 -0
  152. package/dist/features/seller/components/seller-products-styles.d.ts +7 -0
  153. package/dist/features/seller/components/seller-products-styles.js +14 -0
  154. package/dist/features/shell/FormShell.d.ts +7 -1
  155. package/dist/features/shell/FormShell.js +5 -2
  156. package/dist/features/store-extensions/index.d.ts +4 -0
  157. package/dist/features/store-extensions/index.js +4 -0
  158. package/dist/features/store-extensions/repository/rbac.repositories.d.ts +23 -0
  159. package/dist/features/store-extensions/repository/rbac.repositories.js +32 -0
  160. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +91 -0
  161. package/dist/features/store-extensions/repository/store-extensions.repositories.js +127 -0
  162. package/dist/features/store-extensions/schemas/firestore.d.ts +244 -0
  163. package/dist/features/store-extensions/schemas/firestore.js +158 -0
  164. package/dist/features/store-extensions/schemas/rbac.d.ts +65 -0
  165. package/dist/features/store-extensions/schemas/rbac.js +43 -0
  166. package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
  167. package/dist/features/stores/components/InteractiveStoreCard.js +7 -3
  168. package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
  169. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
  170. package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
  171. package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
  172. package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
  173. package/dist/features/stores/components/StoreGuideHubView.js +89 -0
  174. package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
  175. package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
  176. package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
  177. package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
  178. package/dist/features/stores/components/StoreProductsListing.js +6 -10
  179. package/dist/features/stores/components/StoreReviewsListing.js +100 -23
  180. package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
  181. package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
  182. package/dist/features/stores/components/index.d.ts +12 -1
  183. package/dist/features/stores/components/index.js +6 -1
  184. package/dist/features/stores/hooks/useStores.d.ts +5 -0
  185. package/dist/features/stores/hooks/useStores.js +10 -0
  186. package/dist/features/stores/manifest.js +1 -1
  187. package/dist/features/stores/schemas/firestore.d.ts +2 -0
  188. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
  189. package/dist/index.d.ts +60 -9
  190. package/dist/index.js +66 -15
  191. package/dist/next/routing/route-map.d.ts +134 -0
  192. package/dist/next/routing/route-map.js +59 -0
  193. package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
  194. package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
  195. package/dist/providers/db-firebase/sieve.d.ts +49 -0
  196. package/dist/providers/db-firebase/sieve.js +61 -8
  197. package/dist/react/hooks/useFormStatePreservation.d.ts +17 -0
  198. package/dist/react/hooks/useFormStatePreservation.js +62 -0
  199. package/dist/react/hooks/useInlineRowEdit.d.ts +24 -0
  200. package/dist/react/hooks/useInlineRowEdit.js +68 -0
  201. package/dist/react/index.d.ts +4 -0
  202. package/dist/react/index.js +4 -0
  203. package/dist/repositories/index.d.ts +5 -0
  204. package/dist/repositories/index.js +7 -0
  205. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  206. package/dist/seed/claimed-coupons-seed-data.d.ts +13 -0
  207. package/dist/seed/claimed-coupons-seed-data.js +79 -0
  208. package/dist/seed/index.d.ts +2 -0
  209. package/dist/seed/index.js +3 -0
  210. package/dist/seed/manifest.js +17 -0
  211. package/dist/seed/store-extensions-seed-data.d.ts +19 -0
  212. package/dist/seed/store-extensions-seed-data.js +421 -0
  213. package/dist/tailwind-utilities.css +1 -1
  214. package/dist/ui/components/Button.style.css +20 -12
  215. package/dist/ui/components/ClaimCouponButton.d.ts +28 -0
  216. package/dist/ui/components/ClaimCouponButton.js +89 -0
  217. package/dist/ui/components/ListingToolbar.d.ts +8 -1
  218. package/dist/ui/components/ListingToolbar.js +4 -2
  219. package/dist/ui/components/QuickCreateModal.d.ts +15 -0
  220. package/dist/ui/components/QuickCreateModal.js +48 -0
  221. package/dist/ui/components/QuickCreateModal.style.css +84 -0
  222. package/dist/ui/components/StickyBottomBar.d.ts +22 -0
  223. package/dist/ui/components/StickyBottomBar.js +20 -0
  224. package/dist/ui/components/VacationBanner.d.ts +11 -0
  225. package/dist/ui/components/VacationBanner.js +16 -0
  226. package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
  227. package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
  228. package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
  229. package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
  230. package/dist/ui/index.d.ts +9 -1
  231. package/dist/ui/index.js +4 -0
  232. package/package.json +8 -2
@@ -73,15 +73,15 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
73
73
  return (_jsx(Text, { className: `mt-1 text-[11px] font-medium ${low
74
74
  ? "text-amber-600 dark:text-amber-400"
75
75
  : "text-zinc-500 dark:text-zinc-400"}`, children: low ? `Only ${stock} left` : `${stock} in stock` }));
76
- })(), featuresList && product.features && product.features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: product.features, features: featuresList, maxVisible: PRODUCT_FEATURE_CARD_MAX_VISIBLE, className: "mt-2" })), (onAddToCart || onBuyNow) && (_jsxs(Div, { className: "mt-2 grid gap-1.5", style: { gridTemplateColumns: onBuyNow && onAddToCart ? "1fr 1fr" : "1fr" }, children: [onBuyNow && (_jsxs(Button, { type: "button", onClick: (e) => {
76
+ })(), featuresList && product.features && product.features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: product.features, features: featuresList, maxVisible: PRODUCT_FEATURE_CARD_MAX_VISIBLE, className: "mt-2" })), (onAddToCart || onBuyNow) && (_jsxs(Div, { className: "mt-2 grid gap-1.5", style: { gridTemplateColumns: onBuyNow && onAddToCart ? "1fr 1fr" : "1fr" }, children: [onBuyNow && (_jsxs(Button, { type: "button", variant: "primary", size: "sm", onClick: (e) => {
77
77
  e.stopPropagation();
78
78
  e.preventDefault();
79
79
  onBuyNow(product);
80
- }, className: "flex items-center justify-center gap-1 rounded-xl bg-primary py-2 text-xs font-semibold text-white hover:bg-primary/90 active:scale-[0.97] transition-all duration-150 btn-glow", children: [_jsx("svg", { className: "h-3.5 w-3.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2.5, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 10V3L4 14h7v7l9-11h-7z" }) }), "Buy Now"] })), onAddToCart && (_jsxs(Button, { type: "button", onClick: (e) => {
80
+ }, className: "flex items-center justify-center gap-1 text-xs btn-glow", children: [_jsx("svg", { className: "h-3.5 w-3.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2.5, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 10V3L4 14h7v7l9-11h-7z" }) }), "Buy Now"] })), onAddToCart && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: (e) => {
81
81
  e.stopPropagation();
82
82
  e.preventDefault();
83
83
  onAddToCart(product);
84
- }, className: "flex items-center justify-center gap-1 rounded-xl border-2 border-primary/40 bg-primary/5 py-2 text-xs font-semibold text-zinc-800 hover:bg-primary/10 hover:border-primary/60 active:scale-[0.97] transition-all duration-150 dark:text-zinc-100 dark:border-primary-400/50 dark:bg-primary/[0.08] dark:hover:bg-primary/[0.15] dark:hover:border-primary-400/70", children: [_jsx("svg", { className: "h-3.5 w-3.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 3h2l.4 2M7 13h10l4-8H5.4m1.6 8L5 3H3m4 10v7a1 1 0 001 1h8a1 1 0 001-1v-7M9 21h6" }) }), "Cart"] }))] }))] })] })] }));
84
+ }, className: "flex items-center justify-center gap-1 text-xs", children: [_jsx("svg", { className: "h-3.5 w-3.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 3h2l.4 2M7 13h10l4-8H5.4m1.6 8L5 3H3m4 10v7a1 1 0 001 1h8a1 1 0 001-1v-7M9 21h6" }) }), "Cart"] }))] }))] })] })] }));
85
85
  if (href && !selectionMode) {
86
86
  return (_jsx(Link, { href: href, className: "block h-full", children: cardBody }));
87
87
  }
@@ -107,26 +107,34 @@ export function ProductsIndexListing({ initialData }) {
107
107
  });
108
108
  }, [wishlistedIds, localWishlist, showToast, requireAuth]);
109
109
  const handleAddToCart = useCallback((product) => {
110
- localCart.add(product.id, 1, {
110
+ const snapshot = {
111
111
  productTitle: product.title,
112
112
  productImage: product.mainImage,
113
113
  price: product.price,
114
- });
115
- pushCartOp({ op: "add", productId: product.id, quantity: 1, productTitle: product.title, productImage: product.mainImage, price: product.price });
114
+ storeId: product.storeId,
115
+ storeName: product.storeName,
116
+ };
117
+ localCart.add(product.id, 1, snapshot);
118
+ pushCartOp({ op: "add", productId: product.id, quantity: 1, ...snapshot });
116
119
  showToast("Added to cart", "success");
117
120
  }, [localCart, showToast]);
118
121
  const handleBuyNow = useCallback((product) => {
119
122
  requireAuth(ACTION_ID.BUY_NOW, () => {
120
- localCart.add(product.id, 1, {
123
+ const snapshot = {
121
124
  productTitle: product.title,
122
125
  productImage: product.mainImage,
123
126
  price: product.price,
124
- });
125
- pushCartOp({ op: "add", productId: product.id, quantity: 1, productTitle: product.title, productImage: product.mainImage, price: product.price });
127
+ storeId: product.storeId,
128
+ storeName: product.storeName,
129
+ };
130
+ localCart.add(product.id, 1, snapshot);
131
+ pushCartOp({ op: "add", productId: product.id, quantity: 1, ...snapshot });
126
132
  router.push(String(ROUTES.USER.CART));
127
133
  });
128
134
  }, [localCart, router, requireAuth]);
129
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: products.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show sold" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showSold, onClick: () => table.set(TABLE_KEYS.SHOW_SOLD, showSold ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showSold ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showSold ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
135
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: products.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
136
+ { label: "Show sold", active: showSold, onChange: (next) => table.set(TABLE_KEYS.SHOW_SOLD, next ? "true" : "") },
137
+ ] }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
130
138
  {
131
139
  id: ACTION_ID.ADD_TO_CART,
132
140
  label: ACTION_META[ACTION_ID.ADD_TO_CART].label,
@@ -135,8 +143,9 @@ export function ProductsIndexListing({ initialData }) {
135
143
  onClick: () => {
136
144
  const selected = products.filter((p) => selection.selectedIdSet.has(p.id));
137
145
  selected.forEach((p) => {
138
- localCart.add(p.id, 1, { productTitle: p.title, productImage: p.mainImage, price: p.price });
139
- pushCartOp({ op: "add", productId: p.id, quantity: 1, productTitle: p.title, productImage: p.mainImage, price: p.price });
146
+ const snapshot = { productTitle: p.title, productImage: p.mainImage, price: p.price, storeId: p.storeId, storeName: p.storeName };
147
+ localCart.add(p.id, 1, snapshot);
148
+ pushCartOp({ op: "add", productId: p.id, quantity: 1, ...snapshot });
140
149
  });
141
150
  showToast(`${selected.length} items added to cart`, "success");
142
151
  selection.clearSelection();
@@ -15,6 +15,8 @@ export { PrizeRevealModal } from "./PrizeRevealModal";
15
15
  export type { PrizeRevealModalProps, PrizeRevealResponse, } from "./PrizeRevealModal";
16
16
  export { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
17
17
  export type { MarketplacePrizeDrawCardProps, MarketplacePrizeDrawCardData, MarketplacePrizeDrawCardLabels, } from "./MarketplacePrizeDrawCard";
18
+ export { MarketplaceBundleCard } from "./MarketplaceBundleCard";
19
+ export type { MarketplaceBundleCardProps, MarketplaceBundleCardData, MarketplaceBundleCardLabels, } from "./MarketplaceBundleCard";
18
20
  export { PrizeDrawsIndexListing } from "./PrizeDrawsIndexListing";
19
21
  export type { PrizeDrawsIndexListingProps } from "./PrizeDrawsIndexListing";
20
22
  export { PrizeDrawEntryActions } from "./PrizeDrawEntryActions";
@@ -30,14 +32,10 @@ export { BuyBar } from "./BuyBar";
30
32
  export type { BuyBarProps } from "./BuyBar";
31
33
  export { ProductsView } from "./ProductsView";
32
34
  export type { ProductsViewProps } from "./ProductsView";
33
- export { AuctionsView } from "./AuctionsView";
34
- export type { AuctionsViewProps } from "./AuctionsView";
35
35
  export { ProductDetailView } from "./ProductDetailView";
36
36
  export type { ProductDetailViewProps } from "./ProductDetailView";
37
37
  export { AuctionDetailView } from "./AuctionDetailView";
38
38
  export type { AuctionDetailViewProps } from "./AuctionDetailView";
39
- export { PreOrdersView } from "./PreOrdersView";
40
- export type { PreOrdersViewProps } from "./PreOrdersView";
41
39
  export { PreOrderDetailView } from "./PreOrderDetailView";
42
40
  export type { PreOrderDetailViewProps } from "./PreOrderDetailView";
43
41
  export { ProductInfo } from "./ProductInfo";
@@ -7,6 +7,7 @@ export { PrizeDrawItemsEditor } from "./PrizeDrawItemsEditor";
7
7
  export { PrizeDrawCollage } from "./PrizeDrawCollage";
8
8
  export { PrizeRevealModal } from "./PrizeRevealModal";
9
9
  export { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
10
+ export { MarketplaceBundleCard } from "./MarketplaceBundleCard";
10
11
  export { PrizeDrawsIndexListing } from "./PrizeDrawsIndexListing";
11
12
  // PrizeDrawsListingView + PrizeDrawDetailPageView are server components that
12
13
  // import productRepository (→ firebase-admin). Re-exporting them from this
@@ -20,10 +21,8 @@ export { FeatureBadge, FeatureBadgeList } from "./FeatureBadge";
20
21
  export { ProductFeaturesProvider, useProductFeatures, } from "./ProductFeaturesContext";
21
22
  export { BuyBar } from "./BuyBar";
22
23
  export { ProductsView } from "./ProductsView";
23
- export { AuctionsView } from "./AuctionsView";
24
24
  export { ProductDetailView } from "./ProductDetailView";
25
25
  export { AuctionDetailView } from "./AuctionDetailView";
26
- export { PreOrdersView } from "./PreOrdersView";
27
26
  export { PreOrderDetailView } from "./PreOrderDetailView";
28
27
  export { ProductInfo } from "./ProductInfo";
29
28
  export { ProductTabs } from "./ProductTabs";
@@ -2,6 +2,9 @@ import type { CouponItem } from "../types";
2
2
  export interface CouponCardLabels {
3
3
  copy?: string;
4
4
  copied?: string;
5
+ claim?: string;
6
+ claiming?: string;
7
+ claimed?: string;
5
8
  expires?: string;
6
9
  minOrder?: string;
7
10
  off?: string;
@@ -29,6 +32,13 @@ interface CouponCardProps {
29
32
  onEdit?: (id: string) => void;
30
33
  onToggleActive?: (id: string, currentlyActive: boolean) => void | Promise<void>;
31
34
  onDelete?: (id: string) => void | Promise<void>;
35
+ /**
36
+ * Hide the public Claim CTA. Defaults to false. Set true on admin/CRUD
37
+ * surfaces where the user is managing coupons, not redeeming them.
38
+ */
39
+ hideClaim?: boolean;
40
+ /** Source enum forwarded to /api/user/coupons/claim for analytics. */
41
+ claimSource?: "manual" | "promo" | "spin" | "raffle" | "prize-draw";
32
42
  }
33
- export declare function CouponCard({ coupon, labels: labelsProp, onCopy, className, selectable, isSelected, onSelect, onEdit, onToggleActive, onDelete, }: CouponCardProps): import("react/jsx-runtime").JSX.Element;
43
+ export declare function CouponCard({ coupon, labels: labelsProp, onCopy, className, selectable, isSelected, onSelect, onEdit, onToggleActive, onDelete, hideClaim, claimSource, }: CouponCardProps): import("react/jsx-runtime").JSX.Element;
34
44
  export {};
@@ -68,6 +68,9 @@ function formatDiscount(n, labels) {
68
68
  const DEFAULT_LABELS = {
69
69
  copy: "Copy",
70
70
  copied: "Copied!",
71
+ claim: "Claim",
72
+ claiming: "Claiming…",
73
+ claimed: "Use →",
71
74
  expires: "Expires",
72
75
  minOrder: "Min order",
73
76
  off: "OFF",
@@ -81,16 +84,20 @@ const DEFAULT_LABELS = {
81
84
  delete: "Delete",
82
85
  deleteConfirm: "Delete this coupon? This cannot be undone.",
83
86
  };
84
- export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "", selectable = false, isSelected = false, onSelect, onEdit, onToggleActive, onDelete, }) {
87
+ export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "", selectable = false, isSelected = false, onSelect, onEdit, onToggleActive, onDelete, hideClaim, claimSource = "manual", }) {
85
88
  const labels = { ...DEFAULT_LABELS, ...labelsProp };
86
89
  const n = normalize(coupon);
87
90
  const colors = TYPE_COLORS[n.type] ?? TYPE_COLORS.percentage;
88
91
  const [copied, setCopied] = useState(false);
92
+ const [claiming, setClaiming] = useState(false);
89
93
  const [busy, setBusy] = useState(null);
90
94
  const longPress = useLongPress(() => onSelect?.(n.id, !isSelected));
91
95
  const expiry = formatDateSafe(n.expiresAt);
92
96
  const discountLabel = formatDiscount(n, labels);
93
97
  const hasAdminActions = Boolean(onEdit || onToggleActive || onDelete);
98
+ // Show Claim by default on public surfaces; hide automatically when this
99
+ // card is being rendered with admin/CRUD actions wired.
100
+ const showClaim = hideClaim === undefined ? !hasAdminActions : !hideClaim;
94
101
  const handleCopy = () => {
95
102
  if (!n.code)
96
103
  return;
@@ -99,6 +106,33 @@ export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "",
99
106
  onCopy?.(n.code);
100
107
  setTimeout(() => setCopied(false), 2000);
101
108
  };
109
+ const handleClaim = async () => {
110
+ if (!n.code || claiming)
111
+ return;
112
+ setClaiming(true);
113
+ try {
114
+ // Best-effort: claim into the wallet (anon users get 401, which we
115
+ // ignore so the deep-link still works), then jump to cart with the
116
+ // code pre-filled.
117
+ try {
118
+ await fetch("/api/user/coupons/claim", {
119
+ method: "POST",
120
+ headers: { "Content-Type": "application/json" },
121
+ credentials: "include",
122
+ body: JSON.stringify({ couponCode: n.code, source: claimSource }),
123
+ });
124
+ }
125
+ catch {
126
+ /* anon/network — non-fatal */
127
+ }
128
+ if (typeof window !== "undefined") {
129
+ window.location.href = `/cart?coupon=${encodeURIComponent(n.code)}`;
130
+ }
131
+ }
132
+ finally {
133
+ setClaiming(false);
134
+ }
135
+ };
102
136
  const handleToggle = async () => {
103
137
  if (!onToggleActive)
104
138
  return;
@@ -125,7 +159,7 @@ export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "",
125
159
  };
126
160
  return (_jsxs(Div, { className: `group relative flex h-full flex-col rounded-xl border-2 p-4 ${colors.card} ${isSelected ? "ring-2 ring-primary" : ""} ${!n.isActive ? "opacity-70" : ""} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(n.id, !isSelected); }, label: isSelected ? "Deselect coupon" : "Select coupon", position: "top-2 right-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), (hasAdminActions || n.scope) && (_jsxs(Row, { gap: "xs", className: "mb-2 flex-wrap", children: [_jsx(Span, { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${n.isActive
127
161
  ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
128
- : "bg-zinc-200 text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300"}`, children: n.isActive ? labels.active : labels.inactive }), n.scope && (_jsx(Span, { className: "inline-flex items-center rounded-full bg-zinc-200/70 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-700 dark:bg-zinc-700/60 dark:text-zinc-200", children: n.scope }))] })), _jsxs(Div, { className: "mb-3", children: [_jsx(Text, { className: "text-2xl font-extrabold tracking-tight leading-none", children: discountLabel }), n.name && (_jsx(Text, { className: "text-sm mt-0.5 font-medium opacity-80", children: n.name }))] }), n.description && (_jsx(Text, { className: "text-xs opacity-60 mb-3", children: n.description })), _jsxs("div", { className: `flex items-center gap-2 rounded-lg border border-dashed px-3 py-2 ${colors.code}`, children: [_jsx("span", { className: "flex-1 font-mono text-sm font-bold tracking-widest uppercase select-all", children: n.code || "—" }), n.code && (_jsx("button", { type: "button", onClick: handleCopy, className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-semibold transition-all hover:opacity-80 active:scale-95 bg-white/60 dark:bg-black/20 border border-current/20", "aria-label": "Copy coupon code", children: copied ? labels.copied : labels.copy }))] }), _jsxs(Row, { wrap: true, gap: "sm", className: "text-xs opacity-60 mt-2", children: [n.minOrderAmount ? (_jsxs(Span, { children: [labels.minOrder, ": \u20B9", (n.minOrderAmount / 100).toFixed(0)] })) : null, expiry && (_jsxs(Span, { children: [labels.expires, ": ", expiry] })), n.usageLimit ? (_jsxs(Span, { children: [n.usageCount, "/", n.usageLimit, " ", labels.used] })) : n.usageCount > 0 ? (_jsxs(Span, { children: [n.usageCount, " ", labels.used] })) : null] }), hasAdminActions && (_jsxs(Row, { gap: "xs", className: "mt-auto pt-3 justify-end", children: [onEdit && (_jsx("button", { type: "button", onClick: () => onEdit(n.id), title: labels.edit, "aria-label": labels.edit, className: "rounded-lg p-1.5 text-zinc-500 hover:bg-white/60 dark:hover:bg-black/30 hover:text-zinc-800 dark:hover:text-zinc-200 transition-colors", children: _jsx(Pencil, { className: "h-4 w-4" }) })), onToggleActive && (_jsx("button", { type: "button", onClick: handleToggle, disabled: busy === "toggle", title: n.isActive ? labels.deactivate : labels.activate, "aria-label": n.isActive ? labels.deactivate : labels.activate, className: "rounded-lg p-1.5 text-zinc-500 hover:bg-white/60 dark:hover:bg-black/30 transition-colors disabled:opacity-50", children: n.isActive
162
+ : "bg-zinc-200 text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300"}`, children: n.isActive ? labels.active : labels.inactive }), n.scope && (_jsx(Span, { className: "inline-flex items-center rounded-full bg-zinc-200/70 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-700 dark:bg-zinc-700/60 dark:text-zinc-200", children: n.scope }))] })), _jsxs(Div, { className: "mb-3", children: [_jsx(Text, { className: "text-2xl font-extrabold tracking-tight leading-none", children: discountLabel }), n.name && (_jsx(Text, { className: "text-sm mt-0.5 font-medium opacity-80", children: n.name }))] }), n.description && (_jsx(Text, { className: "text-xs opacity-60 mb-3", children: n.description })), _jsxs("div", { className: `flex items-center gap-2 rounded-lg border border-dashed px-3 py-2 ${colors.code}`, children: [_jsx("span", { className: "flex-1 font-mono text-sm font-bold tracking-widest uppercase select-all", children: n.code || "—" }), n.code && (_jsx("button", { type: "button", onClick: handleCopy, className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-semibold transition-all hover:opacity-80 active:scale-95 bg-white/60 dark:bg-black/20 border border-current/20", "aria-label": "Copy coupon code", children: copied ? labels.copied : labels.copy }))] }), showClaim && n.code && n.isActive && (_jsx("div", { className: "mt-2", children: _jsx("button", { type: "button", onClick: handleClaim, disabled: claiming, className: "w-full rounded-md px-3 py-1.5 text-xs font-semibold transition-all active:scale-95 bg-[var(--appkit-color-primary-700)] text-[var(--appkit-color-text-on-primary,#fff)] hover:bg-[var(--appkit-color-primary-800)] disabled:opacity-50", "aria-label": `Claim coupon ${n.code} and apply at checkout`, children: claiming ? labels.claiming : labels.claim }) })), _jsxs(Row, { wrap: true, gap: "sm", className: "text-xs opacity-60 mt-2", children: [n.minOrderAmount ? (_jsxs(Span, { children: [labels.minOrder, ": \u20B9", (n.minOrderAmount / 100).toFixed(0)] })) : null, expiry && (_jsxs(Span, { children: [labels.expires, ": ", expiry] })), n.usageLimit ? (_jsxs(Span, { children: [n.usageCount, "/", n.usageLimit, " ", labels.used] })) : n.usageCount > 0 ? (_jsxs(Span, { children: [n.usageCount, " ", labels.used] })) : null] }), hasAdminActions && (_jsxs(Row, { gap: "xs", className: "mt-auto pt-3 justify-end", children: [onEdit && (_jsx("button", { type: "button", onClick: () => onEdit(n.id), title: labels.edit, "aria-label": labels.edit, className: "rounded-lg p-1.5 text-zinc-500 hover:bg-white/60 dark:hover:bg-black/30 hover:text-zinc-800 dark:hover:text-zinc-200 transition-colors", children: _jsx(Pencil, { className: "h-4 w-4" }) })), onToggleActive && (_jsx("button", { type: "button", onClick: handleToggle, disabled: busy === "toggle", title: n.isActive ? labels.deactivate : labels.activate, "aria-label": n.isActive ? labels.deactivate : labels.activate, className: "rounded-lg p-1.5 text-zinc-500 hover:bg-white/60 dark:hover:bg-black/30 transition-colors disabled:opacity-50", children: n.isActive
129
163
  ? _jsx(ToggleRight, { className: "h-4 w-4 text-emerald-600 dark:text-emerald-400" })
130
164
  : _jsx(ToggleLeft, { className: "h-4 w-4" }) })), onDelete && (_jsx("button", { type: "button", onClick: handleDelete, disabled: busy === "delete", title: labels.delete, "aria-label": labels.delete, className: "rounded-lg p-1.5 text-zinc-500 hover:bg-red-50 dark:hover:bg-red-950/40 hover:text-red-600 dark:hover:text-red-400 transition-colors disabled:opacity-50", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }))] }));
131
165
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Claimed Coupons Repository — plan §10
3
+ *
4
+ * One row per (userId, couponCode). Top-level collection so the user wallet
5
+ * page is a single indexed read. Status is lazily flipped to "expired" on
6
+ * read when `expiresAt < now`. Soft-delete only (status stays, no destroy).
7
+ */
8
+ import { BaseRepository } from "../../../providers/db-firebase";
9
+ import { type ClaimedCouponDocument, type ClaimedCouponSource, type ClaimedCouponSnapshot } from "../schemas";
10
+ export interface ClaimedCouponCreateInput {
11
+ userId: string;
12
+ couponId: string;
13
+ couponCode: string;
14
+ source: ClaimedCouponSource;
15
+ couponSnapshot: ClaimedCouponSnapshot;
16
+ expiresAt?: Date | null;
17
+ }
18
+ export declare class ClaimedCouponsRepository extends BaseRepository<ClaimedCouponDocument> {
19
+ constructor();
20
+ findByUserAndCode(userId: string, couponCode: string): Promise<ClaimedCouponDocument | null>;
21
+ /** Idempotent claim — returns the existing claim if one already exists. */
22
+ claim(input: ClaimedCouponCreateInput): Promise<ClaimedCouponDocument>;
23
+ listForUser(userId: string): Promise<ClaimedCouponDocument[]>;
24
+ markUsed(userId: string, couponCode: string, orderId: string): Promise<void>;
25
+ softRemove(userId: string, couponCode: string): Promise<void>;
26
+ }
27
+ export declare const claimedCouponsRepository: ClaimedCouponsRepository;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Claimed Coupons Repository — plan §10
3
+ *
4
+ * One row per (userId, couponCode). Top-level collection so the user wallet
5
+ * page is a single indexed read. Status is lazily flipped to "expired" on
6
+ * read when `expiresAt < now`. Soft-delete only (status stays, no destroy).
7
+ */
8
+ import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase";
9
+ import { CLAIMED_COUPONS_COLLECTION, createClaimedCouponId, } from "../schemas";
10
+ import { DatabaseError } from "../../../errors";
11
+ export class ClaimedCouponsRepository extends BaseRepository {
12
+ constructor() {
13
+ super(CLAIMED_COUPONS_COLLECTION);
14
+ }
15
+ async findByUserAndCode(userId, couponCode) {
16
+ return this.findById(createClaimedCouponId(userId, couponCode));
17
+ }
18
+ /** Idempotent claim — returns the existing claim if one already exists. */
19
+ async claim(input) {
20
+ try {
21
+ const id = createClaimedCouponId(input.userId, input.couponCode);
22
+ const existing = await this.findById(id);
23
+ if (existing)
24
+ return existing;
25
+ const now = new Date();
26
+ const doc = {
27
+ id,
28
+ userId: input.userId,
29
+ couponId: input.couponId,
30
+ couponCode: input.couponCode.toUpperCase(),
31
+ source: input.source,
32
+ couponSnapshot: input.couponSnapshot,
33
+ status: "active",
34
+ expiresAt: input.expiresAt ?? null,
35
+ claimedAt: now,
36
+ updatedAt: now,
37
+ };
38
+ await this.db
39
+ .collection(this.collection)
40
+ .doc(id)
41
+ .set(prepareForFirestore(doc));
42
+ return doc;
43
+ }
44
+ catch (error) {
45
+ throw new DatabaseError("Failed to claim coupon", error);
46
+ }
47
+ }
48
+ async listForUser(userId) {
49
+ try {
50
+ const snap = await this.db
51
+ .collection(this.collection)
52
+ .where("userId", "==", userId)
53
+ .get();
54
+ const now = Date.now();
55
+ const rows = [];
56
+ for (const docSnap of snap.docs) {
57
+ const raw = this.mapDoc(docSnap);
58
+ // Lazy expire — flip status when validity has passed. Best-effort
59
+ // write-back; consumers see the corrected status immediately.
60
+ if (raw.status === "active" &&
61
+ raw.expiresAt &&
62
+ new Date(raw.expiresAt).getTime() < now) {
63
+ raw.status = "expired";
64
+ void this.db
65
+ .collection(this.collection)
66
+ .doc(raw.id)
67
+ .update({ status: "expired", updatedAt: new Date() })
68
+ .catch(() => {
69
+ /* best-effort */
70
+ });
71
+ }
72
+ rows.push(raw);
73
+ }
74
+ return rows;
75
+ }
76
+ catch (error) {
77
+ throw new DatabaseError("Failed to list claimed coupons", error);
78
+ }
79
+ }
80
+ async markUsed(userId, couponCode, orderId) {
81
+ const id = createClaimedCouponId(userId, couponCode);
82
+ try {
83
+ await this.db
84
+ .collection(this.collection)
85
+ .doc(id)
86
+ .update({
87
+ status: "used",
88
+ usedAt: new Date(),
89
+ usedOrderId: orderId,
90
+ updatedAt: new Date(),
91
+ });
92
+ }
93
+ catch (error) {
94
+ throw new DatabaseError("Failed to mark coupon used", error);
95
+ }
96
+ }
97
+ async softRemove(userId, couponCode) {
98
+ const id = createClaimedCouponId(userId, couponCode);
99
+ try {
100
+ // Soft-delete = bump status to expired with a marker. We never physically
101
+ // delete so the audit trail (claim source + dates) is preserved.
102
+ await this.db
103
+ .collection(this.collection)
104
+ .doc(id)
105
+ .update({
106
+ status: "expired",
107
+ updatedAt: new Date(),
108
+ });
109
+ }
110
+ catch (error) {
111
+ throw new DatabaseError("Failed to remove claimed coupon", error);
112
+ }
113
+ }
114
+ }
115
+ export const claimedCouponsRepository = new ClaimedCouponsRepository();
@@ -70,6 +70,40 @@ export interface CouponUsageDocument {
70
70
  lastUsedAt: Date;
71
71
  orders: string[];
72
72
  }
73
+ export type ClaimedCouponStatus = "active" | "expired" | "used";
74
+ export type ClaimedCouponSource = "manual" | "promo" | "spin" | "raffle" | "prize-draw";
75
+ export interface ClaimedCouponSnapshot {
76
+ name: string;
77
+ description?: string;
78
+ type: CouponType;
79
+ scope: "admin" | "seller";
80
+ storeId?: string;
81
+ discount: DiscountConfig;
82
+ restrictions: RestrictionsConfig;
83
+ }
84
+ export interface ClaimedCouponDocument {
85
+ id: string;
86
+ userId: string;
87
+ couponId: string;
88
+ couponCode: string;
89
+ /** Source surface that initiated the claim — useful for analytics + UX. */
90
+ source: ClaimedCouponSource;
91
+ /** Denormalised snapshot so the wallet list renders without joins. */
92
+ couponSnapshot: ClaimedCouponSnapshot;
93
+ /** Lifecycle: active → used after redemption, → expired when validity.endDate passes. */
94
+ status: ClaimedCouponStatus;
95
+ /** Mirrors coupon.validity.endDate; null when the coupon is open-ended. */
96
+ expiresAt?: Date | null;
97
+ /** Set when status transitions to "used". */
98
+ usedAt?: Date;
99
+ /** Set when status transitions to "used". */
100
+ usedOrderId?: string;
101
+ claimedAt: Date;
102
+ updatedAt: Date;
103
+ }
104
+ export declare const CLAIMED_COUPONS_COLLECTION: "claimedCoupons";
105
+ export declare const CLAIMED_COUPONS_INDEXED_FIELDS: readonly ["userId", "couponCode", "status", "expiresAt"];
106
+ export declare function createClaimedCouponId(userId: string, couponCode: string): string;
73
107
  export declare const COUPONS_COLLECTION: "coupons";
74
108
  export declare const COUPON_USAGE_SUBCOLLECTION: "couponUsage";
75
109
  export declare const COUPONS_INDEXED_FIELDS: readonly ["code", "validity.isActive", "validity.startDate", "validity.endDate", "type", "createdBy"];
@@ -2,6 +2,19 @@
2
2
  * Coupons/Promotions Firestore Document Types & Constants
3
3
  */
4
4
  import { generateCouponId } from "../../../utils/id-generators";
5
+ export const CLAIMED_COUPONS_COLLECTION = "claimedCoupons";
6
+ export const CLAIMED_COUPONS_INDEXED_FIELDS = [
7
+ "userId",
8
+ "couponCode",
9
+ "status",
10
+ "expiresAt",
11
+ ];
12
+ export function createClaimedCouponId(userId, couponCode) {
13
+ // Mirrors the wishlist/history "claimed-{userSlug}-{code}" prefix convention.
14
+ const userSlug = userId.replace(/^user-/, "").toLowerCase();
15
+ const code = couponCode.toLowerCase().replace(/[^a-z0-9]+/g, "-");
16
+ return `claimed-${userSlug}-${code}`;
17
+ }
5
18
  export const COUPONS_COLLECTION = "coupons";
6
19
  export const COUPON_USAGE_SUBCOLLECTION = "couponUsage";
7
20
  export const COUPONS_INDEXED_FIELDS = [
@@ -48,7 +61,7 @@ export const COUPON_TYPE_LABELS = {
48
61
  };
49
62
  export const DEFAULT_COUPON_DATA = {
50
63
  usage: { currentUsage: 0 },
51
- validity: { isActive: false, startDate: new Date(), endDate: undefined },
64
+ validity: { isActive: true, startDate: new Date(), endDate: undefined },
52
65
  restrictions: { firstTimeUserOnly: false, combineWithSellerCoupons: false },
53
66
  stats: { totalUses: 0, totalRevenue: 0, totalDiscount: 0 },
54
67
  };
@@ -6,8 +6,6 @@ export type { ReviewSummaryProps } from "./ReviewSummary";
6
6
  export { ReviewSummary } from "./ReviewSummary";
7
7
  export type { ViewReviewModalProps } from "./ReviewModal";
8
8
  export { ViewReviewModal } from "./ReviewModal";
9
- export { ReviewsListView } from "./ReviewsListView";
10
- export type { ReviewsListViewProps } from "./ReviewsListView";
11
9
  export { ReviewsIndexListing } from "./ReviewsIndexListing";
12
10
  export type { ReviewsIndexListingProps } from "./ReviewsIndexListing";
13
11
  export type { ReviewDetailPageViewProps } from "./ReviewDetailPageView";
@@ -2,5 +2,4 @@ export { ReviewCard, ReviewsList } from "./ReviewsList";
2
2
  export { ReviewFilters, REVIEW_SORT_OPTIONS, REVIEW_FILTER_KEYS, REVIEW_ADMIN_SORT_OPTIONS, REVIEW_SELLER_SORT_OPTIONS, REVIEW_PUBLIC_SORT_OPTIONS, getReviewFilterKeys, getReviewSortOptions, } from "./ReviewFilters";
3
3
  export { ReviewSummary } from "./ReviewSummary";
4
4
  export { ViewReviewModal } from "./ReviewModal";
5
- export { ReviewsListView } from "./ReviewsListView";
6
5
  export { ReviewsIndexListing } from "./ReviewsIndexListing";
@@ -4,14 +4,16 @@ import { InlineCreateSelect } from "../../../ui/components/InlineCreateSelect";
4
4
  import { BrandQuickCreateForm } from "../../admin/components/BrandQuickCreateForm";
5
5
  import { apiClient } from "../../../http";
6
6
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
7
- async function loadBrandOptions(query, page) {
7
+ // Admin: paginated search via /api/admin/brands (admin/mod only).
8
+ // Response via successResponse({ data: items, total }) → apiClient unwraps to { data, total }.
9
+ async function loadAdminBrandOptions(query, page) {
8
10
  const params = new URLSearchParams({
9
11
  q: query,
10
12
  page: String(page),
11
13
  pageSize: "20",
12
14
  });
13
15
  const res = await apiClient.get(`${ADMIN_ENDPOINTS.BRANDS}?${params}`);
14
- const raw = res.brands ?? res.items ?? [];
16
+ const raw = res.data ?? res.items ?? [];
15
17
  const items = raw.map((b) => ({
16
18
  value: String(b.id ?? ""),
17
19
  label: String(b.name ?? ""),
@@ -19,8 +21,26 @@ async function loadBrandOptions(query, page) {
19
21
  const total = res.total ?? items.length;
20
22
  return { items, hasMore: page * 20 < total, nextPage: page + 1 };
21
23
  }
24
+ // Seller/public: unauthenticated /api/brands endpoint returns { items, total }.
25
+ // Client-side query filtering so all sellers can access the brand list.
26
+ async function loadPublicBrandOptions(query, page) {
27
+ const params = new URLSearchParams({
28
+ page: String(page),
29
+ pageSize: "100",
30
+ active: "true",
31
+ });
32
+ const res = await apiClient.get(`/api/brands?${params}`);
33
+ const all = (res.items ?? []).map((b) => ({
34
+ value: String(b.id ?? ""),
35
+ label: String(b.name ?? ""),
36
+ }));
37
+ const q = query.trim().toLowerCase();
38
+ const items = q ? all.filter((b) => b.label.toLowerCase().includes(q)) : all;
39
+ return { items, hasMore: false, nextPage: page + 1 };
40
+ }
22
41
  export function BrandInlineSelect({ value, onChange, placeholder = "Search brands…", disabled, allowCreate = true, }) {
23
- return (_jsx(InlineCreateSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadBrandOptions, placeholder: placeholder, disabled: disabled, createLabel: "brand", drawerTitle: "Create Brand", renderCreateForm: allowCreate
42
+ const loadOptions = allowCreate ? loadAdminBrandOptions : loadPublicBrandOptions;
43
+ return (_jsx(InlineCreateSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadOptions, placeholder: placeholder, disabled: disabled, createLabel: "brand", drawerTitle: "Create Brand", renderCreateForm: allowCreate
24
44
  ? ({ onCreated, onCancel }) => (_jsx(BrandQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel }))
25
45
  : undefined }));
26
46
  }
@@ -4,7 +4,9 @@ import { InlineCreateSelect } from "../../../ui/components/InlineCreateSelect";
4
4
  import { CategoryQuickCreateForm } from "../../admin/components/CategoryQuickCreateForm";
5
5
  import { apiClient } from "../../../http";
6
6
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
7
- async function loadCategoryOptions(query, page) {
7
+ // Admin: paginated + server-side search via /api/admin/categories (admin/mod only).
8
+ // Response via successResponse({ data: items, total }) → apiClient unwraps to { data, total, ... }.
9
+ async function loadAdminCategoryOptions(query, page) {
8
10
  const params = new URLSearchParams({
9
11
  q: query,
10
12
  page: String(page),
@@ -12,15 +14,30 @@ async function loadCategoryOptions(query, page) {
12
14
  flat: "true",
13
15
  });
14
16
  const res = await apiClient.get(`${ADMIN_ENDPOINTS.CATEGORIES}?${params}`);
15
- const items = (res.items ?? []).map((c) => ({
17
+ const items = (res.data ?? []).map((c) => ({
16
18
  value: String(c.id ?? ""),
17
19
  label: String(c.name ?? ""),
18
20
  }));
19
21
  const total = res.total ?? items.length;
20
22
  return { items, hasMore: page * 20 < total, nextPage: page + 1 };
21
23
  }
24
+ // Seller: fetch all flat categories from the public unauthenticated endpoint,
25
+ // filter client-side by query. Public endpoint returns { success, data: CategoryItem[] }.
26
+ async function loadPublicCategoryOptions(query, _page) {
27
+ const params = new URLSearchParams({ flat: "true", pageSize: "200" });
28
+ const res = await fetch(`/api/categories?${params}`, { credentials: "include" });
29
+ const json = await res.json().catch(() => ({}));
30
+ const all = (json.data ?? []).map((c) => ({
31
+ value: String(c.id ?? ""),
32
+ label: String(c.name ?? ""),
33
+ }));
34
+ const q = query.trim().toLowerCase();
35
+ const items = q ? all.filter((c) => c.label.toLowerCase().includes(q)) : all;
36
+ return { items, hasMore: false, nextPage: 2 };
37
+ }
22
38
  export function CategoryInlineSelect({ value, onChange, placeholder = "Search categories…", disabled, allowCreate = false, }) {
23
- return (_jsx(InlineCreateSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadCategoryOptions, placeholder: placeholder, disabled: disabled, createLabel: "category", drawerTitle: "Create Category", renderCreateForm: allowCreate
39
+ const loadOptions = allowCreate ? loadAdminCategoryOptions : loadPublicCategoryOptions;
40
+ return (_jsx(InlineCreateSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadOptions, placeholder: placeholder, disabled: disabled, createLabel: "category", drawerTitle: "Create Category", renderCreateForm: allowCreate
24
41
  ? ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel }))
25
42
  : undefined }));
26
43
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useState } from "react";
4
4
  import { MapPin, Pencil, Plus, Trash2, Star } from "lucide-react";
5
- import { Button, Div, Heading, SideDrawer, Text } from "../../../ui";
5
+ import { Button, Div, Heading, Row, SideDrawer, Text } from "../../../ui";
6
6
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
7
7
  const INPUT_CLS = "w-full rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 dark:placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-[var(--appkit-color-primary)]";
8
8
  const CLS_GRID_2_COL = "grid grid-cols-2 gap-3";
@@ -48,6 +48,7 @@ function AddressCard({ address, onEdit, onDelete, }) {
48
48
  // ---------------------------------------------------------------------------
49
49
  export function SellerAddressesView({ apiBase = SELLER_ENDPOINTS.STORE_ADDRESSES, }) {
50
50
  const [addresses, setAddresses] = useState([]);
51
+ const [listView, setListView] = useState("table");
51
52
  const [isLoading, setIsLoading] = useState(true);
52
53
  const [errorMessage, setErrorMessage] = useState(null);
53
54
  const [drawerOpen, setDrawerOpen] = useState(false);
@@ -143,5 +144,5 @@ export function SellerAddressesView({ apiBase = SELLER_ENDPOINTS.STORE_ADDRESSES
143
144
  };
144
145
  const set = (key, value) => setDraft((p) => ({ ...p, [key]: value }));
145
146
  const handleTextField = (key) => (e) => set(key, e.target.value);
146
- return (_jsxs("div", { className: "min-h-screen", children: [_jsxs("div", { className: "sticky z-10 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-4 py-3 flex items-center justify-between", style: { top: "var(--header-height, 0px)" }, children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Pickup Addresses" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: "Manage your store's pickup and return locations" })] }), _jsxs(Button, { size: "sm", onClick: openAdd, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "Add Address" })] })] }), _jsxs("div", { className: "py-6 px-4 sm:px-6 max-w-2xl", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx("div", { className: "flex justify-center py-16", children: _jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-[var(--appkit-color-primary)] border-t-transparent" }) })) : addresses.length === 0 ? (_jsxs(Div, { className: "rounded-xl border-2 border-dashed border-zinc-200 dark:border-slate-700 py-16 flex flex-col items-center gap-3", children: [_jsx(MapPin, { className: "h-8 w-8 text-zinc-300 dark:text-slate-600" }), _jsx(Text, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "No pickup addresses yet" }), _jsx(Button, { size: "sm", variant: "outline", onClick: openAdd, children: "Add your first address" })] })) : (_jsx("div", { className: "grid gap-3", children: addresses.map((addr) => (_jsx("div", { className: deletingId === addr.id ? "opacity-50 pointer-events-none" : "", children: _jsx(AddressCard, { address: addr, onEdit: () => openEdit(addr), onDelete: () => handleDelete(addr) }) }, addr.id))) }))] }), _jsx(SideDrawer, { isOpen: drawerOpen, onClose: closeDrawer, title: editingId ? "Edit Address" : "Add Address", footer: _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", onClick: closeDrawer, className: "flex-1", children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: saving, className: "flex-1", children: saving ? "Saving…" : editingId ? "Save Changes" : "Add Address" })] }), children: _jsxs("div", { className: "space-y-4 py-1", children: [saveError && (_jsx(Div, { className: "rounded-lg border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-3 py-2 text-sm text-red-700 dark:text-red-200", children: saveError })), _jsx(Field, { label: "Label *", hint: "e.g. Warehouse, Shop, Home", children: _jsx("input", { type: "text", value: draft.label, onChange: handleTextField("label"), placeholder: "Warehouse", maxLength: 60, className: INPUT_CLS }) }), _jsxs("div", { className: CLS_GRID_2_COL, children: [_jsx(Field, { label: "Full Name *", children: _jsx("input", { type: "text", value: draft.fullName, onChange: handleTextField("fullName"), placeholder: "Ravi Kumar", maxLength: 100, className: INPUT_CLS }) }), _jsx(Field, { label: "Phone *", children: _jsx("input", { type: "tel", value: draft.phone, onChange: handleTextField("phone"), placeholder: "+91 98765 43210", maxLength: 20, className: INPUT_CLS }) })] }), _jsx(Field, { label: "Address Line 1 *", children: _jsx("input", { type: "text", value: draft.addressLine1, onChange: handleTextField("addressLine1"), placeholder: "Shop 12, Main Market", maxLength: 200, className: INPUT_CLS }) }), _jsx(Field, { label: "Address Line 2", children: _jsx("input", { type: "text", value: draft.addressLine2, onChange: handleTextField("addressLine2"), placeholder: "Building / Floor (optional)", maxLength: 200, className: INPUT_CLS }) }), _jsx(Field, { label: "Landmark", children: _jsx("input", { type: "text", value: draft.landmark, onChange: handleTextField("landmark"), placeholder: "Near metro station (optional)", maxLength: 100, className: INPUT_CLS }) }), _jsxs("div", { className: CLS_GRID_2_COL, children: [_jsx(Field, { label: "City *", children: _jsx("input", { type: "text", value: draft.city, onChange: handleTextField("city"), placeholder: "Mumbai", maxLength: 100, className: INPUT_CLS }) }), _jsx(Field, { label: "State *", children: _jsx("input", { type: "text", value: draft.state, onChange: handleTextField("state"), placeholder: "Maharashtra", maxLength: 100, className: INPUT_CLS }) })] }), _jsxs("div", { className: CLS_GRID_2_COL, children: [_jsx(Field, { label: "Postal Code *", children: _jsx("input", { type: "text", value: draft.postalCode, onChange: handleTextField("postalCode"), placeholder: "400001", maxLength: 10, className: INPUT_CLS }) }), _jsx(Field, { label: "Country *", children: _jsx("input", { type: "text", value: draft.country, onChange: handleTextField("country"), placeholder: "India", maxLength: 60, className: INPUT_CLS }) })] }), _jsxs("label", { className: "flex items-center gap-3 cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: draft.isDefault, onChange: (e) => set("isDefault", e.target.checked), className: "h-4 w-4 rounded border-zinc-300 dark:border-slate-600 text-[var(--appkit-color-primary)] focus:ring-[var(--appkit-color-primary)]" }), _jsx("span", { className: "text-sm text-zinc-700 dark:text-zinc-300", children: "Set as default pickup address" })] })] }) })] }));
147
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsxs("div", { className: "sticky z-10 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-4 py-3 flex items-center justify-between", style: { top: "var(--header-height, 0px)" }, children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Pickup Addresses" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: "Manage your store's pickup and return locations" })] }), _jsxs(Button, { size: "sm", onClick: openAdd, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "Add Address" })] })] }), _jsxs("div", { className: "py-6 px-4 sm:px-6 max-w-2xl", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx("div", { className: "flex justify-center py-16", children: _jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-[var(--appkit-color-primary)] border-t-transparent" }) })) : addresses.length === 0 ? (_jsxs(Div, { className: "rounded-xl border-2 border-dashed border-zinc-200 dark:border-slate-700 py-16 flex flex-col items-center gap-3", children: [_jsx(MapPin, { className: "h-8 w-8 text-zinc-300 dark:text-slate-600" }), _jsx(Text, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "No pickup addresses yet" }), _jsx(Button, { size: "sm", variant: "outline", onClick: openAdd, children: "Add your first address" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Row, { className: "justify-end mb-2", children: [_jsx(Button, { size: "sm", variant: listView === "table" ? "primary" : "ghost", onClick: () => setListView("table"), children: "Table" }), _jsx(Button, { size: "sm", variant: listView === "cards" ? "primary" : "ghost", onClick: () => setListView("cards"), children: "Cards" })] }), listView === "cards" ? (_jsx("div", { className: "grid gap-3", children: addresses.map((addr) => (_jsx("div", { className: deletingId === addr.id ? "opacity-50 pointer-events-none" : "", children: _jsx(AddressCard, { address: addr, onEdit: () => openEdit(addr), onDelete: () => handleDelete(addr) }) }, addr.id))) })) : (_jsx("div", { className: "overflow-x-auto rounded-lg border border-zinc-200 dark:border-slate-700", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-zinc-50 dark:bg-slate-800", children: _jsxs("tr", { children: [_jsx("th", { className: "text-left px-3 py-2 font-semibold", children: "Label" }), _jsx("th", { className: "text-left px-3 py-2 font-semibold", children: "Name" }), _jsx("th", { className: "text-left px-3 py-2 font-semibold", children: "City" }), _jsx("th", { className: "text-left px-3 py-2 font-semibold", children: "Phone" }), _jsx("th", { className: "text-right px-3 py-2 font-semibold", children: "Actions" })] }) }), _jsx("tbody", { children: addresses.map((addr) => (_jsxs("tr", { className: `border-t border-zinc-100 dark:border-slate-700 ${deletingId === addr.id ? "opacity-50" : ""}`, children: [_jsx("td", { className: "px-3 py-2", children: addr.label }), _jsx("td", { className: "px-3 py-2", children: addr.fullName }), _jsxs("td", { className: "px-3 py-2", children: [addr.city, ", ", addr.state] }), _jsx("td", { className: "px-3 py-2 tabular-nums", children: addr.phone }), _jsx("td", { className: "px-3 py-2 text-right", children: _jsxs(Row, { className: "gap-1 justify-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => openEdit(addr), children: "Edit" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => handleDelete(addr), children: "Delete" })] }) })] }, addr.id))) })] }) }))] }))] }), _jsx(SideDrawer, { isOpen: drawerOpen, onClose: closeDrawer, title: editingId ? "Edit Address" : "Add Address", footer: _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", onClick: closeDrawer, className: "flex-1", children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: saving, className: "flex-1", children: saving ? "Saving…" : editingId ? "Save Changes" : "Add Address" })] }), children: _jsxs("div", { className: "space-y-4 py-1", children: [saveError && (_jsx(Div, { className: "rounded-lg border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-3 py-2 text-sm text-red-700 dark:text-red-200", children: saveError })), _jsx(Field, { label: "Label *", hint: "e.g. Warehouse, Shop, Home", children: _jsx("input", { type: "text", value: draft.label, onChange: handleTextField("label"), placeholder: "Warehouse", maxLength: 60, className: INPUT_CLS }) }), _jsxs("div", { className: CLS_GRID_2_COL, children: [_jsx(Field, { label: "Full Name *", children: _jsx("input", { type: "text", value: draft.fullName, onChange: handleTextField("fullName"), placeholder: "Ravi Kumar", maxLength: 100, className: INPUT_CLS }) }), _jsx(Field, { label: "Phone *", children: _jsx("input", { type: "tel", value: draft.phone, onChange: handleTextField("phone"), placeholder: "+91 98765 43210", maxLength: 20, className: INPUT_CLS }) })] }), _jsx(Field, { label: "Address Line 1 *", children: _jsx("input", { type: "text", value: draft.addressLine1, onChange: handleTextField("addressLine1"), placeholder: "Shop 12, Main Market", maxLength: 200, className: INPUT_CLS }) }), _jsx(Field, { label: "Address Line 2", children: _jsx("input", { type: "text", value: draft.addressLine2, onChange: handleTextField("addressLine2"), placeholder: "Building / Floor (optional)", maxLength: 200, className: INPUT_CLS }) }), _jsx(Field, { label: "Landmark", children: _jsx("input", { type: "text", value: draft.landmark, onChange: handleTextField("landmark"), placeholder: "Near metro station (optional)", maxLength: 100, className: INPUT_CLS }) }), _jsxs("div", { className: CLS_GRID_2_COL, children: [_jsx(Field, { label: "City *", children: _jsx("input", { type: "text", value: draft.city, onChange: handleTextField("city"), placeholder: "Mumbai", maxLength: 100, className: INPUT_CLS }) }), _jsx(Field, { label: "State *", children: _jsx("input", { type: "text", value: draft.state, onChange: handleTextField("state"), placeholder: "Maharashtra", maxLength: 100, className: INPUT_CLS }) })] }), _jsxs("div", { className: CLS_GRID_2_COL, children: [_jsx(Field, { label: "Postal Code *", children: _jsx("input", { type: "text", value: draft.postalCode, onChange: handleTextField("postalCode"), placeholder: "400001", maxLength: 10, className: INPUT_CLS }) }), _jsx(Field, { label: "Country *", children: _jsx("input", { type: "text", value: draft.country, onChange: handleTextField("country"), placeholder: "India", maxLength: 60, className: INPUT_CLS }) })] }), _jsxs("label", { className: "flex items-center gap-3 cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: draft.isDefault, onChange: (e) => set("isDefault", e.target.checked), className: "h-4 w-4 rounded border-zinc-300 dark:border-slate-600 text-[var(--appkit-color-primary)] focus:ring-[var(--appkit-color-primary)]" }), _jsx("span", { className: "text-sm text-zinc-700 dark:text-zinc-300", children: "Set as default pickup address" })] })] }) })] }));
147
148
  }