hey-pharmacist-ecommerce 1.1.11 → 1.1.12

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.
@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
5
5
  import {
6
6
  ArrowRight,
7
7
  BadgePercent,
8
+ CheckCircle2,
8
9
  HeartPulse,
9
10
  ShieldCheck,
10
11
  ShoppingBag,
@@ -24,126 +25,63 @@ export function CartScreen() {
24
25
  const { buildPath } = useBasePath();
25
26
 
26
27
  if (!cart || cart.cartBody.items.length === 0) {
27
- const highlights = [
28
- {
29
- icon: ShieldCheck,
30
- title: 'Pharmacist approved',
31
- description:
32
- 'Every product passes pharmacist review and cold-chain handling standards.',
33
- },
34
- {
35
- icon: BadgePercent,
36
- title: 'Bundle savings',
37
- description: 'Unlock tiered discounts when you combine vitamins, OTC, and wellness kits.',
38
- },
39
- {
40
- icon: HeartPulse,
41
- title: 'Personalized guidance',
42
- description: 'Follow curated collections tailored to your health goals and routines.',
43
- },
44
- ];
45
-
46
28
  return (
47
- <div className="relative min-h-screen overflow-hidden bg-slate-950">
48
- <div className="absolute inset-0 bg-gradient-to-br from-[rgb(var(--header-from))] via-[rgb(var(--header-via))] to-[rgb(var(--header-to))]" />
49
- <div className="absolute inset-0">
50
- <div className="pointer-events-none absolute -top-24 -left-20 h-96 w-96 rounded-full bg-white/20 blur-3xl opacity-60" />
51
- <div className="pointer-events-none absolute bottom-0 right-0 h-[28rem] w-[28rem] rounded-full bg-secondary-500/40 blur-[220px] opacity-70" />
52
- </div>
29
+ <div className="bg-white">
30
+ <div className="min-h-screen bg-white max-w-6xl mx-auto">
31
+ <div className="container mx-auto px-4 py-8">
32
+ <div className="mb-6">
33
+ <h1 className="text-2xl font-bold text-slate-900">Shopping Cart</h1>
34
+ <p className="text-sm text-gray-500 mt-1">0 items in your cart</p>
35
+ </div>
53
36
 
54
- <div className="relative z-10 flex min-h-screen items-center px-6 py-20">
55
- <div className="mx-auto grid w-full max-w-6xl gap-12 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,1fr)] lg:items-center">
56
- <motion.div
57
- initial={{ opacity: 0, y: 24 }}
58
- animate={{ opacity: 1, y: 0 }}
59
- transition={{ duration: 0.4 }}
60
- className="space-y-8 text-white"
61
- >
62
- <span className="inline-flex items-center gap-2 rounded-full bg-white/15 px-4 py-1 text-sm font-semibold uppercase tracking-[0.3em] text-white/80 backdrop-blur">
63
- <ShoppingBag className="h-4 w-4" />
64
- Cart status
65
- </span>
66
- <div className="space-y-4">
67
- <h1 className="text-4xl font-bold md:text-5xl">
68
- Your wellness cart is waiting for a refill
69
- </h1>
70
- <p className="max-w-xl text-lg text-white/75">
71
- Build a pharmacist-curated bundle with fast shipping, personalized recommendations,
72
- and exclusive member perks designed to keep you feeling your best.
73
- </p>
74
- </div>
75
- <div className="flex flex-wrap gap-4">
76
- <Button
77
- size="lg"
78
- className="bg-white text-primary-700 shadow-xl shadow-white/30 hover:bg-white/90 hover:text-primary-700"
79
- onClick={() => router.push(buildPath('/shop'))}
80
- >
81
- Discover products
82
- <ArrowRight className="h-5 w-5" />
83
- </Button>
84
- <button
85
- type="button"
86
- onClick={() => router.push(buildPath('/categories'))}
87
- className="inline-flex items-center gap-2 rounded-full border border-white/40 px-5 py-3 text-sm font-semibold text-white/80 transition hover:border-white hover:bg-white/10"
88
- >
89
- Browse categories
90
- <ArrowRight className="h-4 w-4" />
91
- </button>
92
- </div>
93
- <div className="grid gap-4 sm:grid-cols-2">
94
- {highlights.map(({ icon: Icon, title, description }) => (
95
- <div
96
- key={title}
97
- className="rounded-2xl border border-white/15 bg-white/10 p-5 backdrop-blur transition hover:border-white/25 hover:bg-white/15"
98
- >
99
- <Icon className="h-6 w-6 text-white/90" />
100
- <p className="mt-3 text-sm font-semibold text-white">{title}</p>
101
- <p className="mt-1 text-xs text-white/70">{description}</p>
37
+ <div className="flex flex-col items-center justify-center py-20">
38
+ <motion.div
39
+ initial={{ opacity: 0, y: 24 }}
40
+ animate={{ opacity: 1, y: 0 }}
41
+ className="text-center space-y-6 max-w-md"
42
+ >
43
+ <div className="flex justify-center">
44
+ <div className="rounded-full bg-gray-100 p-6">
45
+ <ShoppingBag className="h-12 w-12 text-gray-400" />
102
46
  </div>
103
- ))}
104
- </div>
105
- </motion.div>
47
+ </div>
106
48
 
107
- <motion.div
108
- initial={{ opacity: 0, y: 24 }}
109
- animate={{ opacity: 1, y: 0 }}
110
- transition={{ delay: 0.1, duration: 0.4 }}
111
- className="rounded-[32px] border border-white/10 bg-white/10 p-10 backdrop-blur-2xl shadow-[0_40px_120px_-45px_rgba(12,5,40,0.6)]"
112
- >
113
- <div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-white/20">
114
- <ShoppingBag className="h-8 w-8 text-white" />
115
- </div>
116
- <h2 className="mt-6 text-3xl font-semibold text-white">
117
- Still building your bag?
118
- </h2>
119
- <p className="mt-3 text-sm text-white/80">
120
- Add supplements, wellness tools, and everyday pharmacy favorites to personalize your
121
- routine. We&apos;ll keep them chilled, curated, and ready for doorstep delivery.
122
- </p>
123
- <ul className="mt-6 space-y-3 text-sm text-white/80">
124
- <li className="flex items-center gap-2 rounded-xl bg-white/10 px-4 py-3">
125
- <ShieldCheck className="h-5 w-5 text-white" />
126
- Pharmacist oversight on every order
127
- </li>
128
- <li className="flex items-center gap-2 rounded-xl bg-white/10 px-4 py-3">
129
- <BadgePercent className="h-5 w-5 text-white" />
130
- Member-only savings unlock at checkout
131
- </li>
132
- <li className="flex items-center gap-2 rounded-xl bg-white/10 px-4 py-3">
133
- <HeartPulse className="h-5 w-5 text-white" />
134
- Personalized care plans for each purchase
135
- </li>
136
- </ul>
137
- <Button
138
- size="lg"
139
- variant="outline"
140
- className="mt-8 w-full border-white/40 text-white hover:border-white hover:bg-white/10"
141
- onClick={() => router.push(buildPath('/shop'))}
142
- >
143
- Start building my cart
144
- <ArrowRight className="h-5 w-5" />
145
- </Button>
146
- </motion.div>
49
+ <div className="space-y-2">
50
+ <h2 className="text-2xl font-bold text-slate-900">
51
+ Your cart is empty
52
+ </h2>
53
+ <p className="text-gray-500">
54
+ Start adding products to your cart to see them here.
55
+ </p>
56
+ </div>
57
+
58
+ <div className="flex flex-wrap gap-3 justify-center">
59
+ <button
60
+ type="button"
61
+ onClick={() => router.push(buildPath('/shop'))}
62
+ className="rounded-full border-2 border-primary-500 bg-primary-500 hover:bg-primary-600 text-white px-6 py-3 text-sm font-medium transition-colors flex items-center justify-center gap-2"
63
+ >
64
+ Discover products
65
+ <ArrowRight className="h-5 w-5" />
66
+ </button>
67
+ </div>
68
+
69
+ <div className="mt-8 space-y-3 pt-6 border-t border-gray-200">
70
+ <div className="flex items-start gap-3 text-sm text-slate-600">
71
+ <CheckCircle2 className="h-5 w-5 text-primary-600 flex-shrink-0 mt-0.5" />
72
+ <span>Free shipping on all orders</span>
73
+ </div>
74
+ <div className="flex items-start gap-3 text-sm text-slate-600">
75
+ <CheckCircle2 className="h-5 w-5 text-primary-600 flex-shrink-0 mt-0.5" />
76
+ <span>Easy returns within 30 days</span>
77
+ </div>
78
+ <div className="flex items-start gap-3 text-sm text-slate-600">
79
+ <CheckCircle2 className="h-5 w-5 text-primary-600 flex-shrink-0 mt-0.5" />
80
+ <span>Secure checkout process</span>
81
+ </div>
82
+ </div>
83
+ </motion.div>
84
+ </div>
147
85
  </div>
148
86
  </div>
149
87
  </div>
@@ -154,134 +92,111 @@ export function CartScreen() {
154
92
  const shipping = 0;
155
93
  const tax = 0;
156
94
  const total = subtotal + shipping + tax;
157
-
95
+ const itemCount = cart.cartBody.items.length;
158
96
 
159
97
  return (
160
- <div className="min-h-screen bg-slate-50">
161
- <section className="relative overflow-hidden bg-gradient-to-br from-[rgb(var(--header-from))] via-[rgb(var(--header-via))] to-[rgb(var(--header-to))] text-white">
162
- <div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(255,255,255,0.35),_transparent_60%)]" />
163
- <div className="relative container mx-auto px-4 py-16 mb-8">
164
- <motion.div
165
- initial={{ opacity: 0, y: 24 }}
166
- animate={{ opacity: 1, y: 0 }}
167
- className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between"
168
- >
169
- <div className="max-w-2xl space-y-4">
170
- <span className="inline-flex items-center gap-2 rounded-full bg-white/15 px-3 py-1 text-sm font-semibold tracking-wide text-white/80 backdrop-blur">
171
- <HeartPulse className="h-4 w-4" />
172
- Wellness essentials, ready when you are
173
- </span>
174
- <h1 className="text-4xl font-bold md:text-5xl">Your curated cart</h1>
175
- <p className="text-white/75 md:text-lg">
176
- Review your selections, unlock exclusive perks, and check out with pharmacist-backed
177
- confidence.
178
- </p>
179
- </div>
180
- <div className="rounded-3xl bg-white/15 p-6 backdrop-blur-md">
181
- <p className="text-sm font-semibold uppercase tracking-[0.35em] text-white/70">
182
- Cart summary
183
- </p>
184
- <p className="mt-4 text-4xl font-semibold">{formatPrice(total)}</p>
185
- <p className="text-sm text-white/70">Taxes and shipping calculated below</p>
186
- </div>
187
- </motion.div>
188
- </div>
189
- </section>
98
+ <div className="bg-white">
99
+ <div className="min-h-screen bg-white max-w-6xl mx-auto">
100
+ <div className="container mx-auto px-4 py-8">
101
+ <div className="mb-6">
102
+ <h1 className="text-2xl font-bold text-slate-900">Shopping Cart</h1>
103
+ <p className="text-sm text-gray-500 mt-1">
104
+ {itemCount} {itemCount === 1 ? 'item' : 'items'} in your cart
105
+ </p>
106
+ </div>
107
+ <div className="grid gap-8 lg:grid-cols-[1.5fr_1fr]">
108
+ {/* Shopping Cart Section */}
109
+ <motion.section
110
+ initial={{ opacity: 0, y: 24 }}
111
+ animate={{ opacity: 1, y: 0 }}
112
+ className="space-y-6"
113
+ >
190
114
 
191
- <div className="relative -mt-16 pb-20">
192
- <div className="container mx-auto px-4">
193
- <div className="grid gap-10 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
194
- <motion.section
195
- initial={{ opacity: 0, y: 24 }}
196
- animate={{ opacity: 1, y: 0 }}
197
- transition={{ delay: 0.05 }}
198
- className="space-y-6 rounded-3xl border border-slate-100 bg-white p-6 shadow-lg shadow-primary-50"
199
- >
200
- <div className="flex flex-wrap items-center justify-between gap-4">
201
- <h2 className="text-xl font-semibold text-slate-900">
202
- Items ({cart.cartBody.items.length})
203
- </h2>
204
- <div 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">
205
- <ShieldCheck className="h-4 w-4" />
206
- Guaranteed cold-chain handling
207
- </div>
208
- </div>
209
- {isLoading && (
210
- <div className="flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm text-slate-600">
211
- <span className="inline-block h-3 w-3 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600" />
212
- Updating cart…
213
- </div>
214
- )}
215
- <div className="space-y-5">
216
- {cart.cartBody.items.map((item: CartItemPopulated) => (
217
- <CartItem key={item.productVariantId} item={item} />
218
- ))}
219
- </div>
220
- </motion.section>
221
115
 
222
- <motion.aside
223
- initial={{ opacity: 0, y: 24 }}
224
- animate={{ opacity: 1, y: 0 }}
225
- transition={{ delay: 0.1 }}
226
- className="space-y-6 lg:sticky lg:top-28"
227
- >
228
- <div className="rounded-3xl border border-slate-100 bg-white p-6 shadow-lg shadow-primary-50">
229
- <div className="flex items-center justify-between">
230
- <h2 className="text-xl font-semibold text-slate-900">Checkout summary</h2>
231
- <span className="inline-flex items-center gap-1 rounded-full bg-primary-50 px-3 py-1 text-xs font-semibold text-primary-700">
232
- <BadgePercent className="h-4 w-4" />
233
- Savings applied
234
- </span>
116
+ {isLoading && (
117
+ <div className="flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-600">
118
+ <span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600" />
119
+ Updating cart…
120
+ </div>
121
+ )}
122
+
123
+ <div className="space-y-4">
124
+ {cart.cartBody.items.map((item: CartItemPopulated) => (
125
+ <CartItem key={item.productVariantId} item={item} />
126
+ ))}
235
127
  </div>
236
- <div className="mt-6 space-y-4 text-sm text-slate-600">
237
- <div className="flex items-center justify-between">
238
- <span>Subtotal</span>
239
- <span className="font-semibold text-slate-900">{formatPrice(subtotal)}</span>
128
+ </motion.section>
129
+
130
+ {/* Order Summary Section */}
131
+ <motion.aside
132
+ initial={{ opacity: 0, y: 24 }}
133
+ animate={{ opacity: 1, y: 0 }}
134
+ transition={{ delay: 0.1 }}
135
+ className="space-y-6 lg:sticky lg:top-8 h-fit"
136
+ >
137
+ <div className="rounded-xl border-2 border-primary-200 bg-white p-6 shadow-md">
138
+ <h2 className="text-xl font-bold text-slate-900 mb-6">Order Summary</h2>
139
+
140
+ <div className="space-y-4 mb-6">
141
+ <div className="flex items-center justify-between text-sm">
142
+ <span className="text-gray-600">Subtotal ({itemCount} {itemCount === 1 ? 'item' : 'items'})</span>
143
+ <span className="font-semibold text-slate-900">{formatPrice(subtotal)}</span>
144
+ </div>
145
+ <div className="flex items-center justify-between text-sm">
146
+ <span className="text-gray-600">Shipping</span>
147
+ <span className="font-semibold text-green-600">
148
+ Will be calculated at checkout
149
+ </span>
150
+ </div>
151
+ <div className="border-t border-gray-200 pt-4 mt-4">
152
+ <div className="flex items-center justify-between">
153
+ <span className="text-lg font-bold text-slate-900">Total</span>
154
+ <span className="text-2xl font-bold text-orange-600">{formatPrice(total)}</span>
155
+ </div>
156
+ </div>
240
157
  </div>
241
- <div className="flex items-center justify-between">
242
- <span>Shipping</span>
243
- <span className="font-semibold">
244
- Will be calculated at checkout
245
- </span>
158
+
159
+ <div className="space-y-3">
160
+ <button
161
+ type="button"
162
+ onClick={() => router.push(buildPath('/checkout'))}
163
+ className="w-full rounded-full border-2 border-primary-500 bg-primary-500 hover:bg-primary-600 text-white px-4 py-3 text-sm font-medium transition-colors flex items-center justify-center gap-2"
164
+ >
165
+ Proceed to Checkout
166
+ <ArrowRight className="h-5 w-5" />
167
+ </button>
168
+ <button
169
+ type="button"
170
+ onClick={() => router.push(buildPath('/shop'))}
171
+ className="w-full rounded-full border-2 border-primary-300 bg-white px-4 py-3 text-sm font-medium text-slate-700 hover:bg-gray-50 transition-colors"
172
+ >
173
+ Continue Shopping
174
+ </button>
246
175
  </div>
247
- <div className="rounded-2xl bg-slate-50 p-4">
248
- <div className="flex items-center justify-between text-base font-semibold text-slate-900">
249
- <span>Order total</span>
250
- <span>{formatPrice(total)}</span>
176
+
177
+ <div className="mt-6 space-y-3 pt-6 border-t border-gray-200">
178
+ <div className="flex items-start gap-3 text-sm text-slate-600">
179
+ <CheckCircle2 className="h-5 w-5 text-primary-600 flex-shrink-0 mt-0.5" />
180
+ <span>Easy returns within 30 days</span>
181
+ </div>
182
+ <div className="flex items-start gap-3 text-sm text-slate-600">
183
+ <CheckCircle2 className="h-5 w-5 text-primary-600 flex-shrink-0 mt-0.5" />
184
+ <span>Secure checkout process</span>
251
185
  </div>
252
- <p className="mt-2 text-xs text-slate-500">
253
- Prices include pharmacy-grade quality control and packaging.
254
- </p>
255
186
  </div>
256
187
  </div>
257
- <Button
258
- size="lg"
259
- className="mt-6 w-full"
260
- onClick={() => router.push(buildPath('/checkout'))}
261
- >
262
- Secure checkout
263
- <ArrowRight className="h-5 w-5" />
264
- </Button>
265
- <button
266
- type="button"
267
- onClick={() => router.push(buildPath('/shop'))}
268
- className="mt-4 w-full text-sm font-semibold text-primary-600 transition hover:text-primary-700"
269
- >
270
- Continue shopping
271
- </button>
272
- </div>
273
188
 
274
- <div className="rounded-3xl border border-primary-100 bg-primary-50/70 p-6 text-sm text-primary-700 shadow-sm">
275
- <p className="font-semibold uppercase tracking-[0.3em]">Need help?</p>
276
- <p className="mt-2 leading-relaxed">
277
- Chat with a pharmacist to optimize your regimen or discuss substitutions before you
278
- check out.
279
- </p>
280
- </div>
281
- </motion.aside>
189
+ <div className="rounded-3xl border border-primary-100 bg-primary-50/70 p-6 text-sm text-primary-700 shadow-sm">
190
+ <p className="font-semibold uppercase tracking-[0.3em]">Need help?</p>
191
+ <p className="mt-2 leading-relaxed">
192
+ Chat with a pharmacist to optimize your regimen or discuss substitutions before you
193
+ check out.
194
+ </p>
195
+ </div>
196
+ </motion.aside>
197
+ </div>
282
198
  </div>
283
199
  </div>
284
200
  </div>
285
- </div>
286
201
  );
287
202
  }
@@ -172,9 +172,14 @@ export function CheckoutScreen() {
172
172
 
173
173
  const rates = response.data.rates || [];
174
174
  if (rates.length > 0) {
175
+ // Sort rates by price (cheapest first) and select the first one
176
+ const sortedRates = [...rates].sort((a, b) => parseFloat(a.amount) - parseFloat(b.amount));
177
+ const cheapestRate = sortedRates[0];
178
+
175
179
  setShippingRates(rates);
176
180
  setShippingRatesError(null);
177
- setSelectedShippingRateId(rates[0].objectId); // Select first rate by default
181
+ setSelectedShippingRateId(cheapestRate.objectId);
182
+ setShippingPrice(parseFloat(cheapestRate.amount));
178
183
  return;
179
184
  }
180
185
 
@@ -255,17 +260,14 @@ export function CheckoutScreen() {
255
260
  })();
256
261
  }, [isDelivery, selectedAddressId, cart]);
257
262
 
258
- // Simulate fetching store addresses for pickup (replace with real API if available)
259
263
  useEffect(() => {
260
264
  if (!isDelivery) {
261
- // Simulate store addresses
262
265
  const stores = [
263
266
  { id: 'store1', name: 'Main Pharmacy', street1: '123 Main St', city: 'Seattle' },
264
267
  { id: 'store2', name: 'Eastside Pickup', street1: '456 East Ave', city: 'Bellevue' },
265
268
  ];
266
269
  setStoreAddresses(stores);
267
270
  setSelectedStoreAddressId(stores[0].id);
268
- // Pickup mode: no shipping cost
269
271
  setShippingPrice(0);
270
272
  } else {
271
273
  setStoreAddresses([]);
@@ -279,7 +281,6 @@ export function CheckoutScreen() {
279
281
  { id: 3, label: 'Payment', status: 'upcoming' as const },
280
282
  ];
281
283
 
282
- // Unified checkout handler (admin-style)
283
284
  const onSubmit = async (data: CheckoutFormData) => {
284
285
  setError(null);
285
286
  if (!isAuthenticated) {
@@ -304,7 +305,6 @@ export function CheckoutScreen() {
304
305
  setError('Please select a shipping method.');
305
306
  return;
306
307
  }
307
- // Validate shipping address fields
308
308
  const requiredFields = ['name', 'street1', 'city', 'zip', 'country'];
309
309
  for (const field of requiredFields) {
310
310
  if (!data.shipping[field as keyof typeof data.shipping]) {
@@ -339,21 +339,17 @@ export function CheckoutScreen() {
339
339
  setIsSubmitting(true);
340
340
  toast('Submitting order...', { icon: <CreditCard className="h-4 w-4" /> });
341
341
  try {
342
- // Prepare items array for backend
343
342
  const items = (cart?.cartBody?.items || []).map((item: any) => ({
344
343
  productVariantId: String(item.productVariantId),
345
344
  quantity: typeof item.quantity === 'number' ? item.quantity : 1,
346
345
  })).filter((item: any) => item.productVariantId);
347
346
 
348
- // Determine billing address ID for card payments (admin-style)
349
347
  let billingAddressId: string | undefined = undefined;
350
348
  if (isDelivery) {
351
349
  billingAddressId = selectedAddressId || undefined;
352
350
  } else if (paymentMethod === 'Card') {
353
351
  billingAddressId = userAddresses.length > 0 ? userAddresses[0].id : undefined;
354
352
  }
355
-
356
- // Prepare order DTO (admin-style)
357
353
  const orderDTO: any = {
358
354
  items,
359
355
  paymentMethod,
@@ -373,7 +369,6 @@ export function CheckoutScreen() {
373
369
  isDelivery ? (selectedShippingRateId || undefined) : undefined,
374
370
  billingAddressId
375
371
  );
376
- // Handle insufficient credit error (fallback: check for error message)
377
372
  if (response?.data?.payment && response.data.payment.hostedInvoiceUrl === 'INSUFFICIENT_CREDIT') {
378
373
  setError('You have insufficient credit to complete this order.');
379
374
  toast.error('You have insufficient credit to complete this order.');
@@ -382,11 +377,23 @@ export function CheckoutScreen() {
382
377
  if (paymentMethod === 'Card') {
383
378
  const paymentUrl = response?.data?.payment?.hostedInvoiceUrl;
384
379
  if (paymentUrl && paymentUrl !== 'INSUFFICIENT_CREDIT') {
385
- window.open(paymentUrl, '_blank');
386
- await clearCart();
387
-
380
+ const newWindow = window.open('', '_blank');
381
+ if (newWindow) {
382
+ newWindow.location.href = paymentUrl;
383
+ await clearCart();
384
+ return;
385
+ } else {
386
+ window.location.href = paymentUrl;
387
+ await clearCart();
388
+ return;
389
+ }
390
+ } else if (!paymentUrl) {
391
+ console.error('No payment URL received in response:', response.data);
392
+ setError('Failed to initiate payment. Please try again or contact support.');
393
+ toast.error('Failed to initiate payment');
388
394
  return;
389
395
  }
396
+
390
397
  toast.success('Order placed successfully!');
391
398
  await clearCart();
392
399
  router.push(buildPath(`/orders/${response.data?.id}`));
@@ -413,49 +420,8 @@ export function CheckoutScreen() {
413
420
  const total = subtotal + shippingPrice + tax;
414
421
 
415
422
  return (
416
- <div className="min-h-screen bg-slate-50">
417
- <section className="relative overflow-hidden bg-gradient-to-br from-[rgb(var(--header-from))] via-[rgb(var(--header-via))] to-[rgb(var(--header-to))] text-white">
418
- <div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(255,255,255,0.35),_transparent_60%)]" />
419
- <div className="relative container mx-auto px-4 py-16">
420
- <div className="grid gap-10 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
421
- <motion.div
422
- initial={{ opacity: 0, y: 24 }}
423
- animate={{ opacity: 1, y: 0 }}
424
- className="space-y-6"
425
- >
426
- <span className="inline-flex items-center gap-2 rounded-full bg-white/15 px-3 py-1 text-sm font-semibold uppercase tracking-[0.35em] text-white/70 backdrop-blur">
427
- <ShieldCheck className="h-4 w-4" />
428
- Secure checkout
429
- </span>
430
- <h1 className="text-4xl font-bold md:text-5xl">
431
- Provide delivery details
432
- </h1>
433
- <p className="text-white/75 md:text-lg">
434
- Our pharmacists handle every package with care. Share your shipping and billing
435
- information so we can dispatch your order within the next 12 hours.
436
- </p>
437
- <div className="flex flex-wrap gap-3">
438
- {checkoutSteps.map((step) => (
439
- <div
440
- key={step.id}
441
- className={`flex items-center gap-3 rounded-2xl px-4 py-2 text-sm font-semibold ${step.status === 'complete'
442
- ? 'bg-white/20 text-white'
443
- : step.status === 'current'
444
- ? 'bg-white text-primary-700'
445
- : 'bg-white/10 text-white/60'
446
- }`}
447
- >
448
- <span className="flex h-7 w-7 items-center justify-center rounded-full bg-white/20 text-xs font-bold">
449
- {step.id}
450
- </span>
451
- {step.label}
452
- </div>
453
- ))}
454
- </div>
455
- </motion.div>
456
- </div>
457
- </div>
458
- </section>
423
+ <div className="min-h-screen bg-slate-50 mb-16">
424
+
459
425
 
460
426
  <form onSubmit={handleSubmit(onSubmit)}>
461
427
  {error && <div className="mb-4 text-red-600 font-semibold">{error}</div>}
@@ -515,7 +481,6 @@ export function CheckoutScreen() {
515
481
  checked={selectedAddressId === addr.id}
516
482
  onChange={() => {
517
483
  setSelectedAddressId(addr.id);
518
- // Update form with selected address
519
484
  setValue('shipping.name', addr.name);
520
485
  setValue('shipping.phone', addr.phone || (undefined as unknown as string));
521
486
  setValue('shipping.street1', addr.street1);
@@ -638,9 +603,10 @@ export function CheckoutScreen() {
638
603
  return (
639
604
  <div
640
605
  key={rate.objectId}
641
- onClick={() => setSelectedShippingRateId(rate.objectId)}
642
- onMouseEnter={() => setShippingPrice(parseFloat(rate.amount))}
643
- onMouseLeave={() => setShippingPrice(parseFloat(rate.amountLocal))}
606
+ onClick={() => {
607
+ setSelectedShippingRateId(rate.objectId);
608
+ setShippingPrice(parseFloat(rate.amount));
609
+ }}
644
610
  className={`relative p-5 border-2 rounded-xl cursor-pointer transition-all duration-200 hover:shadow-md ${isSelected
645
611
  ? 'border-primary-500 bg-primary-50 ring-2 ring-primary-200'
646
612
  : 'border-gray-200 hover:border-gray-300'
@@ -896,6 +862,9 @@ export function CheckoutScreen() {
896
862
 
897
863
  {/* Cart Summary */}
898
864
  <section className="mt-8 pt-6 border-t border-slate-100">
865
+ <h3 className="text-xs font-semibold text-slate-700 uppercase tracking-wider">
866
+ Cart Summary
867
+ </h3>
899
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">
900
869
  {cart?.cartBody?.items?.map((item: any) => (
901
870
  <div
@@ -66,7 +66,7 @@ export function LoginScreen() {
66
66
  initial={{ opacity: 0, x: -24 }}
67
67
  animate={{ opacity: 1, x: 0 }}
68
68
  transition={{ duration: 0.4 }}
69
- className="relative flex flex-col justify-between bg-gradient-to-br from-[rgb(var(--header-from))] via-[rgb(var(--header-via))] to-[rgb(var(--header-to))] px-10 py-14 text-white"
69
+ className="relative flex flex-col justify-between bg-gradient-to-br from-slate-700 via-slate-600 to-slate-700 px-10 py-14 text-white"
70
70
  >
71
71
  <div className="space-y-6">
72
72
  <span className="inline-flex items-center gap-2 rounded-full bg-white/15 px-3 py-1 text-sm font-semibold uppercase tracking-[0.35em] text-white/70 backdrop-blur">