hey-pharmacist-ecommerce 1.1.20 → 1.1.23
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 +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +723 -488
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +725 -490
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/CartItem.tsx +29 -17
- package/src/lib/Apis/api.ts +0 -1
- package/src/lib/Apis/apis/products-api.ts +187 -0
- package/src/lib/Apis/apis/stores-api.ts +244 -0
- package/src/lib/Apis/models/create-product-dto.ts +6 -0
- package/src/lib/Apis/models/create-single-variant-product-dto.ts +6 -0
- package/src/lib/Apis/models/extended-product-dto.ts +6 -0
- package/src/lib/Apis/models/index.ts +2 -0
- package/src/lib/Apis/models/new-client-email-dto.ts +159 -0
- package/src/lib/Apis/models/product.ts +6 -0
- package/src/lib/Apis/models/schedule-tour-email-dto.ts +45 -0
- package/src/lib/Apis/models/update-product-dto.ts +6 -0
- package/src/lib/Apis/sharedConfig.ts +9 -0
- package/src/lib/Apis/wrapper.ts +11 -16
- package/src/lib/api-adapter/config.ts +12 -6
- package/src/providers/CartProvider.tsx +152 -82
- package/src/providers/EcommerceProvider.tsx +16 -16
- package/src/screens/CartScreen.tsx +1 -1
- package/src/screens/CheckoutScreen.tsx +21 -6
- package/src/screens/ProductDetailScreen.tsx +140 -132
- package/src/styles/globals.css +15 -5
- package/src/lib/Apis/apis/inventory-api.ts +0 -267
|
@@ -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
|
-
|
|
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
|
-
|
|
62
|
-
const currentItems =
|
|
71
|
+
const currentCart = cartRef.current;
|
|
72
|
+
const currentItems = currentCart?.cartBody?.items || [];
|
|
63
73
|
const targetVariantId = variantId || productId;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
error
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
229
|
+
await new CartApi(getApiConfiguration()).clearCart();
|
|
158
230
|
setCart(null);
|
|
159
231
|
} catch (error: any) {
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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>;
|
|
@@ -20,9 +20,9 @@ interface EcommerceProviderProps {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function EcommerceProvider({ config, children, withToaster = true, basePath = '' }: EcommerceProviderProps) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// Initialize API adapter with store configuration synchronously
|
|
24
|
+
// This ensures setting up the real backend APIs before children render and fire their effects
|
|
25
|
+
React.useMemo(() => {
|
|
26
26
|
initializeApiAdapter(config);
|
|
27
27
|
}, [config]);
|
|
28
28
|
|
|
@@ -31,19 +31,19 @@ export function EcommerceProvider({ config, children, withToaster = true, basePa
|
|
|
31
31
|
);
|
|
32
32
|
return (
|
|
33
33
|
<QueryClientProvider client={client}>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
<ThemeProvider config={config}>
|
|
35
|
+
<BasePathProvider basePath={basePath}>
|
|
36
|
+
<AuthProvider>
|
|
37
|
+
<NotificationProvider>
|
|
38
|
+
<CartProvider>
|
|
39
|
+
<WishlistProvider>
|
|
40
|
+
{children}
|
|
41
|
+
</WishlistProvider>
|
|
42
|
+
</CartProvider>
|
|
43
|
+
</NotificationProvider>
|
|
44
|
+
</AuthProvider>
|
|
45
|
+
</BasePathProvider>
|
|
46
|
+
</ThemeProvider>
|
|
47
47
|
</QueryClientProvider>
|
|
48
48
|
);
|
|
49
49
|
}
|
|
@@ -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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
</
|
|
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" />
|