hey-pharmacist-ecommerce 1.1.13 → 1.1.15
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 +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1039 -857
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1039 -856
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/components/AccountAddressesTab.tsx +209 -0
- package/src/components/AccountOrdersTab.tsx +151 -0
- package/src/components/AccountOverviewTab.tsx +209 -0
- package/src/components/AccountPaymentTab.tsx +116 -0
- package/src/components/AccountSavedItemsTab.tsx +76 -0
- package/src/components/AccountSettingsTab.tsx +116 -0
- package/src/components/AddressFormModal.tsx +23 -10
- package/src/components/CartItem.tsx +60 -56
- package/src/components/Header.tsx +69 -16
- package/src/components/Notification.tsx +148 -0
- package/src/components/ProductCard.tsx +215 -178
- package/src/components/QuickViewModal.tsx +314 -0
- package/src/components/TabNavigation.tsx +48 -0
- package/src/components/ui/Button.tsx +1 -1
- package/src/components/ui/ConfirmModal.tsx +84 -0
- package/src/hooks/usePaymentMethods.ts +58 -0
- package/src/index.ts +0 -1
- package/src/providers/CartProvider.tsx +22 -6
- package/src/providers/EcommerceProvider.tsx +8 -7
- package/src/providers/FavoritesProvider.tsx +10 -3
- package/src/providers/NotificationProvider.tsx +79 -0
- package/src/providers/WishlistProvider.tsx +34 -9
- package/src/screens/AddressesScreen.tsx +72 -61
- package/src/screens/CartScreen.tsx +48 -32
- package/src/screens/ChangePasswordScreen.tsx +155 -0
- package/src/screens/CheckoutScreen.tsx +162 -125
- package/src/screens/EditProfileScreen.tsx +165 -0
- package/src/screens/LoginScreen.tsx +59 -72
- package/src/screens/NewAddressScreen.tsx +16 -10
- package/src/screens/ProductDetailScreen.tsx +334 -234
- package/src/screens/ProfileScreen.tsx +190 -200
- package/src/screens/RegisterScreen.tsx +51 -70
- package/src/screens/SearchResultsScreen.tsx +2 -1
- package/src/screens/ShopScreen.tsx +260 -384
- package/src/screens/WishlistScreen.tsx +226 -224
- package/src/styles/globals.css +9 -0
- package/src/screens/CategoriesScreen.tsx +0 -122
- package/src/screens/HomeScreen.tsx +0 -211
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { motion } from 'framer-motion';
|
|
5
|
+
import { useForm } from 'react-hook-form';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
8
|
+
import { useRouter } from 'next/navigation';
|
|
9
|
+
import { Lock, ShieldCheck } from 'lucide-react';
|
|
10
|
+
import { Input } from '@/components/ui/Input';
|
|
11
|
+
import { Button } from '@/components/ui/Button';
|
|
12
|
+
import { AuthApi } from '@/lib/Apis/apis/auth-api';
|
|
13
|
+
import { getApiConfiguration } from '@/lib/api-adapter/config';
|
|
14
|
+
import { useAuth } from '@/providers/AuthProvider';
|
|
15
|
+
import { useBasePath } from '@/providers/BasePathProvider';
|
|
16
|
+
|
|
17
|
+
const changePasswordSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
currentPassword: z.string().min(6, 'Current password is required'),
|
|
20
|
+
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
|
21
|
+
confirmPassword: z.string().min(8, 'Confirm your new password'),
|
|
22
|
+
})
|
|
23
|
+
.refine((data) => data.newPassword === data.confirmPassword, {
|
|
24
|
+
path: ['confirmPassword'],
|
|
25
|
+
message: 'Passwords do not match',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
type ChangePasswordFormData = z.infer<typeof changePasswordSchema>;
|
|
29
|
+
|
|
30
|
+
export function ChangePasswordScreen() {
|
|
31
|
+
const router = useRouter();
|
|
32
|
+
const { user } = useAuth();
|
|
33
|
+
const { buildPath } = useBasePath();
|
|
34
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
35
|
+
const [status, setStatus] = useState<{ type: 'success' | 'error'; message: string } | null>(
|
|
36
|
+
null
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
register,
|
|
41
|
+
handleSubmit,
|
|
42
|
+
formState: { errors },
|
|
43
|
+
} = useForm<ChangePasswordFormData>({
|
|
44
|
+
resolver: zodResolver(changePasswordSchema),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!user) {
|
|
48
|
+
router.push(buildPath('/login'));
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const onSubmit = async (data: ChangePasswordFormData) => {
|
|
53
|
+
setIsSubmitting(true);
|
|
54
|
+
setStatus(null);
|
|
55
|
+
try {
|
|
56
|
+
await new AuthApi(getApiConfiguration()).changePassword(
|
|
57
|
+
data.currentPassword,
|
|
58
|
+
data.newPassword
|
|
59
|
+
);
|
|
60
|
+
setStatus({ type: 'success', message: 'Password updated successfully' });
|
|
61
|
+
setTimeout(() => router.push(buildPath('/account')), 600);
|
|
62
|
+
} catch (error: any) {
|
|
63
|
+
setStatus({
|
|
64
|
+
type: 'error',
|
|
65
|
+
message: error?.response?.data?.message || 'Unable to update password',
|
|
66
|
+
});
|
|
67
|
+
} finally {
|
|
68
|
+
setIsSubmitting(false);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className="min-h-screen bg-slate-50 text-slate-900">
|
|
74
|
+
<div className="container mx-auto px-4 pb-16 pt-10">
|
|
75
|
+
<motion.div
|
|
76
|
+
initial={{ opacity: 0, y: 18 }}
|
|
77
|
+
animate={{ opacity: 1, y: 0 }}
|
|
78
|
+
className="mx-auto max-w-2xl rounded-3xl border border-slate-200 bg-white p-8 shadow-xl shadow-primary-50"
|
|
79
|
+
>
|
|
80
|
+
<div className="flex items-center gap-3">
|
|
81
|
+
<span className="flex h-11 w-11 items-center justify-center rounded-2xl bg-primary-50 text-primary-600">
|
|
82
|
+
<Lock className="h-5 w-5" />
|
|
83
|
+
</span>
|
|
84
|
+
<div>
|
|
85
|
+
<p className="text-xs font-semibold uppercase tracking-[0.32em] text-slate-500">
|
|
86
|
+
Security
|
|
87
|
+
</p>
|
|
88
|
+
<h1 className="text-2xl font-semibold text-slate-900">Change password</h1>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<p className="mt-3 text-sm text-slate-600">
|
|
92
|
+
Use a strong password that you have not used elsewhere. Updating your password will sign
|
|
93
|
+
you out of other active sessions.
|
|
94
|
+
</p>
|
|
95
|
+
|
|
96
|
+
{status && (
|
|
97
|
+
<div
|
|
98
|
+
className={`mt-4 flex items-start gap-2 rounded-2xl border px-4 py-3 text-sm ${
|
|
99
|
+
status.type === 'success'
|
|
100
|
+
? 'border-green-200 bg-green-50 text-green-800'
|
|
101
|
+
: 'border-red-200 bg-red-50 text-red-700'
|
|
102
|
+
}`}
|
|
103
|
+
>
|
|
104
|
+
<span className="mt-[2px] text-base">{status.type === 'success' ? '✔' : '!'}</span>
|
|
105
|
+
<span>{status.message}</span>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
<form onSubmit={handleSubmit(onSubmit)} className="mt-8 space-y-5">
|
|
110
|
+
<Input
|
|
111
|
+
type="password"
|
|
112
|
+
label="Current password"
|
|
113
|
+
placeholder="Enter current password"
|
|
114
|
+
{...register('currentPassword')}
|
|
115
|
+
error={errors.currentPassword?.message}
|
|
116
|
+
/>
|
|
117
|
+
<Input
|
|
118
|
+
type="password"
|
|
119
|
+
label="New password"
|
|
120
|
+
placeholder="Enter new password"
|
|
121
|
+
{...register('newPassword')}
|
|
122
|
+
error={errors.newPassword?.message}
|
|
123
|
+
/>
|
|
124
|
+
<Input
|
|
125
|
+
type="password"
|
|
126
|
+
label="Confirm new password"
|
|
127
|
+
placeholder="Re-type new password"
|
|
128
|
+
{...register('confirmPassword')}
|
|
129
|
+
error={errors.confirmPassword?.message}
|
|
130
|
+
/>
|
|
131
|
+
<div className="flex flex-wrap gap-3">
|
|
132
|
+
<Button type="submit" size="lg" isLoading={isSubmitting}>
|
|
133
|
+
Save password
|
|
134
|
+
</Button>
|
|
135
|
+
<Button
|
|
136
|
+
type="button"
|
|
137
|
+
variant="outline"
|
|
138
|
+
size="lg"
|
|
139
|
+
className="border-slate-300 text-slate-800 hover:bg-slate-50"
|
|
140
|
+
onClick={() => router.push(buildPath('/account'))}
|
|
141
|
+
>
|
|
142
|
+
Cancel
|
|
143
|
+
</Button>
|
|
144
|
+
</div>
|
|
145
|
+
</form>
|
|
146
|
+
|
|
147
|
+
<div className="mt-6 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
|
|
148
|
+
<ShieldCheck className="h-4 w-4 text-primary-600" />
|
|
149
|
+
Strong passwords and up-to-date contact details help pharmacists verify changes quickly.
|
|
150
|
+
</div>
|
|
151
|
+
</motion.div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
Plus,
|
|
15
15
|
ShieldCheck,
|
|
16
16
|
Truck,
|
|
17
|
+
Check,
|
|
17
18
|
} from 'lucide-react';
|
|
18
19
|
import { Button } from '@/components/ui/Button';
|
|
19
20
|
import { Input } from '@/components/ui/Input';
|
|
@@ -24,7 +25,6 @@ import { ShippingApi } from '@/lib/Apis/apis/shipping-api';
|
|
|
24
25
|
import { useAddresses } from '@/hooks/useAddresses';
|
|
25
26
|
import { formatPrice } from '@/lib/utils/format';
|
|
26
27
|
import { useRouter } from 'next/navigation';
|
|
27
|
-
import { toast } from 'sonner';
|
|
28
28
|
import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
|
|
29
29
|
import { Card } from '@/components/ui/Card';
|
|
30
30
|
import { AddressFormModal } from '@/components/AddressFormModal';
|
|
@@ -33,6 +33,7 @@ import { useBasePath } from '@/providers/BasePathProvider';
|
|
|
33
33
|
|
|
34
34
|
import { addressSchema } from '@/lib/validations/address';
|
|
35
35
|
import Image from 'next/image';
|
|
36
|
+
import { useNotification } from '@/providers/NotificationProvider';
|
|
36
37
|
|
|
37
38
|
const checkoutSchema = z.object({
|
|
38
39
|
shipping: addressSchema,
|
|
@@ -89,6 +90,7 @@ export function CheckoutScreen() {
|
|
|
89
90
|
const [isAddressModalOpen, setIsAddressModalOpen] = useState(false);
|
|
90
91
|
const [editingAddress, setEditingAddress] = useState<any | null>(null);
|
|
91
92
|
const [shippingPrice, setShippingPrice] = useState(0);
|
|
93
|
+
const notification = useNotification();
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
// Use the addresses hook
|
|
@@ -175,7 +177,7 @@ export function CheckoutScreen() {
|
|
|
175
177
|
// Sort rates by price (cheapest first) and select the first one
|
|
176
178
|
const sortedRates = [...rates].sort((a, b) => parseFloat(a.amount) - parseFloat(b.amount));
|
|
177
179
|
const cheapestRate = sortedRates[0];
|
|
178
|
-
|
|
180
|
+
|
|
179
181
|
setShippingRates(rates);
|
|
180
182
|
setShippingRatesError(null);
|
|
181
183
|
setSelectedShippingRateId(cheapestRate.objectId);
|
|
@@ -284,7 +286,10 @@ export function CheckoutScreen() {
|
|
|
284
286
|
const onSubmit = async (data: CheckoutFormData) => {
|
|
285
287
|
setError(null);
|
|
286
288
|
if (!isAuthenticated) {
|
|
287
|
-
|
|
289
|
+
notification.error(
|
|
290
|
+
'Sign-in required',
|
|
291
|
+
'Please log in to complete your checkout.'
|
|
292
|
+
);
|
|
288
293
|
setTimeout(() => router.push(buildPath('/login?redirect=/checkout')), 50);
|
|
289
294
|
return;
|
|
290
295
|
}
|
|
@@ -337,7 +342,10 @@ export function CheckoutScreen() {
|
|
|
337
342
|
}
|
|
338
343
|
|
|
339
344
|
setIsSubmitting(true);
|
|
340
|
-
|
|
345
|
+
notification.info(
|
|
346
|
+
'Submitting order',
|
|
347
|
+
'We are processing your order and preparing your payment options.'
|
|
348
|
+
);
|
|
341
349
|
try {
|
|
342
350
|
const items = (cart?.cartBody?.items || []).map((item: any) => ({
|
|
343
351
|
productVariantId: String(item.productVariantId),
|
|
@@ -371,7 +379,10 @@ export function CheckoutScreen() {
|
|
|
371
379
|
);
|
|
372
380
|
if (response?.data?.payment && response.data.payment.hostedInvoiceUrl === 'INSUFFICIENT_CREDIT') {
|
|
373
381
|
setError('You have insufficient credit to complete this order.');
|
|
374
|
-
|
|
382
|
+
notification.error(
|
|
383
|
+
'Insufficient credit',
|
|
384
|
+
'You do not have enough credit to complete this order.'
|
|
385
|
+
);
|
|
375
386
|
return;
|
|
376
387
|
}
|
|
377
388
|
if (paymentMethod === 'Card') {
|
|
@@ -390,22 +401,34 @@ export function CheckoutScreen() {
|
|
|
390
401
|
} else if (!paymentUrl) {
|
|
391
402
|
console.error('No payment URL received in response:', response.data);
|
|
392
403
|
setError('Failed to initiate payment. Please try again or contact support.');
|
|
393
|
-
|
|
404
|
+
notification.error(
|
|
405
|
+
'Payment setup failed',
|
|
406
|
+
'We could not start the payment flow. Please try again or contact support.'
|
|
407
|
+
);
|
|
394
408
|
return;
|
|
395
409
|
}
|
|
396
|
-
|
|
397
|
-
|
|
410
|
+
|
|
411
|
+
notification.success(
|
|
412
|
+
'Order placed',
|
|
413
|
+
'Your order has been placed successfully. You will be redirected to payment.'
|
|
414
|
+
);
|
|
398
415
|
await clearCart();
|
|
399
416
|
router.push(buildPath(`/orders/${response.data?.id}`));
|
|
400
417
|
} else {
|
|
401
|
-
|
|
418
|
+
notification.success(
|
|
419
|
+
'Order placed',
|
|
420
|
+
'Your order has been placed successfully.'
|
|
421
|
+
);
|
|
402
422
|
await clearCart();
|
|
403
423
|
router.push(buildPath(`/orders/${response.data?.id}`));
|
|
404
424
|
}
|
|
405
425
|
} catch (err: any) {
|
|
406
426
|
const msg = err?.message || (err?.response?.data?.message) || 'Failed to place order';
|
|
407
427
|
setError(msg);
|
|
408
|
-
|
|
428
|
+
notification.error(
|
|
429
|
+
'Could not place order',
|
|
430
|
+
msg
|
|
431
|
+
);
|
|
409
432
|
} finally {
|
|
410
433
|
setIsSubmitting(false);
|
|
411
434
|
}
|
|
@@ -420,8 +443,8 @@ export function CheckoutScreen() {
|
|
|
420
443
|
const total = subtotal + shippingPrice + tax;
|
|
421
444
|
|
|
422
445
|
return (
|
|
423
|
-
<div className="min-h-screen bg-
|
|
424
|
-
|
|
446
|
+
<div className="min-h-screen bg-white pb-16">
|
|
447
|
+
|
|
425
448
|
|
|
426
449
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
427
450
|
{error && <div className="mb-4 text-red-600 font-semibold">{error}</div>}
|
|
@@ -431,11 +454,20 @@ export function CheckoutScreen() {
|
|
|
431
454
|
animate={{ opacity: 1, y: 0 }}
|
|
432
455
|
className="space-y-8"
|
|
433
456
|
>
|
|
434
|
-
|
|
457
|
+
{/* Header */}
|
|
458
|
+
<div className="mb-12">
|
|
459
|
+
<h1 className="font-['Poppins',sans-serif] font-semibold text-[#2B4B7C] mb-2 text-3xl">
|
|
460
|
+
Checkout
|
|
461
|
+
</h1>
|
|
462
|
+
<p className="font-['Poppins',sans-serif] text-[14px] text-[#676c80] text-md leading-relaxed">
|
|
463
|
+
Complete your order information below
|
|
464
|
+
</p>
|
|
465
|
+
</div>
|
|
466
|
+
<section className="bg-white border-2 border-gray-100 rounded-[24px] p-8 text-[#2B4B7C]">
|
|
435
467
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
436
468
|
<div>
|
|
437
|
-
<h2 className="
|
|
438
|
-
|
|
469
|
+
<h2 className="font-['Poppins',sans-serif] font-semibold text-[#2B4B7C] mb-2 text-2xl">
|
|
470
|
+
Contact Information
|
|
439
471
|
</h2>
|
|
440
472
|
<p className="text-sm text-slate-500">
|
|
441
473
|
We use temperature-aware packaging and real-time tracking on every shipment.
|
|
@@ -527,9 +559,9 @@ export function CheckoutScreen() {
|
|
|
527
559
|
try {
|
|
528
560
|
await removeAddress(addr.id);
|
|
529
561
|
if (selectedAddressId === addr.id) setSelectedAddressId(null);
|
|
530
|
-
|
|
562
|
+
notification.success('Address deleted');
|
|
531
563
|
} catch (e) {
|
|
532
|
-
|
|
564
|
+
notification.error('Failed to delete address');
|
|
533
565
|
}
|
|
534
566
|
}}
|
|
535
567
|
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"
|
|
@@ -570,10 +602,10 @@ export function CheckoutScreen() {
|
|
|
570
602
|
|
|
571
603
|
{/* Shipping Options - Only show if there's a shipping cost */}
|
|
572
604
|
{isDelivery && selectedAddressId && (
|
|
573
|
-
<
|
|
574
|
-
<div className="flex items-center gap-3 text-xl font-semibold text-gray-900 pb-4 mb-
|
|
575
|
-
<Truck className="text-accent w-
|
|
576
|
-
<
|
|
605
|
+
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8 text-[#2B4B7C]">
|
|
606
|
+
<div className="flex items-center gap-3 text-xl font-semibold text-gray-900 pb-4 mb-8 border-b">
|
|
607
|
+
<Truck className="text-accent w-8 h-8 flex items-center justify-center text-[#2B4B7C]" />
|
|
608
|
+
<h2 className="font-['Poppins',sans-serif] font-semibold text-[#2B4B7C] text-2xl">Shipping Options</h2>
|
|
577
609
|
</div>
|
|
578
610
|
|
|
579
611
|
{shippingRatesLoading ? (
|
|
@@ -735,25 +767,18 @@ export function CheckoutScreen() {
|
|
|
735
767
|
</div>
|
|
736
768
|
)}
|
|
737
769
|
|
|
738
|
-
</
|
|
770
|
+
</div>
|
|
739
771
|
)}
|
|
740
|
-
</motion.div>
|
|
741
|
-
|
|
742
|
-
<motion.aside
|
|
743
|
-
initial={{ opacity: 0, y: 32 }}
|
|
744
|
-
animate={{ opacity: 1, y: 0 }}
|
|
745
|
-
transition={{ duration: 0.5, ease: 'easeOut', delay: 0.1 }}
|
|
746
|
-
className="space-y-10 lg:sticky lg:top-24"
|
|
747
|
-
>
|
|
748
|
-
{/* Order Summary */}
|
|
749
|
-
<div className="rounded-3xl border border-slate-100 bg-white p-6 shadow-xl shadow-primary-50/50 transition hover:shadow-primary-100/60">
|
|
750
|
-
<h2 className="text-2xl font-semibold text-slate-900">Order Summary</h2>
|
|
751
772
|
|
|
773
|
+
{/* Delivery and Payment Methods Section */}
|
|
774
|
+
<div className="space-y-6">
|
|
752
775
|
{/* Delivery Method */}
|
|
753
|
-
<
|
|
754
|
-
<
|
|
755
|
-
|
|
756
|
-
|
|
776
|
+
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8">
|
|
777
|
+
<div className="flex items-center gap-3 mb-6">
|
|
778
|
+
<Truck className="w-8 h-8 text-[#2B4B7C]" />
|
|
779
|
+
<h2 className="text-2xl font-semibold text-[#2B4B7C]">Delivery Method</h2>
|
|
780
|
+
</div>
|
|
781
|
+
|
|
757
782
|
<div className="grid grid-cols-1 gap-3">
|
|
758
783
|
{[
|
|
759
784
|
{
|
|
@@ -769,88 +794,106 @@ export function CheckoutScreen() {
|
|
|
769
794
|
desc: 'Collect from pharmacy',
|
|
770
795
|
},
|
|
771
796
|
].map((option) => {
|
|
772
|
-
const active = isDelivery === option.value
|
|
797
|
+
const active = isDelivery === option.value;
|
|
773
798
|
return (
|
|
774
799
|
<button
|
|
775
800
|
key={option.label}
|
|
776
801
|
type="button"
|
|
777
|
-
onClick={() =>
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
802
|
+
onClick={() => {
|
|
803
|
+
setIsDelivery(option.value);
|
|
804
|
+
// Reset selected shipping rate when changing delivery method
|
|
805
|
+
if (option.value) {
|
|
806
|
+
setSelectedShippingRateId(null);
|
|
807
|
+
setShippingPrice(0);
|
|
808
|
+
}
|
|
809
|
+
}}
|
|
810
|
+
className={`relative flex w-full items-center justify-between rounded-xl border-2 p-4 transition-all duration-200 ${active
|
|
811
|
+
? 'border-primary-500 bg-primary-50'
|
|
812
|
+
: 'border-gray-200 hover:border-primary-300'
|
|
782
813
|
}`}
|
|
783
814
|
>
|
|
784
815
|
<div className="flex items-center gap-3">
|
|
785
816
|
<div
|
|
786
|
-
className={`p-2 rounded-lg ${active
|
|
787
|
-
? 'bg-primary-100 text-primary-600'
|
|
788
|
-
: 'bg-slate-100 text-slate-600 group-hover:bg-slate-50'
|
|
817
|
+
className={`p-2 rounded-lg ${active ? 'bg-primary-100 text-primary-600' : 'bg-gray-100 text-gray-600'
|
|
789
818
|
}`}
|
|
790
819
|
>
|
|
791
820
|
{option.icon}
|
|
792
821
|
</div>
|
|
793
822
|
<div className="text-left">
|
|
794
|
-
<div className="font-medium text-
|
|
795
|
-
|
|
796
|
-
</div>
|
|
797
|
-
<p className="text-xs text-slate-500">{option.desc}</p>
|
|
823
|
+
<div className="font-medium text-gray-900">{option.label}</div>
|
|
824
|
+
<p className="text-sm text-gray-500">{option.desc}</p>
|
|
798
825
|
</div>
|
|
799
826
|
</div>
|
|
800
827
|
{active && (
|
|
801
|
-
<div className="w-5 h-5 rounded-full bg-primary-500 flex items-center justify-center
|
|
802
|
-
<
|
|
828
|
+
<div className="w-5 h-5 rounded-full bg-primary-500 flex items-center justify-center">
|
|
829
|
+
<Check className="w-3 h-3 text-white" />
|
|
803
830
|
</div>
|
|
804
831
|
)}
|
|
805
832
|
</button>
|
|
806
|
-
)
|
|
833
|
+
);
|
|
807
834
|
})}
|
|
808
835
|
</div>
|
|
809
|
-
</
|
|
836
|
+
</div>
|
|
810
837
|
|
|
811
838
|
{/* Payment Method */}
|
|
812
|
-
<
|
|
813
|
-
<
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
839
|
+
<div className="bg-white border-2 border-gray-100 rounded-[24px] p-8">
|
|
840
|
+
<div className="flex items-center gap-3 mb-6">
|
|
841
|
+
<CreditCard className="w-8 h-8 text-[#2B4B7C]" />
|
|
842
|
+
<h2 className="text-2xl font-semibold text-[#2B4B7C]">Payment Method</h2>
|
|
843
|
+
</div>
|
|
844
|
+
|
|
845
|
+
<div className="grid gap-4 md:grid-cols-3">
|
|
817
846
|
{PAYMENT_METHODS.map((pm) => {
|
|
818
|
-
const active = paymentMethod === pm.value
|
|
847
|
+
const active = paymentMethod === pm.value;
|
|
848
|
+
|
|
819
849
|
return (
|
|
820
850
|
<button
|
|
821
851
|
key={pm.value}
|
|
822
852
|
type="button"
|
|
823
853
|
onClick={() => setPaymentMethod(pm.value)}
|
|
824
|
-
className={`
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
854
|
+
className={`
|
|
855
|
+
group relative flex w-full items-center justify-between rounded-xl border-2 p-3
|
|
856
|
+
transition-all duration-200 ease-out
|
|
857
|
+
focus:outline-none f
|
|
858
|
+
${active
|
|
859
|
+
? `${pm.activeClass} shadow-md scale-[1.02]`
|
|
860
|
+
: `${pm.className} border-gray-200 bg-white hover:shadow-sm hover:-translate-y-0.5`
|
|
861
|
+
}
|
|
862
|
+
`}
|
|
828
863
|
>
|
|
829
864
|
<div className="flex items-center gap-3">
|
|
830
865
|
<div
|
|
831
|
-
className={`
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
866
|
+
className={`
|
|
867
|
+
flex items-center justify-center rounded-lg p-2
|
|
868
|
+
transition-colors duration-200
|
|
869
|
+
${pm.value === 'Card'
|
|
870
|
+
? ' text-blue-600'
|
|
871
|
+
: pm.value === 'Cash'
|
|
872
|
+
? ' text-amber-600'
|
|
873
|
+
: ' text-emerald-600'
|
|
874
|
+
}
|
|
875
|
+
${active ? 'opacity-80' : 'group-hover:opacity-90'}
|
|
876
|
+
`}
|
|
837
877
|
>
|
|
838
878
|
{pm.icon}
|
|
839
879
|
</div>
|
|
840
|
-
|
|
880
|
+
|
|
881
|
+
<span className="text-sm font-semibold text-gray-900">
|
|
841
882
|
{pm.label}
|
|
842
883
|
</span>
|
|
843
884
|
</div>
|
|
885
|
+
|
|
844
886
|
{active && (
|
|
845
|
-
<div className="
|
|
846
|
-
<
|
|
887
|
+
<div className="flex h-6 w-6 items-center justify-center rounded-full shadow-sm">
|
|
888
|
+
<Check className="h-3.5 w-3.5 text-white" />
|
|
847
889
|
</div>
|
|
848
890
|
)}
|
|
849
891
|
</button>
|
|
850
|
-
)
|
|
892
|
+
);
|
|
851
893
|
})}
|
|
852
894
|
</div>
|
|
853
|
-
|
|
895
|
+
|
|
896
|
+
<p className="text-sm text-gray-500 mt-4">
|
|
854
897
|
{paymentMethod === 'Card' &&
|
|
855
898
|
'You will be redirected to a secure payment page.'}
|
|
856
899
|
{paymentMethod === 'Cash' &&
|
|
@@ -858,39 +901,48 @@ export function CheckoutScreen() {
|
|
|
858
901
|
{paymentMethod === 'Credit' &&
|
|
859
902
|
'Use your available account credit for this order.'}
|
|
860
903
|
</p>
|
|
861
|
-
</
|
|
904
|
+
</div>
|
|
905
|
+
</div>
|
|
906
|
+
</motion.div>
|
|
907
|
+
|
|
908
|
+
<motion.aside
|
|
909
|
+
initial={{ opacity: 0, y: 32 }}
|
|
910
|
+
animate={{ opacity: 1, y: 0 }}
|
|
911
|
+
transition={{ duration: 0.5, ease: 'easeOut', delay: 0.1 }}
|
|
912
|
+
className="space-y-10 lg:sticky lg:top-24 lg:col-span-1"
|
|
913
|
+
>
|
|
914
|
+
{/* Order Summary */}
|
|
915
|
+
<div className="bg-gradient-to-br from-[#5B9BD5]/10 to-[#2B4B7C]/10 rounded-[24px] p-8 border-2 border-[#5B9BD5]/20 sticky top-24">
|
|
916
|
+
<h2 className="font-['Poppins',sans-serif] font-semibold text-[#2B4B7C] mb-6 text-2xl">Order Summary</h2>
|
|
917
|
+
|
|
862
918
|
|
|
863
919
|
{/* Cart Summary */}
|
|
864
920
|
<section className="mt-8 pt-6 border-t border-slate-100">
|
|
865
|
-
<
|
|
866
|
-
Cart Summary
|
|
867
|
-
</h3>
|
|
868
|
-
<div className="max-h-60 space-y-4 overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-slate-200 hover:scrollbar-thumb-slate-300">
|
|
921
|
+
<div className="space-y-4 mb-6">
|
|
869
922
|
{cart?.cartBody?.items?.map((item: any) => (
|
|
870
|
-
<div
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
>
|
|
874
|
-
<div className="flex h-16 w-16 items-center justify-center rounded-xl bg-slate-100 text-slate-400">
|
|
875
|
-
<Image src={item.productVariantData.productMedia?.[0]?.file || '/placeholder-product.jpg'}
|
|
876
|
-
alt={item.productVariantData.name} width={64} height={64} className="object-contain" />
|
|
923
|
+
<div key={`${item.productId}-${item.color}-${item.size}`} className="flex gap-3">
|
|
924
|
+
<div className="w-16 h-16 rounded-xl overflow-hidden bg-white shrink-0">
|
|
925
|
+
<Image src={item.productVariantData.productMedia?.[0]?.file || '/placeholder-product.jpg'} alt={item.productVariantData.name} className="w-full h-full object-cover" height={200} width={200} />
|
|
877
926
|
</div>
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
<p className="text-sm font-semibold text-slate-900">
|
|
927
|
+
<div className="flex-1 min-w-0">
|
|
928
|
+
<p className="font-['Poppins',sans-serif] font-medium text-[12px] text-[#2B4B7C] mb-1">
|
|
881
929
|
{item?.productVariantData?.name}
|
|
882
930
|
</p>
|
|
883
|
-
<p className="text-
|
|
931
|
+
<p className="font-['Poppins',sans-serif] text-[11px] text-[#676c80]">
|
|
932
|
+
{item?.productVariantData?.brand} • Qty: {item.quantity}
|
|
933
|
+
</p>
|
|
934
|
+
<p className="font-['Poppins',sans-serif] font-semibold text-[12px] text-[#E67E50] mt-1">
|
|
935
|
+
{formatPrice(item.productVariantData.finalPrice * item.quantity)}
|
|
936
|
+
</p>
|
|
884
937
|
</div>
|
|
885
|
-
<p className="text-sm font-semibold text-slate-900">
|
|
886
|
-
{formatPrice(item.productVariantData.finalPrice * item.quantity)}
|
|
887
|
-
</p>
|
|
888
938
|
</div>
|
|
889
939
|
))}
|
|
890
940
|
</div>
|
|
891
941
|
|
|
942
|
+
<div className="h-px bg-[#5B9BD5]/20 my-4" />
|
|
943
|
+
|
|
892
944
|
{/* Totals */}
|
|
893
|
-
<div className="
|
|
945
|
+
<div className="text-sm text-slate-600 space-y-3 py-4">
|
|
894
946
|
<div className="flex items-center justify-between">
|
|
895
947
|
<span>Subtotal</span>
|
|
896
948
|
<span className="font-semibold text-slate-900">
|
|
@@ -909,24 +961,29 @@ export function CheckoutScreen() {
|
|
|
909
961
|
<span>Estimated tax</span>
|
|
910
962
|
<span className="font-semibold">{formatPrice(tax)}</span>
|
|
911
963
|
</div>
|
|
912
|
-
<div className="
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
</
|
|
917
|
-
<p className="mt-1 text-xs text-slate-500">
|
|
918
|
-
Tax is estimated. Final amount confirmed after payment.
|
|
919
|
-
</p>
|
|
964
|
+
<div className="h-px bg-[#5B9BD5]/20 mt-6" />
|
|
965
|
+
|
|
966
|
+
<div className="flex items-center justify-between mb-6">
|
|
967
|
+
<span className="font-['Poppins',sans-serif] font-semibold text-[16px] text-[#2B4B7C]">Total</span>
|
|
968
|
+
<span className="font-['Poppins',sans-serif] font-bold text-[24px] text-[#E67E50]">{formatPrice(total)}</span>
|
|
920
969
|
</div>
|
|
970
|
+
{/* <p className="mt-1 text-xs text-slate-500">
|
|
971
|
+
Tax is estimated. Final amount confirmed after payment.
|
|
972
|
+
</p> */}
|
|
973
|
+
</div>
|
|
974
|
+
|
|
975
|
+
<div className="bg-white/80 rounded-xl p-4">
|
|
976
|
+
<p className="font-['Poppins',sans-serif] text-[11px] text-[#676c80] leading-[1.6]">
|
|
977
|
+
<strong className="text-[#2B4B7C]">Payment:</strong> We'll contact you to arrange payment upon pickup or delivery. We accept cash, credit cards, and all major payment methods.
|
|
978
|
+
</p>
|
|
921
979
|
</div>
|
|
922
980
|
</section>
|
|
923
981
|
|
|
924
982
|
{/* Checkout Button */}
|
|
925
983
|
<Button
|
|
926
984
|
type="submit"
|
|
927
|
-
size="lg"
|
|
928
985
|
isLoading={isSubmitting}
|
|
929
|
-
className="
|
|
986
|
+
className="font-['Poppins',sans-serif] font-medium text-[14px] px-6 py-2 rounded-full bg-[#E67E50] text-white hover:bg-[#d66f45] hover:shadow-lg transition-all duration-300 mt-4 w-full"
|
|
930
987
|
>
|
|
931
988
|
<CreditCard className="h-5 w-5" />
|
|
932
989
|
{isSubmitting ? 'Placing order...' : 'Place Secure Order'}
|
|
@@ -938,26 +995,6 @@ export function CheckoutScreen() {
|
|
|
938
995
|
</p>
|
|
939
996
|
</div>
|
|
940
997
|
|
|
941
|
-
{/* Why Patients Choose Us */}
|
|
942
|
-
<div className="rounded-3xl border border-primary-100 bg-primary-50/80 p-6 text-sm text-primary-700 shadow-sm hover:shadow-md transition-shadow">
|
|
943
|
-
<p className="font-semibold uppercase tracking-[0.3em] text-primary-800">
|
|
944
|
-
Why Patients Choose Us
|
|
945
|
-
</p>
|
|
946
|
-
<ul className="mt-4 space-y-3 text-primary-700 leading-relaxed">
|
|
947
|
-
<li className="flex items-start gap-3">
|
|
948
|
-
<PackageCheck className="mt-0.5 h-4 w-4 shrink-0" />
|
|
949
|
-
<span>Pharmacy-grade verification on every order.</span>
|
|
950
|
-
</li>
|
|
951
|
-
<li className="flex items-start gap-3">
|
|
952
|
-
<ShieldCheck className="mt-0.5 h-4 w-4 shrink-0" />
|
|
953
|
-
<span>Cold-chain logistics for sensitive medications.</span>
|
|
954
|
-
</li>
|
|
955
|
-
<li className="flex items-start gap-3">
|
|
956
|
-
<Truck className="mt-0.5 h-4 w-4 shrink-0" />
|
|
957
|
-
<span>Real-time tracking and SMS updates from prep to delivery.</span>
|
|
958
|
-
</li>
|
|
959
|
-
</ul>
|
|
960
|
-
</div>
|
|
961
998
|
</motion.aside>
|
|
962
999
|
|
|
963
1000
|
</div>
|