hey-pharmacist-ecommerce 1.1.19 → 1.1.22

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.
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
- import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
3
+ import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react';
4
4
  import { useAuth } from './AuthProvider';
5
- import { CartResponseDto } from '@/lib/Apis/models';
5
+ import { CartResponseDto, CartItemPopulated } from '@/lib/Apis/models';
6
6
  import { CartApi } from '@/lib/Apis';
7
7
  import { getApiConfiguration } from '@/lib/api-adapter';
8
8
  import { useNotification } from './NotificationProvider';
@@ -37,6 +37,13 @@ export function CartProvider({ children }: CartProviderProps) {
37
37
  const { isAuthenticated } = useAuth();
38
38
  const notification = useNotification();
39
39
 
40
+ // Ref to always have the latest cart state in async functions without stale closures
41
+ const cartRef = useRef<CartResponseDto | null>(cart);
42
+
43
+ useEffect(() => {
44
+ cartRef.current = cart;
45
+ }, [cart]);
46
+
40
47
  const refreshCart = useCallback(async () => {
41
48
  if (!isAuthenticated) {
42
49
  setCart(null);
@@ -45,7 +52,9 @@ export function CartProvider({ children }: CartProviderProps) {
45
52
 
46
53
  try {
47
54
  const response = await new CartApi(getApiConfiguration()).getUserCart();
48
- setCart(response.data);
55
+ if (response.data) {
56
+ setCart(response.data);
57
+ }
49
58
  } catch (error) {
50
59
  console.error('Failed to fetch cart:', error);
51
60
  }
@@ -55,125 +64,186 @@ export function CartProvider({ children }: CartProviderProps) {
55
64
  refreshCart();
56
65
  }, [refreshCart]);
57
66
 
58
- const addToCart = async (productId: string, quantity: number = 1, variantId?: string) => {
67
+ const addToCart = useCallback(async (productId: string, quantity: number = 1, variantId?: string) => {
68
+ if (!isAuthenticated) return;
59
69
  setIsLoading(true);
60
70
  try {
61
- // Get current cart items
62
- const currentItems = cart?.cartBody?.items || [];
71
+ const currentCart = cartRef.current;
72
+ const currentItems = currentCart?.cartBody?.items || [];
63
73
  const targetVariantId = variantId || productId;
64
-
65
- // Check if item already exists in cart
66
- const existingItemIndex = currentItems.findIndex(
67
- (item: any) => item.productVariantId === targetVariantId
68
- );
69
-
70
- // Build the items array
71
- const items = [...currentItems];
72
-
73
- if (existingItemIndex >= 0) {
74
- // Update quantity if item exists
75
- items[existingItemIndex] = {
76
- ...items[existingItemIndex],
77
- quantity: (items[existingItemIndex].quantity || 0) + quantity
74
+
75
+ let itemsFound = false;
76
+ const simplifiedItems = currentItems.map((item: CartItemPopulated) => {
77
+ let q = Number(item.quantity) || 0;
78
+ if (String(item.productVariantId) === String(targetVariantId)) {
79
+ itemsFound = true;
80
+ q += Number(quantity);
81
+ }
82
+ return {
83
+ _id: item._id,
84
+ productVariantId: item.productVariantId,
85
+ quantity: q
78
86
  };
79
- } else {
80
- // Add new item
81
- (items as any).push({
87
+ });
88
+
89
+ if (!itemsFound) {
90
+ simplifiedItems.push({
82
91
  productVariantId: targetVariantId,
83
- quantity,
84
- });
92
+ quantity: Number(quantity)
93
+ } as any);
94
+ }
95
+
96
+ const payload = {
97
+ _id: currentCart?.cartBody?._id,
98
+ items: simplifiedItems
99
+ };
100
+
101
+ const response = await new CartApi(getApiConfiguration()).handleUserCart(payload as any);
102
+ if (response.data) {
103
+ setCart(response.data);
104
+ notification.success('Added to cart', 'The item was added to your cart.');
85
105
  }
86
-
87
- const response = await new CartApi(getApiConfiguration()).handleUserCart({ items });
88
- setCart(response.data);
89
- notification.success(
90
- 'Added to cart',
91
- 'The item was added to your cart.'
92
- );
93
106
  } catch (error: any) {
94
- notification.error(
95
- 'Could not add to cart',
96
- error.response?.data?.message || 'Something went wrong while adding this item to your cart.'
97
- );
98
- throw error;
107
+ console.error('Add to cart error:', error);
108
+ notification.error('Could not add to cart', error.response?.data?.message || 'Something went wrong.');
99
109
  } finally {
100
110
  setIsLoading(false);
101
111
  }
102
- };
112
+ }, [isAuthenticated, notification]);
103
113
 
104
- const updateQuantity = async (productId: string, quantity: number) => {
114
+ const updateQuantity = useCallback(async (productId: string, quantity: number) => {
115
+ const currentCart = cartRef.current;
116
+ if (!currentCart) return;
105
117
  setIsLoading(true);
106
- try {
107
- // Get current cart items
108
- const currentItems = cart?.cartBody?.items || [];
109
-
110
- // Build the items array with updated quantity
111
- const items = currentItems.map((item: any) => {
112
- if (item.productVariantId === productId) {
113
- return {
114
- ...item,
115
- quantity
116
- };
118
+
119
+ const targetQ = Number(quantity);
120
+ const oldCart = currentCart;
121
+
122
+ // Optimistic UI Update
123
+ setCart(prev => {
124
+ if (!prev) return null;
125
+ const newItems = prev.cartBody.items.map(item => {
126
+ if (String(item.productVariantId) === String(productId)) {
127
+ return { ...item, quantity: targetQ };
117
128
  }
118
129
  return item;
119
130
  });
120
-
121
- const response = await new CartApi(getApiConfiguration()).handleUserCart({ items });
122
- setCart(response.data);
131
+ const newSubtotal = newItems.reduce((acc, item) => acc + (item.productVariantData.finalPrice || 0) * item.quantity, 0);
132
+ return {
133
+ ...prev,
134
+ subTotal: newSubtotal,
135
+ total: newSubtotal + (prev.shipping || 0) + (prev.tax || 0),
136
+ cartBody: { ...prev.cartBody, items: newItems }
137
+ };
138
+ });
139
+
140
+ try {
141
+ const simplifiedItems = oldCart.cartBody.items.map(item => ({
142
+ _id: item._id,
143
+ productVariantId: item.productVariantId,
144
+ quantity: String(item.productVariantId) === String(productId) ? targetQ : item.quantity
145
+ }));
146
+
147
+ const payload = {
148
+ _id: oldCart.cartBody._id,
149
+ items: simplifiedItems
150
+ };
151
+ console.log("payload", payload);
152
+ const response = await new CartApi(getApiConfiguration()).handleUserCart(payload as any);
153
+
154
+ if (response.data) {
155
+ setCart(response.data);
156
+ } else {
157
+ await refreshCart();
158
+ }
123
159
  } catch (error: any) {
124
- notification.error(
125
- 'Could not update cart',
126
- error.response?.data?.message || 'There was a problem updating the item quantity. Please try again.'
127
- );
128
- throw error;
160
+ console.error('Update quantity error:', error);
161
+ setCart(oldCart); // Rollback
162
+ notification.error('Could not update cart', error.response?.data?.message || 'Failed to update quantity.');
129
163
  } finally {
130
164
  setIsLoading(false);
131
165
  }
132
- };
166
+ }, [notification, refreshCart]);
133
167
 
134
- const removeFromCart = async (productId: string) => {
168
+ const removeFromCart = useCallback(async (productId: string) => {
169
+ const currentCart = cartRef.current;
170
+ if (!currentCart) return;
135
171
  setIsLoading(true);
172
+ const oldCart = currentCart;
173
+
174
+ // Optimistic Update
175
+ setCart(prev => {
176
+ if (!prev) return null;
177
+ const newItems = prev.cartBody.items.filter(item => String(item.productVariantId) !== String(productId));
178
+ const newSubtotal = newItems.reduce((acc, item) => acc + (item.productVariantData.finalPrice || 0) * item.quantity, 0);
179
+ return {
180
+ ...prev,
181
+ subTotal: newSubtotal,
182
+ total: newSubtotal + (prev.shipping || 0) + (prev.tax || 0),
183
+ cartBody: { ...prev.cartBody, items: newItems }
184
+ };
185
+ });
186
+
136
187
  try {
137
- // Get current cart items and filter out the item to remove
138
- const currentItems = cart?.cartBody?.items || [];
139
- const items = currentItems.filter((item: any) => item.productVariantId !== productId);
140
-
141
- const response = await new CartApi(getApiConfiguration()).handleUserCart({ items });
142
- setCart(response.data);
188
+ console.log('Removing item with productVariantId:', productId);
189
+ const itemsToKeep = currentCart.cartBody.items.filter(item => String(item.productVariantId) !== String(productId));
190
+ console.log('Items to keep count:', itemsToKeep.length);
191
+
192
+ const simplifiedItems = itemsToKeep.map(item => ({
193
+ _id: item._id,
194
+ productVariantId: item.productVariantId,
195
+ quantity: item.quantity
196
+ }));
197
+
198
+ if (simplifiedItems.length === 0) {
199
+ console.log('Cart will be empty, calling clearCart');
200
+ await new CartApi(getApiConfiguration()).clearCart();
201
+ setCart(null);
202
+ } else {
203
+ const payload = {
204
+ _id: currentCart.cartBody._id,
205
+ items: simplifiedItems
206
+ };
207
+ console.log("Remove from cart payload:", JSON.stringify(payload, null, 2));
208
+ const response = await new CartApi(getApiConfiguration()).handleUserCart(payload as any);
209
+ console.log("Remove from cart response:", response.status, response.data);
210
+ if (response.data) {
211
+ setCart(response.data);
212
+ }
213
+ }
143
214
  } catch (error: any) {
144
- notification.error(
145
- 'Could not remove item',
146
- error.response?.data?.message || 'There was a problem removing this item from your cart.'
147
- );
148
- throw error;
215
+ console.error('Remove from cart error:', error);
216
+ if (error.response) {
217
+ console.error('Error response data:', error.response.data);
218
+ }
219
+ setCart(oldCart); // Rollback
220
+ notification.error('Could not remove item', error.response?.data?.message || 'Failed to remove item.');
149
221
  } finally {
150
222
  setIsLoading(false);
151
223
  }
152
- };
224
+ }, [notification]);
153
225
 
154
- const clearCart = async () => {
226
+ const clearCart = useCallback(async () => {
155
227
  setIsLoading(true);
156
228
  try {
157
- const response = await new CartApi(getApiConfiguration()).clearCart();
229
+ await new CartApi(getApiConfiguration()).clearCart();
158
230
  setCart(null);
159
231
  } catch (error: any) {
160
- notification.error(
161
- 'Could not clear cart',
162
- error.response?.data?.message || 'We could not clear your cart. Please try again.'
163
- );
164
- throw error;
232
+ console.error('Clear cart error:', error);
233
+ notification.error('Could not clear cart', error.response?.data?.message || 'Failed to clear cart.');
165
234
  } finally {
166
235
  setIsLoading(false);
167
236
  }
168
- };
169
- const value: CartContextValue = {
237
+ }, [notification]);
238
+
239
+ const value = {
170
240
  cart,
171
241
  isLoading,
172
242
  addToCart,
173
243
  updateQuantity,
174
244
  removeFromCart,
175
245
  clearCart,
176
- refreshCart,
246
+ refreshCart
177
247
  };
178
248
 
179
249
  return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
@@ -176,7 +176,7 @@ export function CartScreen() {
176
176
  <button
177
177
  type="submit"
178
178
  onClick={handleSubmit}
179
- 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"
179
+ className="w-full rounded-full border-2 border-[#E67E50] bg-[#E67E50] hover:bg-[#E67E50]/80 text-white px-4 py-3 text-sm font-medium transition-colors flex items-center justify-center gap-2"
180
180
  >
181
181
  Proceed to Checkout
182
182
  <ArrowRight className="h-5 w-5" />
@@ -15,6 +15,7 @@ import {
15
15
  ShieldCheck,
16
16
  Truck,
17
17
  Check,
18
+ AlertCircle,
18
19
  } from 'lucide-react';
19
20
  import { Button } from '@/components/ui/Button';
20
21
  import { Input } from '@/components/ui/Input';
@@ -423,7 +424,15 @@ export function CheckoutScreen() {
423
424
  router.push(buildPath(`/orders/${response.data?.id}`));
424
425
  }
425
426
  } catch (err: any) {
426
- const msg = err?.message || (err?.response?.data?.message) || 'Failed to place order';
427
+ console.error('Checkout error:', err);
428
+ let msg = err?.response?.data?.message || err?.message || 'Failed to place order';
429
+
430
+ if (msg.toLowerCase().includes('insufficient balance')) {
431
+ msg = 'Insufficient balance in your account to complete this order.';
432
+ } else if (msg.toLowerCase().includes('browser was not found')) {
433
+ msg = 'A temporary server error occurred while processing your invoice. Please contact support.';
434
+ }
435
+
427
436
  setError(msg);
428
437
  notification.error(
429
438
  'Could not place order',
@@ -447,7 +456,6 @@ export function CheckoutScreen() {
447
456
 
448
457
 
449
458
  <form onSubmit={handleSubmit(onSubmit)}>
450
- {error && <div className="mb-4 text-red-600 font-semibold">{error}</div>}
451
459
  <div className="pt-12 container mx-auto grid gap-10 px-4 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
452
460
  <motion.div
453
461
  initial={{ opacity: 0, y: 24 }}
@@ -980,14 +988,21 @@ export function CheckoutScreen() {
980
988
  </section>
981
989
 
982
990
  {/* Checkout Button */}
983
- <Button
991
+ {error && (
992
+ <div className="mt-4 p-4 rounded-xl bg-red-50 border border-red-200 text-red-600 text-sm font-medium animate-in fade-in slide-in-from-top-1 duration-200">
993
+ <div className="flex items-start gap-2">
994
+ <AlertCircle className="h-4 w-4 shrink-0 mt-0.5" />
995
+ <span>{error}</span>
996
+ </div>
997
+ </div>
998
+ )}
999
+ <button
984
1000
  type="submit"
985
- isLoading={isSubmitting}
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"
1001
+ className="font-['Poppins',sans-serif] font-medium text-[14px] px-6 py-3 rounded-full text-white hover:bg-[#d66f45] hover:shadow-lg transition-all duration-300 mt-4 w-full bg-[#E67E50] hover:bg-[#2B4B7C] flex items-center justify-center gap-2"
987
1002
  >
988
1003
  <CreditCard className="h-5 w-5" />
989
1004
  {isSubmitting ? 'Placing order...' : 'Place Secure Order'}
990
- </Button>
1005
+ </button>
991
1006
 
992
1007
  <p className="mt-4 flex items-center justify-center gap-2 text-xs text-slate-500">
993
1008
  <Lock className="h-4 w-4" />