hey-pharmacist-ecommerce 1.1.28 → 1.1.30
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/index.d.mts +10552 -1370
- package/dist/index.d.ts +10552 -1370
- package/dist/index.js +4696 -1281
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4640 -1283
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/AccountOrdersTab.tsx +1 -1
- package/src/components/AccountSettingsTab.tsx +88 -6
- package/src/components/CartItem.tsx +1 -1
- package/src/components/Header.tsx +8 -2
- package/src/components/OrderCard.tsx +4 -4
- package/src/components/ProductCard.tsx +59 -42
- package/src/components/QuickViewModal.tsx +13 -13
- package/src/hooks/useAddresses.ts +4 -1
- package/src/hooks/usePaymentMethods.ts +26 -31
- package/src/hooks/useProducts.ts +63 -64
- package/src/hooks/useStoreCapabilities.ts +87 -0
- package/src/hooks/useWishlistProducts.ts +4 -5
- package/src/index.ts +6 -0
- package/src/lib/Apis/api.ts +0 -1
- package/src/lib/Apis/apis/auth-api.ts +37 -36
- package/src/lib/Apis/apis/categories-api.ts +97 -0
- package/src/lib/Apis/apis/products-api.ts +942 -405
- package/src/lib/Apis/apis/shipping-api.ts +105 -0
- package/src/lib/Apis/apis/stores-api.ts +356 -0
- package/src/lib/Apis/apis/sub-categories-api.ts +97 -0
- package/src/lib/Apis/apis/users-api.ts +8 -8
- package/src/lib/Apis/models/address-created-request.ts +0 -12
- package/src/lib/Apis/models/address.ts +0 -12
- package/src/lib/Apis/models/api-key-info-dto.ts +49 -0
- package/src/lib/Apis/models/category-populated.ts +0 -12
- package/src/lib/Apis/models/category-sub-category-populated.ts +2 -2
- package/src/lib/Apis/models/category.ts +0 -18
- package/src/lib/Apis/models/{table-cell-dto.ts → change-password-dto.ts} +6 -6
- package/src/lib/Apis/models/create-address-dto.ts +0 -12
- package/src/lib/Apis/models/create-discount-dto.ts +0 -8
- package/src/lib/Apis/models/create-product-dto.ts +30 -23
- package/src/lib/Apis/models/create-store-address-dto.ts +0 -12
- package/src/lib/Apis/models/create-store-dto-settings.ts +51 -0
- package/src/lib/Apis/models/create-store-dto.ts +7 -0
- package/src/lib/Apis/models/create-sub-category-dto.ts +6 -0
- package/src/lib/Apis/models/create-variant-dto.ts +26 -32
- package/src/lib/Apis/models/discount.ts +0 -8
- package/src/lib/Apis/models/index.ts +16 -7
- package/src/lib/Apis/models/paginated-products-dto.ts +6 -6
- package/src/lib/Apis/models/populated-discount.ts +0 -8
- package/src/lib/Apis/models/product-summary.ts +69 -0
- package/src/lib/Apis/models/product-variant.ts +31 -68
- package/src/lib/Apis/models/product.ts +138 -0
- package/src/lib/Apis/models/products-insights-dto.ts +12 -0
- package/src/lib/Apis/models/reorder-categories-dto.ts +27 -0
- package/src/lib/Apis/models/reorder-products-dto.ts +49 -0
- package/src/lib/Apis/models/{table-dto.ts → reorder-products-success-response-dto.ts} +8 -9
- package/src/lib/Apis/models/reorder-subcategories-dto.ts +33 -0
- package/src/lib/Apis/models/{shallow-parent-category-dto.ts → reorder-success-response-dto.ts} +7 -7
- package/src/lib/Apis/models/shipment-with-order.ts +18 -0
- package/src/lib/Apis/models/shipment.ts +18 -0
- package/src/lib/Apis/models/single-product-media.ts +0 -12
- package/src/lib/Apis/models/store-api-keys-response-dto.ts +34 -0
- package/src/lib/Apis/models/store-capabilities-dto.ts +63 -0
- package/src/lib/Apis/models/store-entity.ts +7 -0
- package/src/lib/Apis/models/store.ts +7 -0
- package/src/lib/Apis/models/sub-category.ts +6 -12
- package/src/lib/Apis/models/update-address-dto.ts +0 -12
- package/src/lib/Apis/models/update-api-keys-dto.ts +39 -0
- package/src/lib/Apis/models/update-discount-dto.ts +0 -8
- package/src/lib/Apis/models/update-manual-shipment-status-dto.ts +47 -0
- package/src/lib/Apis/models/update-product-dto.ts +30 -19
- package/src/lib/Apis/models/update-store-dto.ts +7 -0
- package/src/lib/Apis/models/update-sub-category-dto.ts +6 -0
- package/src/lib/Apis/models/{update-product-variant-dto.ts → update-variant-dto.ts} +46 -46
- package/src/lib/Apis/models/variant-id-inventory-body.ts +27 -0
- package/src/lib/api-adapter/config.ts +53 -0
- package/src/lib/validations/address.ts +1 -1
- package/src/providers/FavoritesProvider.tsx +5 -5
- package/src/providers/WishlistProvider.tsx +4 -4
- package/src/screens/CartScreen.tsx +1 -1
- package/src/screens/ChangePasswordScreen.tsx +2 -6
- package/src/screens/CheckoutScreen.tsx +402 -288
- package/src/screens/ForgotPasswordScreen.tsx +153 -0
- package/src/screens/ProductDetailScreen.tsx +51 -60
- package/src/screens/RegisterScreen.tsx +31 -31
- package/src/screens/ResetPasswordScreen.tsx +208 -0
- package/src/screens/SearchResultsScreen.tsx +264 -26
- package/src/screens/ShopScreen.tsx +42 -45
- package/src/screens/WishlistScreen.tsx +35 -31
- package/src/lib/Apis/apis/product-variants-api.ts +0 -552
- package/src/lib/Apis/models/create-single-variant-product-dto.ts +0 -154
- package/src/lib/Apis/models/extended-product-dto.ts +0 -206
- package/src/lib/Apis/models/frequently-bought-product-dto.ts +0 -71
|
@@ -24,6 +24,7 @@ import { useAuth } from '@/providers/AuthProvider';
|
|
|
24
24
|
import { OrdersApi } from '@/lib/Apis/apis/orders-api';
|
|
25
25
|
import { ShippingApi } from '@/lib/Apis/apis/shipping-api';
|
|
26
26
|
import { useAddresses } from '@/hooks/useAddresses';
|
|
27
|
+
import { useStoreCapabilities } from '@/hooks/useStoreCapabilities';
|
|
27
28
|
import { formatPrice } from '@/lib/utils/format';
|
|
28
29
|
import { useRouter } from 'next/navigation';
|
|
29
30
|
import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
|
|
@@ -48,32 +49,28 @@ type CheckoutFormData = z.infer<typeof checkoutSchema>;
|
|
|
48
49
|
const SHIPPING_THRESHOLD = 100;
|
|
49
50
|
const TAX_RATE = 0.08;
|
|
50
51
|
|
|
51
|
-
//
|
|
52
|
-
const
|
|
52
|
+
// Base payment methods config (filtered at runtime based on capabilities)
|
|
53
|
+
const PAYMENT_METHODS_CONFIG = [
|
|
53
54
|
{
|
|
55
|
+
id: 'Card',
|
|
54
56
|
label: 'Card',
|
|
55
57
|
value: 'Card',
|
|
56
58
|
icon: <CreditCard className="w-5 h-5" />,
|
|
57
59
|
description: 'Pay securely with your credit or debit card',
|
|
58
60
|
className: 'border-blue-500 hover:bg-blue-50',
|
|
59
61
|
activeClass: 'bg-blue-50 border-blue-500 text-blue-700',
|
|
62
|
+
requiresStripe: true,
|
|
60
63
|
},
|
|
61
64
|
{
|
|
65
|
+
id: 'Cash',
|
|
62
66
|
label: 'Cash',
|
|
63
67
|
value: 'Cash',
|
|
64
68
|
icon: <PackageCheck className="w-5 h-5" />,
|
|
65
69
|
description: 'Pay with cash on delivery or at pickup',
|
|
66
70
|
className: 'border-amber-500 hover:bg-amber-50',
|
|
67
71
|
activeClass: 'bg-amber-50 border-amber-500 text-amber-700',
|
|
72
|
+
requiresStripe: false,
|
|
68
73
|
},
|
|
69
|
-
// {
|
|
70
|
-
// label: 'Credit',
|
|
71
|
-
// value: 'Credit',
|
|
72
|
-
// icon: <ShieldCheck className="w-5 h-5" />,
|
|
73
|
-
// description: 'Use your account credit',
|
|
74
|
-
// className: 'border-emerald-500 hover:bg-emerald-50',
|
|
75
|
-
// activeClass: 'bg-emerald-50 border-emerald-500 text-emerald-700',
|
|
76
|
-
// },
|
|
77
74
|
];
|
|
78
75
|
|
|
79
76
|
export function CheckoutScreen() {
|
|
@@ -82,8 +79,9 @@ export function CheckoutScreen() {
|
|
|
82
79
|
const { isAuthenticated, user } = useAuth();
|
|
83
80
|
const { buildPath } = useBasePath();
|
|
84
81
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
85
|
-
const [isDelivery, setIsDelivery] = useState(
|
|
86
|
-
const [
|
|
82
|
+
const [isDelivery, setIsDelivery] = useState<boolean | 'custom' | null>(null); // Start with no selection
|
|
83
|
+
const [hasSetDefaultDelivery, setHasSetDefaultDelivery] = useState(false);
|
|
84
|
+
const [paymentMethod, setPaymentMethod] = useState<string | null>(null); // null until capabilities load
|
|
87
85
|
const [error, setError] = useState<string | null>(null);
|
|
88
86
|
const [selectedAddressId, setSelectedAddressId] = useState<string | null>(null);
|
|
89
87
|
const [storeAddresses, setStoreAddresses] = useState<any[]>([]); // For pickup selection
|
|
@@ -98,6 +96,73 @@ export function CheckoutScreen() {
|
|
|
98
96
|
addresses: userAddresses,
|
|
99
97
|
defaultAddress
|
|
100
98
|
} = useAddresses();
|
|
99
|
+
|
|
100
|
+
// Get store capabilities for payment/delivery options
|
|
101
|
+
const {
|
|
102
|
+
capabilities,
|
|
103
|
+
isLoading: capabilitiesLoading,
|
|
104
|
+
showCardPayment,
|
|
105
|
+
showShippoDelivery,
|
|
106
|
+
showCustomDelivery,
|
|
107
|
+
} = useStoreCapabilities();
|
|
108
|
+
|
|
109
|
+
// Filter payment methods based on capabilities
|
|
110
|
+
const availablePaymentMethods = PAYMENT_METHODS_CONFIG.filter(pm => {
|
|
111
|
+
if (pm.requiresStripe) {
|
|
112
|
+
return showCardPayment;
|
|
113
|
+
}
|
|
114
|
+
return true; // Cash is always available
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Set default payment method once capabilities are loaded
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!capabilitiesLoading && paymentMethod === null && availablePaymentMethods.length > 0) {
|
|
120
|
+
// Prefer Card if available, otherwise use first available
|
|
121
|
+
const preferCard = availablePaymentMethods.find(pm => pm.id === 'Card');
|
|
122
|
+
setPaymentMethod(preferCard ? 'Card' : availablePaymentMethods[0].value);
|
|
123
|
+
}
|
|
124
|
+
}, [capabilitiesLoading, paymentMethod, availablePaymentMethods]);
|
|
125
|
+
|
|
126
|
+
// Set default delivery method once capabilities are loaded
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!capabilitiesLoading && !hasSetDefaultDelivery && capabilities) {
|
|
129
|
+
// Only default to Pickup if NO delivery options are available
|
|
130
|
+
if (!showShippoDelivery && !showCustomDelivery) {
|
|
131
|
+
setIsDelivery(false);
|
|
132
|
+
}
|
|
133
|
+
// Otherwise leave as null (User must choose)
|
|
134
|
+
setHasSetDefaultDelivery(true);
|
|
135
|
+
}
|
|
136
|
+
}, [capabilitiesLoading, hasSetDefaultDelivery, capabilities, showShippoDelivery, showCustomDelivery]);
|
|
137
|
+
|
|
138
|
+
// Build delivery options based on capabilities
|
|
139
|
+
const deliveryOptions = [
|
|
140
|
+
// Shippo delivery (if enabled)
|
|
141
|
+
...(showShippoDelivery ? [{
|
|
142
|
+
id: 'shippo',
|
|
143
|
+
label: 'Delivery',
|
|
144
|
+
icon: <Truck className="w-5 h-5" />,
|
|
145
|
+
value: true as const,
|
|
146
|
+
desc: 'Shipped to your address',
|
|
147
|
+
}] : []),
|
|
148
|
+
// Custom/Store delivery (if enabled)
|
|
149
|
+
...(showCustomDelivery ? [{
|
|
150
|
+
id: 'custom',
|
|
151
|
+
label: capabilities?.customDeliveryTitle || 'Store Delivery',
|
|
152
|
+
icon: <Truck className="w-5 h-5" />,
|
|
153
|
+
value: 'custom' as const,
|
|
154
|
+
desc: capabilities?.customDeliveryFlatRate
|
|
155
|
+
? `Flat rate: ${formatPrice(capabilities.customDeliveryFlatRate)}`
|
|
156
|
+
: 'Flat rate delivery',
|
|
157
|
+
}] : []),
|
|
158
|
+
{
|
|
159
|
+
id: 'pickup',
|
|
160
|
+
label: 'Pickup',
|
|
161
|
+
icon: <MapPin className="w-5 h-5" />,
|
|
162
|
+
value: false as const,
|
|
163
|
+
desc: 'Collect from pharmacy',
|
|
164
|
+
},
|
|
165
|
+
];
|
|
101
166
|
const [selectedShippingRateId, setSelectedShippingRateId] = useState<string | null>(null);
|
|
102
167
|
const [shippingRates, setShippingRates] = useState<any[]>([]);
|
|
103
168
|
const [shippingRatesLoading, setShippingRatesLoading] = useState(false);
|
|
@@ -116,13 +181,21 @@ export function CheckoutScreen() {
|
|
|
116
181
|
sameAsShipping: true,
|
|
117
182
|
shipping: {
|
|
118
183
|
name: user ? `${user.firstname} ${user.lastname}` : '',
|
|
119
|
-
phone: user?.phoneNumber ||
|
|
184
|
+
phone: user?.phoneNumber || '',
|
|
120
185
|
country: 'United States',
|
|
186
|
+
// street1: '',
|
|
187
|
+
// city: '',
|
|
188
|
+
// state: '',
|
|
189
|
+
// zip: '',
|
|
121
190
|
},
|
|
122
191
|
billing: {
|
|
123
192
|
name: user ? `${user.firstname} ${user.lastname}` : '',
|
|
124
|
-
phone: user?.phoneNumber ||
|
|
193
|
+
phone: user?.phoneNumber || '',
|
|
125
194
|
country: 'United States',
|
|
195
|
+
// street1: '',
|
|
196
|
+
// city: '',
|
|
197
|
+
// state: '',
|
|
198
|
+
// zip: '',
|
|
126
199
|
},
|
|
127
200
|
},
|
|
128
201
|
});
|
|
@@ -153,7 +226,7 @@ export function CheckoutScreen() {
|
|
|
153
226
|
setSelectedAddressId(defaultAddress.id);
|
|
154
227
|
// Update form with default address
|
|
155
228
|
setValue('shipping.name', defaultAddress.name);
|
|
156
|
-
setValue('shipping.phone', defaultAddress.phone ||
|
|
229
|
+
setValue('shipping.phone', defaultAddress.phone || '');
|
|
157
230
|
setValue('shipping.street1', defaultAddress.street1);
|
|
158
231
|
setValue('shipping.street2', defaultAddress.street2 || '');
|
|
159
232
|
setValue('shipping.city', defaultAddress.city);
|
|
@@ -223,7 +296,7 @@ export function CheckoutScreen() {
|
|
|
223
296
|
}
|
|
224
297
|
|
|
225
298
|
useEffect(() => {
|
|
226
|
-
if (
|
|
299
|
+
if (isDelivery !== true || !selectedAddressId || !cart || cart?.cartBody?.items?.length === 0 || !cart?.cartBody?.items) {
|
|
227
300
|
setShippingRates([]);
|
|
228
301
|
setSelectedShippingRateId(null);
|
|
229
302
|
return;
|
|
@@ -253,17 +326,24 @@ export function CheckoutScreen() {
|
|
|
253
326
|
}, [isDelivery, selectedAddressId, cart]);
|
|
254
327
|
|
|
255
328
|
useEffect(() => {
|
|
256
|
-
|
|
329
|
+
let cancelled = false;
|
|
330
|
+
|
|
331
|
+
if (isDelivery === false) {
|
|
257
332
|
(async () => {
|
|
258
333
|
const api = new ShippingApi(AXIOS_CONFIG);
|
|
259
334
|
const res = await api.getStoreAddress();
|
|
260
|
-
if
|
|
335
|
+
// Only update state if this effect hasn't been cleaned up
|
|
336
|
+
if (!cancelled && res.data) {
|
|
261
337
|
setStoreAddresses([res.data]);
|
|
262
338
|
setShippingPrice(0);
|
|
263
339
|
}
|
|
264
340
|
})();
|
|
265
341
|
}
|
|
266
|
-
|
|
342
|
+
|
|
343
|
+
// Cleanup function to prevent stale updates when delivery method changes
|
|
344
|
+
return () => {
|
|
345
|
+
cancelled = true;
|
|
346
|
+
};
|
|
267
347
|
}, [isDelivery]);
|
|
268
348
|
|
|
269
349
|
|
|
@@ -285,12 +365,22 @@ export function CheckoutScreen() {
|
|
|
285
365
|
setError('Please select a payment method.');
|
|
286
366
|
return;
|
|
287
367
|
}
|
|
288
|
-
if (isDelivery) {
|
|
368
|
+
if (isDelivery === null) {
|
|
369
|
+
setError('Please select a delivery method.');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
// Check for delivery-related requirements (both shippo and custom delivery need address)
|
|
373
|
+
const isShippoDelivery = isDelivery === true;
|
|
374
|
+
const isCustomDelivery = isDelivery === 'custom';
|
|
375
|
+
const needsDeliveryAddress = isShippoDelivery || isCustomDelivery;
|
|
376
|
+
|
|
377
|
+
if (needsDeliveryAddress) {
|
|
289
378
|
if (!selectedAddressId) {
|
|
290
379
|
setError('Please select a shipping address.');
|
|
291
380
|
return;
|
|
292
381
|
}
|
|
293
|
-
|
|
382
|
+
// Only require shipping rate selection for Shippo delivery
|
|
383
|
+
if (isShippoDelivery && !selectedShippingRateId) {
|
|
294
384
|
setError('Please select a shipping method.');
|
|
295
385
|
return;
|
|
296
386
|
}
|
|
@@ -301,7 +391,8 @@ export function CheckoutScreen() {
|
|
|
301
391
|
return;
|
|
302
392
|
}
|
|
303
393
|
}
|
|
304
|
-
} else {
|
|
394
|
+
} else if (isDelivery === false) {
|
|
395
|
+
// Pickup case
|
|
305
396
|
if (storeAddresses.length === 0) {
|
|
306
397
|
setError('Store pickup location is not available.');
|
|
307
398
|
return;
|
|
@@ -338,22 +429,31 @@ export function CheckoutScreen() {
|
|
|
338
429
|
} else if (paymentMethod === 'Card') {
|
|
339
430
|
billingAddressId = userAddresses.length > 0 ? userAddresses[0].id : undefined;
|
|
340
431
|
}
|
|
432
|
+
// Build manualShipping object for custom delivery
|
|
433
|
+
const manualShipping = isDelivery === 'custom' && selectedAddressId ? {
|
|
434
|
+
amount: capabilities?.customDeliveryFlatRate || 0,
|
|
435
|
+
userAddressId: selectedAddressId,
|
|
436
|
+
shippingTitle: capabilities?.customDeliveryTitle || 'Store Delivery',
|
|
437
|
+
} : undefined;
|
|
438
|
+
|
|
341
439
|
const orderDTO: any = {
|
|
342
440
|
items,
|
|
343
441
|
paymentMethod,
|
|
344
442
|
orderStatus: 'Pending',
|
|
345
|
-
chargeTax: true,
|
|
346
443
|
orderRemindingDates: [],
|
|
347
444
|
shippingAddress: data.shipping,
|
|
348
445
|
billingAddress: sameAsShipping ? data.shipping : data.billing,
|
|
446
|
+
...(manualShipping && { manualShipping }),
|
|
349
447
|
};
|
|
350
448
|
|
|
351
449
|
const api = new OrdersApi(AXIOS_CONFIG);
|
|
450
|
+
// Convert isDelivery to boolean for API (custom delivery counts as delivery)
|
|
451
|
+
const isDeliveryBoolean = isDelivery !== false;
|
|
352
452
|
const response = await api.createCheckout(
|
|
353
453
|
orderDTO,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
isDelivery ? (selectedShippingRateId || undefined) : undefined,
|
|
454
|
+
isDeliveryBoolean,
|
|
455
|
+
undefined, // dont pass userId
|
|
456
|
+
isDeliveryBoolean && isDelivery === true ? (selectedShippingRateId || undefined) : undefined,
|
|
357
457
|
billingAddressId
|
|
358
458
|
);
|
|
359
459
|
if (response?.data?.payment && response.data.payment.hostedInvoiceUrl === 'INSUFFICIENT_CREDIT') {
|
|
@@ -425,7 +525,7 @@ export function CheckoutScreen() {
|
|
|
425
525
|
router.push(buildPath('/cart'));
|
|
426
526
|
return null;
|
|
427
527
|
}
|
|
428
|
-
const subtotal = cart.total;
|
|
528
|
+
const subtotal = cart.cartBody.items.reduce((total, item) => total + item.productVariantData.finalPrice * item.quantity, 0);
|
|
429
529
|
const tax = 0;
|
|
430
530
|
const total = subtotal + shippingPrice + tax;
|
|
431
531
|
|
|
@@ -449,143 +549,267 @@ export function CheckoutScreen() {
|
|
|
449
549
|
Complete your order information below
|
|
450
550
|
</p>
|
|
451
551
|
</div>
|
|
452
|
-
|
|
453
|
-
|
|
552
|
+
{/* Delivery and Payment Methods Section - Side by Side */}
|
|
553
|
+
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8">
|
|
554
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
555
|
+
{/* Delivery Method */}
|
|
454
556
|
<div>
|
|
455
|
-
<
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
557
|
+
<div className="flex items-center gap-3 mb-6">
|
|
558
|
+
<Truck className="w-6 h-6 text-[#2B4B7C]" />
|
|
559
|
+
<h2 className="text-xl font-semibold text-[#2B4B7C]">Delivery Method</h2>
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
<div className="space-y-3">
|
|
563
|
+
{deliveryOptions.map((option) => {
|
|
564
|
+
const active = isDelivery === option.value;
|
|
565
|
+
return (
|
|
566
|
+
<button
|
|
567
|
+
key={option.id}
|
|
568
|
+
type="button"
|
|
569
|
+
onClick={() => {
|
|
570
|
+
setIsDelivery(option.value);
|
|
571
|
+
// Reset shipping rate when changing delivery method
|
|
572
|
+
setSelectedShippingRateId(null);
|
|
573
|
+
// Set shipping price based on delivery type
|
|
574
|
+
if (option.value === 'custom' && capabilities?.customDeliveryFlatRate) {
|
|
575
|
+
setShippingPrice(capabilities.customDeliveryFlatRate);
|
|
576
|
+
} else if (option.value === false) {
|
|
577
|
+
setShippingPrice(0);
|
|
578
|
+
} else {
|
|
579
|
+
setShippingPrice(0); // Will be set when shipping rate is selected
|
|
580
|
+
}
|
|
581
|
+
}}
|
|
582
|
+
className={`relative flex w-full items-center justify-between rounded-xl border-2 p-3 transition-all duration-200 ${active
|
|
583
|
+
? 'border-primary-500 bg-primary-50'
|
|
584
|
+
: 'border-gray-200 hover:border-primary-300'
|
|
585
|
+
}`}
|
|
586
|
+
>
|
|
587
|
+
<div className="flex items-center gap-3">
|
|
588
|
+
<div
|
|
589
|
+
className={`p-2 rounded-lg ${active ? 'bg-primary-100 text-primary-600' : 'bg-gray-100 text-gray-600'
|
|
590
|
+
}`}
|
|
591
|
+
>
|
|
592
|
+
{option.icon}
|
|
593
|
+
</div>
|
|
594
|
+
<div className="text-left">
|
|
595
|
+
<div className="font-medium text-gray-900">{option.label}</div>
|
|
596
|
+
<p className="text-xs text-gray-500">{option.desc}</p>
|
|
597
|
+
</div>
|
|
598
|
+
</div>
|
|
599
|
+
{active && (
|
|
600
|
+
<div className="w-5 h-5 rounded-full bg-primary-500 flex items-center justify-center shrink-0">
|
|
601
|
+
<Check className="w-3 h-3 text-white" />
|
|
602
|
+
</div>
|
|
603
|
+
)}
|
|
604
|
+
</button>
|
|
605
|
+
);
|
|
606
|
+
})}
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
|
|
610
|
+
{/* Payment Method */}
|
|
611
|
+
<div>
|
|
612
|
+
<div className="flex items-center gap-3 mb-6">
|
|
613
|
+
<CreditCard className="w-6 h-6 text-[#2B4B7C]" />
|
|
614
|
+
<h2 className="text-xl font-semibold text-[#2B4B7C]">Payment Method</h2>
|
|
615
|
+
</div>
|
|
616
|
+
|
|
617
|
+
<div className="space-y-3">
|
|
618
|
+
{availablePaymentMethods.map((pm) => {
|
|
619
|
+
const active = paymentMethod === pm.value;
|
|
620
|
+
|
|
621
|
+
return (
|
|
622
|
+
<button
|
|
623
|
+
key={pm.value}
|
|
624
|
+
type="button"
|
|
625
|
+
onClick={() => setPaymentMethod(pm.value)}
|
|
626
|
+
className={`
|
|
627
|
+
group relative flex w-full items-center justify-between rounded-xl border-2 p-3
|
|
628
|
+
transition-all duration-200 ease-out
|
|
629
|
+
focus:outline-hidden
|
|
630
|
+
${active
|
|
631
|
+
? `${pm.activeClass} shadow-md scale-[1.02]`
|
|
632
|
+
: `${pm.className} border-gray-200 bg-white hover:shadow-xs hover:-translate-y-0.5`
|
|
633
|
+
}
|
|
634
|
+
`}
|
|
635
|
+
>
|
|
636
|
+
<div className="flex items-center gap-3">
|
|
637
|
+
<div
|
|
638
|
+
className={`
|
|
639
|
+
flex items-center justify-center rounded-lg p-2
|
|
640
|
+
transition-colors duration-200
|
|
641
|
+
${pm.value === 'Card'
|
|
642
|
+
? ' text-blue-600'
|
|
643
|
+
: pm.value === 'Cash'
|
|
644
|
+
? ' text-amber-600'
|
|
645
|
+
: ' text-emerald-600'
|
|
646
|
+
}
|
|
647
|
+
${active ? 'opacity-80' : 'group-hover:opacity-90'}
|
|
648
|
+
`}
|
|
649
|
+
>
|
|
650
|
+
{pm.icon}
|
|
651
|
+
</div>
|
|
652
|
+
|
|
653
|
+
<span className="text-sm font-semibold text-gray-900">
|
|
654
|
+
{pm.label}
|
|
655
|
+
</span>
|
|
656
|
+
</div>
|
|
657
|
+
|
|
658
|
+
{active && (
|
|
659
|
+
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-current shrink-0">
|
|
660
|
+
<Check className="h-3 w-3 text-white" />
|
|
661
|
+
</div>
|
|
662
|
+
)}
|
|
663
|
+
</button>
|
|
664
|
+
);
|
|
665
|
+
})}
|
|
666
|
+
</div>
|
|
667
|
+
|
|
668
|
+
<p className="text-xs text-slate-500 mt-4 px-1">
|
|
669
|
+
{paymentMethod === 'Card' &&
|
|
670
|
+
'You will be redirected to a secure payment page.'}
|
|
671
|
+
{paymentMethod === 'Cash' &&
|
|
672
|
+
'Pay with cash at the time of delivery or pickup.'}
|
|
460
673
|
</p>
|
|
461
674
|
</div>
|
|
462
|
-
<span className="inline-flex items-center gap-2 rounded-full bg-primary-50 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-primary-700">
|
|
463
|
-
<Truck className="h-4 w-4" />
|
|
464
|
-
Dispatch in 12h
|
|
465
|
-
</span>
|
|
466
675
|
</div>
|
|
676
|
+
</div>
|
|
467
677
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
<div
|
|
472
|
-
<
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
481
|
-
Add New Address
|
|
482
|
-
</Button>
|
|
483
|
-
</div>
|
|
678
|
+
{isDelivery !== null && (
|
|
679
|
+
<section className="bg-white border-2 border-gray-100 rounded-[24px] p-8 text-[#2B4B7C]">
|
|
680
|
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
681
|
+
<div>
|
|
682
|
+
<h2 className="font-['Poppins',sans-serif] font-semibold text-[#2B4B7C] mb-2 text-2xl">
|
|
683
|
+
{isDelivery ? 'Delivery Address' : 'Contact Information'}
|
|
684
|
+
</h2>
|
|
685
|
+
<p className="text-sm text-slate-500">
|
|
686
|
+
We use temperature-aware packaging and real-time tracking on every shipment.
|
|
687
|
+
</p>
|
|
688
|
+
</div>
|
|
689
|
+
</div>
|
|
484
690
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
691
|
+
<div className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
692
|
+
{/* Address selection for delivery */}
|
|
693
|
+
{isDelivery && (
|
|
694
|
+
<div className="md:col-span-2 space-y-4">
|
|
695
|
+
<div className="flex items-center justify-between">
|
|
696
|
+
<label className="block font-semibold">Select Address</label>
|
|
697
|
+
<Button
|
|
698
|
+
type="button"
|
|
699
|
+
variant="outline-solid"
|
|
700
|
+
size="sm"
|
|
701
|
+
onClick={() => { setEditingAddress(null); setIsAddressModalOpen(true); }}
|
|
702
|
+
>
|
|
703
|
+
<Plus className="h-4 w-4 mr-2" />
|
|
704
|
+
Add New Address
|
|
705
|
+
</Button>
|
|
706
|
+
</div>
|
|
707
|
+
|
|
708
|
+
{userAddresses.length > 0 ? (
|
|
709
|
+
<div className="grid gap-4">
|
|
710
|
+
{userAddresses.map(addr => (
|
|
711
|
+
<label
|
|
712
|
+
key={addr.id}
|
|
713
|
+
className={`group relative flex items-start gap-3 p-4 rounded-2xl border ${selectedAddressId === addr.id
|
|
714
|
+
? 'border-primary-500 bg-primary-50 shadow-xs'
|
|
715
|
+
: 'border-slate-200 bg-white'
|
|
716
|
+
} cursor-pointer hover:border-primary-300 transition-colors`}
|
|
717
|
+
>
|
|
718
|
+
<input
|
|
719
|
+
type="radio"
|
|
720
|
+
name="selectedAddress"
|
|
721
|
+
value={addr.id}
|
|
722
|
+
checked={selectedAddressId === addr.id}
|
|
723
|
+
onChange={() => {
|
|
724
|
+
setSelectedAddressId(addr.id);
|
|
725
|
+
setValue('shipping.name', addr.name);
|
|
726
|
+
setValue('shipping.phone', addr.phone || '');
|
|
727
|
+
setValue('shipping.street1', addr.street1);
|
|
728
|
+
setValue('shipping.street2', addr.street2 || '');
|
|
729
|
+
setValue('shipping.city', addr.city);
|
|
730
|
+
setValue('shipping.state', addr.state);
|
|
731
|
+
setValue('shipping.zip', addr.zip);
|
|
732
|
+
setValue('shipping.country', addr.country);
|
|
733
|
+
}}
|
|
734
|
+
className="mt-1"
|
|
735
|
+
/>
|
|
736
|
+
<div className="flex-1">
|
|
737
|
+
<p className="font-semibold text-slate-900">{addr.name}</p>
|
|
738
|
+
<p className="text-sm text-slate-600">{addr.street1}</p>
|
|
739
|
+
{addr.street2 && (
|
|
740
|
+
<p className="text-sm text-slate-600">{addr.street2}</p>
|
|
531
741
|
)}
|
|
532
|
-
<
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
<
|
|
538
|
-
|
|
539
|
-
<
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
742
|
+
<p className="text-sm text-slate-600">
|
|
743
|
+
{addr.city}, {addr.state} {addr.zip}
|
|
744
|
+
</p>
|
|
745
|
+
<p className="text-sm text-slate-600">{addr.country}</p>
|
|
746
|
+
{addr.phone && (
|
|
747
|
+
<p className="text-sm text-slate-600 mt-1">{addr.phone}</p>
|
|
748
|
+
)}
|
|
749
|
+
<div className="mt-3 flex items-center gap-2">
|
|
750
|
+
{addr.isDefault && (
|
|
751
|
+
<span className="inline-flex items-center gap-1 rounded-full bg-primary-100 px-2.5 py-0.5 text-xs font-semibold text-primary-700">
|
|
752
|
+
Default
|
|
753
|
+
</span>
|
|
754
|
+
)}
|
|
755
|
+
<button
|
|
756
|
+
type="button"
|
|
757
|
+
onClick={(e) => { e.preventDefault(); setEditingAddress(addr); setIsAddressModalOpen(true); }}
|
|
758
|
+
className="inline-flex items-center gap-1 rounded-full border border-slate-200 px-2.5 py-0.5 text-xs font-medium text-slate-600 hover:border-primary-300 hover:text-primary-600"
|
|
759
|
+
>
|
|
760
|
+
<Edit3 className="h-3.5 w-3.5" /> Edit
|
|
761
|
+
</button>
|
|
762
|
+
<button
|
|
763
|
+
type="button"
|
|
764
|
+
onClick={async (e) => {
|
|
765
|
+
e.preventDefault();
|
|
766
|
+
const yes = window.confirm('Delete this address?');
|
|
767
|
+
if (!yes) return;
|
|
768
|
+
try {
|
|
769
|
+
await removeAddress(addr.id);
|
|
770
|
+
if (selectedAddressId === addr.id) setSelectedAddressId(null);
|
|
771
|
+
notification.success('Address deleted');
|
|
772
|
+
} catch (e) {
|
|
773
|
+
notification.error('Failed to delete address');
|
|
774
|
+
}
|
|
775
|
+
}}
|
|
776
|
+
className="inline-flex items-center gap-1 rounded-full border border-red-200 px-2.5 py-0.5 text-xs font-semibold text-red-600 hover:border-red-300 hover:text-red-700"
|
|
777
|
+
>
|
|
778
|
+
<Trash2 className="h-3.5 w-3.5" /> Delete
|
|
779
|
+
</button>
|
|
780
|
+
</div>
|
|
557
781
|
</div>
|
|
558
|
-
</
|
|
559
|
-
|
|
782
|
+
</label>
|
|
783
|
+
))}
|
|
784
|
+
</div>
|
|
785
|
+
) : (
|
|
786
|
+
<div className="text-center py-8 bg-slate-50 rounded-lg">
|
|
787
|
+
<MapPin className="h-12 w-12 mx-auto text-slate-400" />
|
|
788
|
+
<p className="mt-2 text-slate-600">No addresses found</p>
|
|
789
|
+
<p className="text-sm text-slate-500">Add a new address to continue</p>
|
|
790
|
+
</div>
|
|
791
|
+
)}
|
|
792
|
+
</div>
|
|
793
|
+
)}
|
|
794
|
+
{/* Store address selection for pickup */}
|
|
795
|
+
{isDelivery === false && storeAddresses.length > 0 && (
|
|
796
|
+
<div className="md:col-span-2">
|
|
797
|
+
<label className="block mb-2 font-semibold">Select Pickup Location</label>
|
|
798
|
+
<div className="flex items-center gap-2">
|
|
799
|
+
{storeAddresses.map(addr => (
|
|
800
|
+
<div key={addr.id} className="w-full border rounded-sm p-2">
|
|
801
|
+
<p>{addr.name} - {addr.street1}, {addr.city}, {addr.state} {addr.zip}</p>
|
|
802
|
+
</div>
|
|
560
803
|
))}
|
|
561
804
|
</div>
|
|
562
|
-
) : (
|
|
563
|
-
<div className="text-center py-8 bg-slate-50 rounded-lg">
|
|
564
|
-
<MapPin className="h-12 w-12 mx-auto text-slate-400" />
|
|
565
|
-
<p className="mt-2 text-slate-600">No addresses found</p>
|
|
566
|
-
<p className="text-sm text-slate-500">Add a new address to continue</p>
|
|
567
|
-
</div>
|
|
568
|
-
)}
|
|
569
|
-
</div>
|
|
570
|
-
)}
|
|
571
|
-
{/* Store address selection for pickup */}
|
|
572
|
-
{!isDelivery && storeAddresses.length > 0 && (
|
|
573
|
-
<div className="md:col-span-2">
|
|
574
|
-
<label className="block mb-2 font-semibold">Select Pickup Location</label>
|
|
575
|
-
<div className="flex items-center gap-2">
|
|
576
|
-
{storeAddresses.map(addr => (
|
|
577
|
-
<div key={addr.id} className="w-full border rounded-sm p-2">
|
|
578
|
-
<p>{addr.name} - {addr.street1}, {addr.city}, {addr.state} {addr.zip}</p>
|
|
579
|
-
</div>
|
|
580
|
-
))}
|
|
581
805
|
</div>
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
</
|
|
585
|
-
|
|
806
|
+
)}
|
|
807
|
+
</div>
|
|
808
|
+
</section>
|
|
809
|
+
)}
|
|
586
810
|
|
|
587
|
-
{/* Shipping Options - Only show if there's a shipping cost */}
|
|
588
|
-
{isDelivery && selectedAddressId && (
|
|
811
|
+
{/* Shipping Options - Only show if there's a shipping cost and it's Shippo delivery */}
|
|
812
|
+
{isDelivery === true && selectedAddressId && (
|
|
589
813
|
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8 text-[#2B4B7C]">
|
|
590
814
|
<div className="flex items-center gap-3 text-xl font-semibold text-gray-900 pb-4 mb-8 border-b">
|
|
591
815
|
<Truck className="w-8 h-8 flex items-center justify-center text-[#2B4B7C]" />
|
|
@@ -730,139 +954,7 @@ export function CheckoutScreen() {
|
|
|
730
954
|
</div>
|
|
731
955
|
)}
|
|
732
956
|
|
|
733
|
-
{/* Delivery and Payment Methods Section */}
|
|
734
|
-
<div className="space-y-6">
|
|
735
|
-
{/* Delivery Method */}
|
|
736
|
-
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8">
|
|
737
|
-
<div className="flex items-center gap-3 mb-6">
|
|
738
|
-
<Truck className="w-8 h-8 text-[#2B4B7C]" />
|
|
739
|
-
<h2 className="text-2xl font-semibold text-[#2B4B7C]">Delivery Method</h2>
|
|
740
|
-
</div>
|
|
741
|
-
|
|
742
|
-
<div className="grid grid-cols-1 gap-3">
|
|
743
|
-
{[
|
|
744
|
-
{
|
|
745
|
-
label: 'Delivery',
|
|
746
|
-
icon: <Truck className="w-5 h-5" />,
|
|
747
|
-
value: true,
|
|
748
|
-
desc: 'Shipped to your address',
|
|
749
|
-
},
|
|
750
|
-
{
|
|
751
|
-
label: 'Pickup',
|
|
752
|
-
icon: <MapPin className="w-5 h-5" />,
|
|
753
|
-
value: false,
|
|
754
|
-
desc: 'Collect from pharmacy',
|
|
755
|
-
},
|
|
756
|
-
].map((option) => {
|
|
757
|
-
const active = isDelivery === option.value;
|
|
758
|
-
return (
|
|
759
|
-
<button
|
|
760
|
-
key={option.label}
|
|
761
|
-
type="button"
|
|
762
|
-
onClick={() => {
|
|
763
|
-
setIsDelivery(option.value);
|
|
764
|
-
// Reset selected shipping rate when changing delivery method
|
|
765
|
-
if (option.value) {
|
|
766
|
-
setSelectedShippingRateId(null);
|
|
767
|
-
setShippingPrice(0);
|
|
768
|
-
}
|
|
769
|
-
}}
|
|
770
|
-
className={`relative flex w-full items-center justify-between rounded-xl border-2 p-4 transition-all duration-200 ${active
|
|
771
|
-
? 'border-primary-500 bg-primary-50'
|
|
772
|
-
: 'border-gray-200 hover:border-primary-300'
|
|
773
|
-
}`}
|
|
774
|
-
>
|
|
775
|
-
<div className="flex items-center gap-3">
|
|
776
|
-
<div
|
|
777
|
-
className={`p-2 rounded-lg ${active ? 'bg-primary-100 text-primary-600' : 'bg-gray-100 text-gray-600'
|
|
778
|
-
}`}
|
|
779
|
-
>
|
|
780
|
-
{option.icon}
|
|
781
|
-
</div>
|
|
782
|
-
<div className="text-left">
|
|
783
|
-
<div className="font-medium text-gray-900">{option.label}</div>
|
|
784
|
-
<p className="text-sm text-gray-500">{option.desc}</p>
|
|
785
|
-
</div>
|
|
786
|
-
</div>
|
|
787
|
-
{active && (
|
|
788
|
-
<div className="w-5 h-5 rounded-full bg-primary-500 flex items-center justify-center">
|
|
789
|
-
<Check className="w-3 h-3 text-white" />
|
|
790
|
-
</div>
|
|
791
|
-
)}
|
|
792
|
-
</button>
|
|
793
|
-
);
|
|
794
|
-
})}
|
|
795
|
-
</div>
|
|
796
|
-
</div>
|
|
797
|
-
|
|
798
|
-
{/* Payment Method */}
|
|
799
|
-
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8">
|
|
800
|
-
<div className="flex items-center gap-3 mb-6">
|
|
801
|
-
<CreditCard className="w-8 h-8 text-[#2B4B7C]" />
|
|
802
|
-
<h2 className="text-2xl font-semibold text-[#2B4B7C]">Payment Method</h2>
|
|
803
|
-
</div>
|
|
804
|
-
|
|
805
|
-
<div className="grid gap-4 md:grid-cols-3">
|
|
806
|
-
{PAYMENT_METHODS.map((pm) => {
|
|
807
|
-
const active = paymentMethod === pm.value;
|
|
808
957
|
|
|
809
|
-
return (
|
|
810
|
-
<button
|
|
811
|
-
key={pm.value}
|
|
812
|
-
type="button"
|
|
813
|
-
onClick={() => setPaymentMethod(pm.value)}
|
|
814
|
-
className={`
|
|
815
|
-
group relative flex w-full items-center justify-between rounded-xl border-2 p-3
|
|
816
|
-
transition-all duration-200 ease-out
|
|
817
|
-
focus:outline-hidden f
|
|
818
|
-
${active
|
|
819
|
-
? `${pm.activeClass} shadow-md scale-[1.02]`
|
|
820
|
-
: `${pm.className} border-gray-200 bg-white hover:shadow-xs hover:-translate-y-0.5`
|
|
821
|
-
}
|
|
822
|
-
`}
|
|
823
|
-
>
|
|
824
|
-
<div className="flex items-center gap-3">
|
|
825
|
-
<div
|
|
826
|
-
className={`
|
|
827
|
-
flex items-center justify-center rounded-lg p-2
|
|
828
|
-
transition-colors duration-200
|
|
829
|
-
${pm.value === 'Card'
|
|
830
|
-
? ' text-blue-600'
|
|
831
|
-
: pm.value === 'Cash'
|
|
832
|
-
? ' text-amber-600'
|
|
833
|
-
: ' text-emerald-600'
|
|
834
|
-
}
|
|
835
|
-
${active ? 'opacity-80' : 'group-hover:opacity-90'}
|
|
836
|
-
`}
|
|
837
|
-
>
|
|
838
|
-
{pm.icon}
|
|
839
|
-
</div>
|
|
840
|
-
|
|
841
|
-
<span className="text-sm font-semibold text-gray-900">
|
|
842
|
-
{pm.label}
|
|
843
|
-
</span>
|
|
844
|
-
</div>
|
|
845
|
-
|
|
846
|
-
{active && (
|
|
847
|
-
<div className="flex h-6 w-6 items-center justify-center rounded-full shadow-xs">
|
|
848
|
-
<Check className="h-3.5 w-3.5 text-white" />
|
|
849
|
-
</div>
|
|
850
|
-
)}
|
|
851
|
-
</button>
|
|
852
|
-
);
|
|
853
|
-
})}
|
|
854
|
-
</div>
|
|
855
|
-
|
|
856
|
-
<p className="text-sm text-gray-500 mt-4">
|
|
857
|
-
{paymentMethod === 'Card' &&
|
|
858
|
-
'You will be redirected to a secure payment page.'}
|
|
859
|
-
{paymentMethod === 'Cash' &&
|
|
860
|
-
'Pay with cash at the time of delivery or pickup.'}
|
|
861
|
-
{/* {paymentMethod === 'Credit' &&
|
|
862
|
-
'Use your available account credit for this order.'} */}
|
|
863
|
-
</p>
|
|
864
|
-
</div>
|
|
865
|
-
</div>
|
|
866
958
|
</motion.div>
|
|
867
959
|
|
|
868
960
|
<motion.aside
|
|
@@ -880,9 +972,9 @@ export function CheckoutScreen() {
|
|
|
880
972
|
<section className="mt-8 pt-6 border-t border-slate-100">
|
|
881
973
|
<div className="space-y-4 mb-6">
|
|
882
974
|
{cart?.cartBody?.items?.map((item: any) => (
|
|
883
|
-
<div key={
|
|
975
|
+
<div key={item.productVariantId || item.id} className="flex gap-3">
|
|
884
976
|
<div className="w-16 h-16 rounded-xl overflow-hidden bg-white shrink-0">
|
|
885
|
-
<Image src={item.productVariantData
|
|
977
|
+
<Image src={item.productVariantData?.media?.[0]?.file || '/placeholder-product.jpg'} alt={item.productVariantData.name} className="w-full h-full object-cover" height={200} width={200} />
|
|
886
978
|
</div>
|
|
887
979
|
<div className="flex-1 min-w-0">
|
|
888
980
|
<p className="font-['Poppins',sans-serif] font-medium text-[12px] text-[#2B4B7C] mb-1">
|
|
@@ -948,9 +1040,31 @@ export function CheckoutScreen() {
|
|
|
948
1040
|
</div>
|
|
949
1041
|
</div>
|
|
950
1042
|
)}
|
|
1043
|
+
{Object.keys(errors).length > 0 && (
|
|
1044
|
+
<div className="mt-4 p-4 rounded-xl bg-amber-50 border border-amber-200 text-amber-700 text-sm font-medium">
|
|
1045
|
+
<div className="flex items-start gap-2">
|
|
1046
|
+
<AlertCircle className="h-4 w-4 shrink-0 mt-0.5" />
|
|
1047
|
+
<div>
|
|
1048
|
+
<p className="font-semibold mb-1">Please fix the following errors:</p>
|
|
1049
|
+
<ul className="list-disc list-inside space-y-1">
|
|
1050
|
+
{errors.shipping?.name && <li>{errors.shipping.name.message}</li>}
|
|
1051
|
+
{errors.shipping?.phone && <li>{errors.shipping.phone.message}</li>}
|
|
1052
|
+
{errors.shipping?.street1 && <li>{errors.shipping.street1.message}</li>}
|
|
1053
|
+
{errors.shipping?.city && <li>{errors.shipping.city.message}</li>}
|
|
1054
|
+
{errors.shipping?.state && <li>{errors.shipping.state.message}</li>}
|
|
1055
|
+
{errors.shipping?.zip && <li>{errors.shipping.zip.message}</li>}
|
|
1056
|
+
{errors.shipping?.country && <li>{errors.shipping.country.message}</li>}
|
|
1057
|
+
{!sameAsShipping && errors.billing?.name && <li>Billing: {errors.billing.name.message}</li>}
|
|
1058
|
+
{!sameAsShipping && errors.billing?.street1 && <li>Billing: {errors.billing.street1.message}</li>}
|
|
1059
|
+
</ul>
|
|
1060
|
+
</div>
|
|
1061
|
+
</div>
|
|
1062
|
+
</div>
|
|
1063
|
+
)}
|
|
951
1064
|
<button
|
|
952
1065
|
type="submit"
|
|
953
|
-
|
|
1066
|
+
disabled={isSubmitting}
|
|
1067
|
+
className="font-['Poppins',sans-serif] font-medium text-[14px] px-6 py-3 rounded-full text-white hover:bg-[#d66f45] hover:shadow-lg transition-all duration-300 mt-4 w-full bg-[#E67E50] hover:bg-[#2B4B7C] flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
954
1068
|
>
|
|
955
1069
|
<CreditCard className="h-5 w-5" />
|
|
956
1070
|
{isSubmitting ? 'Placing order...' : 'Place Secure Order'}
|
|
@@ -974,7 +1088,7 @@ export function CheckoutScreen() {
|
|
|
974
1088
|
refresh().then(() => {
|
|
975
1089
|
setSelectedAddressId(addr.id);
|
|
976
1090
|
setValue('shipping.name', addr.name);
|
|
977
|
-
setValue('shipping.phone', addr.phone ||
|
|
1091
|
+
setValue('shipping.phone', addr.phone || '');
|
|
978
1092
|
setValue('shipping.street1', addr.street1);
|
|
979
1093
|
setValue('shipping.street2', addr.street2 || '');
|
|
980
1094
|
setValue('shipping.city', addr.city);
|