hey-pharmacist-ecommerce 1.1.29 → 1.1.31
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 +10957 -1331
- package/dist/index.d.ts +10957 -1331
- package/dist/index.js +12364 -5144
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9353 -2205
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/src/components/AccountReviewsTab.tsx +97 -0
- package/src/components/CouponCodeInput.tsx +190 -0
- package/src/components/Header.tsx +5 -1
- package/src/components/Notification.tsx +1 -1
- package/src/components/NotificationBell.tsx +33 -0
- package/src/components/NotificationCard.tsx +211 -0
- package/src/components/NotificationDrawer.tsx +195 -0
- package/src/components/OrderCard.tsx +164 -99
- package/src/components/ProductReviewsSection.tsx +30 -0
- package/src/components/RatingDistribution.tsx +86 -0
- package/src/components/ReviewCard.tsx +59 -0
- package/src/components/ReviewForm.tsx +207 -0
- package/src/components/ReviewPromptBanner.tsx +98 -0
- package/src/components/ReviewsList.tsx +151 -0
- package/src/components/StarRating.tsx +98 -0
- package/src/hooks/useDiscounts.ts +7 -0
- package/src/hooks/useOrders.ts +15 -0
- package/src/hooks/useReviews.ts +230 -0
- package/src/hooks/useStoreCapabilities.ts +87 -0
- package/src/index.ts +29 -0
- package/src/lib/Apis/apis/auth-api.ts +19 -7
- package/src/lib/Apis/apis/categories-api.ts +97 -0
- package/src/lib/Apis/apis/discounts-api.ts +23 -72
- package/src/lib/Apis/apis/notifications-api.ts +196 -231
- package/src/lib/Apis/apis/products-api.ts +181 -0
- package/src/lib/Apis/apis/review-api.ts +283 -4
- package/src/lib/Apis/apis/shipping-api.ts +105 -0
- package/src/lib/Apis/apis/stores-api.ts +536 -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/bulk-channel-toggle-dto.ts +52 -0
- package/src/lib/Apis/models/cart-body-populated.ts +3 -3
- package/src/lib/Apis/models/channel-settings-dto.ts +39 -0
- package/src/lib/Apis/models/{discount-paginated-response.ts → completed-order-dto.ts} +21 -16
- package/src/lib/Apis/models/create-address-dto.ts +0 -12
- package/src/lib/Apis/models/create-discount-dto.ts +31 -100
- package/src/lib/Apis/models/create-review-dto.ts +4 -4
- package/src/lib/Apis/models/create-shippo-account-dto.ts +45 -0
- 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 +13 -0
- package/src/lib/Apis/models/create-variant-dto.ts +0 -6
- package/src/lib/Apis/models/discount.ts +37 -106
- package/src/lib/Apis/models/discounts-insights-dto.ts +12 -0
- package/src/lib/Apis/models/index.ts +24 -7
- package/src/lib/Apis/models/{manual-discount.ts → manual-discount-dto.ts} +10 -10
- package/src/lib/Apis/models/manual-order-dto.ts +3 -3
- package/src/lib/Apis/models/populated-discount.ts +41 -109
- package/src/lib/Apis/models/preference-update-item.ts +59 -0
- package/src/lib/Apis/models/product-light-dto.ts +40 -0
- package/src/lib/Apis/models/product-variant.ts +0 -6
- 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/{check-notifications-response-dto.ts → reorder-products-success-response-dto.ts} +7 -7
- package/src/lib/Apis/models/reorder-subcategories-dto.ts +33 -0
- package/src/lib/Apis/models/reorder-success-response-dto.ts +33 -0
- package/src/lib/Apis/models/review-status-dto.ts +34 -0
- package/src/lib/Apis/models/review.ts +9 -3
- package/src/lib/Apis/models/reviewable-order-dto.ts +58 -0
- package/src/lib/Apis/models/reviewable-product-dto.ts +81 -0
- 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/shippo-account-response-dto.ts +51 -0
- 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 +13 -0
- package/src/lib/Apis/models/store.ts +13 -0
- 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 +31 -100
- package/src/lib/Apis/models/update-manual-shipment-status-dto.ts +47 -0
- package/src/lib/Apis/models/update-notification-settings-dto.ts +28 -0
- package/src/lib/Apis/models/update-review-dto.ts +4 -4
- package/src/lib/Apis/models/update-store-dto.ts +13 -0
- package/src/lib/Apis/models/update-variant-dto.ts +0 -6
- package/src/lib/Apis/models/{pick-type-class.ts → variant-light-dto.ts} +20 -14
- package/src/lib/utils/discount.ts +155 -0
- package/src/lib/validations/discount.ts +11 -0
- package/src/providers/CartProvider.tsx +2 -2
- package/src/providers/DiscountProvider.tsx +97 -0
- package/src/providers/EcommerceProvider.tsx +13 -5
- package/src/providers/NotificationCenterProvider.tsx +436 -0
- package/src/screens/CartScreen.tsx +1 -1
- package/src/screens/CheckoutScreen.tsx +402 -290
- package/src/screens/NotificationSettingsScreen.tsx +413 -0
- package/src/screens/OrderDetailScreen.tsx +283 -0
- package/src/screens/OrderReviewsScreen.tsx +308 -0
- package/src/screens/OrdersScreen.tsx +31 -7
- package/src/screens/ProductDetailScreen.tsx +24 -11
- package/src/screens/ProfileScreen.tsx +5 -0
- package/src/screens/ResetPasswordScreen.tsx +10 -4
- package/src/lib/Apis/models/create-notification-dto.ts +0 -75
- package/src/lib/Apis/models/notification.ts +0 -93
- package/src/lib/Apis/models/single-notification-dto.ts +0 -99
|
@@ -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';
|
|
@@ -35,6 +36,8 @@ import { useBasePath } from '@/providers/BasePathProvider';
|
|
|
35
36
|
import { addressSchema } from '@/lib/validations/address';
|
|
36
37
|
import Image from 'next/image';
|
|
37
38
|
import { useNotification } from '@/providers/NotificationProvider';
|
|
39
|
+
import { CouponCodeInput } from '@/components/CouponCodeInput';
|
|
40
|
+
import { useDiscounts } from '@/hooks/useDiscounts';
|
|
38
41
|
|
|
39
42
|
const checkoutSchema = z.object({
|
|
40
43
|
shipping: addressSchema,
|
|
@@ -48,32 +51,28 @@ type CheckoutFormData = z.infer<typeof checkoutSchema>;
|
|
|
48
51
|
const SHIPPING_THRESHOLD = 100;
|
|
49
52
|
const TAX_RATE = 0.08;
|
|
50
53
|
|
|
51
|
-
//
|
|
52
|
-
const
|
|
54
|
+
// Base payment methods config (filtered at runtime based on capabilities)
|
|
55
|
+
const PAYMENT_METHODS_CONFIG = [
|
|
53
56
|
{
|
|
57
|
+
id: 'Card',
|
|
54
58
|
label: 'Card',
|
|
55
59
|
value: 'Card',
|
|
56
60
|
icon: <CreditCard className="w-5 h-5" />,
|
|
57
61
|
description: 'Pay securely with your credit or debit card',
|
|
58
62
|
className: 'border-blue-500 hover:bg-blue-50',
|
|
59
63
|
activeClass: 'bg-blue-50 border-blue-500 text-blue-700',
|
|
64
|
+
requiresStripe: true,
|
|
60
65
|
},
|
|
61
66
|
{
|
|
67
|
+
id: 'Cash',
|
|
62
68
|
label: 'Cash',
|
|
63
69
|
value: 'Cash',
|
|
64
70
|
icon: <PackageCheck className="w-5 h-5" />,
|
|
65
71
|
description: 'Pay with cash on delivery or at pickup',
|
|
66
72
|
className: 'border-amber-500 hover:bg-amber-50',
|
|
67
73
|
activeClass: 'bg-amber-50 border-amber-500 text-amber-700',
|
|
74
|
+
requiresStripe: false,
|
|
68
75
|
},
|
|
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
76
|
];
|
|
78
77
|
|
|
79
78
|
export function CheckoutScreen() {
|
|
@@ -82,8 +81,9 @@ export function CheckoutScreen() {
|
|
|
82
81
|
const { isAuthenticated, user } = useAuth();
|
|
83
82
|
const { buildPath } = useBasePath();
|
|
84
83
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
85
|
-
const [isDelivery, setIsDelivery] = useState(
|
|
86
|
-
const [
|
|
84
|
+
const [isDelivery, setIsDelivery] = useState<boolean | 'custom' | null>(null); // Start with no selection
|
|
85
|
+
const [hasSetDefaultDelivery, setHasSetDefaultDelivery] = useState(false);
|
|
86
|
+
const [paymentMethod, setPaymentMethod] = useState<string | null>(null); // null until capabilities load
|
|
87
87
|
const [error, setError] = useState<string | null>(null);
|
|
88
88
|
const [selectedAddressId, setSelectedAddressId] = useState<string | null>(null);
|
|
89
89
|
const [storeAddresses, setStoreAddresses] = useState<any[]>([]); // For pickup selection
|
|
@@ -91,13 +91,80 @@ export function CheckoutScreen() {
|
|
|
91
91
|
const [editingAddress, setEditingAddress] = useState<any | null>(null);
|
|
92
92
|
const [shippingPrice, setShippingPrice] = useState(0);
|
|
93
93
|
const notification = useNotification();
|
|
94
|
-
|
|
94
|
+
const { appliedCoupon, calculateCouponDiscount } = useDiscounts();
|
|
95
95
|
|
|
96
96
|
// Use the addresses hook
|
|
97
97
|
const {
|
|
98
98
|
addresses: userAddresses,
|
|
99
99
|
defaultAddress
|
|
100
100
|
} = useAddresses();
|
|
101
|
+
|
|
102
|
+
// Get store capabilities for payment/delivery options
|
|
103
|
+
const {
|
|
104
|
+
capabilities,
|
|
105
|
+
isLoading: capabilitiesLoading,
|
|
106
|
+
showCardPayment,
|
|
107
|
+
showShippoDelivery,
|
|
108
|
+
showCustomDelivery,
|
|
109
|
+
} = useStoreCapabilities();
|
|
110
|
+
|
|
111
|
+
// Filter payment methods based on capabilities
|
|
112
|
+
const availablePaymentMethods = PAYMENT_METHODS_CONFIG.filter(pm => {
|
|
113
|
+
if (pm.requiresStripe) {
|
|
114
|
+
return showCardPayment;
|
|
115
|
+
}
|
|
116
|
+
return true; // Cash is always available
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Set default payment method once capabilities are loaded
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!capabilitiesLoading && paymentMethod === null && availablePaymentMethods.length > 0) {
|
|
122
|
+
// Prefer Card if available, otherwise use first available
|
|
123
|
+
const preferCard = availablePaymentMethods.find(pm => pm.id === 'Card');
|
|
124
|
+
setPaymentMethod(preferCard ? 'Card' : availablePaymentMethods[0].value);
|
|
125
|
+
}
|
|
126
|
+
}, [capabilitiesLoading, paymentMethod, availablePaymentMethods]);
|
|
127
|
+
|
|
128
|
+
// Set default delivery method once capabilities are loaded
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (!capabilitiesLoading && !hasSetDefaultDelivery && capabilities) {
|
|
131
|
+
// Only default to Pickup if NO delivery options are available
|
|
132
|
+
if (!showShippoDelivery && !showCustomDelivery) {
|
|
133
|
+
setIsDelivery(false);
|
|
134
|
+
}
|
|
135
|
+
// Otherwise leave as null (User must choose)
|
|
136
|
+
setHasSetDefaultDelivery(true);
|
|
137
|
+
}
|
|
138
|
+
}, [capabilitiesLoading, hasSetDefaultDelivery, capabilities, showShippoDelivery, showCustomDelivery]);
|
|
139
|
+
|
|
140
|
+
// Build delivery options based on capabilities
|
|
141
|
+
const deliveryOptions = [
|
|
142
|
+
// Shippo delivery (if enabled)
|
|
143
|
+
...(showShippoDelivery ? [{
|
|
144
|
+
id: 'shippo',
|
|
145
|
+
label: 'Delivery',
|
|
146
|
+
icon: <Truck className="w-5 h-5" />,
|
|
147
|
+
value: true as const,
|
|
148
|
+
desc: 'Shipped to your address',
|
|
149
|
+
}] : []),
|
|
150
|
+
// Custom/Store delivery (if enabled)
|
|
151
|
+
...(showCustomDelivery ? [{
|
|
152
|
+
id: 'custom',
|
|
153
|
+
label: capabilities?.customDeliveryTitle || 'Store Delivery',
|
|
154
|
+
icon: <Truck className="w-5 h-5" />,
|
|
155
|
+
value: 'custom' as const,
|
|
156
|
+
desc: capabilities?.customDeliveryFlatRate
|
|
157
|
+
? `Flat rate: ${formatPrice(capabilities.customDeliveryFlatRate)}`
|
|
158
|
+
: 'Flat rate delivery',
|
|
159
|
+
}] : []),
|
|
160
|
+
{
|
|
161
|
+
id: 'pickup',
|
|
162
|
+
label: 'Pickup',
|
|
163
|
+
icon: <MapPin className="w-5 h-5" />,
|
|
164
|
+
value: false as const,
|
|
165
|
+
desc: 'Collect from pharmacy',
|
|
166
|
+
},
|
|
167
|
+
];
|
|
101
168
|
const [selectedShippingRateId, setSelectedShippingRateId] = useState<string | null>(null);
|
|
102
169
|
const [shippingRates, setShippingRates] = useState<any[]>([]);
|
|
103
170
|
const [shippingRatesLoading, setShippingRatesLoading] = useState(false);
|
|
@@ -118,19 +185,19 @@ export function CheckoutScreen() {
|
|
|
118
185
|
name: user ? `${user.firstname} ${user.lastname}` : '',
|
|
119
186
|
phone: user?.phoneNumber || '',
|
|
120
187
|
country: 'United States',
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
188
|
+
street1: '',
|
|
189
|
+
city: '',
|
|
190
|
+
state: '',
|
|
191
|
+
zip: '',
|
|
125
192
|
},
|
|
126
193
|
billing: {
|
|
127
194
|
name: user ? `${user.firstname} ${user.lastname}` : '',
|
|
128
195
|
phone: user?.phoneNumber || '',
|
|
129
196
|
country: 'United States',
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
197
|
+
street1: '',
|
|
198
|
+
city: '',
|
|
199
|
+
state: '',
|
|
200
|
+
zip: '',
|
|
134
201
|
},
|
|
135
202
|
},
|
|
136
203
|
});
|
|
@@ -231,7 +298,7 @@ export function CheckoutScreen() {
|
|
|
231
298
|
}
|
|
232
299
|
|
|
233
300
|
useEffect(() => {
|
|
234
|
-
if (
|
|
301
|
+
if (isDelivery !== true || !selectedAddressId || !cart || cart?.cartBody?.items?.length === 0 || !cart?.cartBody?.items) {
|
|
235
302
|
setShippingRates([]);
|
|
236
303
|
setSelectedShippingRateId(null);
|
|
237
304
|
return;
|
|
@@ -261,17 +328,24 @@ export function CheckoutScreen() {
|
|
|
261
328
|
}, [isDelivery, selectedAddressId, cart]);
|
|
262
329
|
|
|
263
330
|
useEffect(() => {
|
|
264
|
-
|
|
331
|
+
let cancelled = false;
|
|
332
|
+
|
|
333
|
+
if (isDelivery === false) {
|
|
265
334
|
(async () => {
|
|
266
335
|
const api = new ShippingApi(AXIOS_CONFIG);
|
|
267
336
|
const res = await api.getStoreAddress();
|
|
268
|
-
if
|
|
337
|
+
// Only update state if this effect hasn't been cleaned up
|
|
338
|
+
if (!cancelled && res.data) {
|
|
269
339
|
setStoreAddresses([res.data]);
|
|
270
340
|
setShippingPrice(0);
|
|
271
341
|
}
|
|
272
342
|
})();
|
|
273
343
|
}
|
|
274
|
-
|
|
344
|
+
|
|
345
|
+
// Cleanup function to prevent stale updates when delivery method changes
|
|
346
|
+
return () => {
|
|
347
|
+
cancelled = true;
|
|
348
|
+
};
|
|
275
349
|
}, [isDelivery]);
|
|
276
350
|
|
|
277
351
|
|
|
@@ -293,12 +367,22 @@ export function CheckoutScreen() {
|
|
|
293
367
|
setError('Please select a payment method.');
|
|
294
368
|
return;
|
|
295
369
|
}
|
|
296
|
-
if (isDelivery) {
|
|
370
|
+
if (isDelivery === null) {
|
|
371
|
+
setError('Please select a delivery method.');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
// Check for delivery-related requirements (both shippo and custom delivery need address)
|
|
375
|
+
const isShippoDelivery = isDelivery === true;
|
|
376
|
+
const isCustomDelivery = isDelivery === 'custom';
|
|
377
|
+
const needsDeliveryAddress = isShippoDelivery || isCustomDelivery;
|
|
378
|
+
|
|
379
|
+
if (needsDeliveryAddress) {
|
|
297
380
|
if (!selectedAddressId) {
|
|
298
381
|
setError('Please select a shipping address.');
|
|
299
382
|
return;
|
|
300
383
|
}
|
|
301
|
-
|
|
384
|
+
// Only require shipping rate selection for Shippo delivery
|
|
385
|
+
if (isShippoDelivery && !selectedShippingRateId) {
|
|
302
386
|
setError('Please select a shipping method.');
|
|
303
387
|
return;
|
|
304
388
|
}
|
|
@@ -309,7 +393,8 @@ export function CheckoutScreen() {
|
|
|
309
393
|
return;
|
|
310
394
|
}
|
|
311
395
|
}
|
|
312
|
-
} else {
|
|
396
|
+
} else if (isDelivery === false) {
|
|
397
|
+
// Pickup case
|
|
313
398
|
if (storeAddresses.length === 0) {
|
|
314
399
|
setError('Store pickup location is not available.');
|
|
315
400
|
return;
|
|
@@ -346,6 +431,13 @@ export function CheckoutScreen() {
|
|
|
346
431
|
} else if (paymentMethod === 'Card') {
|
|
347
432
|
billingAddressId = userAddresses.length > 0 ? userAddresses[0].id : undefined;
|
|
348
433
|
}
|
|
434
|
+
// Build manualShipping object for custom delivery
|
|
435
|
+
const manualShipping = isDelivery === 'custom' && selectedAddressId ? {
|
|
436
|
+
amount: capabilities?.customDeliveryFlatRate || 0,
|
|
437
|
+
userAddressId: selectedAddressId,
|
|
438
|
+
shippingTitle: capabilities?.customDeliveryTitle || 'Store Delivery',
|
|
439
|
+
} : undefined;
|
|
440
|
+
|
|
349
441
|
const orderDTO: any = {
|
|
350
442
|
items,
|
|
351
443
|
paymentMethod,
|
|
@@ -353,14 +445,18 @@ export function CheckoutScreen() {
|
|
|
353
445
|
orderRemindingDates: [],
|
|
354
446
|
shippingAddress: data.shipping,
|
|
355
447
|
billingAddress: sameAsShipping ? data.shipping : data.billing,
|
|
448
|
+
...(appliedCoupon && { discountId: appliedCoupon._id || appliedCoupon.id }),
|
|
449
|
+
...(manualShipping && { manualShipping }),
|
|
356
450
|
};
|
|
357
451
|
|
|
358
452
|
const api = new OrdersApi(AXIOS_CONFIG);
|
|
453
|
+
// Convert isDelivery to boolean for API (custom delivery counts as delivery)
|
|
454
|
+
const isDeliveryBoolean = isDelivery !== false;
|
|
359
455
|
const response = await api.createCheckout(
|
|
360
456
|
orderDTO,
|
|
361
|
-
|
|
457
|
+
isDeliveryBoolean,
|
|
362
458
|
undefined, // dont pass userId
|
|
363
|
-
isDelivery ? (selectedShippingRateId || undefined) : undefined,
|
|
459
|
+
isDeliveryBoolean && isDelivery === true ? (selectedShippingRateId || undefined) : undefined,
|
|
364
460
|
billingAddressId
|
|
365
461
|
);
|
|
366
462
|
if (response?.data?.payment && response.data.payment.hostedInvoiceUrl === 'INSUFFICIENT_CREDIT') {
|
|
@@ -428,13 +524,23 @@ export function CheckoutScreen() {
|
|
|
428
524
|
}
|
|
429
525
|
};
|
|
430
526
|
|
|
527
|
+
// Redirect if cart is empty - use useEffect to avoid rendering during render
|
|
528
|
+
React.useEffect(() => {
|
|
529
|
+
if (!cart || cart?.cartBody?.items?.length === 0 || !cart?.cartBody?.items) {
|
|
530
|
+
router.push(buildPath('/cart'));
|
|
531
|
+
}
|
|
532
|
+
}, [cart, router]);
|
|
533
|
+
|
|
431
534
|
if (!cart || cart?.cartBody?.items?.length === 0 || !cart?.cartBody?.items) {
|
|
432
|
-
router.push(buildPath('/cart'));
|
|
433
535
|
return null;
|
|
434
536
|
}
|
|
435
|
-
|
|
537
|
+
|
|
538
|
+
const subtotal = Math.round(cart.cartBody.items.reduce((total, item) => total + item.productVariantData.finalPrice * item.quantity, 0) * 100) / 100;
|
|
539
|
+
const discountAmount = appliedCoupon ? calculateCouponDiscount(subtotal) : 0;
|
|
540
|
+
const subtotalAfterDiscount = subtotal - discountAmount;
|
|
436
541
|
const tax = 0;
|
|
437
|
-
const total =
|
|
542
|
+
const total = subtotalAfterDiscount + shippingPrice + tax;
|
|
543
|
+
|
|
438
544
|
|
|
439
545
|
return (
|
|
440
546
|
<div className="min-h-screen bg-white pb-16">
|
|
@@ -456,143 +562,267 @@ export function CheckoutScreen() {
|
|
|
456
562
|
Complete your order information below
|
|
457
563
|
</p>
|
|
458
564
|
</div>
|
|
459
|
-
|
|
460
|
-
|
|
565
|
+
{/* Delivery and Payment Methods Section - Side by Side */}
|
|
566
|
+
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8">
|
|
567
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
568
|
+
{/* Delivery Method */}
|
|
461
569
|
<div>
|
|
462
|
-
<
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
570
|
+
<div className="flex items-center gap-3 mb-6">
|
|
571
|
+
<Truck className="w-6 h-6 text-[#2B4B7C]" />
|
|
572
|
+
<h2 className="text-xl font-semibold text-[#2B4B7C]">Delivery Method</h2>
|
|
573
|
+
</div>
|
|
574
|
+
|
|
575
|
+
<div className="space-y-3">
|
|
576
|
+
{deliveryOptions.map((option) => {
|
|
577
|
+
const active = isDelivery === option.value;
|
|
578
|
+
return (
|
|
579
|
+
<button
|
|
580
|
+
key={option.id}
|
|
581
|
+
type="button"
|
|
582
|
+
onClick={() => {
|
|
583
|
+
setIsDelivery(option.value);
|
|
584
|
+
// Reset shipping rate when changing delivery method
|
|
585
|
+
setSelectedShippingRateId(null);
|
|
586
|
+
// Set shipping price based on delivery type
|
|
587
|
+
if (option.value === 'custom' && capabilities?.customDeliveryFlatRate) {
|
|
588
|
+
setShippingPrice(capabilities.customDeliveryFlatRate);
|
|
589
|
+
} else if (option.value === false) {
|
|
590
|
+
setShippingPrice(0);
|
|
591
|
+
} else {
|
|
592
|
+
setShippingPrice(0); // Will be set when shipping rate is selected
|
|
593
|
+
}
|
|
594
|
+
}}
|
|
595
|
+
className={`relative flex w-full items-center justify-between rounded-xl border-2 p-3 transition-all duration-200 ${active
|
|
596
|
+
? 'border-primary-500 bg-primary-50'
|
|
597
|
+
: 'border-gray-200 hover:border-primary-300'
|
|
598
|
+
}`}
|
|
599
|
+
>
|
|
600
|
+
<div className="flex items-center gap-3">
|
|
601
|
+
<div
|
|
602
|
+
className={`p-2 rounded-lg ${active ? 'bg-primary-100 text-primary-600' : 'bg-gray-100 text-gray-600'
|
|
603
|
+
}`}
|
|
604
|
+
>
|
|
605
|
+
{option.icon}
|
|
606
|
+
</div>
|
|
607
|
+
<div className="text-left">
|
|
608
|
+
<div className="font-medium text-gray-900">{option.label}</div>
|
|
609
|
+
<p className="text-xs text-gray-500">{option.desc}</p>
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
{active && (
|
|
613
|
+
<div className="w-5 h-5 rounded-full bg-primary-500 flex items-center justify-center shrink-0">
|
|
614
|
+
<Check className="w-3 h-3 text-white" />
|
|
615
|
+
</div>
|
|
616
|
+
)}
|
|
617
|
+
</button>
|
|
618
|
+
);
|
|
619
|
+
})}
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
|
|
623
|
+
{/* Payment Method */}
|
|
624
|
+
<div>
|
|
625
|
+
<div className="flex items-center gap-3 mb-6">
|
|
626
|
+
<CreditCard className="w-6 h-6 text-[#2B4B7C]" />
|
|
627
|
+
<h2 className="text-xl font-semibold text-[#2B4B7C]">Payment Method</h2>
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
<div className="space-y-3">
|
|
631
|
+
{availablePaymentMethods.map((pm) => {
|
|
632
|
+
const active = paymentMethod === pm.value;
|
|
633
|
+
|
|
634
|
+
return (
|
|
635
|
+
<button
|
|
636
|
+
key={pm.value}
|
|
637
|
+
type="button"
|
|
638
|
+
onClick={() => setPaymentMethod(pm.value)}
|
|
639
|
+
className={`
|
|
640
|
+
group relative flex w-full items-center justify-between rounded-xl border-2 p-3
|
|
641
|
+
transition-all duration-200 ease-out
|
|
642
|
+
focus:outline-hidden
|
|
643
|
+
${active
|
|
644
|
+
? `${pm.activeClass} shadow-md scale-[1.02]`
|
|
645
|
+
: `${pm.className} border-gray-200 bg-white hover:shadow-xs hover:-translate-y-0.5`
|
|
646
|
+
}
|
|
647
|
+
`}
|
|
648
|
+
>
|
|
649
|
+
<div className="flex items-center gap-3">
|
|
650
|
+
<div
|
|
651
|
+
className={`
|
|
652
|
+
flex items-center justify-center rounded-lg p-2
|
|
653
|
+
transition-colors duration-200
|
|
654
|
+
${pm.value === 'Card'
|
|
655
|
+
? ' text-blue-600'
|
|
656
|
+
: pm.value === 'Cash'
|
|
657
|
+
? ' text-amber-600'
|
|
658
|
+
: ' text-emerald-600'
|
|
659
|
+
}
|
|
660
|
+
${active ? 'opacity-80' : 'group-hover:opacity-90'}
|
|
661
|
+
`}
|
|
662
|
+
>
|
|
663
|
+
{pm.icon}
|
|
664
|
+
</div>
|
|
665
|
+
|
|
666
|
+
<span className="text-sm font-semibold text-gray-900">
|
|
667
|
+
{pm.label}
|
|
668
|
+
</span>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
{active && (
|
|
672
|
+
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-current shrink-0">
|
|
673
|
+
<Check className="h-3 w-3 text-white" />
|
|
674
|
+
</div>
|
|
675
|
+
)}
|
|
676
|
+
</button>
|
|
677
|
+
);
|
|
678
|
+
})}
|
|
679
|
+
</div>
|
|
680
|
+
|
|
681
|
+
<p className="text-xs text-slate-500 mt-4 px-1">
|
|
682
|
+
{paymentMethod === 'Card' &&
|
|
683
|
+
'You will be redirected to a secure payment page.'}
|
|
684
|
+
{paymentMethod === 'Cash' &&
|
|
685
|
+
'Pay with cash at the time of delivery or pickup.'}
|
|
467
686
|
</p>
|
|
468
687
|
</div>
|
|
469
|
-
<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">
|
|
470
|
-
<Truck className="h-4 w-4" />
|
|
471
|
-
Dispatch in 12h
|
|
472
|
-
</span>
|
|
473
688
|
</div>
|
|
689
|
+
</div>
|
|
474
690
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
<div
|
|
479
|
-
<
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
488
|
-
Add New Address
|
|
489
|
-
</Button>
|
|
490
|
-
</div>
|
|
691
|
+
{isDelivery !== null && (
|
|
692
|
+
<section className="bg-white border-2 border-gray-100 rounded-[24px] p-8 text-[#2B4B7C]">
|
|
693
|
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
694
|
+
<div>
|
|
695
|
+
<h2 className="font-['Poppins',sans-serif] font-semibold text-[#2B4B7C] mb-2 text-2xl">
|
|
696
|
+
{isDelivery ? 'Delivery Address' : 'Contact Information'}
|
|
697
|
+
</h2>
|
|
698
|
+
<p className="text-sm text-slate-500">
|
|
699
|
+
We use temperature-aware packaging and real-time tracking on every shipment.
|
|
700
|
+
</p>
|
|
701
|
+
</div>
|
|
702
|
+
</div>
|
|
491
703
|
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
704
|
+
<div className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
705
|
+
{/* Address selection for delivery */}
|
|
706
|
+
{isDelivery && (
|
|
707
|
+
<div className="md:col-span-2 space-y-4">
|
|
708
|
+
<div className="flex items-center justify-between">
|
|
709
|
+
<label className="block font-semibold">Select Address</label>
|
|
710
|
+
<Button
|
|
711
|
+
type="button"
|
|
712
|
+
variant="outline-solid"
|
|
713
|
+
size="sm"
|
|
714
|
+
onClick={() => { setEditingAddress(null); setIsAddressModalOpen(true); }}
|
|
715
|
+
>
|
|
716
|
+
<Plus className="h-4 w-4 mr-2" />
|
|
717
|
+
Add New Address
|
|
718
|
+
</Button>
|
|
719
|
+
</div>
|
|
720
|
+
|
|
721
|
+
{userAddresses.length > 0 ? (
|
|
722
|
+
<div className="grid gap-4">
|
|
723
|
+
{userAddresses.map(addr => (
|
|
724
|
+
<label
|
|
725
|
+
key={addr.id}
|
|
726
|
+
className={`group relative flex items-start gap-3 p-4 rounded-2xl border ${selectedAddressId === addr.id
|
|
727
|
+
? 'border-primary-500 bg-primary-50 shadow-xs'
|
|
728
|
+
: 'border-slate-200 bg-white'
|
|
729
|
+
} cursor-pointer hover:border-primary-300 transition-colors`}
|
|
730
|
+
>
|
|
731
|
+
<input
|
|
732
|
+
type="radio"
|
|
733
|
+
name="selectedAddress"
|
|
734
|
+
value={addr.id}
|
|
735
|
+
checked={selectedAddressId === addr.id}
|
|
736
|
+
onChange={() => {
|
|
737
|
+
setSelectedAddressId(addr.id);
|
|
738
|
+
setValue('shipping.name', addr.name);
|
|
739
|
+
setValue('shipping.phone', addr.phone || '');
|
|
740
|
+
setValue('shipping.street1', addr.street1);
|
|
741
|
+
setValue('shipping.street2', addr.street2 || '');
|
|
742
|
+
setValue('shipping.city', addr.city);
|
|
743
|
+
setValue('shipping.state', addr.state);
|
|
744
|
+
setValue('shipping.zip', addr.zip);
|
|
745
|
+
setValue('shipping.country', addr.country);
|
|
746
|
+
}}
|
|
747
|
+
className="mt-1"
|
|
748
|
+
/>
|
|
749
|
+
<div className="flex-1">
|
|
750
|
+
<p className="font-semibold text-slate-900">{addr.name}</p>
|
|
751
|
+
<p className="text-sm text-slate-600">{addr.street1}</p>
|
|
752
|
+
{addr.street2 && (
|
|
753
|
+
<p className="text-sm text-slate-600">{addr.street2}</p>
|
|
538
754
|
)}
|
|
539
|
-
<
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
<
|
|
545
|
-
|
|
546
|
-
<
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
755
|
+
<p className="text-sm text-slate-600">
|
|
756
|
+
{addr.city}, {addr.state} {addr.zip}
|
|
757
|
+
</p>
|
|
758
|
+
<p className="text-sm text-slate-600">{addr.country}</p>
|
|
759
|
+
{addr.phone && (
|
|
760
|
+
<p className="text-sm text-slate-600 mt-1">{addr.phone}</p>
|
|
761
|
+
)}
|
|
762
|
+
<div className="mt-3 flex items-center gap-2">
|
|
763
|
+
{addr.isDefault && (
|
|
764
|
+
<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">
|
|
765
|
+
Default
|
|
766
|
+
</span>
|
|
767
|
+
)}
|
|
768
|
+
<button
|
|
769
|
+
type="button"
|
|
770
|
+
onClick={(e) => { e.preventDefault(); setEditingAddress(addr); setIsAddressModalOpen(true); }}
|
|
771
|
+
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"
|
|
772
|
+
>
|
|
773
|
+
<Edit3 className="h-3.5 w-3.5" /> Edit
|
|
774
|
+
</button>
|
|
775
|
+
<button
|
|
776
|
+
type="button"
|
|
777
|
+
onClick={async (e) => {
|
|
778
|
+
e.preventDefault();
|
|
779
|
+
const yes = window.confirm('Delete this address?');
|
|
780
|
+
if (!yes) return;
|
|
781
|
+
try {
|
|
782
|
+
await removeAddress(addr.id);
|
|
783
|
+
if (selectedAddressId === addr.id) setSelectedAddressId(null);
|
|
784
|
+
notification.success('Address deleted');
|
|
785
|
+
} catch (e) {
|
|
786
|
+
notification.error('Failed to delete address');
|
|
787
|
+
}
|
|
788
|
+
}}
|
|
789
|
+
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"
|
|
790
|
+
>
|
|
791
|
+
<Trash2 className="h-3.5 w-3.5" /> Delete
|
|
792
|
+
</button>
|
|
793
|
+
</div>
|
|
564
794
|
</div>
|
|
565
|
-
</
|
|
566
|
-
|
|
795
|
+
</label>
|
|
796
|
+
))}
|
|
797
|
+
</div>
|
|
798
|
+
) : (
|
|
799
|
+
<div className="text-center py-8 bg-slate-50 rounded-lg">
|
|
800
|
+
<MapPin className="h-12 w-12 mx-auto text-slate-400" />
|
|
801
|
+
<p className="mt-2 text-slate-600">No addresses found</p>
|
|
802
|
+
<p className="text-sm text-slate-500">Add a new address to continue</p>
|
|
803
|
+
</div>
|
|
804
|
+
)}
|
|
805
|
+
</div>
|
|
806
|
+
)}
|
|
807
|
+
{/* Store address selection for pickup */}
|
|
808
|
+
{isDelivery === false && storeAddresses.length > 0 && (
|
|
809
|
+
<div className="md:col-span-2">
|
|
810
|
+
<label className="block mb-2 font-semibold">Select Pickup Location</label>
|
|
811
|
+
<div className="flex items-center gap-2">
|
|
812
|
+
{storeAddresses.map(addr => (
|
|
813
|
+
<div key={addr.id} className="w-full border rounded-sm p-2">
|
|
814
|
+
<p>{addr.name} - {addr.street1}, {addr.city}, {addr.state} {addr.zip}</p>
|
|
815
|
+
</div>
|
|
567
816
|
))}
|
|
568
817
|
</div>
|
|
569
|
-
) : (
|
|
570
|
-
<div className="text-center py-8 bg-slate-50 rounded-lg">
|
|
571
|
-
<MapPin className="h-12 w-12 mx-auto text-slate-400" />
|
|
572
|
-
<p className="mt-2 text-slate-600">No addresses found</p>
|
|
573
|
-
<p className="text-sm text-slate-500">Add a new address to continue</p>
|
|
574
|
-
</div>
|
|
575
|
-
)}
|
|
576
|
-
</div>
|
|
577
|
-
)}
|
|
578
|
-
{/* Store address selection for pickup */}
|
|
579
|
-
{!isDelivery && storeAddresses.length > 0 && (
|
|
580
|
-
<div className="md:col-span-2">
|
|
581
|
-
<label className="block mb-2 font-semibold">Select Pickup Location</label>
|
|
582
|
-
<div className="flex items-center gap-2">
|
|
583
|
-
{storeAddresses.map(addr => (
|
|
584
|
-
<div key={addr.id} className="w-full border rounded-sm p-2">
|
|
585
|
-
<p>{addr.name} - {addr.street1}, {addr.city}, {addr.state} {addr.zip}</p>
|
|
586
|
-
</div>
|
|
587
|
-
))}
|
|
588
818
|
</div>
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
</
|
|
592
|
-
|
|
819
|
+
)}
|
|
820
|
+
</div>
|
|
821
|
+
</section>
|
|
822
|
+
)}
|
|
593
823
|
|
|
594
|
-
{/* Shipping Options - Only show if there's a shipping cost */}
|
|
595
|
-
{isDelivery && selectedAddressId && (
|
|
824
|
+
{/* Shipping Options - Only show if there's a shipping cost and it's Shippo delivery */}
|
|
825
|
+
{isDelivery === true && selectedAddressId && (
|
|
596
826
|
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8 text-[#2B4B7C]">
|
|
597
827
|
<div className="flex items-center gap-3 text-xl font-semibold text-gray-900 pb-4 mb-8 border-b">
|
|
598
828
|
<Truck className="w-8 h-8 flex items-center justify-center text-[#2B4B7C]" />
|
|
@@ -737,139 +967,7 @@ export function CheckoutScreen() {
|
|
|
737
967
|
</div>
|
|
738
968
|
)}
|
|
739
969
|
|
|
740
|
-
{/* Delivery and Payment Methods Section */}
|
|
741
|
-
<div className="space-y-6">
|
|
742
|
-
{/* Delivery Method */}
|
|
743
|
-
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8">
|
|
744
|
-
<div className="flex items-center gap-3 mb-6">
|
|
745
|
-
<Truck className="w-8 h-8 text-[#2B4B7C]" />
|
|
746
|
-
<h2 className="text-2xl font-semibold text-[#2B4B7C]">Delivery Method</h2>
|
|
747
|
-
</div>
|
|
748
|
-
|
|
749
|
-
<div className="grid grid-cols-1 gap-3">
|
|
750
|
-
{[
|
|
751
|
-
{
|
|
752
|
-
label: 'Delivery',
|
|
753
|
-
icon: <Truck className="w-5 h-5" />,
|
|
754
|
-
value: true,
|
|
755
|
-
desc: 'Shipped to your address',
|
|
756
|
-
},
|
|
757
|
-
{
|
|
758
|
-
label: 'Pickup',
|
|
759
|
-
icon: <MapPin className="w-5 h-5" />,
|
|
760
|
-
value: false,
|
|
761
|
-
desc: 'Collect from pharmacy',
|
|
762
|
-
},
|
|
763
|
-
].map((option) => {
|
|
764
|
-
const active = isDelivery === option.value;
|
|
765
|
-
return (
|
|
766
|
-
<button
|
|
767
|
-
key={option.label}
|
|
768
|
-
type="button"
|
|
769
|
-
onClick={() => {
|
|
770
|
-
setIsDelivery(option.value);
|
|
771
|
-
// Reset selected shipping rate when changing delivery method
|
|
772
|
-
if (option.value) {
|
|
773
|
-
setSelectedShippingRateId(null);
|
|
774
|
-
setShippingPrice(0);
|
|
775
|
-
}
|
|
776
|
-
}}
|
|
777
|
-
className={`relative flex w-full items-center justify-between rounded-xl border-2 p-4 transition-all duration-200 ${active
|
|
778
|
-
? 'border-primary-500 bg-primary-50'
|
|
779
|
-
: 'border-gray-200 hover:border-primary-300'
|
|
780
|
-
}`}
|
|
781
|
-
>
|
|
782
|
-
<div className="flex items-center gap-3">
|
|
783
|
-
<div
|
|
784
|
-
className={`p-2 rounded-lg ${active ? 'bg-primary-100 text-primary-600' : 'bg-gray-100 text-gray-600'
|
|
785
|
-
}`}
|
|
786
|
-
>
|
|
787
|
-
{option.icon}
|
|
788
|
-
</div>
|
|
789
|
-
<div className="text-left">
|
|
790
|
-
<div className="font-medium text-gray-900">{option.label}</div>
|
|
791
|
-
<p className="text-sm text-gray-500">{option.desc}</p>
|
|
792
|
-
</div>
|
|
793
|
-
</div>
|
|
794
|
-
{active && (
|
|
795
|
-
<div className="w-5 h-5 rounded-full bg-primary-500 flex items-center justify-center">
|
|
796
|
-
<Check className="w-3 h-3 text-white" />
|
|
797
|
-
</div>
|
|
798
|
-
)}
|
|
799
|
-
</button>
|
|
800
|
-
);
|
|
801
|
-
})}
|
|
802
|
-
</div>
|
|
803
|
-
</div>
|
|
804
|
-
|
|
805
|
-
{/* Payment Method */}
|
|
806
|
-
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8">
|
|
807
|
-
<div className="flex items-center gap-3 mb-6">
|
|
808
|
-
<CreditCard className="w-8 h-8 text-[#2B4B7C]" />
|
|
809
|
-
<h2 className="text-2xl font-semibold text-[#2B4B7C]">Payment Method</h2>
|
|
810
|
-
</div>
|
|
811
|
-
|
|
812
|
-
<div className="grid gap-4 md:grid-cols-3">
|
|
813
|
-
{PAYMENT_METHODS.map((pm) => {
|
|
814
|
-
const active = paymentMethod === pm.value;
|
|
815
|
-
|
|
816
|
-
return (
|
|
817
|
-
<button
|
|
818
|
-
key={pm.value}
|
|
819
|
-
type="button"
|
|
820
|
-
onClick={() => setPaymentMethod(pm.value)}
|
|
821
|
-
className={`
|
|
822
|
-
group relative flex w-full items-center justify-between rounded-xl border-2 p-3
|
|
823
|
-
transition-all duration-200 ease-out
|
|
824
|
-
focus:outline-hidden f
|
|
825
|
-
${active
|
|
826
|
-
? `${pm.activeClass} shadow-md scale-[1.02]`
|
|
827
|
-
: `${pm.className} border-gray-200 bg-white hover:shadow-xs hover:-translate-y-0.5`
|
|
828
|
-
}
|
|
829
|
-
`}
|
|
830
|
-
>
|
|
831
|
-
<div className="flex items-center gap-3">
|
|
832
|
-
<div
|
|
833
|
-
className={`
|
|
834
|
-
flex items-center justify-center rounded-lg p-2
|
|
835
|
-
transition-colors duration-200
|
|
836
|
-
${pm.value === 'Card'
|
|
837
|
-
? ' text-blue-600'
|
|
838
|
-
: pm.value === 'Cash'
|
|
839
|
-
? ' text-amber-600'
|
|
840
|
-
: ' text-emerald-600'
|
|
841
|
-
}
|
|
842
|
-
${active ? 'opacity-80' : 'group-hover:opacity-90'}
|
|
843
|
-
`}
|
|
844
|
-
>
|
|
845
|
-
{pm.icon}
|
|
846
|
-
</div>
|
|
847
|
-
|
|
848
|
-
<span className="text-sm font-semibold text-gray-900">
|
|
849
|
-
{pm.label}
|
|
850
|
-
</span>
|
|
851
|
-
</div>
|
|
852
970
|
|
|
853
|
-
{active && (
|
|
854
|
-
<div className="flex h-6 w-6 items-center justify-center rounded-full shadow-xs">
|
|
855
|
-
<Check className="h-3.5 w-3.5 text-white" />
|
|
856
|
-
</div>
|
|
857
|
-
)}
|
|
858
|
-
</button>
|
|
859
|
-
);
|
|
860
|
-
})}
|
|
861
|
-
</div>
|
|
862
|
-
|
|
863
|
-
<p className="text-sm text-gray-500 mt-4">
|
|
864
|
-
{paymentMethod === 'Card' &&
|
|
865
|
-
'You will be redirected to a secure payment page.'}
|
|
866
|
-
{paymentMethod === 'Cash' &&
|
|
867
|
-
'Pay with cash at the time of delivery or pickup.'}
|
|
868
|
-
{/* {paymentMethod === 'Credit' &&
|
|
869
|
-
'Use your available account credit for this order.'} */}
|
|
870
|
-
</p>
|
|
871
|
-
</div>
|
|
872
|
-
</div>
|
|
873
971
|
</motion.div>
|
|
874
972
|
|
|
875
973
|
<motion.aside
|
|
@@ -908,6 +1006,14 @@ export function CheckoutScreen() {
|
|
|
908
1006
|
|
|
909
1007
|
<div className="h-px bg-[#5B9BD5]/20 my-4" />
|
|
910
1008
|
|
|
1009
|
+
{/* Coupon Code Section */}
|
|
1010
|
+
<div className="mb-6">
|
|
1011
|
+
<CouponCodeInput
|
|
1012
|
+
userId={user?.id}
|
|
1013
|
+
className="mb-4"
|
|
1014
|
+
/>
|
|
1015
|
+
</div>
|
|
1016
|
+
|
|
911
1017
|
{/* Totals */}
|
|
912
1018
|
<div className="text-sm text-slate-600 space-y-3 py-4">
|
|
913
1019
|
<div className="flex items-center justify-between">
|
|
@@ -916,6 +1022,12 @@ export function CheckoutScreen() {
|
|
|
916
1022
|
{formatPrice(subtotal)}
|
|
917
1023
|
</span>
|
|
918
1024
|
</div>
|
|
1025
|
+
{discountAmount > 0 && (
|
|
1026
|
+
<div className="flex items-center justify-between text-green-600">
|
|
1027
|
+
<span>Discount ({appliedCoupon?.code})</span>
|
|
1028
|
+
<span className="font-semibold">-{formatPrice(discountAmount)}</span>
|
|
1029
|
+
</div>
|
|
1030
|
+
)}
|
|
919
1031
|
{isDelivery && (
|
|
920
1032
|
<div className="flex items-center justify-between">
|
|
921
1033
|
<span>Shipping</span>
|