hey-pharmacist-ecommerce 1.1.12 → 1.1.14
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 +1123 -972
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1123 -971
- 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/FilterChips.tsx +54 -80
- package/src/components/Header.tsx +69 -16
- package/src/components/Notification.tsx +148 -0
- package/src/components/OrderCard.tsx +89 -56
- 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/useOrders.ts +1 -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/OrdersScreen.tsx +91 -148
- 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
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
4
4
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
5
|
-
import {
|
|
5
|
+
import { Star, ShoppingCart, Eye } from 'lucide-react';
|
|
6
6
|
import { ExtendedProductDTO, Product } from '@/lib/Apis';
|
|
7
7
|
import { formatPrice } from '@/lib/utils/format';
|
|
8
8
|
import { useWishlist } from '@/providers/WishlistProvider';
|
|
9
9
|
import { useCart } from '@/providers/CartProvider';
|
|
10
10
|
import Image from 'next/image';
|
|
11
|
-
import { toast } from 'sonner';
|
|
12
11
|
import { useRouter } from 'next/navigation';
|
|
13
12
|
import { useBasePath } from '@/providers/BasePathProvider';
|
|
14
|
-
|
|
13
|
+
import { QuickViewModal } from './QuickViewModal';
|
|
14
|
+
import { useNotification } from '@/providers/NotificationProvider';
|
|
15
15
|
|
|
16
16
|
interface ProductCardProps {
|
|
17
17
|
product: ExtendedProductDTO;
|
|
@@ -38,7 +38,8 @@ export function ProductCard({
|
|
|
38
38
|
const [isImageLoaded, setIsImageLoaded] = useState(false);
|
|
39
39
|
const [selectedVariantImage, setSelectedVariantImage] = useState<string | null>(null);
|
|
40
40
|
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
|
41
|
-
|
|
41
|
+
const [showQuickView, setShowQuickView] = useState(false);
|
|
42
|
+
const notification = useNotification();
|
|
42
43
|
// Handle image load state
|
|
43
44
|
const handleImageLoad = useCallback(() => {
|
|
44
45
|
setIsImageLoaded(true);
|
|
@@ -59,31 +60,49 @@ export function ProductCard({
|
|
|
59
60
|
if (isInWishlist(product?._id || '')) {
|
|
60
61
|
await removeFromWishlist(product?._id || '');
|
|
61
62
|
setIsFavorite(false);
|
|
62
|
-
|
|
63
|
+
notification.success(
|
|
64
|
+
'Removed from wishlist',
|
|
65
|
+
`${product.name} was removed from your wishlist.`
|
|
66
|
+
);
|
|
63
67
|
} else {
|
|
64
68
|
await addToWishlist(product as unknown as Product);
|
|
65
69
|
setIsFavorite(true);
|
|
66
|
-
|
|
70
|
+
notification.success(
|
|
71
|
+
'Added to wishlist',
|
|
72
|
+
`${product.name} was added to your wishlist.`
|
|
73
|
+
);
|
|
67
74
|
}
|
|
68
75
|
onFavorite?.(product);
|
|
69
76
|
} catch (error) {
|
|
70
77
|
console.error('Error updating wishlist:', error);
|
|
71
|
-
|
|
78
|
+
notification.error(
|
|
79
|
+
'Could not update wishlist',
|
|
80
|
+
'We could not update your wishlist. Please try again.'
|
|
81
|
+
);
|
|
72
82
|
}
|
|
73
83
|
};
|
|
74
84
|
|
|
75
85
|
// Update favorite state when props or wishlist changes
|
|
76
86
|
useEffect(() => {
|
|
77
87
|
setIsFavorite(isInWishlist(product?._id || '') || isFavorited);
|
|
78
|
-
|
|
88
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
79
89
|
}, [isFavorited, isInWishlist, product?._id]);
|
|
80
90
|
|
|
81
|
-
// Reset selected variant image when product changes
|
|
91
|
+
// Reset selected variant image when product changes and auto-select first variant
|
|
82
92
|
useEffect(() => {
|
|
83
93
|
setSelectedVariantImage(null);
|
|
84
94
|
setSelectedVariantId(null);
|
|
85
95
|
setIsImageLoaded(false);
|
|
86
|
-
|
|
96
|
+
|
|
97
|
+
// Auto-select first variant if available
|
|
98
|
+
if (product.productVariants && product.productVariants.length > 0) {
|
|
99
|
+
const firstVariant = product.productVariants[0];
|
|
100
|
+
if (firstVariant.productMedia && firstVariant.productMedia.length > 0) {
|
|
101
|
+
setSelectedVariantImage(firstVariant.productMedia[0].file);
|
|
102
|
+
setSelectedVariantId(firstVariant.id || firstVariant._id || null);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}, [product._id, product.productVariants]);
|
|
87
106
|
|
|
88
107
|
// Handle keyboard navigation
|
|
89
108
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
@@ -138,108 +157,190 @@ export function ProductCard({
|
|
|
138
157
|
|
|
139
158
|
|
|
140
159
|
return (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
key={imageSource.src}
|
|
178
|
-
/>
|
|
179
|
-
|
|
180
|
-
{/* Badges */}
|
|
181
|
-
<div className="absolute top-3 left-3 flex flex-col gap-2 z-10">
|
|
182
|
-
{product.isDiscounted && (
|
|
183
|
-
<motion.span
|
|
184
|
-
initial={{ scale: 0.9, opacity: 0 }}
|
|
185
|
-
animate={{ scale: 1, opacity: 1 }}
|
|
186
|
-
className="inline-flex items-center justify-center px-2.5 py-1 rounded-full text-xs font-bold text-white bg-green-600 shadow-md"
|
|
187
|
-
>
|
|
188
|
-
-{product.discountAmount}%
|
|
189
|
-
</motion.span>
|
|
190
|
-
)}
|
|
160
|
+
<>
|
|
161
|
+
<motion.div
|
|
162
|
+
className="bg-white rounded-[16px] overflow-hidden border-2 border-gray-100 hover:border-[#5B9BD5] hover:shadow-lg transition-all duration-300 group h-full flex flex-col"
|
|
163
|
+
whileHover={{ y: -4 }}
|
|
164
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
165
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
166
|
+
aria-labelledby={`product-${product.id}-title`}
|
|
167
|
+
role="article"
|
|
168
|
+
tabIndex={0}
|
|
169
|
+
onClick={handleCardClick}
|
|
170
|
+
onKeyDown={handleKeyDown}
|
|
171
|
+
>
|
|
172
|
+
<div
|
|
173
|
+
onClick={handleCardClick}
|
|
174
|
+
className="relative aspect-square overflow-hidden bg-gray-50 cursor-pointer flex-shrink-0"
|
|
175
|
+
>
|
|
176
|
+
<Image
|
|
177
|
+
src={imageSource.src}
|
|
178
|
+
alt={imageSource.alt}
|
|
179
|
+
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
|
180
|
+
height={300}
|
|
181
|
+
width={300}
|
|
182
|
+
priority={false}
|
|
183
|
+
onLoad={handleImageLoad}
|
|
184
|
+
onError={() => setIsImageLoaded(true)}
|
|
185
|
+
key={imageSource.src}
|
|
186
|
+
/>
|
|
187
|
+
<button
|
|
188
|
+
onClick={(e) => {
|
|
189
|
+
e.stopPropagation();
|
|
190
|
+
setShowQuickView(true);
|
|
191
|
+
}}
|
|
192
|
+
className="absolute top-2 left-2 p-2 rounded-full bg-white/90 backdrop-blur-sm hover:bg-white shadow-md opacity-0 group-hover:opacity-100 transition-all"
|
|
193
|
+
>
|
|
194
|
+
<Eye className="size-4 text-[#2B4B7C]" />
|
|
195
|
+
</button>
|
|
191
196
|
|
|
192
197
|
{product.inventoryCount === 0 && (
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
</
|
|
198
|
+
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center">
|
|
199
|
+
<div className="bg-white rounded-full px-4 py-2">
|
|
200
|
+
<span className="font-['Poppins',sans-serif] font-bold text-[11px] text-[#2B4B7C] uppercase">
|
|
201
|
+
Out of Stock
|
|
202
|
+
</span>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
200
205
|
)}
|
|
201
206
|
</div>
|
|
202
207
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
208
|
+
<div className="p-4 flex-1 flex flex-col">
|
|
209
|
+
{/* Product Info */}
|
|
210
|
+
<div className="p-0 flex-1 flex flex-col">
|
|
211
|
+
{/* ALL Status Badges */}
|
|
212
|
+
<div className="flex items-center gap-1 mb-2 flex-wrap">
|
|
213
|
+
{product.isDiscounted && (
|
|
214
|
+
<span className="bg-[#E67E50] text-white rounded-full px-2 py-0.5 flex items-center gap-1">
|
|
215
|
+
<span className="font-['Poppins',sans-serif] font-bold text-[8px] uppercase">-{product.discountAmount}%</span>
|
|
216
|
+
</span>
|
|
217
|
+
)}
|
|
218
|
+
{/* {product.dealOfWeek && (
|
|
219
|
+
<span className="bg-gradient-to-r from-[#E67E50] to-[#d66f45] text-white rounded-full px-2 py-0.5 flex items-center gap-1">
|
|
220
|
+
<TrendingUp className="size-2.5" />
|
|
221
|
+
<span className="font-['Poppins',sans-serif] font-semibold text-[8px] uppercase">Deal</span>
|
|
222
|
+
</span>
|
|
223
|
+
)}
|
|
224
|
+
{product.bestseller && !product.dealOfWeek && (
|
|
225
|
+
<span className="bg-[#2B4B7C] text-white rounded-full px-2 py-0.5 flex items-center gap-1">
|
|
226
|
+
<TrendingUp className="size-2.5" />
|
|
227
|
+
<span className="font-['Poppins',sans-serif] font-semibold text-[8px] uppercase">Best</span>
|
|
228
|
+
</span>
|
|
229
|
+
)}
|
|
230
|
+
{product.newArrival && (
|
|
231
|
+
<span className="bg-[#5B9BD5] text-white rounded-full px-2 py-0.5 flex items-center gap-1">
|
|
232
|
+
<Sparkles className="size-2.5" />
|
|
233
|
+
<span className="font-['Poppins',sans-serif] font-semibold text-[8px] uppercase">New</span>
|
|
234
|
+
</span>
|
|
235
|
+
)} */}
|
|
236
|
+
</div>
|
|
237
|
+
<div className="mb-1">
|
|
238
|
+
<p className="font-['Poppins',sans-serif] text-xs text-[#5B9BD5] uppercase tracking-wide font-medium">
|
|
239
|
+
{product.brand}
|
|
240
|
+
</p>
|
|
241
|
+
</div>
|
|
216
242
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
243
|
+
<h3 className="text-sm font-['Poppins',sans-serif] font-semibold text-[#2B4B7C] mb-3">
|
|
244
|
+
{displayName}
|
|
245
|
+
</h3>
|
|
246
|
+
{/* Rating */}
|
|
247
|
+
<div className="flex items-center gap-1.5 mb-2">
|
|
248
|
+
<div className="flex items-center gap-0.5">
|
|
249
|
+
{[...Array(5)].map((_, i) => (
|
|
250
|
+
<Star
|
|
251
|
+
key={i}
|
|
252
|
+
className={`size-4 ${i < Math.floor(product.rating ? product.rating : 0)
|
|
253
|
+
? 'text-[#E67E50] fill-[#E67E50]'
|
|
254
|
+
: 'text-gray-300'
|
|
255
|
+
}`}
|
|
256
|
+
/>
|
|
257
|
+
))}
|
|
258
|
+
</div>
|
|
259
|
+
<span className="font-['Poppins',sans-serif] text-[10px] text-[#676c80]">
|
|
260
|
+
({product.reviews?.length || 0})
|
|
261
|
+
</span>
|
|
262
|
+
</div>
|
|
225
263
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
264
|
+
{/* Price */}
|
|
265
|
+
<div className="flex items-center gap-1.5 mb-3">
|
|
266
|
+
<span className="font-['Poppins',sans-serif] font-bold text-md text-primary-600">
|
|
267
|
+
${product.finalPrice.toFixed(2)}
|
|
268
|
+
</span>
|
|
269
|
+
{product.isDiscounted && (
|
|
270
|
+
<span className="font-['Poppins',sans-serif] text-sm text-[#676c80] line-through">
|
|
271
|
+
${formatPrice(product.priceBeforeDiscount)}
|
|
272
|
+
</span>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
233
275
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
276
|
+
{/* Variant Image Swatches */}
|
|
277
|
+
{variantImages.length > 0 && (
|
|
278
|
+
<div className="flex items-center gap-1.5 mb-3">
|
|
279
|
+
{variantImages.map((variant, index) => (
|
|
280
|
+
<button
|
|
281
|
+
key={variant.variantId || index}
|
|
282
|
+
type="button"
|
|
283
|
+
onClick={(e) => {
|
|
284
|
+
e.stopPropagation();
|
|
285
|
+
// Toggle: if clicking the same variant, deselect it
|
|
286
|
+
if (selectedVariantId === variant.variantId) {
|
|
287
|
+
setSelectedVariantImage(null);
|
|
288
|
+
setSelectedVariantId(null);
|
|
289
|
+
} else {
|
|
290
|
+
setSelectedVariantImage(variant.image);
|
|
291
|
+
setSelectedVariantId(variant.variantId || null);
|
|
292
|
+
}
|
|
293
|
+
setIsImageLoaded(false);
|
|
294
|
+
}}
|
|
295
|
+
className={`relative w-8 h-8 rounded-full overflow-hidden border-2 transition-all ${selectedVariantId === variant.variantId
|
|
296
|
+
? 'border-primary-500 ring-2 ring-primary-200'
|
|
297
|
+
: 'border-gray-200 hover:border-primary-300'
|
|
298
|
+
}`}
|
|
299
|
+
aria-label={`Select ${variant.color || 'variant'} color`}
|
|
300
|
+
>
|
|
301
|
+
<Image
|
|
302
|
+
src={variant.image}
|
|
303
|
+
alt={variant.color || `Variant ${index + 1}`}
|
|
304
|
+
fill
|
|
305
|
+
className="object-cover"
|
|
306
|
+
sizes="32px"
|
|
307
|
+
/>
|
|
308
|
+
</button>
|
|
309
|
+
))}
|
|
310
|
+
</div>
|
|
311
|
+
)}
|
|
312
|
+
</div>
|
|
313
|
+
<button
|
|
314
|
+
type="button"
|
|
315
|
+
onClick={async (e) => {
|
|
316
|
+
e.stopPropagation();
|
|
317
|
+
if (!selectedVariantId && variantImages.length > 0) {
|
|
318
|
+
notification.error(
|
|
319
|
+
'Select a variant',
|
|
320
|
+
'Please choose a variant before adding this item to your cart.'
|
|
321
|
+
);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
await addToCart(
|
|
326
|
+
product._id || product.id,
|
|
327
|
+
1,
|
|
328
|
+
selectedVariantId || undefined
|
|
329
|
+
);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error('Failed to add to cart', error);
|
|
332
|
+
}
|
|
333
|
+
}}
|
|
334
|
+
disabled={isAddingToCart || (variantImages.length > 0 && !selectedVariantId)}
|
|
335
|
+
className="w-full font-['Poppins',sans-serif] font-medium text-[11px] px-3 py-2 rounded-full bg-[#5B9BD5] text-white hover:bg-[#4a8ac4] hover:shadow-lg transition-all duration-300 flex items-center justify-center gap-1.5 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
336
|
+
>
|
|
337
|
+
<ShoppingCart className="h-4 w-4" />
|
|
338
|
+
{selectedVariant?.inventoryCount === 0 ? 'Out of Stock' : 'Add to Cart'}
|
|
339
|
+
</button>
|
|
239
340
|
</div>
|
|
240
341
|
|
|
241
|
-
{/* Price */}
|
|
242
|
-
<div className="flex items-baseline gap-2">
|
|
342
|
+
{/* prev Price */}
|
|
343
|
+
{/* <div className="flex items-baseline gap-2">
|
|
243
344
|
<div className="flex flex-col">
|
|
244
345
|
<span className="text-2xl font-bold text-gray-900">
|
|
245
346
|
{formatPrice(product.finalPrice)}
|
|
@@ -255,83 +356,19 @@ export function ProductCard({
|
|
|
255
356
|
{formatPrice(product.priceBeforeDiscount)}
|
|
256
357
|
</span>
|
|
257
358
|
)}
|
|
258
|
-
</div>
|
|
359
|
+
</div> */}
|
|
259
360
|
|
|
260
|
-
{/* Variant Image Swatches */}
|
|
261
|
-
{variantImages.length > 0 && (
|
|
262
|
-
<div className="flex items-center gap-2 mt-3">
|
|
263
|
-
{variantImages.map((variant, index) => (
|
|
264
|
-
<button
|
|
265
|
-
key={variant.variantId || index}
|
|
266
|
-
type="button"
|
|
267
|
-
onClick={(e) => {
|
|
268
|
-
e.stopPropagation();
|
|
269
|
-
// Toggle: if clicking the same variant, deselect it
|
|
270
|
-
if (selectedVariantId === variant.variantId) {
|
|
271
|
-
setSelectedVariantImage(null);
|
|
272
|
-
setSelectedVariantId(null);
|
|
273
|
-
} else {
|
|
274
|
-
setSelectedVariantImage(variant.image);
|
|
275
|
-
setSelectedVariantId(variant.variantId || null);
|
|
276
|
-
}
|
|
277
|
-
setIsImageLoaded(false);
|
|
278
|
-
}}
|
|
279
|
-
className={`relative w-8 h-8 rounded-full overflow-hidden border-2 transition-all ${
|
|
280
|
-
selectedVariantId === variant.variantId
|
|
281
|
-
? 'border-primary-500 ring-2 ring-primary-200'
|
|
282
|
-
: 'border-gray-200 hover:border-primary-300'
|
|
283
|
-
}`}
|
|
284
|
-
aria-label={`Select ${variant.color || 'variant'} color`}
|
|
285
|
-
>
|
|
286
|
-
<Image
|
|
287
|
-
src={variant.image}
|
|
288
|
-
alt={variant.color || `Variant ${index + 1}`}
|
|
289
|
-
fill
|
|
290
|
-
className="object-cover"
|
|
291
|
-
sizes="32px"
|
|
292
|
-
/>
|
|
293
|
-
</button>
|
|
294
|
-
))}
|
|
295
|
-
</div>
|
|
296
|
-
)}
|
|
297
|
-
</div>
|
|
298
|
-
<div className="mt-auto p-4 pt-0">
|
|
299
|
-
<button
|
|
300
|
-
type="button"
|
|
301
|
-
onClick={async (e) => {
|
|
302
|
-
e.stopPropagation();
|
|
303
|
-
if (!selectedVariantId && variantImages.length > 0) {
|
|
304
|
-
toast.error('Please select a variant');
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
try {
|
|
308
|
-
await addToCart(
|
|
309
|
-
product._id || product.id,
|
|
310
|
-
1,
|
|
311
|
-
selectedVariantId || undefined
|
|
312
|
-
);
|
|
313
|
-
} catch (error) {
|
|
314
|
-
console.error('Failed to add to cart', error);
|
|
315
|
-
}
|
|
316
|
-
}}
|
|
317
|
-
disabled={isAddingToCart || (variantImages.length > 0 && !selectedVariantId)}
|
|
318
|
-
className="w-full flex items-center justify-center gap-2 rounded-full px-3 py-2.5 text-sm font-medium bg-primary-400 hover:bg-primary-500 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
319
|
-
>
|
|
320
|
-
<ShoppingCart className="h-4 w-4" />
|
|
321
|
-
Add to Cart
|
|
322
|
-
</button>
|
|
323
361
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
</motion.article>
|
|
362
|
+
</motion.div>
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
{showQuickView && (
|
|
366
|
+
<QuickViewModal
|
|
367
|
+
product={product}
|
|
368
|
+
onClose={() => setShowQuickView(false)}
|
|
369
|
+
onNavigateToProduct={() => handleCardClick}
|
|
370
|
+
/>
|
|
371
|
+
)}
|
|
372
|
+
</>
|
|
336
373
|
);
|
|
337
374
|
}
|