@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.
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +1 -1
- package/dist/_internal/client/features/seller/print-center/InventoryLabel.d.ts +12 -0
- package/dist/_internal/client/features/seller/print-center/InventoryLabel.js +49 -0
- package/dist/_internal/client/features/seller/print-center/LabelDesignPicker.d.ts +7 -0
- package/dist/_internal/client/features/seller/print-center/LabelDesignPicker.js +32 -0
- package/dist/_internal/client/features/seller/print-center/OrderPackingLabel.d.ts +28 -0
- package/dist/_internal/client/features/seller/print-center/OrderPackingLabel.js +33 -0
- package/dist/_internal/client/features/seller/print-center/PhysicalLocationModal.d.ts +12 -0
- package/dist/_internal/client/features/seller/print-center/PhysicalLocationModal.js +30 -0
- package/dist/_internal/client/features/seller/print-center/PrintCenterView.d.ts +12 -0
- package/dist/_internal/client/features/seller/print-center/PrintCenterView.js +102 -0
- package/dist/_internal/client/features/seller/print-center/PrintGrid.d.ts +12 -0
- package/dist/_internal/client/features/seller/print-center/PrintGrid.js +13 -0
- package/dist/_internal/client/features/seller/print-center/StoreCard.d.ts +19 -0
- package/dist/_internal/client/features/seller/print-center/StoreCard.js +30 -0
- package/dist/_internal/client/features/seller/print-center/WebsiteCard.d.ts +11 -0
- package/dist/_internal/client/features/seller/print-center/WebsiteCard.js +25 -0
- package/dist/_internal/client/features/seller/print-center/index.d.ts +11 -0
- package/dist/_internal/client/features/seller/print-center/index.js +10 -0
- package/dist/_internal/client/features/seller/print-center/types.d.ts +20 -0
- package/dist/_internal/client/features/seller/print-center/types.js +20 -0
- package/dist/_internal/client/features/seller/print-center/useInventoryPdf.d.ts +18 -0
- package/dist/_internal/client/features/seller/print-center/useInventoryPdf.js +76 -0
- package/dist/_internal/server/jobs/core/draftPrune.d.ts +2 -0
- package/dist/_internal/server/jobs/core/draftPrune.js +15 -0
- package/dist/_internal/server/jobs/core/onBidPlaced.js +1 -1
- package/dist/_internal/server/jobs/core/promotions.d.ts +1 -0
- package/dist/_internal/server/jobs/core/promotions.js +21 -6
- package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +18 -1
- package/dist/_internal/server/jobs/handlers/draftPrune.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/draftPrune.js +2 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +2 -0
- package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +15 -1
- package/dist/_internal/shared/actions/action-registry.js +32 -0
- package/dist/_internal/shared/features/blog/schema.d.ts +8 -8
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +10 -10
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/_internal/shared/features/products/schema.d.ts +8 -8
- package/dist/_internal/shared/features/promotions/schema.d.ts +6 -6
- package/dist/_internal/shared/features/reviews/schema.d.ts +2 -2
- package/dist/client.d.ts +13 -1
- package/dist/client.js +7 -1
- package/dist/configs/next.js +1 -0
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/core/server-action.d.ts +4 -0
- package/dist/errors/error-codes.d.ts +18 -0
- package/dist/errors/error-codes.js +18 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/messages.d.ts +1 -0
- package/dist/errors/messages.js +1 -0
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/schemas/index.d.ts +12 -12
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/auctions/actions/bid-actions.js +27 -11
- package/dist/features/auctions/components/PlaceBidFormClient.js +10 -2
- package/dist/features/auctions/repository/bid.repository.d.ts +7 -0
- package/dist/features/auctions/repository/bid.repository.js +16 -0
- package/dist/features/auctions/schemas/index.d.ts +18 -18
- package/dist/features/auth/schemas/index.d.ts +2 -2
- package/dist/features/before-after/schemas/index.d.ts +2 -2
- package/dist/features/blog/actions/blog-actions.d.ts +32 -32
- package/dist/features/blog/components/BlogPostForm.js +1 -1
- package/dist/features/blog/schemas/index.d.ts +38 -38
- package/dist/features/categories/schemas/index.d.ts +10 -10
- package/dist/features/collections/schemas/index.d.ts +4 -4
- package/dist/features/consultation/schemas/index.d.ts +9 -9
- package/dist/features/corporate/schemas/index.d.ts +4 -4
- package/dist/features/events/schemas/index.d.ts +50 -50
- package/dist/features/faq/actions/faq-actions.d.ts +6 -6
- package/dist/features/faq/schemas/index.d.ts +8 -8
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +4 -4
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/layout/AutoBreadcrumbs.js +1 -1
- package/dist/features/media/AvatarUpload.js +1 -1
- package/dist/features/media/types/index.d.ts +4 -4
- package/dist/features/media/upload/ImageUpload.js +18 -9
- package/dist/features/media/upload/MediaUploadList.js +2 -2
- package/dist/features/orders/schemas/firestore.d.ts +6 -0
- package/dist/features/orders/schemas/index.d.ts +10 -10
- package/dist/features/orders/types/index.d.ts +5 -0
- package/dist/features/payments/schemas/index.d.ts +4 -4
- package/dist/features/pre-orders/schemas/index.d.ts +8 -8
- package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
- package/dist/features/products/components/ProductDetailActions.d.ts +16 -0
- package/dist/features/products/components/ProductDetailActions.js +94 -0
- package/dist/features/products/components/ProductDetailPageView.d.ts +16 -1
- package/dist/features/products/components/ProductDetailPageView.js +22 -3
- package/dist/features/products/components/ProductForm.js +20 -2
- package/dist/features/products/repository/products.repository.d.ts +3 -1
- package/dist/features/products/repository/products.repository.js +12 -1
- package/dist/features/products/schemas/firestore.d.ts +7 -0
- package/dist/features/products/schemas/index.d.ts +34 -34
- package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
- package/dist/features/products/types/index.d.ts +6 -0
- package/dist/features/promotions/schemas/index.d.ts +14 -14
- package/dist/features/reviews/schemas/index.d.ts +18 -18
- package/dist/features/seller/actions/offer-actions.js +10 -1
- package/dist/features/seller/components/PhysicalLocationModal.d.ts +12 -0
- package/dist/features/seller/components/PhysicalLocationModal.js +30 -0
- package/dist/features/seller/components/PrintCenterView.d.ts +40 -0
- package/dist/features/seller/components/PrintCenterView.js +6 -0
- package/dist/features/seller/components/SellerOrdersView.js +48 -3
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -0
- package/dist/features/seller/components/SellerProductShell.js +26 -4
- package/dist/features/seller/components/SellerProductsView.js +54 -13
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/components/index.d.ts +1 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/seller/schemas/firestore.d.ts +2 -0
- package/dist/features/seller/schemas/firestore.js +1 -0
- package/dist/features/seller/schemas/index.d.ts +22 -22
- package/dist/features/shell/FormShell.js +1 -1
- package/dist/features/shell/StepForm.d.ts +3 -1
- package/dist/features/shell/StepForm.js +3 -3
- package/dist/features/stores/schemas/index.d.ts +4 -4
- package/dist/index.d.ts +9 -1
- package/dist/index.js +6 -1
- package/dist/next/routing/route-map.d.ts +8 -0
- package/dist/next/routing/route-map.js +3 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/RowActionMenu.js +45 -10
- package/dist/ui/components/SideDrawer.style.css +2 -2
- package/dist/ui/forms/FieldCheckbox.d.ts +13 -0
- package/dist/ui/forms/FieldCheckbox.js +25 -0
- package/dist/ui/forms/FieldInput.d.ts +11 -0
- package/dist/ui/forms/FieldInput.js +25 -0
- package/dist/ui/forms/FieldSelect.d.ts +17 -0
- package/dist/ui/forms/FieldSelect.js +25 -0
- package/dist/ui/forms/FormShell.d.ts +48 -0
- package/dist/ui/forms/FormShell.js +176 -0
- package/dist/ui/forms/FormShell.style.css +287 -0
- package/dist/ui/forms/index.d.ts +8 -0
- package/dist/ui/forms/index.js +4 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.js +5 -0
- package/dist/utils/action-response.d.ts +6 -0
- package/dist/utils/action-response.js +22 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/validation/schemas.d.ts +10 -10
- 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;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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: [
|
|
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 })),
|
|
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..." }),
|
|
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
|
}
|