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.
Files changed (45) hide show
  1. package/dist/index.d.mts +2 -4
  2. package/dist/index.d.ts +2 -4
  3. package/dist/index.js +1039 -857
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1039 -856
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +3 -3
  8. package/src/components/AccountAddressesTab.tsx +209 -0
  9. package/src/components/AccountOrdersTab.tsx +151 -0
  10. package/src/components/AccountOverviewTab.tsx +209 -0
  11. package/src/components/AccountPaymentTab.tsx +116 -0
  12. package/src/components/AccountSavedItemsTab.tsx +76 -0
  13. package/src/components/AccountSettingsTab.tsx +116 -0
  14. package/src/components/AddressFormModal.tsx +23 -10
  15. package/src/components/CartItem.tsx +60 -56
  16. package/src/components/Header.tsx +69 -16
  17. package/src/components/Notification.tsx +148 -0
  18. package/src/components/ProductCard.tsx +215 -178
  19. package/src/components/QuickViewModal.tsx +314 -0
  20. package/src/components/TabNavigation.tsx +48 -0
  21. package/src/components/ui/Button.tsx +1 -1
  22. package/src/components/ui/ConfirmModal.tsx +84 -0
  23. package/src/hooks/usePaymentMethods.ts +58 -0
  24. package/src/index.ts +0 -1
  25. package/src/providers/CartProvider.tsx +22 -6
  26. package/src/providers/EcommerceProvider.tsx +8 -7
  27. package/src/providers/FavoritesProvider.tsx +10 -3
  28. package/src/providers/NotificationProvider.tsx +79 -0
  29. package/src/providers/WishlistProvider.tsx +34 -9
  30. package/src/screens/AddressesScreen.tsx +72 -61
  31. package/src/screens/CartScreen.tsx +48 -32
  32. package/src/screens/ChangePasswordScreen.tsx +155 -0
  33. package/src/screens/CheckoutScreen.tsx +162 -125
  34. package/src/screens/EditProfileScreen.tsx +165 -0
  35. package/src/screens/LoginScreen.tsx +59 -72
  36. package/src/screens/NewAddressScreen.tsx +16 -10
  37. package/src/screens/ProductDetailScreen.tsx +334 -234
  38. package/src/screens/ProfileScreen.tsx +190 -200
  39. package/src/screens/RegisterScreen.tsx +51 -70
  40. package/src/screens/SearchResultsScreen.tsx +2 -1
  41. package/src/screens/ShopScreen.tsx +260 -384
  42. package/src/screens/WishlistScreen.tsx +226 -224
  43. package/src/styles/globals.css +9 -0
  44. package/src/screens/CategoriesScreen.tsx +0 -122
  45. package/src/screens/HomeScreen.tsx +0 -211
@@ -0,0 +1,314 @@
1
+ import { useState } from 'react';
2
+ import { X, ShoppingCart, Check, Star, Package, ExternalLink, Minus, Plus } from 'lucide-react';
3
+ import { ExtendedProductDTO, Product } from '@/lib/Apis';
4
+ import { useCart } from '@/providers/CartProvider';
5
+ import Image from 'next/image';
6
+ import { ProductVariantInventoryStatusEnum } from '@/lib/Apis';
7
+ import { useNotification } from '@/providers/NotificationProvider';
8
+
9
+ interface QuickViewModalProps {
10
+ product: ExtendedProductDTO;
11
+ onClose: () => void;
12
+ onNavigateToProduct?: (productId: string) => void;
13
+ }
14
+
15
+ export function QuickViewModal({ product, onClose, onNavigateToProduct }: QuickViewModalProps) {
16
+ const [selectedVariantIndex, setSelectedVariantIndex] = useState(0);
17
+ const [selectedSizeIndex, setSelectedSizeIndex] = useState(0);
18
+ const [selectedImageIndex, setSelectedImageIndex] = useState(0);
19
+ const [addedToCart, setAddedToCart] = useState(false);
20
+ const [quantity, setQuantity] = useState(1);
21
+ const { addToCart } = useCart();
22
+ const [isAddingToCart, setIsAddingToCart] = useState(false);
23
+ const notification = useNotification();
24
+
25
+ const handleQuantityChange = (newQuantity: number) => {
26
+ if (newQuantity >= 1 && newQuantity <= (selectedVariant.inventoryCount || 10)) {
27
+ setQuantity(newQuantity);
28
+ }
29
+ };
30
+ const selectedVariant = product.productVariants[selectedVariantIndex];
31
+ const selectedSize = selectedVariant.productMedia[selectedSizeIndex];
32
+ const displayPrice = product.finalPrice;
33
+
34
+ const handleAddToCart = async () => {
35
+ if (!product || !selectedVariant) return;
36
+
37
+ setIsAddingToCart(true);
38
+ try {
39
+ console.log(selectedVariant)
40
+ await addToCart(
41
+ product.id,
42
+ quantity,
43
+ selectedVariant._id
44
+ );
45
+ notification.success(
46
+ 'Added to cart',
47
+ `${quantity} × ${product.name}${selectedVariant.name ? ` (${selectedVariant.name})` : ''} added to your cart.`
48
+ );
49
+ } catch (error) {
50
+ console.error('Failed to add to cart', error);
51
+ notification.error(
52
+ 'Could not add to cart',
53
+ 'Something went wrong while adding this item. Please try again.'
54
+ );
55
+ } finally {
56
+ setIsAddingToCart(false);
57
+ }
58
+ };
59
+
60
+ return (
61
+ <div
62
+ className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm"
63
+ onClick={onClose}
64
+ >
65
+ <div
66
+ className="bg-white rounded-[32px] max-w-5xl w-full max-h-[90vh] overflow-y-auto"
67
+ onClick={(e) => e.stopPropagation()}
68
+ >
69
+ <div className="p-8">
70
+ {/* Header */}
71
+ <div className="flex items-start justify-between mb-6">
72
+ <div>
73
+ <p className="font-['Poppins',sans-serif] text-[11px] text-primary uppercase tracking-wide font-medium mb-2">
74
+ {product.brand} • {product.parentCategories[0].name}
75
+ </p>
76
+ <h2 className="font-['Poppins',sans-serif] font-semibold text-secondary tracking-[-1px]">
77
+ {product.name}
78
+ </h2>
79
+
80
+ {/* Rating */}
81
+ <div className="flex items-center gap-2 mt-2">
82
+ <div className="flex items-center gap-0.5">
83
+ {[...Array(5)].map((_, i) => (
84
+ <Star
85
+ key={i}
86
+ className={`size-4 ${
87
+ i < Math.floor(product.rating? product.rating : 0)
88
+ ? 'text-accent fill-accent'
89
+ : 'text-gray-300'
90
+ }`}
91
+ />
92
+ ))}
93
+ </div>
94
+ <span className="font-['Poppins',sans-serif] text-[13px] text-muted">
95
+ {product.rating} ({product.reviews? product.reviews.length : 0} reviews)
96
+ </span>
97
+ </div>
98
+ </div>
99
+ <button
100
+ onClick={onClose}
101
+ className="p-2 hover:bg-gray-100 rounded-full transition-colors"
102
+ >
103
+ <X className="size-6 text-muted" />
104
+ </button>
105
+ </div>
106
+
107
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
108
+ {/* Product Image */}
109
+ <div className="space-y-4">
110
+ <div className="relative aspect-[3/4] rounded-[24px] overflow-hidden bg-gray-50">
111
+ <img
112
+ src={selectedVariant.productMedia[selectedImageIndex]?.file || selectedVariant.productMedia[0]?.file}
113
+ alt={product.name}
114
+ className="w-full h-full object-cover"
115
+ />
116
+ {/* Badges */}
117
+ <div className="absolute top-4 left-4 flex flex-col gap-2">
118
+ {product.finalPrice && (
119
+ <div className="bg-accent text-white rounded-full px-3 py-1.5">
120
+ <span className="font-['Poppins',sans-serif] font-bold text-[11px] uppercase">
121
+ -{product.discountAmount}%
122
+ </span>
123
+ </div>
124
+ )}
125
+ {/* {product.bestseller && (
126
+ <div className="bg-secondary text-white rounded-full px-3 py-1.5">
127
+ <span className="font-['Poppins',sans-serif] font-semibold text-[10px] uppercase">
128
+ Bestseller
129
+ </span>
130
+ </div>
131
+ )} */}
132
+ </div>
133
+ </div>
134
+
135
+ {/* Thumbnail Images */}
136
+ {selectedVariant.productMedia.length > 1 && (
137
+ <div className="grid grid-cols-4 gap-3">
138
+ {selectedVariant.productMedia.map((image: any, index: any) => (
139
+ <div
140
+ key={index}
141
+ className={`aspect-square rounded-xl overflow-hidden cursor-pointer transition-opacity ${selectedImageIndex === index ? 'ring-2 ring-primary' : 'bg-gray-50 hover:opacity-75'}`}
142
+ onClick={() => setSelectedImageIndex(index)}
143
+ >
144
+ <img
145
+ src={image.file}
146
+ alt={`${product.name} ${index + 1}`}
147
+ className="w-full h-full object-cover"
148
+ />
149
+ </div>
150
+ ))}
151
+ </div>
152
+ )}
153
+ </div>
154
+
155
+ {/* Product Details */}
156
+ <div className="flex flex-col">
157
+ {/* Price */}
158
+ <div className="flex items-center gap-3 mb-4">
159
+ <span className="font-['Poppins',sans-serif] font-bold text-[32px] text-accent">
160
+ ${displayPrice.toFixed(2)}
161
+ </span>
162
+ {product.isDiscounted && (
163
+ <span className="font-['Poppins',sans-serif] text-[20px] text-muted line-through">
164
+ ${product.finalPrice.toFixed(2)}
165
+ </span>
166
+ )}
167
+ </div>
168
+
169
+ {/* Stock Status */}
170
+ <div className="mb-6">
171
+ {selectedVariant.inventoryCount === 0 ? (
172
+ <span className="font-['Poppins',sans-serif] text-[12px] text-red-500 font-medium">
173
+ Out of Stock
174
+ </span>
175
+ ) : selectedVariant.inventoryCount <= 10 ? (
176
+ <span className="font-['Poppins',sans-serif] text-[12px] text-orange-500 font-medium flex items-center gap-1">
177
+ <Package className="size-3" />
178
+ Only {selectedVariant.inventoryCount} left in stock
179
+ </span>
180
+ ) : (
181
+ <span className="font-['Poppins',sans-serif] text-[12px] text-green-600 font-medium flex items-center gap-1">
182
+ <Package className="size-3" />
183
+ In Stock
184
+ </span>
185
+ )}
186
+ </div>
187
+
188
+ {/* Description */}
189
+ <p className="font-['Poppins',sans-serif] text-[14px] text-muted leading-[1.7] mb-6">
190
+ <div dangerouslySetInnerHTML={{ __html: product.description }} />
191
+ </p>
192
+
193
+ {/* Color Selection */}
194
+ <div className="mb-6">
195
+ <h3 className="font-['Poppins',sans-serif] font-semibold text-[13px] text-secondary mb-3">
196
+ Selected Variant: <span className="font-normal text-muted">{product.productVariants[selectedVariantIndex].name}</span>
197
+ </h3>
198
+ <div className="flex flex-wrap gap-3">
199
+ {product.productVariants.map((variant: any, index: any) => (
200
+ <button
201
+ key={variant.id}
202
+ onClick={() => {
203
+ setSelectedVariantIndex(index);
204
+ setSelectedSizeIndex(0);
205
+ setSelectedImageIndex(0); // Reset selected image index when variant changes
206
+ }}
207
+ className={`size-10 rounded-full border-2 transition-all ${
208
+ selectedVariantIndex === index
209
+ ? 'border-primary scale-110'
210
+ : 'border-gray-200 hover:border-primary/50'
211
+ }`}
212
+ style={{ backgroundColor: variant.colorHex }}
213
+ title={variant.color}
214
+ >
215
+ <Image
216
+ src={variant.productMedia?.[0]?.file || ''}
217
+ alt={variant.color || `Variant ${index + 1}`}
218
+ className="object-cover"
219
+ height={32}
220
+ width={32}
221
+ />
222
+ </button>
223
+ ))}
224
+ </div>
225
+ </div>
226
+ {/* Quick Features */}
227
+ <div className="mb-6 p-4 bg-gray-50 rounded-xl">
228
+ <ul className="space-y-2">
229
+ {product.tags.slice(0, 3).map((feature: any, index: any) => (
230
+ <li key={index} className="flex items-start gap-2">
231
+ <Check className="size-4 text-primary shrink-0 mt-0.5" />
232
+ <span className="font-['Poppins',sans-serif] text-[12px] text-muted">
233
+ {feature}
234
+ </span>
235
+ </li>
236
+ ))}
237
+ </ul>
238
+ </div>
239
+
240
+ {/* Quantity Selector */}
241
+ <div className="mb-6">
242
+ <h3 className="font-['Poppins',sans-serif] font-semibold text-[13px] text-secondary mb-3">
243
+ Quantity
244
+ </h3>
245
+ <div className="flex items-center gap-4">
246
+ <button
247
+ onClick={() => handleQuantityChange(quantity - 1)}
248
+ disabled={quantity <= 1}
249
+ className="p-2 rounded-full border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
250
+ >
251
+ <Minus className="size-4 text-secondary" />
252
+ </button>
253
+ <span className="w-8 text-center font-medium">{quantity}</span>
254
+ <button
255
+ onClick={() => handleQuantityChange(quantity + 1)}
256
+ disabled={quantity >= (selectedVariant.inventoryCount || 10)}
257
+ className="p-2 rounded-full border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
258
+ >
259
+ <Plus className="size-4 text-secondary" />
260
+ </button>
261
+ </div>
262
+ </div>
263
+
264
+ {/* Action Buttons */}
265
+ <div className="flex flex-col gap-3 mt-auto">
266
+ <button
267
+ onClick={handleAddToCart}
268
+ disabled={addedToCart || selectedVariant.inventoryCount === 0}
269
+ className={`w-full font-['Poppins',sans-serif] font-medium text-[14px] px-6 py-4 rounded-full transition-all duration-300 flex items-center justify-center gap-3 ${
270
+ addedToCart
271
+ ? 'bg-green-500 text-white'
272
+ : 'bg-accent text-white hover:bg-[#d66f45] hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed'
273
+ }`}
274
+ >
275
+ {isAddingToCart ? (
276
+ <>
277
+ <svg className="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
278
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
279
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
280
+ </svg>
281
+ Loading...
282
+ </>
283
+ ) : (
284
+ <>
285
+ <ShoppingCart className="h-4 w-4" />
286
+ {!selectedVariant
287
+ ? 'Select a variant'
288
+ : selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK
289
+ ? 'Out of Stock'
290
+ : 'Add to Cart'}
291
+
292
+ </>
293
+ )
294
+ }
295
+ </button>
296
+
297
+ <button
298
+ onClick={() => {
299
+ onClose();
300
+ onNavigateToProduct?.(product.id);
301
+ }}
302
+ className="w-full font-['Poppins',sans-serif] font-medium text-[13px] px-6 py-3 rounded-full bg-white text-secondary border-2 border-primary hover:bg-gray-50 transition-all flex items-center justify-center gap-2"
303
+ >
304
+ View Full Details
305
+ <ExternalLink className="size-4" />
306
+ </button>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ );
314
+ }
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { LucideIcon } from 'lucide-react';
5
+
6
+ interface Tab {
7
+ id: string;
8
+ label: string;
9
+ icon: LucideIcon;
10
+ }
11
+
12
+ interface TabNavigationProps {
13
+ tabs: Tab[];
14
+ activeTab: string;
15
+ onTabChange: (tabId: string) => void;
16
+ }
17
+
18
+ export function TabNavigation({ tabs, activeTab, onTabChange }: TabNavigationProps) {
19
+ return (
20
+ <div className="mx-auto flex items-center max-w-7xl px-4 py-4">
21
+ <nav className="flex overflow-x-auto scrollbar-hide justify-center gap-4" aria-label="Account tabs">
22
+ {tabs.map((tab) => {
23
+ const isActive = activeTab === tab.id;
24
+ const Icon = tab.icon;
25
+
26
+ return (
27
+ <button
28
+ key={tab.id}
29
+ onClick={() => onTabChange(tab.id)}
30
+ className={`
31
+ flex items-center gap-2 px-6 py-3 text-sm font-medium whitespace-nowrap
32
+ border-b-2 transition-colors
33
+ ${isActive
34
+ ? 'bg-secondary text-white rounded-xl hover:transition-all hover:duration-300 hover:ease-in-out hover:-translate-y-1'
35
+ : 'bg-white text-muted rounded-xl hover:text-secondary hover:transition-all hover:duration-150 hover:ease-in-out hover:-translate-y-1'
36
+ }
37
+ `}
38
+ aria-current={isActive ? 'page' : undefined}
39
+ >
40
+ <Icon className="h-5 w-5" />
41
+ {tab.label}
42
+ </button>
43
+ );
44
+ })}
45
+ </nav>
46
+ </div>
47
+ );
48
+ }
@@ -23,7 +23,7 @@ export function Button({
23
23
  children,
24
24
  ...props
25
25
  }: ButtonProps) {
26
- const baseStyles = 'font-medium rounded-lg transition-all duration-200 inline-flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500';
26
+ const baseStyles = 'font-medium rounded-full transition-all duration-200 inline-flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500';
27
27
 
28
28
  const variants = {
29
29
  primary: 'bg-primary-600 text-white hover:bg-primary-700 shadow-lg shadow-primary-500/30 hover:shadow-xl hover:shadow-primary-500/40',
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { Modal } from './Modal';
5
+ import { Button } from './Button';
6
+ import { AlertTriangle } from 'lucide-react';
7
+
8
+ interface ConfirmModalProps {
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ onConfirm: () => void;
12
+ title: string;
13
+ message: string;
14
+ confirmText?: string;
15
+ cancelText?: string;
16
+ variant?: 'danger' | 'warning' | 'info';
17
+ isLoading?: boolean;
18
+ }
19
+
20
+ export function ConfirmModal({
21
+ isOpen,
22
+ onClose,
23
+ onConfirm,
24
+ title,
25
+ message,
26
+ confirmText = 'Confirm',
27
+ cancelText = 'Cancel',
28
+ variant = 'danger',
29
+ isLoading = false,
30
+ }: ConfirmModalProps) {
31
+ const handleConfirm = () => {
32
+ onConfirm();
33
+ };
34
+
35
+ const variantStyles = {
36
+ danger: {
37
+ icon: 'text-red-600 bg-red-100',
38
+ button: 'secondary' as const,
39
+ },
40
+ warning: {
41
+ icon: 'text-amber-600 bg-amber-100',
42
+ button: 'primary' as const,
43
+ },
44
+ info: {
45
+ icon: 'text-secondary bg-blue-100',
46
+ button: 'primary' as const,
47
+ },
48
+ };
49
+
50
+ const style = variantStyles[variant];
51
+
52
+ return (
53
+ <Modal isOpen={isOpen} onClose={onClose} size="sm">
54
+ <div className="flex flex-col items-center text-center">
55
+ {/* Icon */}
56
+ <div className={`w-12 h-12 rounded-full ${style.icon} flex items-center justify-center mb-4`}>
57
+ <AlertTriangle className="w-6 h-6" />
58
+ </div>
59
+
60
+ {/* Title */}
61
+ <h3 className="text-xl font-semibold text-secondary mb-2">{title}</h3>
62
+
63
+ {/* Message */}
64
+ <p className="text-muted mb-6">{message}</p>
65
+
66
+ {/* Actions */}
67
+ <div className="flex gap-3 w-full justify-center">
68
+ <button
69
+ className='px-6 py-2 border border-slate-200 rounded-full text-slate-700 hover:bg-slate-100 transition-colors'
70
+ onClick={onClose}
71
+ >
72
+ {cancelText}
73
+ </button>
74
+ <button
75
+ className='px-6 py-2 border border-red-600 rounded-full text-red-600 hover:bg-red-100 transition-colors bg-red-50'
76
+ onClick={handleConfirm}
77
+ >
78
+ {confirmText}
79
+ </button>
80
+ </div>
81
+ </div>
82
+ </Modal>
83
+ );
84
+ }
@@ -0,0 +1,58 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { PaymentMethodsApi } from '@/lib/Apis';
3
+ import { getApiConfiguration } from '@/lib/api-adapter';
4
+ import { PaymentMethod } from '@/lib/Apis/models';
5
+
6
+ export function usePaymentMethods() {
7
+ const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]);
8
+ const [isLoading, setIsLoading] = useState(true);
9
+ const [error, setError] = useState<Error | null>(null);
10
+
11
+ const fetchPaymentMethods = useCallback(async () => {
12
+ setIsLoading(true);
13
+ setError(null);
14
+ try {
15
+ const response = await new PaymentMethodsApi(getApiConfiguration()).getPaymentMethods();
16
+ setPaymentMethods(response.data.paymentMethods || []);
17
+ } catch (err) {
18
+ setError(err as Error);
19
+ } finally {
20
+ setIsLoading(false);
21
+ }
22
+ }, []);
23
+
24
+ const deletePaymentMethod = useCallback(async (paymentMethodId: string) => {
25
+ try {
26
+ await new PaymentMethodsApi(getApiConfiguration()).detachPaymentMethod({
27
+ paymentMethodId,
28
+ });
29
+ await fetchPaymentMethods();
30
+ } catch (err) {
31
+ throw err;
32
+ }
33
+ }, [fetchPaymentMethods]);
34
+
35
+ const setDefaultPaymentMethod = useCallback(async (paymentMethodId: string) => {
36
+ try {
37
+ await new PaymentMethodsApi(getApiConfiguration()).changeDefaultPayment({
38
+ paymentMethodId,
39
+ });
40
+ await fetchPaymentMethods();
41
+ } catch (err) {
42
+ throw err;
43
+ }
44
+ }, [fetchPaymentMethods]);
45
+
46
+ useEffect(() => {
47
+ fetchPaymentMethods();
48
+ }, [fetchPaymentMethods]);
49
+
50
+ return {
51
+ paymentMethods,
52
+ isLoading,
53
+ error,
54
+ refetch: fetchPaymentMethods,
55
+ deletePaymentMethod,
56
+ setDefaultPaymentMethod,
57
+ };
58
+ }
package/src/index.ts CHANGED
@@ -21,7 +21,6 @@ export { CurrentOrdersScreen } from './screens/CurrentOrdersScreen';
21
21
  export { AddressesScreen } from './screens/AddressesScreen';
22
22
  export { default as WishlistScreen } from './screens/WishlistScreen';
23
23
  export { default as SearchResultsScreen } from './screens/SearchResultsScreen';
24
- export { CategoriesScreen } from './screens/CategoriesScreen';
25
24
  export { default as NewAddressScreen } from './screens/NewAddressScreen';
26
25
 
27
26
  // Components
@@ -2,10 +2,10 @@
2
2
 
3
3
  import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
4
4
  import { useAuth } from './AuthProvider';
5
- import { toast } from 'sonner';
6
5
  import { CartResponseDto } from '@/lib/Apis/models';
7
6
  import { CartApi } from '@/lib/Apis';
8
7
  import { getApiConfiguration } from '@/lib/api-adapter';
8
+ import { useNotification } from './NotificationProvider';
9
9
 
10
10
  interface CartContextValue {
11
11
  cart: CartResponseDto | null;
@@ -35,6 +35,7 @@ export function CartProvider({ children }: CartProviderProps) {
35
35
  const [cart, setCart] = useState<CartResponseDto | null>(null);
36
36
  const [isLoading, setIsLoading] = useState(false);
37
37
  const { isAuthenticated } = useAuth();
38
+ const notification = useNotification();
38
39
 
39
40
  const refreshCart = useCallback(async () => {
40
41
  if (!isAuthenticated) {
@@ -85,9 +86,15 @@ export function CartProvider({ children }: CartProviderProps) {
85
86
 
86
87
  const response = await new CartApi(getApiConfiguration()).handleUserCart({ items });
87
88
  setCart(response.data);
88
- toast.success('Added to cart!');
89
+ notification.success(
90
+ 'Added to cart',
91
+ 'The item was added to your cart.'
92
+ );
89
93
  } catch (error: any) {
90
- toast.error(error.response?.data?.message || 'Failed to add to cart');
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
+ );
91
98
  throw error;
92
99
  } finally {
93
100
  setIsLoading(false);
@@ -114,7 +121,10 @@ export function CartProvider({ children }: CartProviderProps) {
114
121
  const response = await new CartApi(getApiConfiguration()).handleUserCart({ items });
115
122
  setCart(response.data);
116
123
  } catch (error: any) {
117
- toast.error(error.response?.data?.message || 'Failed to update cart');
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
+ );
118
128
  throw error;
119
129
  } finally {
120
130
  setIsLoading(false);
@@ -131,7 +141,10 @@ export function CartProvider({ children }: CartProviderProps) {
131
141
  const response = await new CartApi(getApiConfiguration()).handleUserCart({ items });
132
142
  setCart(response.data);
133
143
  } catch (error: any) {
134
- toast.error(error.response?.data?.message || 'Failed to remove from cart');
144
+ notification.error(
145
+ 'Could not remove item',
146
+ error.response?.data?.message || 'There was a problem removing this item from your cart.'
147
+ );
135
148
  throw error;
136
149
  } finally {
137
150
  setIsLoading(false);
@@ -144,7 +157,10 @@ export function CartProvider({ children }: CartProviderProps) {
144
157
  const response = await new CartApi(getApiConfiguration()).clearCart();
145
158
  setCart(null);
146
159
  } catch (error: any) {
147
- toast.error(error.response?.data?.message || 'Failed to clear cart');
160
+ notification.error(
161
+ 'Could not clear cart',
162
+ error.response?.data?.message || 'We could not clear your cart. Please try again.'
163
+ );
148
164
  throw error;
149
165
  } finally {
150
166
  setIsLoading(false);
@@ -8,9 +8,9 @@ import { CartProvider } from './CartProvider';
8
8
  import { WishlistProvider } from './WishlistProvider';
9
9
  import { BasePathProvider } from './BasePathProvider';
10
10
  import { initializeApiAdapter } from '@/lib/api-adapter';
11
- import { Toaster } from 'sonner';
12
11
  import { QueryClientProvider } from '@tanstack/react-query';
13
12
  import { QueryClient } from '@tanstack/react-query';
13
+ import { NotificationProvider } from './NotificationProvider';
14
14
 
15
15
  interface EcommerceProviderProps {
16
16
  config: EcommerceConfig;
@@ -34,12 +34,13 @@ export function EcommerceProvider({ config, children, withToaster = true, basePa
34
34
  <ThemeProvider config={config}>
35
35
  <BasePathProvider basePath={basePath}>
36
36
  <AuthProvider>
37
- <CartProvider>
38
- <WishlistProvider>
39
- {children}
40
- {withToaster && <Toaster position="top-right" richColors />}
41
- </WishlistProvider>
42
- </CartProvider>
37
+ <NotificationProvider>
38
+ <CartProvider>
39
+ <WishlistProvider>
40
+ {children}
41
+ </WishlistProvider>
42
+ </CartProvider>
43
+ </NotificationProvider>
43
44
  </AuthProvider>
44
45
  </BasePathProvider>
45
46
  </ThemeProvider>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { ExtendedProductDTO } from '@/lib/Apis';
4
4
  import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
5
- import { toast } from 'sonner';
5
+ import { useNotification } from './NotificationProvider';
6
6
 
7
7
  interface FavoritesContextType {
8
8
  favorites: string[];
@@ -17,6 +17,7 @@ const FavoritesContext = createContext<FavoritesContextType | undefined>(undefin
17
17
  export function FavoritesProvider({ children }: { children: ReactNode }) {
18
18
  const [favorites, setFavorites] = useState<string[]>([]);
19
19
  const [isClient, setIsClient] = useState(false);
20
+ const notification = useNotification();
20
21
 
21
22
  const getFavoritesKey = () => {
22
23
  if (typeof window === 'undefined') return 'favorites';
@@ -55,13 +56,19 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
55
56
  const addToFavorites = (product: ExtendedProductDTO) => {
56
57
  if (!favorites.includes(product.id)) {
57
58
  setFavorites(prev => [...prev, product.id]);
58
- toast.success(`${product.name} added to favorites`);
59
+ notification.success(
60
+ 'Added to favorites',
61
+ `${product.name} was added to your favorites.`
62
+ );
59
63
  }
60
64
  };
61
65
 
62
66
  const removeFromFavorites = (productId: string) => {
63
67
  setFavorites(prev => prev.filter(id => id !== productId));
64
- toast.info('Removed from favorites');
68
+ notification.info(
69
+ 'Removed from favorites',
70
+ 'The item has been removed from your favorites.'
71
+ );
65
72
  };
66
73
 
67
74
  const toggleFavorite = (product: ExtendedProductDTO) => {