hey-pharmacist-ecommerce 1.1.13 → 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.
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
@@ -17,6 +17,8 @@ import {
17
17
  Sparkles,
18
18
  Star,
19
19
  Truck,
20
+ ChevronLeft,
21
+ RotateCcw
20
22
  } from 'lucide-react';
21
23
  import Link from 'next/link';
22
24
  import Image from 'next/image';
@@ -26,7 +28,7 @@ import { ProductCard } from '@/components/ProductCard';
26
28
  import { useProduct } from '@/hooks/useProducts';
27
29
  import { useCart } from '@/providers/CartProvider';
28
30
  import { formatDate, formatPrice } from '@/lib/utils/format';
29
- import { toast } from 'sonner';
31
+ import { useNotification } from '@/providers/NotificationProvider';
30
32
  import { useRouter } from 'next/navigation';
31
33
  import { ProductVariantsApi } from '@/lib/Apis/apis/product-variants-api';
32
34
  import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
@@ -56,6 +58,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
56
58
  const { buildPath } = useBasePath();
57
59
  const { product: productData, isLoading } = useProduct(productId);
58
60
  const { addToCart } = useCart();
61
+ const notification = useNotification();
59
62
 
60
63
  // State declarations
61
64
  const [selectedVariant, setSelectedVariant] = useState<ProductVariant | null>(null);
@@ -65,6 +68,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
65
68
  const [relatedProducts, setRelatedProducts] = useState<any[]>([]);
66
69
  const [initialProductData, setInitialProductData] = useState<any>(null);
67
70
  const [activeImageIndex, setActiveImageIndex] = useState(0);
71
+ const [activeTab, setActiveTab] = useState<'description' | 'reviews'>('description');
68
72
 
69
73
 
70
74
  useEffect(() => {
@@ -72,6 +76,9 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
72
76
  }, [selectedVariant]);
73
77
 
74
78
  const product = useMemo(() => {
79
+ console.log('productData', productData);
80
+ console.log('selectedVariant', selectedVariant);
81
+ console.log('initialProductData', initialProductData);
75
82
  if (initialProductData && !productData) {
76
83
  return initialProductData;
77
84
  }
@@ -221,12 +228,16 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
221
228
  quantity,
222
229
  selectedVariant.id
223
230
  );
224
- toast.success('Added to cart', {
225
- description: `${quantity} × ${product.name}${selectedVariant.name ? ` (${selectedVariant.name})` : ''}`,
226
- });
231
+ notification.success(
232
+ 'Added to cart',
233
+ `${quantity} × ${product.name}${selectedVariant.name ? ` (${selectedVariant.name})` : ''} added to your cart.`
234
+ );
227
235
  } catch (error) {
228
236
  console.error('Failed to add to cart', error);
229
- toast.error('Could not add to cart. Please try again.');
237
+ notification.error(
238
+ 'Could not add to cart',
239
+ 'Something went wrong while adding this item. Please try again.'
240
+ );
230
241
  } finally {
231
242
  setIsAddingToCart(false);
232
243
  }
@@ -247,16 +258,25 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
247
258
  try {
248
259
  if (isFavorited) {
249
260
  await removeFromWishlist(product.id);
250
- toast.info('Removed from saved items');
261
+ notification.info(
262
+ 'Removed from saved items',
263
+ `${product.name} was removed from your saved items.`
264
+ );
251
265
  } else {
252
266
  await addToWishlist(product);
253
- toast.success('Saved to your favorites');
267
+ notification.success(
268
+ 'Saved to favorites',
269
+ `${product.name} was added to your favorites.`
270
+ );
254
271
  }
255
272
  // Update the local state after successful API call
256
273
  setIsFavorited(!isFavorited);
257
274
  } catch (error) {
258
275
  console.error('Error updating wishlist:', error);
259
- toast.error('Failed to update wishlist');
276
+ notification.error(
277
+ 'Could not update favorites',
278
+ 'We could not update your favorites. Please try again.'
279
+ );
260
280
  }
261
281
  };
262
282
 
@@ -312,22 +332,23 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
312
332
 
313
333
 
314
334
  return (
315
- <div className="bg-white">
335
+ <div className="min-h-screen bg-white">
336
+ {/* Breadcrumb */}
337
+ <div className="bg-gray-50 border-b border-gray-200 py-4">
338
+ <div className="max-w-[1400px] mx-auto px-8 md:px-12">
339
+ <button
340
+ onClick={() => router.push(buildPath('/shop'))}
341
+ className="flex items-center gap-2 font-['Poppins',sans-serif] text-[13px] text-muted hover:text-primary transition-colors"
342
+ >
343
+ <ChevronLeft className="size-4" />
344
+ Back to Shop
345
+ </button>
346
+ </div>
347
+ </div>
316
348
 
317
- <div className="min-h-screen bg-white max-w-7xl mx-auto">
349
+ <section className="py-12">
318
350
  <div className="relative pb-20 pt-8">
319
351
  <div className="container mx-auto px-4">
320
- {/* Back Button */}
321
- <div className="mb-6">
322
- <Button
323
- variant="ghost"
324
- className="text-slate-600 hover:text-slate-900 hover:bg-slate-100"
325
- onClick={() => router.push(buildPath('/shop'))}
326
- >
327
- <ArrowLeft className="h-4 w-4 mr-2" />
328
- Back to Shop
329
- </Button>
330
- </div>
331
352
  <div className="grid gap-10 lg:grid-cols-[minmax(0,50fr)_minmax(0,50fr)]">
332
353
  <div className="space-y-10">
333
354
  <section className="rounded-3xl ">
@@ -358,22 +379,30 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
358
379
  className="object-contain"
359
380
  />
360
381
  ) : null}
361
- {discount > 0 && (
362
- <Badge
363
- variant="danger"
364
- className="absolute left-6 top-6 shadow-lg shadow-red-500/20"
365
- >
366
- Save {discount}%
367
- </Badge>
368
- )}
369
- {!variantInStock && (
370
- <Badge
371
- variant="secondary"
372
- className="absolute right-6 top-6 bg-white/90 text-slate-700 shadow-lg border-slate-200"
373
- >
374
- Out of Stock
375
- </Badge>
376
- )}
382
+
383
+ <div className="absolute top-6 left-6 flex flex-col gap-3">
384
+ {product.salePrice && (
385
+ <div className="bg-[#E67E50] text-white rounded-full px-4 py-2">
386
+ <span className="font-['Poppins',sans-serif] font-bold text-[12px] uppercase tracking-wide">
387
+ Save {product.discount}%
388
+ </span>
389
+ </div>
390
+ )}
391
+ {/* {product.bestseller && (
392
+ <div className="bg-secondary text-white rounded-full px-4 py-2">
393
+ <span className="font-['Poppins',sans-serif] font-semibold text-[11px] uppercase tracking-wide">
394
+ Bestseller
395
+ </span>
396
+ </div>
397
+ )}
398
+ {product.newArrival && (
399
+ <div className="bg-primary text-white rounded-full px-4 py-2">
400
+ <span className="font-['Poppins',sans-serif] font-semibold text-[11px] uppercase tracking-wide">
401
+ New Arrival
402
+ </span>
403
+ </div>
404
+ )} */}
405
+ </div>
377
406
  </motion.div>
378
407
 
379
408
  {/* Variant Photos Below Main Photo */}
@@ -385,8 +414,8 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
385
414
  type="button"
386
415
  onClick={() => setActiveImageIndex(index)}
387
416
  className={`relative aspect-square overflow-hidden rounded-lg border-2 transition-all ${activeImageIndex === index
388
- ? 'border-primary-500 ring-2 ring-primary-200 ring-offset-2 shadow-md'
389
- : 'border-slate-200 hover:border-primary-300'
417
+ ? 'border-primary/50 ring-2 ring-primary/80 ring-offset-2 shadow-md'
418
+ : 'border-slate-200 hover:border-primary/50'
390
419
  }`}
391
420
  >
392
421
  <Image
@@ -403,50 +432,104 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
403
432
  )}
404
433
  </div>
405
434
  </section>
406
-
407
-
408
-
409
-
410
435
  </div>
411
436
 
412
437
 
413
438
  <aside className="space-y-6 lg:sticky lg:top-24">
414
- <div className="">
415
439
  {/* Category Path */}
416
- {product.parentCategories && product.parentCategories.length > 0 && (
417
- <div className="mb-4 text-sm text-slate-600">
418
- {product.parentCategories.map((cat: Category, index: number) => (
419
- <span key={cat.id || index}>
420
- {cat.name}
421
- {index < product.parentCategories.length - 1 && ' • '}
440
+ <div className="mb-4">
441
+ <p className="font-['Poppins',sans-serif] text-[12px] text-primary uppercase tracking-wide font-medium mb-2">
442
+ {product.brand} • {product.parentCategories?.[0].name}
443
+ </p>
444
+ <h1 className="text-3xl font-['Poppins',sans-serif] font-semibold text-secondary tracking-[-1.5px] mb-3">
445
+ {product.name}
446
+ </h1>
447
+ {/* Rating */}
448
+ <div className="flex items-center gap-3">
449
+ <div className="flex items-center gap-1">
450
+ {[...Array(5)].map((_, i) => (
451
+ <Star
452
+ key={i}
453
+ className={`size-4 ${
454
+ i < Math.floor(product.rating)
455
+ ? 'text-[#E67E50] fill-[#E67E50]'
456
+ : 'text-gray-300'
457
+ }`}
458
+ />
459
+ ))}
460
+ </div>
461
+ <span className="font-['Poppins',sans-serif] text-[14px] text-muted">
462
+ {product.rating} ({product.reviewCount? product.reviewCount : 0} reviews)
463
+ </span>
464
+ </div>
465
+
466
+ <div className="flex items-center gap-3 mb-6 pb-6 border-b-2 border-gray-100">
467
+ <span className="font-['Poppins',sans-serif] font-bold text-[40px] text-[#E67E50]">
468
+ {selectedVariant ? formatPrice(selectedVariant.finalPrice) : formatPrice(product.finalPrice || product.price || 0)}
469
+ </span>
470
+ {variantComparePrice && variantComparePrice > variantPrice && (
471
+ <>
472
+ <span className="font-['Poppins',sans-serif] text-[24px] text-muted line-through">
473
+ ${product.price.toFixed(2)}
474
+ </span>
475
+ <div className="px-3 py-1 rounded-full bg-[#E67E50]/10">
476
+ <span className="font-['Poppins',sans-serif] font-semibold text-[13px] text-[#E67E50]">
477
+ Save ${formatPrice(variantComparePrice)}
422
478
  </span>
423
- ))}
424
- {product.parentSubCategories && product.parentSubCategories.length > 0 && (
425
- <>
426
- {' • '}
427
- {product.parentSubCategories.map((subCat: Category, index: number) => (
428
- <span key={subCat.id || index}>
429
- {subCat.name}
430
- {index < product.parentSubCategories.length - 1 && ' • '}
479
+ </div>
480
+ </>
481
+ )}
482
+ </div>
483
+
484
+ {/* Stock Status */}
485
+ {selectedVariant && (
486
+ <div className="mb-6">
487
+ <div className="flex items-center gap-2 mb-2">
488
+ {selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK ||
489
+ selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.LOWSTOCK ? (
490
+ <>
491
+ <div className="size-3 rounded-full bg-red-500" />
492
+ <span className="font-['Poppins',sans-serif] text-[13px] text-red-600 font-medium">
493
+ Out of Stock
431
494
  </span>
432
- ))}
495
+ </>
496
+ ) : selectedVariant.inventoryCount <= 10 ? (
497
+ <>
498
+ <div className="size-3 rounded-full bg-orange-500 animate-pulse" />
499
+ <span className="font-['Poppins',sans-serif] text-[13px] text-orange-600 font-medium">
500
+ Only {selectedVariant.inventoryCount} left in stock - Order soon!
501
+ </span>
502
+ </>
503
+ ) : (
504
+ <>
505
+ <div className="size-3 rounded-full bg-green-500" />
506
+ <span className="font-['Poppins',sans-serif] text-[13px] text-green-600 font-medium">
507
+ In Stock
508
+ </span>
433
509
  </>
434
510
  )}
511
+
435
512
  </div>
513
+ <p className="font-['Poppins',sans-serif] text-[12px] text-muted">
514
+ SKU: {selectedVariant?.sku}
515
+ </p>
516
+ </div>
436
517
  )}
437
518
 
438
- {/* Product Name */}
439
- <h1 className="text-2xl font-bold text-slate-900 mb-6">
440
- {product.name}
441
- </h1>
519
+ {/* Description */}
520
+ {product.description && (
521
+ <p className="font-['Poppins',sans-serif] text-[14px] text-muted leading-[1.7] mb-8">
522
+ <div dangerouslySetInnerHTML={{ __html: product.description }} />
523
+ </p>
524
+ )}
442
525
 
443
526
  {/* Variant Selector with Images */}
444
527
  {product?.productVariants && product.productVariants.length > 0 && (
445
528
  <div className="mb-6">
446
- <label className="block text-sm font-medium text-slate-700 mb-3">
529
+ <h3 className="font-['Poppins',sans-serif] font-semibold text-[14px] text-secondary mb-3">
447
530
  Select Variant
448
- </label>
449
- <div className="space-y-3">
531
+ </h3>
532
+ <div className="flex flex-wrap gap-3">
450
533
  {product.productVariants.map((variant: ProductVariant) => {
451
534
  const isSelected = selectedVariant?.id === variant.id;
452
535
  const variantImage = variant.productMedia?.[0]?.file
@@ -458,13 +541,13 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
458
541
  key={variant.id}
459
542
  type="button"
460
543
  onClick={() => handleVariantSelect(variant)}
461
- className={`flex w-full items-center gap-3 rounded-lg border-2 px-4 py-3 text-left transition ${isSelected
462
- ? 'border-primary-500 bg-primary-50'
463
- : 'border-slate-200 bg-white hover:border-primary-300'
464
- }`}
544
+ className={`flex items-start gap-3 px-4 py-2.5 rounded-xl border-2 transition-all ${
545
+ isSelected
546
+ ? 'border-primary bg-primary/5'
547
+ : 'border-gray-200 hover:border-primary/50'
548
+ }`}
465
549
  >
466
- <div className={`relative h-12 w-12 flex-shrink-0 overflow-hidden rounded-full border-2 ${isSelected ? 'border-primary-500' : 'border-slate-200'
467
- }`}>
550
+ <div className={`relative h-12 w-12 flex-shrink-0 overflow-hidden rounded-full border-2 ${isSelected ? 'border-primary' : 'border-slate-200'}`}>
468
551
  <Image
469
552
  src={variantImage}
470
553
  alt={variant.name || 'Variant image'}
@@ -474,15 +557,15 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
474
557
  />
475
558
  </div>
476
559
  <div className="flex-1">
477
- <p className={`text-sm font-medium ${isSelected ? 'text-primary-700' : 'text-slate-900'}`}>
560
+ <p className={`text-start text-sm font-medium ${isSelected ? 'text-secondary' : 'text-slate-900'}`}>
478
561
  {variant.name}
479
562
  </p>
480
563
  {variant.sku && (
481
- <p className="text-xs text-slate-500 mt-0.5">SKU: {variant.sku}</p>
564
+ <p className="text-start text-xs text-slate-500 mt-0.5">SKU: {variant.sku}</p>
482
565
  )}
483
566
  </div>
484
567
  {isSelected && (
485
- <Check className="h-5 w-5 text-primary-600 flex-shrink-0" />
568
+ <Check className="h-5 w-5 text-secondary flex-shrink-0" />
486
569
  )}
487
570
  </button>
488
571
  );
@@ -491,79 +574,34 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
491
574
  </div>
492
575
  )}
493
576
 
494
- {/* Price */}
495
- <div className="flex items-baseline gap-3 mb-6">
496
- <p className="text-3xl font-bold text-orange-600">
497
- {selectedVariant ? formatPrice(selectedVariant.finalPrice) : formatPrice(product.finalPrice || product.price || 0)}
498
- </p>
499
- {variantComparePrice && variantComparePrice > variantPrice && (
500
- <p className="text-base text-slate-400 line-through">
501
- {formatPrice(variantComparePrice)}
502
- </p>
503
- )}
504
- {discount > 0 && (
505
- <Badge variant="danger" size="sm">
506
- -{discount}%
507
- </Badge>
508
- )}
509
- </div>
510
-
511
- {/* Stock Status */}
512
- {selectedVariant && (
513
- <div className="mb-4 text-sm">
514
- {selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK ||
515
- selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.LOWSTOCK ? (
516
- <div className="text-red-600 font-medium">Out of Stock</div>
517
- ) : (
518
- <div className="text-orange-600 font-medium">
519
- Only {selectedVariant.inventoryCount || product.inventoryCount || 0} left in stock - Order soon!
520
- </div>
521
- )}
522
- </div>
523
- )}
524
-
525
- {/* SKU */}
526
- {variantSku && variantSku !== 'N/A' && (
527
- <div className="mb-4 text-sm text-slate-600">
528
- <span className="font-medium">SKU:</span> {variantSku}
529
- </div>
530
- )}
531
-
532
- {/* Description */}
533
- {product.description && (
534
- <p className="mb-6 text-sm text-slate-600">
535
- {product.description.replace(/<[^>]*>/g, '').substring(0, 150)}
536
- {product.description.length > 150 && '...'}
537
- </p>
538
- )}
539
577
 
540
578
  {/* Quantity Selector */}
541
- <div className="mb-6">
542
- <label className="block text-sm font-medium text-slate-700 mb-2">Quantity</label>
579
+ <div className="mb-8">
580
+ <h3 className="font-['Poppins',sans-serif] font-semibold text-[14px] text-secondary mb-3">
581
+ Quantity
582
+ </h3>
543
583
  <div className="flex items-center gap-3">
544
- <div className="flex items-center rounded-lg border border-slate-200 bg-white">
584
+ <div className="flex items-center gap-4 bg-gray-100 rounded-full px-6 py-3">
585
+
545
586
  <button
546
587
  type="button"
547
588
  onClick={() => setQuantity((current) => Math.max(1, current - 1))}
548
- className="px-3 py-2 hover:bg-slate-50"
549
- aria-label="Decrease quantity"
550
- >
551
- <Minus className="h-4 w-4 text-slate-600" />
552
- </button>
553
- <span className="w-12 text-center text-sm font-semibold text-slate-900">
554
- {quantity}
555
- </span>
556
- <button
557
- type="button"
558
- onClick={() => setQuantity((current) => {
559
- const maxQty = selectedVariant?.inventoryCount || product.inventoryCount || 999;
560
- return Math.min(maxQty, current + 1);
561
- })}
562
- className="px-3 py-2 hover:bg-slate-50"
589
+
590
+ className="font-['Poppins',sans-serif] font-bold text-[18px] text-secondary hover:text-primary transition-colors"
563
591
  aria-label="Increase quantity"
564
592
  >
565
- <Plus className="h-4 w-4 text-slate-600" />
593
+ -
566
594
  </button>
595
+ <span className="font-['Poppins',sans-serif] font-semibold text-[16px] text-secondary min-w-[30px] text-center">
596
+ {quantity}
597
+ </span>
598
+ <button
599
+ onClick={() => setQuantity(Math.min(selectedVariant?.inventoryCount || product.inventoryCount || 999, quantity + 1))}
600
+ disabled={quantity >= (selectedVariant?.inventoryCount || product.inventoryCount || 0)}
601
+ className="font-['Poppins',sans-serif] font-bold text-[18px] text-secondary hover:text-primary transition-colors disabled:opacity-50"
602
+ >
603
+ +
604
+ </button>
567
605
  </div>
568
606
  {selectedVariant && (
569
607
  <span className="text-sm text-slate-500">
@@ -574,128 +612,190 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
574
612
  </div>
575
613
 
576
614
  {/* Action Buttons */}
577
- <div className="flex flex-col gap-4">
578
- <Button
579
- size="md"
580
- className="w-full bg-orange-500 hover:bg-orange-600 text-white py-2.5"
615
+ <div className="flex flex-col sm:flex-row gap-3 mb-8">
616
+ <button
617
+ className="flex-1 font-['Poppins',sans-serif] font-medium text-[14px] px-8 py-4 rounded-full transition-all duration-300 flex items-center justify-center gap-3 bg-[#E67E50] text-white hover:bg-[#d66f45] hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed"
581
618
  onClick={handleAddToCart}
582
- isLoading={isAddingToCart}
583
619
  disabled={!selectedVariant || selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK}
584
620
  >
585
- <ShoppingCart className="h-4 w-4" />
586
- {!selectedVariant
587
- ? 'Select a variant'
588
- : selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK
589
- ? 'Out of Stock'
590
- : 'Add to Cart'}
591
- </Button>
592
- <Button
593
- size="md"
594
- variant="outline"
595
- className="w-full py-2.5"
596
- onClick={handleToggleFavorite}
597
- >
598
- <Heart
599
- className={`h-4 w-4 mr-2 ${isFavorited ? 'fill-red-500 text-red-500' : 'text-slate-500'}`}
600
- />
601
- {isFavorited ? 'Saved' : 'Save for later'}
602
- </Button>
621
+ {isAddingToCart ? (
622
+ <>
623
+ <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">
624
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
625
+ <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>
626
+ </svg>
627
+ Loading...
628
+ </>
629
+ ) : (
630
+ <>
631
+ <ShoppingCart className="h-4 w-4" />
632
+ {!selectedVariant
633
+ ? 'Select a variant'
634
+ : selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK
635
+ ? 'Out of Stock'
636
+ : 'Add to Cart'}
637
+
638
+ </>
639
+ )
640
+ }
641
+ </button>
642
+ <button className="sm:w-auto px-6 py-4 rounded-full border-2 border-primary hover:bg-primary/5 transition-all flex items-center justify-center"
643
+ onClick={handleToggleFavorite}>
644
+ <Heart className={`h-4 w-4 ${isFavorited ? 'fill-red-500 text-red-500' : 'text-primary'}`}/>
645
+ </button>
603
646
  </div>
604
647
 
605
648
  </div>
606
649
 
607
- <div className="rounded-3xl border border-primary-100 bg-primary-50/70 p-6 text-sm text-primary-700 shadow-sm">
608
- <p className="font-semibold uppercase tracking-[0.25em]">Need advice?</p>
609
- <p className="mt-2 leading-relaxed">
610
- Chat with a pharmacist in real time before completing your purchase. We will help
611
- you choose supporting supplements and answer dosing questions.
612
- </p>
650
+ {/* Benefits */}
651
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 p-6 bg-gradient-to-br from-[#5B9BD5]/5 to-[#2B4B7C]/5 rounded-[24px]">
652
+ <div className="flex items-start gap-3">
653
+ <div className="size-10 rounded-full bg-white flex items-center justify-center shrink-0">
654
+ <Truck className="size-5 text-primary" />
655
+ </div>
656
+ <div>
657
+ <p className="font-['Poppins',sans-serif] font-semibold text-[12px] text-secondary mb-1">
658
+ Free Shipping
659
+ </p>
660
+ <p className="font-['Poppins',sans-serif] text-[11px] text-muted">
661
+ On all orders
662
+ </p>
663
+ </div>
613
664
  </div>
614
- </aside>
615
- </div>
616
- <section className="mt-10">
617
- <div className="rounded-3xl border border-slate-100 bg-white p-8 shadow-sm">
618
- <div className="flex flex-wrap items-center gap-4 pb-6">
619
- <div className="flex items-center gap-1 text-amber-500">
620
- {Array.from({ length: 5 }).map((_, index) => (
621
- <Star key={index} className="h-4 w-4 fill-current" />
622
- ))}
665
+ <div className="flex items-start gap-3">
666
+ <div className="size-10 rounded-full bg-white flex items-center justify-center shrink-0">
667
+ <RotateCcw className="size-5 text-primary" />
668
+ </div>
669
+ <div>
670
+ <p className="font-['Poppins',sans-serif] font-semibold text-[12px] text-secondary mb-1">
671
+ Easy Returns
672
+ </p>
673
+ <p className="font-['Poppins',sans-serif] text-[11px] text-muted">
674
+ 30-day return policy
675
+ </p>
623
676
  </div>
624
- <span className="text-sm font-medium text-slate-500">
625
- Rated 4.8 • Patients love the results
626
- </span>
627
677
  </div>
628
- <div className="space-y-8">
629
- {product.description && (
630
- <div className="space-y-3">
631
- <h3 className="text-lg font-semibold text-slate-900">Description</h3>
632
- <p className="text-base leading-relaxed text-slate-600" dangerouslySetInnerHTML={{ __html: product.description }} />
633
- </div>
634
- )}
678
+ <div className="flex items-start gap-3">
679
+ <div className="size-10 rounded-full bg-white flex items-center justify-center shrink-0">
680
+ <Shield className="size-5 text-primary" />
681
+ </div>
682
+ <div>
683
+ <p className="font-['Poppins',sans-serif] font-semibold text-[12px] text-secondary mb-1">
684
+ Secure Checkout
685
+ </p>
686
+ <p className="font-['Poppins',sans-serif] text-[11px] text-muted">
687
+ Safe & protected
688
+ </p>
689
+ </div>
690
+ </div>
691
+ <div className="flex items-start gap-3">
692
+ <div className="size-10 rounded-full bg-white flex items-center justify-center shrink-0">
693
+ <Package className="size-5 text-primary" />
694
+ </div>
695
+ <div>
696
+ <p className="font-['Poppins',sans-serif] font-semibold text-[12px] text-secondary mb-1">
697
+ Quality Guaranteed
698
+ </p>
699
+ <p className="font-['Poppins',sans-serif] text-[11px] text-muted">
700
+ Premium materials
701
+ </p>
702
+ </div>
635
703
  </div>
636
704
  </div>
637
- </section>
638
-
639
-
640
-
641
- <div className="rounded-2xl border border-slate-200 bg-slate-50/60 p-5 mt-10">
642
- <p className="text- font-semibold uppercase tracking-[0.3em] text-slate-500">
643
- Product details
644
- </p>
645
- <div className="mt-3 space-y-2 text-sm text-slate-600">
646
- <p>
647
- <span className="font-medium text-slate-700">Variant:</span> {currentVariant.name}
648
- </p>
649
- <p>
650
- <span className="font-medium text-slate-700">SKU:</span> {variantSku}
651
- </p>
652
- <p>
653
- <span className="font-medium text-slate-700">Status:</span>{' '}
654
- <span className={variantInStock ? 'text-green-600' : 'text-amber-600'}>
655
- {variantInStock ? 'In Stock' : 'Out of Stock'}
656
- </span>
657
- </p>
658
- <p>
659
- <span className="font-medium text-slate-700">Last updated:</span>{' '}
660
- {lastUpdatedLabel}
661
- </p>
662
- <p>
663
- <span className="font-medium text-slate-700">Ships from:</span> Local
664
- pharmacy distribution center
665
- </p>
666
- </div>
705
+ </aside>
667
706
  </div>
668
- {relatedProducts.length > 0 && (
669
- <section className="mt-20">
670
- <div className="flex items-center justify-between">
707
+
708
+
709
+ {/* Product Details Tabs */}
710
+ <div className="mb-16">
711
+ <div className="flex items-center gap-2 mb-8 border-b-2 border-gray-100">
712
+ {(['description', 'reviews'] as const).map((tab) => (
713
+ <button
714
+ key={tab}
715
+ onClick={() => setActiveTab(tab)}
716
+ className={`font-['Poppins',sans-serif] font-medium text-[14px] px-6 py-4 transition-all ${
717
+ activeTab === tab
718
+ ? 'text-primary border-b-2 border-primary -mb-0.5'
719
+ : 'text-muted hover:text-primary'
720
+ }`}
721
+ >
722
+ {tab.charAt(0).toUpperCase() + tab.slice(1)}
723
+ </button>
724
+ ))}
725
+ </div>
726
+
727
+ <div className="bg-white rounded-[24px] p-8 border-2 border-gray-100">
728
+ {activeTab === 'description' && (
671
729
  <div>
672
- <h2 className="text-2xl font-semibold text-slate-900">You may also like</h2>
673
- <p className="mt-1 text-sm text-slate-500">
674
- Hand-picked recommendations that pair nicely with this product.
730
+ <h3 className="font-['Poppins',sans-serif] font-semibold text-secondary mb-4">
731
+ Product Description
732
+ </h3>
733
+ <p className="font-['Poppins',sans-serif] text-[14px] text-muted leading-[1.8] mb-4">
734
+ <div dangerouslySetInnerHTML={{ __html: product.description }} />
735
+
675
736
  </p>
737
+
738
+ <div className="mt-6">
739
+ <h4 className="font-['Poppins',sans-serif] font-semibold text-[13px] text-secondary mb-3">
740
+ Last updated:
741
+ </h4>
742
+ <p className="font-['Poppins',sans-serif] text-[14px] text-muted">
743
+ {lastUpdatedLabel}
744
+ </p>
745
+ </div>
746
+
747
+ {/* shipped from */}
748
+ <div className="mt-6">
749
+ <h4 className="font-['Poppins',sans-serif] font-semibold text-[13px] text-secondary mb-3">
750
+ Shipped from:
751
+ </h4>
752
+ <p className="font-['Poppins',sans-serif] text-[14px] text-muted">
753
+ Local pharmacy distribution center
754
+ </p>
755
+ </div>
676
756
  </div>
677
- <Link href={buildPath('/shop')} className="hidden md:inline-flex">
678
- <Button variant="ghost" className="text-primary-600">
679
- View all products
680
- </Button>
681
- </Link>
682
- </div>
683
- <div className="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
684
- {relatedProducts?.map((relatedProduct) => (
757
+ )}
758
+
759
+ {activeTab === 'reviews' && (
760
+ <div className="text-center py-8">
761
+ <Star className="size-12 text-[#E67E50] mx-auto mb-4" />
762
+ <h3 className="font-['Poppins',sans-serif] font-semibold text-secondary mb-2">
763
+ {product.rating} out of 5 stars
764
+ </h3>
765
+ <p className="font-['Poppins',sans-serif] text-[14px] text-muted">
766
+ Based on {product.reviewCount} customer reviews
767
+ </p>
768
+ </div>
769
+ )}
770
+ </div>
771
+ </div>
772
+
773
+
774
+ {/* Related Products */}
775
+ {relatedProducts.length > 0 && (
776
+ <div>
777
+ <h2 className="font-['Poppins',sans-serif] font-semibold text-secondary mb-8 text-2xl">
778
+ You May Also Like
779
+ </h2>
780
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
781
+ {relatedProducts.map(relatedProduct => (
782
+
685
783
  <ProductCard
686
- key={relatedProduct.id || relatedProduct._id}
784
+ key={relatedProduct.id}
687
785
  product={relatedProduct}
786
+ // viewMode="grid"
688
787
  onClickProduct={(item) => {
689
788
  router.push(buildPath(`/products/${item._id || item.id}`));
690
789
  }}
691
790
  />
692
791
  ))}
693
792
  </div>
694
- </section>
793
+ </div>
695
794
  )}
795
+
696
796
  </div>
697
797
  </div>
698
- </div>
798
+ </section>
699
799
  </div>
700
800
  );
701
801
  }