@mohasinac/appkit 2.7.36 → 2.7.38

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 (144) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +1 -1
  2. package/dist/_internal/client/features/seller/print-center/InventoryLabel.d.ts +12 -0
  3. package/dist/_internal/client/features/seller/print-center/InventoryLabel.js +49 -0
  4. package/dist/_internal/client/features/seller/print-center/LabelDesignPicker.d.ts +7 -0
  5. package/dist/_internal/client/features/seller/print-center/LabelDesignPicker.js +32 -0
  6. package/dist/_internal/client/features/seller/print-center/OrderPackingLabel.d.ts +28 -0
  7. package/dist/_internal/client/features/seller/print-center/OrderPackingLabel.js +33 -0
  8. package/dist/_internal/client/features/seller/print-center/PhysicalLocationModal.d.ts +12 -0
  9. package/dist/_internal/client/features/seller/print-center/PhysicalLocationModal.js +30 -0
  10. package/dist/_internal/client/features/seller/print-center/PrintCenterView.d.ts +12 -0
  11. package/dist/_internal/client/features/seller/print-center/PrintCenterView.js +102 -0
  12. package/dist/_internal/client/features/seller/print-center/PrintGrid.d.ts +12 -0
  13. package/dist/_internal/client/features/seller/print-center/PrintGrid.js +13 -0
  14. package/dist/_internal/client/features/seller/print-center/StoreCard.d.ts +19 -0
  15. package/dist/_internal/client/features/seller/print-center/StoreCard.js +30 -0
  16. package/dist/_internal/client/features/seller/print-center/WebsiteCard.d.ts +11 -0
  17. package/dist/_internal/client/features/seller/print-center/WebsiteCard.js +25 -0
  18. package/dist/_internal/client/features/seller/print-center/index.d.ts +11 -0
  19. package/dist/_internal/client/features/seller/print-center/index.js +10 -0
  20. package/dist/_internal/client/features/seller/print-center/types.d.ts +20 -0
  21. package/dist/_internal/client/features/seller/print-center/types.js +20 -0
  22. package/dist/_internal/client/features/seller/print-center/useInventoryPdf.d.ts +18 -0
  23. package/dist/_internal/client/features/seller/print-center/useInventoryPdf.js +76 -0
  24. package/dist/_internal/server/jobs/core/draftPrune.d.ts +2 -0
  25. package/dist/_internal/server/jobs/core/draftPrune.js +15 -0
  26. package/dist/_internal/server/jobs/core/onBidPlaced.js +1 -1
  27. package/dist/_internal/server/jobs/core/promotions.d.ts +1 -0
  28. package/dist/_internal/server/jobs/core/promotions.js +21 -6
  29. package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +18 -1
  30. package/dist/_internal/server/jobs/handlers/draftPrune.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/draftPrune.js +2 -0
  32. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  33. package/dist/_internal/server/jobs/handlers/index.js +2 -0
  34. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +15 -1
  35. package/dist/_internal/shared/actions/action-registry.js +32 -0
  36. package/dist/_internal/shared/features/blog/schema.d.ts +8 -8
  37. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +10 -10
  38. package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
  39. package/dist/_internal/shared/features/products/schema.d.ts +8 -8
  40. package/dist/_internal/shared/features/promotions/schema.d.ts +6 -6
  41. package/dist/_internal/shared/features/reviews/schema.d.ts +2 -2
  42. package/dist/client.d.ts +13 -1
  43. package/dist/client.js +7 -1
  44. package/dist/configs/next.js +1 -0
  45. package/dist/constants/api-endpoints.d.ts +6 -0
  46. package/dist/constants/api-endpoints.js +2 -0
  47. package/dist/core/server-action.d.ts +4 -0
  48. package/dist/errors/error-codes.d.ts +18 -0
  49. package/dist/errors/error-codes.js +18 -0
  50. package/dist/errors/index.d.ts +1 -1
  51. package/dist/errors/index.js +1 -1
  52. package/dist/errors/messages.d.ts +1 -0
  53. package/dist/errors/messages.js +1 -0
  54. package/dist/features/account/components/UserSidebar.js +1 -1
  55. package/dist/features/account/schemas/index.d.ts +12 -12
  56. package/dist/features/admin/components/AdminSidebar.js +1 -1
  57. package/dist/features/auctions/actions/bid-actions.js +27 -11
  58. package/dist/features/auctions/components/PlaceBidFormClient.js +10 -2
  59. package/dist/features/auctions/repository/bid.repository.d.ts +7 -0
  60. package/dist/features/auctions/repository/bid.repository.js +16 -0
  61. package/dist/features/auctions/schemas/index.d.ts +18 -18
  62. package/dist/features/auth/schemas/index.d.ts +2 -2
  63. package/dist/features/before-after/schemas/index.d.ts +2 -2
  64. package/dist/features/blog/actions/blog-actions.d.ts +32 -32
  65. package/dist/features/blog/components/BlogPostForm.js +1 -1
  66. package/dist/features/blog/schemas/index.d.ts +38 -38
  67. package/dist/features/categories/schemas/index.d.ts +10 -10
  68. package/dist/features/collections/schemas/index.d.ts +4 -4
  69. package/dist/features/consultation/schemas/index.d.ts +9 -9
  70. package/dist/features/corporate/schemas/index.d.ts +4 -4
  71. package/dist/features/events/schemas/index.d.ts +50 -50
  72. package/dist/features/faq/actions/faq-actions.d.ts +6 -6
  73. package/dist/features/faq/schemas/index.d.ts +8 -8
  74. package/dist/features/homepage/actions/homepage-section-actions.d.ts +4 -4
  75. package/dist/features/layout/AppLayoutShell.js +1 -1
  76. package/dist/features/layout/AutoBreadcrumbs.js +1 -1
  77. package/dist/features/media/AvatarUpload.js +1 -1
  78. package/dist/features/media/types/index.d.ts +4 -4
  79. package/dist/features/media/upload/ImageUpload.js +18 -9
  80. package/dist/features/media/upload/MediaUploadList.js +2 -2
  81. package/dist/features/orders/schemas/firestore.d.ts +6 -0
  82. package/dist/features/orders/schemas/index.d.ts +10 -10
  83. package/dist/features/orders/types/index.d.ts +5 -0
  84. package/dist/features/payments/schemas/index.d.ts +4 -4
  85. package/dist/features/pre-orders/schemas/index.d.ts +8 -8
  86. package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
  87. package/dist/features/products/components/ProductDetailActions.d.ts +16 -0
  88. package/dist/features/products/components/ProductDetailActions.js +94 -0
  89. package/dist/features/products/components/ProductDetailPageView.d.ts +16 -1
  90. package/dist/features/products/components/ProductDetailPageView.js +22 -3
  91. package/dist/features/products/components/ProductForm.js +20 -2
  92. package/dist/features/products/repository/products.repository.d.ts +3 -1
  93. package/dist/features/products/repository/products.repository.js +12 -1
  94. package/dist/features/products/schemas/firestore.d.ts +7 -0
  95. package/dist/features/products/schemas/index.d.ts +34 -34
  96. package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
  97. package/dist/features/products/types/index.d.ts +6 -0
  98. package/dist/features/promotions/schemas/index.d.ts +14 -14
  99. package/dist/features/reviews/schemas/index.d.ts +18 -18
  100. package/dist/features/seller/actions/offer-actions.js +10 -1
  101. package/dist/features/seller/components/PhysicalLocationModal.d.ts +12 -0
  102. package/dist/features/seller/components/PhysicalLocationModal.js +30 -0
  103. package/dist/features/seller/components/PrintCenterView.d.ts +40 -0
  104. package/dist/features/seller/components/PrintCenterView.js +6 -0
  105. package/dist/features/seller/components/SellerOrdersView.js +48 -3
  106. package/dist/features/seller/components/SellerProductShell.d.ts +1 -0
  107. package/dist/features/seller/components/SellerProductShell.js +26 -4
  108. package/dist/features/seller/components/SellerProductsView.js +54 -13
  109. package/dist/features/seller/components/SellerSidebar.js +1 -1
  110. package/dist/features/seller/components/index.d.ts +1 -0
  111. package/dist/features/seller/components/index.js +1 -0
  112. package/dist/features/seller/schemas/firestore.d.ts +2 -0
  113. package/dist/features/seller/schemas/firestore.js +1 -0
  114. package/dist/features/seller/schemas/index.d.ts +22 -22
  115. package/dist/features/shell/FormShell.js +1 -1
  116. package/dist/features/shell/StepForm.d.ts +3 -1
  117. package/dist/features/shell/StepForm.js +3 -3
  118. package/dist/features/stores/schemas/index.d.ts +4 -4
  119. package/dist/index.d.ts +9 -1
  120. package/dist/index.js +6 -1
  121. package/dist/next/routing/route-map.d.ts +8 -0
  122. package/dist/next/routing/route-map.js +3 -0
  123. package/dist/tailwind-utilities.css +1 -1
  124. package/dist/ui/components/RowActionMenu.js +45 -10
  125. package/dist/ui/components/SideDrawer.style.css +2 -2
  126. package/dist/ui/forms/FieldCheckbox.d.ts +13 -0
  127. package/dist/ui/forms/FieldCheckbox.js +25 -0
  128. package/dist/ui/forms/FieldInput.d.ts +11 -0
  129. package/dist/ui/forms/FieldInput.js +25 -0
  130. package/dist/ui/forms/FieldSelect.d.ts +17 -0
  131. package/dist/ui/forms/FieldSelect.js +25 -0
  132. package/dist/ui/forms/FormShell.d.ts +48 -0
  133. package/dist/ui/forms/FormShell.js +176 -0
  134. package/dist/ui/forms/FormShell.style.css +287 -0
  135. package/dist/ui/forms/index.d.ts +8 -0
  136. package/dist/ui/forms/index.js +4 -0
  137. package/dist/ui/index.d.ts +9 -0
  138. package/dist/ui/index.js +5 -0
  139. package/dist/utils/action-response.d.ts +6 -0
  140. package/dist/utils/action-response.js +22 -0
  141. package/dist/utils/index.d.ts +1 -0
  142. package/dist/utils/index.js +1 -0
  143. package/dist/validation/schemas.d.ts +10 -10
  144. package/package.json +2 -2
@@ -16,8 +16,8 @@ export declare const orderItemSchema: z.ZodObject<{
16
16
  productId: string;
17
17
  quantity: number;
18
18
  currency?: string | undefined;
19
- storeId?: string | undefined;
20
19
  image?: string | undefined;
20
+ storeId?: string | undefined;
21
21
  attributes?: Record<string, string> | undefined;
22
22
  }, {
23
23
  title: string;
@@ -25,8 +25,8 @@ export declare const orderItemSchema: z.ZodObject<{
25
25
  productId: string;
26
26
  quantity: number;
27
27
  currency?: string | undefined;
28
- storeId?: string | undefined;
29
28
  image?: string | undefined;
29
+ storeId?: string | undefined;
30
30
  attributes?: Record<string, string> | undefined;
31
31
  }>;
32
32
  export declare const orderTimelineSchema: z.ZodObject<{
@@ -76,8 +76,8 @@ export declare const orderSchema: z.ZodObject<{
76
76
  productId: string;
77
77
  quantity: number;
78
78
  currency?: string | undefined;
79
- storeId?: string | undefined;
80
79
  image?: string | undefined;
80
+ storeId?: string | undefined;
81
81
  attributes?: Record<string, string> | undefined;
82
82
  }, {
83
83
  title: string;
@@ -85,8 +85,8 @@ export declare const orderSchema: z.ZodObject<{
85
85
  productId: string;
86
86
  quantity: number;
87
87
  currency?: string | undefined;
88
- storeId?: string | undefined;
89
88
  image?: string | undefined;
89
+ storeId?: string | undefined;
90
90
  attributes?: Record<string, string> | undefined;
91
91
  }>, "many">;
92
92
  address: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -131,8 +131,8 @@ export declare const orderSchema: z.ZodObject<{
131
131
  productId: string;
132
132
  quantity: number;
133
133
  currency?: string | undefined;
134
- storeId?: string | undefined;
135
134
  image?: string | undefined;
135
+ storeId?: string | undefined;
136
136
  attributes?: Record<string, string> | undefined;
137
137
  }[];
138
138
  total: number;
@@ -143,10 +143,10 @@ export declare const orderSchema: z.ZodObject<{
143
143
  createdAt?: string | undefined;
144
144
  updatedAt?: string | undefined;
145
145
  notes?: string | undefined;
146
+ tax?: number | undefined;
146
147
  trackingNumber?: string | undefined;
147
148
  couponCode?: string | undefined;
148
149
  shippingCarrier?: string | undefined;
149
- tax?: number | undefined;
150
150
  discount?: number | undefined;
151
151
  paymentGateway?: string | undefined;
152
152
  shippingCost?: number | undefined;
@@ -166,8 +166,8 @@ export declare const orderSchema: z.ZodObject<{
166
166
  productId: string;
167
167
  quantity: number;
168
168
  currency?: string | undefined;
169
- storeId?: string | undefined;
170
169
  image?: string | undefined;
170
+ storeId?: string | undefined;
171
171
  attributes?: Record<string, string> | undefined;
172
172
  }[];
173
173
  total: number;
@@ -178,10 +178,10 @@ export declare const orderSchema: z.ZodObject<{
178
178
  createdAt?: string | undefined;
179
179
  updatedAt?: string | undefined;
180
180
  notes?: string | undefined;
181
+ tax?: number | undefined;
181
182
  trackingNumber?: string | undefined;
182
183
  couponCode?: string | undefined;
183
184
  shippingCarrier?: string | undefined;
184
- tax?: number | undefined;
185
185
  discount?: number | undefined;
186
186
  paymentGateway?: string | undefined;
187
187
  shippingCost?: number | undefined;
@@ -202,15 +202,15 @@ export declare const orderListParamsSchema: z.ZodObject<{
202
202
  perPage: z.ZodOptional<z.ZodNumber>;
203
203
  }, "strip", z.ZodTypeAny, {
204
204
  sort?: string | undefined;
205
- perPage?: number | undefined;
206
205
  page?: number | undefined;
206
+ perPage?: number | undefined;
207
207
  userId?: string | undefined;
208
208
  paymentStatus?: string | undefined;
209
209
  orderStatus?: "pending" | "processing" | "refunded" | "confirmed" | "shipped" | "delivered" | "cancelled" | "return_requested" | "returned" | undefined;
210
210
  }, {
211
211
  sort?: string | undefined;
212
- perPage?: number | undefined;
213
212
  page?: number | undefined;
213
+ perPage?: number | undefined;
214
214
  userId?: string | undefined;
215
215
  paymentStatus?: string | undefined;
216
216
  orderStatus?: "pending" | "processing" | "refunded" | "confirmed" | "shipped" | "delivered" | "cancelled" | "return_requested" | "returned" | undefined;
@@ -71,6 +71,11 @@ export interface Order {
71
71
  shippingCarrier?: string;
72
72
  notes?: string;
73
73
  timeline?: OrderTimeline[];
74
+ physicalLocation?: {
75
+ zone: string;
76
+ shelf: string;
77
+ bin: string;
78
+ };
74
79
  createdAt?: string;
75
80
  updatedAt?: string;
76
81
  }
@@ -39,8 +39,8 @@ export declare const paymentRecordSchema: z.ZodObject<{
39
39
  createdAt: z.ZodOptional<z.ZodString>;
40
40
  }, "strip", z.ZodTypeAny, {
41
41
  currency: string;
42
+ status: "pending" | "failed" | "captured" | "authorized" | "refunded" | "partially_refunded";
42
43
  id: string;
43
- status: "failed" | "captured" | "pending" | "authorized" | "refunded" | "partially_refunded";
44
44
  amount: number;
45
45
  orderId: string;
46
46
  gateway: "bank_transfer" | "upi" | "razorpay" | "stripe" | "paypal" | "cod" | "whatsapp";
@@ -52,8 +52,8 @@ export declare const paymentRecordSchema: z.ZodObject<{
52
52
  orderId: string;
53
53
  gateway: "bank_transfer" | "upi" | "razorpay" | "stripe" | "paypal" | "cod" | "whatsapp";
54
54
  currency?: string | undefined;
55
+ status?: "pending" | "failed" | "captured" | "authorized" | "refunded" | "partially_refunded" | undefined;
55
56
  createdAt?: string | undefined;
56
- status?: "failed" | "captured" | "pending" | "authorized" | "refunded" | "partially_refunded" | undefined;
57
57
  gatewayPaymentId?: string | undefined;
58
58
  }>;
59
59
  export declare const paymentGatewayConfigSchema: z.ZodObject<{
@@ -64,14 +64,14 @@ export declare const paymentGatewayConfigSchema: z.ZodObject<{
64
64
  sortOrder: z.ZodDefault<z.ZodNumber>;
65
65
  }, "strip", z.ZodTypeAny, {
66
66
  id: string;
67
+ displayName: string;
67
68
  gateway: "bank_transfer" | "upi" | "razorpay" | "stripe" | "paypal" | "cod" | "whatsapp";
68
69
  isEnabled: boolean;
69
- displayName: string;
70
70
  sortOrder: number;
71
71
  }, {
72
72
  id: string;
73
- gateway: "bank_transfer" | "upi" | "razorpay" | "stripe" | "paypal" | "cod" | "whatsapp";
74
73
  displayName: string;
74
+ gateway: "bank_transfer" | "upi" | "razorpay" | "stripe" | "paypal" | "cod" | "whatsapp";
75
75
  isEnabled?: boolean | undefined;
76
76
  sortOrder?: number | undefined;
77
77
  }>;
@@ -22,18 +22,18 @@ export declare const preorderItemSchema: z.ZodObject<{
22
22
  source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
23
23
  youtubeId: z.ZodOptional<z.ZodString>;
24
24
  }, "strip", z.ZodTypeAny, {
25
- type: "video" | "image" | "file";
26
25
  url: string;
27
- source?: "upload" | "youtube" | "external" | undefined;
26
+ type: "image" | "video" | "file";
28
27
  alt?: string | undefined;
29
28
  thumbnailUrl?: string | undefined;
29
+ source?: "upload" | "youtube" | "external" | undefined;
30
30
  youtubeId?: string | undefined;
31
31
  }, {
32
- type: "video" | "image" | "file";
33
32
  url: string;
34
- source?: "upload" | "youtube" | "external" | undefined;
33
+ type: "image" | "video" | "file";
35
34
  alt?: string | undefined;
36
35
  thumbnailUrl?: string | undefined;
36
+ source?: "upload" | "youtube" | "external" | undefined;
37
37
  youtubeId?: string | undefined;
38
38
  }>, "many">>;
39
39
  salePrice: z.ZodNumber;
@@ -56,11 +56,11 @@ export declare const preorderItemSchema: z.ZodObject<{
56
56
  brand?: string | undefined;
57
57
  images?: string[] | undefined;
58
58
  media?: {
59
- type: "video" | "image" | "file";
60
59
  url: string;
61
- source?: "upload" | "youtube" | "external" | undefined;
60
+ type: "image" | "video" | "file";
62
61
  alt?: string | undefined;
63
62
  thumbnailUrl?: string | undefined;
63
+ source?: "upload" | "youtube" | "external" | undefined;
64
64
  youtubeId?: string | undefined;
65
65
  }[] | undefined;
66
66
  franchise?: string | undefined;
@@ -75,11 +75,11 @@ export declare const preorderItemSchema: z.ZodObject<{
75
75
  brand?: string | undefined;
76
76
  images?: string[] | undefined;
77
77
  media?: {
78
- type: "video" | "image" | "file";
79
78
  url: string;
80
- source?: "upload" | "youtube" | "external" | undefined;
79
+ type: "image" | "video" | "file";
81
80
  alt?: string | undefined;
82
81
  thumbnailUrl?: string | undefined;
82
+ source?: "upload" | "youtube" | "external" | undefined;
83
83
  youtubeId?: string | undefined;
84
84
  }[] | undefined;
85
85
  active?: boolean | undefined;
@@ -93,7 +93,7 @@ export function PrizeDrawItemsEditor({ items, onChange, onUploadImage, onUploadV
93
93
  }), disabled: locked, placeholder: "2999" }) })] }), _jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { className: "text-sm font-medium", children: ["Images (", (it.images ?? []).length, "/", MAX_IMAGES_PER_ITEM, ")"] }), _jsx(Row, { gap: "sm", className: "flex-wrap", children: Array.from({ length: MAX_IMAGES_PER_ITEM }).map((_, slot) => {
94
94
  const existing = (it.images ?? [])[slot];
95
95
  return (_jsxs(Div, { className: "w-40", children: [_jsx(ImageUpload, { currentImage: existing, label: existing ? "Replace" : `Image ${slot + 1}`, onUpload: (file) => onUploadImage(file, it.itemNumber), onChange: (url) => setImage(index, slot, url) }), existing && !locked ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeImage(index, slot), children: "Remove" })) : null] }, slot));
96
- }) })] }), onUploadVideo ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium", children: "Video (optional)" }), _jsx("input", { type: "file", accept: "video/mp4,video/webm", disabled: locked, onChange: (e) => handleVideoFileChange(e, index, it.itemNumber) }), it.video?.url ? (_jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] truncate", children: ["Current: ", it.video.url] })) : null] })) : null] })] }, `prize-item-${it.itemNumber}-${index}`));
96
+ }) })] }), onUploadVideo ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium", children: "Video (optional)" }), _jsx("input", { type: "file", accept: "video/*", disabled: locked, onChange: (e) => handleVideoFileChange(e, index, it.itemNumber) }), it.video?.url ? (_jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] truncate", children: ["Current: ", it.video.url] })) : null] })) : null] })] }, `prize-item-${it.itemNumber}-${index}`));
97
97
  }) }), items.length < MIN_ITEMS ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-red-50 px-3 py-2 text-sm text-red-900 dark:bg-red-900/30 dark:text-red-100", children: ["At least ", MIN_ITEMS, " prizes are required."] })) : null] }));
98
98
  }
99
99
  export default PrizeDrawItemsEditor;
@@ -0,0 +1,16 @@
1
+ export interface ProductDetailActionsProps {
2
+ productId: string;
3
+ productSlug: string;
4
+ productTitle: string;
5
+ productImage?: string;
6
+ price?: number;
7
+ currency?: string;
8
+ inStock: boolean;
9
+ variant?: "desktop" | "mobile";
10
+ }
11
+ /**
12
+ * Wires the three primary product-detail CTAs (Buy Now / Add to Cart / Wishlist)
13
+ * to the server APIs with guest-fallback. Replaces the previously-unwired
14
+ * placeholder buttons inside ProductDetailPageView.
15
+ */
16
+ export declare function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, inStock, variant, }: ProductDetailActionsProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,94 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Button, Stack, LoginRequiredModal } from "../../../ui";
6
+ import { useToast } from "../../../ui/components/Toast";
7
+ import { useAddToCart } from "../../cart/hooks/useAddToCart";
8
+ import { apiClient } from "../../../http";
9
+ import { addToGuestWishlist } from "../../wishlist/utils/guest-wishlist";
10
+ import { ROUTES } from "../../../next";
11
+ import { useAuthGate } from "../../../react/hooks/useAuthGate";
12
+ import { ACTION_ID } from "../constants/action-defs";
13
+ /**
14
+ * Wires the three primary product-detail CTAs (Buy Now / Add to Cart / Wishlist)
15
+ * to the server APIs with guest-fallback. Replaces the previously-unwired
16
+ * placeholder buttons inside ProductDetailPageView.
17
+ */
18
+ export function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, inStock, variant = "desktop", }) {
19
+ const router = useRouter();
20
+ const { showToast } = useToast();
21
+ const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
22
+ const [busy, setBusy] = useState(null);
23
+ const [wishlisted, setWishlisted] = useState(false);
24
+ const addCart = useAddToCart({
25
+ onSuccess: () => showToast("Added to cart", "success"),
26
+ onError: (err) => showToast(err?.message ?? "Could not add to cart", "error"),
27
+ });
28
+ async function handleAddToCart(then) {
29
+ setBusy("cart");
30
+ try {
31
+ await addCart.mutate({
32
+ productId,
33
+ quantity: 1,
34
+ productTitle,
35
+ productImage,
36
+ price,
37
+ });
38
+ then?.();
39
+ }
40
+ finally {
41
+ setBusy(null);
42
+ }
43
+ }
44
+ async function handleBuyNow() {
45
+ requireAuth(ACTION_ID.BUY_NOW, async () => {
46
+ await handleAddToCart(() => router.push(String(ROUTES.USER.CHECKOUT)));
47
+ });
48
+ }
49
+ async function doWishlistAdd() {
50
+ if (wishlisted)
51
+ return;
52
+ setBusy("wish");
53
+ try {
54
+ try {
55
+ await apiClient.post("/api/wishlist", { productId });
56
+ }
57
+ catch (err) {
58
+ const status = err?.status;
59
+ if (status === 401 || status === 403) {
60
+ addToGuestWishlist(productId, "product", {
61
+ title: productTitle,
62
+ image: productImage,
63
+ });
64
+ }
65
+ else {
66
+ throw err;
67
+ }
68
+ }
69
+ setWishlisted(true);
70
+ showToast("Saved to wishlist", "success");
71
+ }
72
+ catch (err) {
73
+ const msg = err?.message ?? "Could not save to wishlist";
74
+ showToast(msg, "error");
75
+ }
76
+ finally {
77
+ setBusy(null);
78
+ }
79
+ }
80
+ function handleWishlist() {
81
+ requireAuth(ACTION_ID.ADD_TO_WISHLIST, () => {
82
+ void doWishlistAdd();
83
+ });
84
+ }
85
+ const authModal = (_jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage }));
86
+ if (variant === "mobile") {
87
+ return (_jsxs(_Fragment, { children: [authModal, _jsx(Button, { variant: "secondary", size: "sm", className: "shrink-0", disabled: !inStock || busy !== null, onClick: () => handleAddToCart(), children: busy === "cart" ? "Adding…" : "Add to Cart" }), _jsx(Button, { variant: "primary", size: "sm", className: "flex-1", disabled: !inStock || busy !== null, onClick: handleBuyNow, children: !inStock
88
+ ? "Out of Stock"
89
+ : busy === "buy"
90
+ ? "Loading…"
91
+ : "Buy Now" })] }));
92
+ }
93
+ return (_jsxs(_Fragment, { children: [authModal, _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: !inStock || busy !== null, onClick: handleBuyNow, children: !inStock ? "Out of Stock" : busy === "buy" ? "Loading…" : "Buy Now" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: !inStock || busy !== null, onClick: () => handleAddToCart(), children: !inStock ? "Out of Stock" : busy === "cart" ? "Adding…" : "Add to Cart" }), _jsx(Button, { variant: "ghost", size: "md", className: "w-full", disabled: busy !== null || wishlisted, onClick: handleWishlist, children: wishlisted ? "♥ In Wishlist" : "♡ Add to Wishlist" })] })] }));
94
+ }
@@ -18,6 +18,21 @@ export interface ProductDetailPageViewProps {
18
18
  currency: string;
19
19
  minOfferPercent: number;
20
20
  }) => React.ReactNode;
21
+ /**
22
+ * Renders the primary action buttons (Buy Now / Add to Cart / Wishlist).
23
+ * Receives variant so a single consumer can render desktop + mobile differently.
24
+ * When omitted, disabled placeholder buttons render — a loud signal of drift.
25
+ */
26
+ renderPrimaryActions?: (opts: {
27
+ productId: string;
28
+ productSlug: string;
29
+ productTitle: string;
30
+ productImage?: string;
31
+ price: number | null;
32
+ currency: string;
33
+ inStock: boolean;
34
+ variant: "desktop" | "mobile";
35
+ }) => React.ReactNode;
21
36
  /**
22
37
  * SSR-loaded productFeatures (platform + store-scope). When present, the
23
38
  * product.features[] IDs render as FeatureBadge pills; the legacy text
@@ -26,4 +41,4 @@ export interface ProductDetailPageViewProps {
26
41
  */
27
42
  productFeatures?: ProductFeatureDocument[];
28
43
  }
29
- export declare function ProductDetailPageView({ slug, initialProduct, renderOfferAction, productFeatures, }: ProductDetailPageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
44
+ export declare function ProductDetailPageView({ slug, initialProduct, renderOfferAction, renderPrimaryActions, productFeatures, }: ProductDetailPageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import Link from "next/link";
3
3
  import { productRepository, reviewRepository } from "../../../repositories";
4
4
  const CLS_BREADCRUMB_LINK = "hover:text-primary-600 transition-colors";
5
+ const ACTION_NOT_WIRED = "Action not wired";
5
6
  import { ROUTES } from "../../../next";
6
7
  import { getDefaultCurrency } from "../../../core/baseline-resolver";
7
8
  import { Button, Container, Div, Heading, Main, RichText, Row, Section, Span, Stack, Text, } from "../../../ui";
@@ -88,7 +89,7 @@ function StarRating({ value }) {
88
89
  // ---------------------------------------------------------------------------
89
90
  // Component
90
91
  // ---------------------------------------------------------------------------
91
- export async function ProductDetailPageView({ slug, initialProduct, renderOfferAction, productFeatures, }) {
92
+ export async function ProductDetailPageView({ slug, initialProduct, renderOfferAction, renderPrimaryActions, productFeatures, }) {
92
93
  // Use pre-fetched data when available to avoid a redundant repository call.
93
94
  // The page layer wraps getProductForDetail in React.cache(), so both
94
95
  // generateMetadata() and this component share the same in-flight promise.
@@ -247,7 +248,16 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
247
248
  codAvailable: "Cash on Delivery",
248
249
  wishlistCount: (n) => `${n} wishlisted`,
249
250
  categoryProductCount: (n, cat) => `${n} in ${cat}`,
250
- } }), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this product" }), _jsx("ul", { className: "space-y-1.5", children: features.map((f, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-500 mb-0.5", children: "Sold by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderActions: () => (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [formattedPrice && (_jsxs(Div, { children: [_jsxs(Row, { align: "baseline", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice }), formattedOriginal && discount && (_jsxs(_Fragment, { children: [_jsx(Span, { className: "text-sm text-zinc-400 line-through dark:text-zinc-500", children: formattedOriginal }), _jsxs(Span, { className: "rounded-full bg-red-500 px-2 py-0.5 text-xs font-bold text-white", children: ["-", discount, "%"] })] }))] }), inStock && effectiveStock !== null && effectiveStock <= 10 && (_jsxs(Text, { className: "mt-1 text-xs text-amber-600 dark:text-amber-400", children: ["Only ", effectiveStock, " left \u2014 order soon!"] }))] })), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: !inStock, children: inStock ? "Buy Now" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: !inStock, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsx(Button, { variant: "ghost", size: "md", className: "w-full", children: "\u2661 Add to Wishlist" }), allowOffers && productType === "simple" && price !== null && renderOfferAction?.({
251
+ } }), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this product" }), _jsx("ul", { className: "space-y-1.5", children: features.map((f, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-500 mb-0.5", children: "Sold by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderActions: () => (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [formattedPrice && (_jsxs(Div, { children: [_jsxs(Row, { align: "baseline", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice }), formattedOriginal && discount && (_jsxs(_Fragment, { children: [_jsx(Span, { className: "text-sm text-zinc-400 line-through dark:text-zinc-500", children: formattedOriginal }), _jsxs(Span, { className: "rounded-full bg-red-500 px-2 py-0.5 text-xs font-bold text-white", children: ["-", discount, "%"] })] }))] }), inStock && effectiveStock !== null && effectiveStock <= 10 && (_jsxs(Text, { className: "mt-1 text-xs text-amber-600 dark:text-amber-400", children: ["Only ", effectiveStock, " left \u2014 order soon!"] }))] })), _jsxs(Stack, { gap: "sm", children: [renderPrimaryActions ? (renderPrimaryActions({
252
+ productId: product.id,
253
+ productSlug: product.slug ?? slug,
254
+ productTitle: product.title,
255
+ productImage: product.mainImage,
256
+ price,
257
+ currency,
258
+ inStock,
259
+ variant: "desktop",
260
+ })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsx(Button, { variant: "ghost", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: "\u2661 Add to Wishlist" })] })), allowOffers && productType === "simple" && price !== null && renderOfferAction?.({
251
261
  productId: product.id,
252
262
  price,
253
263
  currency,
@@ -266,5 +276,14 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
266
276
  content: _jsx(CustomSectionTabContent, { section: s }),
267
277
  })) })), renderBundleSection: bundleMemberships.length > 0
268
278
  ? () => (_jsxs("div", { className: "rounded-xl border border-teal-200 dark:border-teal-800/60 bg-teal-50/60 dark:bg-teal-900/20 p-5", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(Span, { className: "text-teal-600 dark:text-teal-400 text-base", "aria-hidden": "true", children: "\uD83D\uDCE6" }), _jsxs(Text, { className: "text-sm font-semibold text-teal-800 dark:text-teal-200", children: ["Part of ", bundleMemberships.length === 1 ? "a bundle" : `${bundleMemberships.length} bundles`] })] }), _jsx("div", { className: "flex flex-col gap-3", children: bundleMemberships.map((b) => (_jsxs("div", { className: "flex items-center justify-between gap-4 rounded-lg border border-teal-200 dark:border-teal-800/50 bg-white dark:bg-teal-900/30 px-4 py-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx(Text, { className: "text-xs text-teal-600 dark:text-teal-400 font-medium uppercase tracking-wide mb-0.5", children: "Included in bundle" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-100 truncate", children: b.title })] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: "flex-shrink-0 rounded-lg bg-teal-600 hover:bg-teal-700 px-3 py-1.5 text-xs font-semibold text-white transition-colors", children: "View Bundle \u2192" })] }, b.id))) })] }))
269
- : undefined, renderRelated: () => relatedItems.length > 0 ? (_jsx(RelatedProductsCarousel, { items: relatedItems })) : null }), _jsxs(BuyBar, { children: [formattedPrice && (_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice })), _jsx(Button, { variant: "secondary", size: "sm", className: "shrink-0", disabled: !inStock, children: "Add to Cart" }), _jsx(Button, { variant: "primary", size: "sm", className: "flex-1", disabled: !inStock, children: inStock ? "Buy Now" : "Out of Stock" })] })] })] }));
279
+ : undefined, renderRelated: () => relatedItems.length > 0 ? (_jsx(RelatedProductsCarousel, { items: relatedItems })) : null }), _jsxs(BuyBar, { children: [formattedPrice && (_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice })), renderPrimaryActions ? (renderPrimaryActions({
280
+ productId: product.id,
281
+ productSlug: product.slug ?? slug,
282
+ productTitle: product.title,
283
+ productImage: product.mainImage,
284
+ price,
285
+ currency,
286
+ inStock,
287
+ variant: "mobile",
288
+ })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "secondary", size: "sm", className: "shrink-0", disabled: true, title: ACTION_NOT_WIRED, children: "Add to Cart" }), _jsx(Button, { variant: "primary", size: "sm", className: "flex-1", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" })] }))] })] })] }));
270
289
  }
@@ -90,7 +90,7 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
90
90
  name: product.title || "product",
91
91
  category: (product.categorySlugs?.[0] ?? product.category) || "uncategorized",
92
92
  store: product.storeName || "store",
93
- }), onChange: (url) => update({ mainImage: url }), label: t("formMainImage"), helperText: "Recommended: 800x800px (1:1)" })), isReadonly && product.mainImage && (_jsx(FormField, { name: "mainImage", label: t("formMainImage"), type: "text", value: product.mainImage, onChange: () => { }, disabled: true })), !isReadonly && (_jsx(MediaUploadList, { label: t("formGalleryImages"), value: galleryImages, onChange: (fields) => update({ images: fields.map((f) => f.url) }), onUpload: handleGalleryUpload, accept: "image/*", maxItems: 5, maxSizeMB: 10, helperText: t("formGalleryImagesHelper"), onAbort: onMediaAbort })), !isReadonly && (_jsx(MediaUploadField, { label: t("formVideo"), value: product.video?.url || "", onChange: (url) => update({
93
+ }), onChange: (url) => update({ mainImage: url }), label: t("formMainImage"), helperText: "Recommended: 800x800px (1:1)" })), isReadonly && product.mainImage && (_jsx(FormField, { name: "mainImage", label: t("formMainImage"), type: "text", value: product.mainImage, onChange: () => { }, disabled: true })), !isReadonly && (_jsx(MediaUploadList, { label: t("formGalleryImages"), value: galleryImages, onChange: (fields) => update({ images: fields.map((f) => f.url) }), onUpload: handleGalleryUpload, accept: "image/*,video/*", maxItems: 5, maxSizeMB: 10, helperText: t("formGalleryImagesHelper"), onAbort: onMediaAbort })), !isReadonly && (_jsx(MediaUploadField, { label: t("formVideo"), value: product.video?.url || "", onChange: (url) => update({
94
94
  video: url
95
95
  ? {
96
96
  url,
@@ -178,5 +178,23 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
178
178
  ? "auction"
179
179
  : isPreOrderListing(product)
180
180
  ? "preorder"
181
- : "product"), storeId: product.storeId, disabled: isReadonly }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1 text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Custom Sections" }), _jsx(Text, { className: "mb-3 text-xs text-zinc-500 dark:text-zinc-400", children: "Add up to 3 custom tabs to your product page \u2014 e.g. \"Box Contents\", \"Compatibility\", \"Grading Details\"." }), _jsx(CustomSectionsEditor, { sections: product.customSections ?? [], onChange: (sections) => update({ customSections: sections }) })] }), _jsx(FormField, { name: "shippingInfo", label: t("formShipping"), type: "textarea", value: product.shippingInfo || "", onChange: (value) => update({ shippingInfo: value }), disabled: isReadonly, placeholder: "Shipping information..." }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), product.storeName && (_jsx(FormField, { name: "storeName", label: t("formSeller"), type: "text", value: product.storeName, onChange: () => { }, disabled: true }))] }));
181
+ : "product"), storeId: product.storeId, disabled: isReadonly }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1 text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Custom Sections" }), _jsx(Text, { className: "mb-3 text-xs text-zinc-500 dark:text-zinc-400", children: "Add up to 3 custom tabs to your product page \u2014 e.g. \"Box Contents\", \"Compatibility\", \"Grading Details\"." }), _jsx(CustomSectionsEditor, { sections: product.customSections ?? [], onChange: (sections) => update({ customSections: sections }) })] }), _jsx(FormField, { name: "shippingInfo", label: t("formShipping"), type: "textarea", value: product.shippingInfo || "", onChange: (value) => update({ shippingInfo: value }), disabled: isReadonly, placeholder: "Shipping information..." }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1 text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Physical Inventory" }), _jsx(Text, { className: "mb-3 text-xs text-zinc-500 dark:text-zinc-400", children: "Assign a storage location for warehouse or shelf organisation. Used by the Print & Label Center." }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { name: "physicalLocation.zone", label: "Zone", type: "text", placeholder: "e.g. A", value: product.physicalLocation?.zone ?? "", onChange: (v) => update({
182
+ physicalLocation: {
183
+ zone: v,
184
+ shelf: product.physicalLocation?.shelf ?? "",
185
+ bin: product.physicalLocation?.bin ?? "",
186
+ },
187
+ }), disabled: isReadonly }), _jsx(FormField, { name: "physicalLocation.shelf", label: "Shelf", type: "text", placeholder: "e.g. 3", value: product.physicalLocation?.shelf ?? "", onChange: (v) => update({
188
+ physicalLocation: {
189
+ zone: product.physicalLocation?.zone ?? "",
190
+ shelf: v,
191
+ bin: product.physicalLocation?.bin ?? "",
192
+ },
193
+ }), disabled: isReadonly }), _jsx(FormField, { name: "physicalLocation.bin", label: "Bin", type: "text", placeholder: "e.g. Blue", value: product.physicalLocation?.bin ?? "", onChange: (v) => update({
194
+ physicalLocation: {
195
+ zone: product.physicalLocation?.zone ?? "",
196
+ shelf: product.physicalLocation?.shelf ?? "",
197
+ bin: v,
198
+ },
199
+ }), disabled: isReadonly })] })] }), product.storeName && (_jsx(FormField, { name: "storeName", label: t("formSeller"), type: "text", value: product.storeName, onChange: () => { }, disabled: true }))] }));
182
200
  }
@@ -239,7 +239,7 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
239
239
  /**
240
240
  * Cloud Functions: stage an atomic bid-count increment into a caller-owned WriteBatch.
241
241
  */
242
- incrementBidCountInBatch(batch: WriteBatch, productId: string, currentBid: number): void;
242
+ incrementBidCountInBatch(batch: WriteBatch, productId: string, currentBid: number, leadingBidderId?: string): void;
243
243
  /**
244
244
  * SB6-C / SB4-H — atomically bump `prizeCurrentEntries` by `count`. The
245
245
  * checkout transaction calls this after pool-cap validation; the reveal
@@ -248,6 +248,8 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
248
248
  */
249
249
  incrementPrizeEntriesInBatch(batch: WriteBatch, productId: string, count: number): void;
250
250
  productRef(productId: string): DocumentReference;
251
+ /** Cloud Functions: returns refs for draft products older than `cutoff` (for 30-day prune). */
252
+ getStaleDraftRefs(cutoff: Date): Promise<DocumentReference[]>;
251
253
  }
252
254
  export declare class ProductsRepository extends ProductRepository {
253
255
  }
@@ -385,10 +385,11 @@ export class ProductRepository extends BaseRepository {
385
385
  /**
386
386
  * Cloud Functions: stage an atomic bid-count increment into a caller-owned WriteBatch.
387
387
  */
388
- incrementBidCountInBatch(batch, productId, currentBid) {
388
+ incrementBidCountInBatch(batch, productId, currentBid, leadingBidderId) {
389
389
  batch.update(this.db.collection(this.collection).doc(productId), {
390
390
  currentBid,
391
391
  bidCount: increment(1),
392
+ ...(leadingBidderId && { leadingBidderId }),
392
393
  // SB-UNI-H 2026-05-13 — flip `bidsHaveStarted` when any bid lands.
393
394
  // Idempotent on subsequent bids; the BIN button is hidden by the PDP
394
395
  // as soon as this is true (eBay-style hybrid auction rule).
@@ -411,6 +412,16 @@ export class ProductRepository extends BaseRepository {
411
412
  productRef(productId) {
412
413
  return this.db.collection(this.collection).doc(productId);
413
414
  }
415
+ /** Cloud Functions: returns refs for draft products older than `cutoff` (for 30-day prune). */
416
+ async getStaleDraftRefs(cutoff) {
417
+ const snap = await this.db
418
+ .collection(this.collection)
419
+ .where("status", "==", "draft")
420
+ .where("updatedAt", "<", cutoff)
421
+ .limit(200)
422
+ .get();
423
+ return snap.docs.map((d) => d.ref);
424
+ }
414
425
  }
415
426
  ProductRepository.CACHE_TTL_MS = 30000;
416
427
  ProductRepository.SIEVE_FIELDS = {
@@ -146,6 +146,7 @@ export interface ProductDocument {
146
146
  startingBid?: number;
147
147
  currentBid?: number;
148
148
  bidCount?: number;
149
+ leadingBidderId?: string;
149
150
  reservePrice?: number;
150
151
  buyNowPrice?: number;
151
152
  minBidIncrement?: number;
@@ -237,6 +238,12 @@ export interface ProductDocument {
237
238
  etaDaysMax?: number;
238
239
  }>;
239
240
  };
241
+ /** Physical storage location for inventory management (Print & Label Center). */
242
+ physicalLocation?: {
243
+ zone: string;
244
+ shelf: string;
245
+ bin: string;
246
+ };
240
247
  createdAt: Date;
241
248
  updatedAt: Date;
242
249
  }