hey-pharmacist-ecommerce 1.1.10 → 1.1.12

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.
@@ -33,6 +33,7 @@ import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
33
33
  import { useWishlist } from '@/providers/WishlistProvider';
34
34
  import { ProductsApi, ProductVariant, ProductVariantInventoryStatusEnum } from '@/lib/Apis';
35
35
  import { useBasePath } from '@/providers/BasePathProvider';
36
+ import { Category } from '@/lib/types';
36
37
 
37
38
  const safeFormatDate = (date?: Date | string, format: 'long' | 'short' = 'long'): string => {
38
39
  if (!date) return 'N/A';
@@ -103,6 +104,18 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
103
104
  }));
104
105
  }
105
106
 
107
+ // Fallback to product media if no variant media
108
+ if (product?.productMedia?.length) {
109
+ return product.productMedia.map((media: any) => ({
110
+ src: media.file,
111
+ width: 800,
112
+ height: 1000,
113
+ alt: product?.name || 'Product image',
114
+ ...media,
115
+ url: media.file,
116
+ }));
117
+ }
118
+
106
119
  if (product?.images?.length) {
107
120
  return product.images.map((image: string) => ({
108
121
  src: image,
@@ -194,6 +207,8 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
194
207
 
195
208
  const handleVariantSelect = async (variant: ProductVariant) => {
196
209
  setSelectedVariant(variant);
210
+ // Reset to first image of the selected variant
211
+ setActiveImageIndex(0);
197
212
  };
198
213
 
199
214
  const handleAddToCart = async () => {
@@ -293,414 +308,392 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
293
308
  );
294
309
  }
295
310
 
296
- const benefitPills =
297
- product.tags && product.tags.length > 0
298
- ? product.tags.slice(0, 6)
299
- : ['Pharmacist approved', 'Gentle on daily routines', 'Backed by real customers'];
300
-
301
- const highlightCards = [
302
- {
303
- icon: ShieldCheck,
304
- title: 'Pharmacy grade assurance',
305
- description: 'Sourced from trusted suppliers and reviewed by licensed professionals.',
306
- },
307
- {
308
- icon: Truck,
309
- title: 'Fast, cold-chain ready shipping',
310
- description: 'Carefully packed and dispatched within 24 hours on business days.',
311
- },
312
- {
313
- icon: Award,
314
- title: 'Loved by patients',
315
- description: 'Average rating 4.8/5 with over 120 verified customer experiences.',
316
- },
317
- ];
318
311
 
319
312
 
320
313
 
321
314
  return (
322
- <div className="min-h-screen bg-slate-50">
323
- <section className="relative overflow-hidden bg-gradient-to-br from-[rgb(var(--header-from))] via-[rgb(var(--header-via))] to-[rgb(var(--header-to))] text-white mb-8">
324
- <div
325
- className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(255,255,255,0.35),_transparent_60%)]"
326
- aria-hidden="true"
327
- />
328
- <div className="absolute inset-0 bg-[radial-gradient(circle_at_bottom_right,_rgba(255,255,255,0.25),_transparent_55%)] opacity-70" />
329
- <div className="relative container mx-auto px-4 py-16">
330
- <div className="flex flex-col gap-6">
331
- <div className="flex items-center justify-between">
315
+ <div className="bg-white">
316
+
317
+ <div className="min-h-screen bg-white max-w-7xl mx-auto">
318
+ <div className="relative pb-20 pt-8">
319
+ <div className="container mx-auto px-4">
320
+ {/* Back Button */}
321
+ <div className="mb-6">
332
322
  <Button
333
323
  variant="ghost"
334
- className="text-white hover:bg-white/10"
324
+ className="text-slate-600 hover:text-slate-900 hover:bg-slate-100"
335
325
  onClick={() => router.push(buildPath('/shop'))}
336
326
  >
337
- <ArrowLeft className="h-5 w-5" />
338
- Continue shopping
327
+ <ArrowLeft className="h-4 w-4 mr-2" />
328
+ Back to Shop
339
329
  </Button>
340
- <div className="hidden items-center gap-3 text-sm text-white/80 md:flex">
341
- <Link href={buildPath('/')} className="transition hover:text-white">
342
- Home
343
- </Link>
344
- <ChevronRight className="h-4 w-4" />
345
- <Link href={buildPath('/shop')} className="transition hover:text-white">
346
- Shop
347
- </Link>
348
- <ChevronRight className="h-4 w-4" />
349
- <span className="truncate font-medium text-white">{product.name}</span>
350
- </div>
351
330
  </div>
352
- <motion.div
353
- initial={{ opacity: 0, y: 24 }}
354
- animate={{ opacity: 1, y: 0 }}
355
- className="max-w-3xl space-y-4"
356
- >
357
- {product.category && (
358
- <Badge
359
- variant="secondary"
360
- className="bg-white/15 text-white backdrop-blur-sm"
361
- >
362
- {product.category}
363
- </Badge>
364
- )}
365
- <h1 className="text-4xl font-bold leading-tight md:text-5xl">{product.name}</h1>
366
- <div className="flex flex-wrap items-center gap-3 text-sm text-white/80">
367
- <span className="inline-flex items-center gap-2 rounded-full bg-white/10 px-3 py-1 font-medium text-white">
368
- <Sparkles className="h-4 w-4" />
369
- Ready to ship today
370
- </span>
371
- <span className="inline-flex items-center gap-2 rounded-full bg-white/10 px-3 py-1 font-medium text-white">
372
- <Shield className="h-4 w-4" />
373
- 30-day happiness guarantee
374
- </span>
331
+ <div className="grid gap-10 lg:grid-cols-[minmax(0,50fr)_minmax(0,50fr)]">
332
+ <div className="space-y-10">
333
+ <section className="rounded-3xl ">
334
+ <div className="space-y-6">
335
+ <motion.div
336
+ key={selectedVariant?.id || 'default'}
337
+ initial={{ opacity: 0.4, scale: 0.98 }}
338
+ animate={{ opacity: 1, scale: 1 }}
339
+ transition={{ duration: 0.35 }}
340
+ className="relative overflow-hidden rounded-3xl bg-slate-100 h-[420px] md:h-[560px]"
341
+ >
342
+ {variantImages.length > 0 && variantImages[activeImageIndex] ? (
343
+ <Image
344
+ src={variantImages[activeImageIndex].src || variantImages[activeImageIndex].url || variantImages[activeImageIndex].file}
345
+ alt={currentVariant.name || product.name}
346
+ fill
347
+ priority
348
+ sizes="(max-width: 1024px) 100vw, 800px"
349
+ className="object-contain"
350
+ />
351
+ ) : product?.productMedia?.[0]?.file ? (
352
+ <Image
353
+ src={product.productMedia[0].file}
354
+ alt={product.name}
355
+ fill
356
+ priority
357
+ sizes="(max-width: 1024px) 100vw, 800px"
358
+ className="object-contain"
359
+ />
360
+ ) : 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
+ )}
377
+ </motion.div>
378
+
379
+ {/* Variant Photos Below Main Photo */}
380
+ {variantImages.length > 0 && (
381
+ <div className="grid grid-cols-8 gap-2">
382
+ {variantImages.map((image: { src: string; alt?: string }, index: number) => (
383
+ <button
384
+ key={image.src + index}
385
+ type="button"
386
+ onClick={() => setActiveImageIndex(index)}
387
+ 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'
390
+ }`}
391
+ >
392
+ <Image
393
+ src={image?.src}
394
+ alt={image.alt || `Product image ${index + 1}`}
395
+ className="h-full w-full object-cover object-center"
396
+ width={80}
397
+ height={80}
398
+ unoptimized={true}
399
+ />
400
+ </button>
401
+ ))}
402
+ </div>
403
+ )}
404
+ </div>
405
+ </section>
406
+
407
+
408
+
409
+
375
410
  </div>
376
- </motion.div>
377
- </div>
378
- </div>
379
- </section>
380
411
 
381
- <div className="relative -mt-16 pb-20">
382
- <div className="container mx-auto px-4">
383
- <div className="grid gap-10 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
384
- <div className="space-y-10">
385
- <section className="rounded-3xl border border-white bg-white/70 p-6 shadow-xl shadow-primary-100/40 backdrop-blur">
386
- <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_220px]">
387
- <motion.div
388
- key={variantImages[activeImageIndex]}
389
- initial={{ opacity: 0.4, scale: 0.98 }}
390
- animate={{ opacity: 1, scale: 1 }}
391
- transition={{ duration: 0.35 }}
392
- className="relative overflow-hidden rounded-3xl bg-slate-100 h-[420px] md:h-[560px]"
393
- >
394
- <Image
395
- src={variantImages[activeImageIndex]}
396
- alt={currentVariant.name || product.name}
397
- fill
398
- priority
399
- sizes="(max-width: 1024px) 100vw, 800px"
400
- className="object-contain"
401
- />
402
- {discount > 0 && (
403
- <Badge
404
- variant="danger"
405
- className="absolute left-6 top-6 shadow-lg shadow-red-500/20"
406
- >
407
- Save {discount}%
408
- </Badge>
409
- )}
410
- {!variantInStock && (
411
- <Badge
412
- variant="secondary"
413
- className="absolute right-6 top-6 bg-white/90 text-slate-700 shadow-lg border-slate-200"
414
- >
415
- Out of Stock
416
- </Badge>
417
- )}
418
- </motion.div>
419
412
 
420
- <div className="flex flex-col gap-4">
421
- {product?.productVariants?.length > 0 && (
422
- <div className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm">
423
- <p className="mb-2 text-sm font-semibold uppercase tracking-[0.25em] text-slate-400">
424
- Variant
425
- </p>
426
- <div className="flex flex-wrap gap-2">
427
- {product?.productVariants?.map((variant: ProductVariant, index: number) => (
413
+ <aside className="space-y-6 lg:sticky lg:top-24">
414
+ <div className="">
415
+ {/* 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 && ' • '}
422
+ </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 && ' • '}
431
+ </span>
432
+ ))}
433
+ </>
434
+ )}
435
+ </div>
436
+ )}
437
+
438
+ {/* Product Name */}
439
+ <h1 className="text-2xl font-bold text-slate-900 mb-6">
440
+ {product.name}
441
+ </h1>
442
+
443
+ {/* Variant Selector with Images */}
444
+ {product?.productVariants && product.productVariants.length > 0 && (
445
+ <div className="mb-6">
446
+ <label className="block text-sm font-medium text-slate-700 mb-3">
447
+ Select Variant
448
+ </label>
449
+ <div className="space-y-3">
450
+ {product.productVariants.map((variant: ProductVariant) => {
451
+ const isSelected = selectedVariant?.id === variant.id;
452
+ const variantImage = variant.productMedia?.[0]?.file
453
+ || product.productMedia?.[0]?.file
454
+ || product.images?.[0]
455
+ || '/placeholder-product.jpg';
456
+ return (
428
457
  <button
429
458
  key={variant.id}
430
459
  type="button"
431
460
  onClick={() => handleVariantSelect(variant)}
432
- className={`rounded-full px-4 py-2 text-sm font-medium transition ${selectedVariant?.id === variant.id
433
- ? 'bg-primary-600 text-white'
434
- : 'bg-slate-100 text-slate-700 hover:bg-slate-200'
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'
435
464
  }`}
436
465
  >
437
- {variant.name}
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
+ }`}>
468
+ <Image
469
+ src={variantImage}
470
+ alt={variant.name || 'Variant image'}
471
+ fill
472
+ className="object-cover"
473
+ sizes="48px"
474
+ />
475
+ </div>
476
+ <div className="flex-1">
477
+ <p className={`text-sm font-medium ${isSelected ? 'text-primary-700' : 'text-slate-900'}`}>
478
+ {variant.name}
479
+ </p>
480
+ {variant.sku && (
481
+ <p className="text-xs text-slate-500 mt-0.5">SKU: {variant.sku}</p>
482
+ )}
483
+ </div>
484
+ {isSelected && (
485
+ <Check className="h-5 w-5 text-primary-600 flex-shrink-0" />
486
+ )}
438
487
  </button>
439
- ))}
440
- </div>
488
+ );
489
+ })}
441
490
  </div>
491
+ </div>
492
+ )}
493
+
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>
442
508
  )}
509
+ </div>
443
510
 
444
- <div className="grid grid-cols-3 gap-3">
445
- {variantImages.map((image: { src: string; alt?: string }, index: number) => (
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
+
540
+ {/* Quantity Selector */}
541
+ <div className="mb-6">
542
+ <label className="block text-sm font-medium text-slate-700 mb-2">Quantity</label>
543
+ <div className="flex items-center gap-3">
544
+ <div className="flex items-center rounded-lg border border-slate-200 bg-white">
446
545
  <button
447
- key={image.src + index}
448
546
  type="button"
449
- onClick={() => setActiveImageIndex(index)}
450
- className={`relative aspect-square overflow-hidden rounded-2xl border transition ${activeImageIndex === index
451
- ? 'border-primary-500 shadow-lg shadow-primary-200/60'
452
- : 'border-transparent hover:border-primary-200'
453
- }`}
547
+ onClick={() => setQuantity((current) => Math.max(1, current - 1))}
548
+ className="px-3 py-2 hover:bg-slate-50"
549
+ aria-label="Decrease quantity"
454
550
  >
455
- <Image
456
- src={image.src}
457
- alt={image.alt || `Product image ${index + 1}`}
458
- className="h-full w-full object-cover object-center"
459
- width={200}
460
- height={200}
461
- unoptimized={true}
462
- />
551
+ <Minus className="h-4 w-4 text-slate-600" />
463
552
  </button>
464
- ))}
465
- </div>
466
- </div>
467
- </div>
468
- </section>
469
-
470
- <section className="grid gap-6 lg:grid-cols-3">
471
- {highlightCards.map((card) => {
472
- const Icon = card.icon;
473
- return (
474
- <div
475
- key={card.title}
476
- className="rounded-3xl border border-slate-100 bg-white p-6 shadow-sm transition hover:-translate-y-1 hover:shadow-lg"
477
- >
478
- <div className="flex items-center gap-3">
479
- <span className="rounded-2xl bg-primary-50 p-3 text-primary-600">
480
- <Icon className="h-5 w-5" />
553
+ <span className="w-12 text-center text-sm font-semibold text-slate-900">
554
+ {quantity}
481
555
  </span>
482
- <h3 className="text-base font-semibold text-slate-900">{card.title}</h3>
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"
563
+ aria-label="Increase quantity"
564
+ >
565
+ <Plus className="h-4 w-4 text-slate-600" />
566
+ </button>
483
567
  </div>
484
- <p className="mt-3 text-sm leading-relaxed text-slate-600">{card.description}</p>
485
- </div>
486
- );
487
- })}
488
- </section>
489
-
490
- <section className="grid gap-6 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
491
- <div className="rounded-3xl border border-slate-100 bg-white p-8 shadow-sm">
492
- <div className="flex flex-wrap items-center gap-4 pb-6">
493
- <div className="flex items-center gap-1 text-amber-500">
494
- {Array.from({ length: 5 }).map((_, index) => (
495
- <Star key={index} className="h-4 w-4 fill-current" />
496
- ))}
568
+ {selectedVariant && (
569
+ <span className="text-sm text-slate-500">
570
+ {selectedVariant.inventoryCount || product.inventoryCount || 0} available
571
+ </span>
572
+ )}
497
573
  </div>
498
- <span className="text-sm font-medium text-slate-500">
499
- Rated 4.8 • Patients love the results
500
- </span>
501
- </div>
502
- <div className="space-y-8">
503
- {product.description && (
504
- <div className="space-y-3">
505
- <h3 className="text-lg font-semibold text-slate-900">Description</h3>
506
- <p className="text-base leading-relaxed text-slate-600" dangerouslySetInnerHTML={{ __html: product.description }} />
507
- </div>
508
- )}
509
574
  </div>
510
- </div>
511
- <div className="h-full rounded-3xl border border-slate-100 bg-gradient-to-br from-primary-50 via-white to-secondary-50 p-8 shadow-sm">
512
- <h3 className="text-sm font-semibold uppercase tracking-[0.3em] text-primary-500">
513
- Care tips
514
- </h3>
515
- <div className="mt-4 space-y-4 text-sm text-slate-600">
516
- <p className="leading-relaxed">
517
- Store in a cool, dry place away from direct sunlight. Check packaging for
518
- allergen statements.
519
- </p>
520
- <p className="leading-relaxed">
521
- Consult with your local pharmacist if you are combining with other treatments
522
- or have chronic conditions.
523
- </p>
524
- <p className="rounded-2xl bg-white/60 p-4 leading-relaxed text-primary-700">
525
- Questions? Our care team is on standby — reach us via chat for tailored
526
- support before you checkout.
527
- </p>
528
- </div>
529
- </div>
530
- </section>
531
575
 
532
- <section className="rounded-3xl border border-slate-100 bg-white p-8 shadow-sm">
533
- <h2 className="text-xl font-semibold text-slate-900">Product insights</h2>
534
- <div className="mt-6 grid gap-6 md:grid-cols-2">
535
- <div className="rounded-2xl border border-slate-200 bg-slate-50/60 p-5">
536
- <p className="text-xs font-semibold uppercase tracking-[0.3em] text-slate-500">
537
- Availability
538
- </p>
539
- <div className="mt-3 flex items-center gap-2 text-sm text-slate-600">
540
- <Check className="h-4 w-4 text-primary-500" />
541
- {product.inStock ? 'Available for dispatch today' : 'Currently restocking'}
542
- </div>
543
- {product.stock !== undefined && (
544
- <p className="mt-2 text-xs text-slate-500">
545
- {product.stock > 0
546
- ? `${product.stock} units remaining in inventory`
547
- : 'Join the waitlist to be notified first'}
548
- </p>
549
- )}
550
- </div>
551
- <div className="rounded-2xl border border-slate-200 bg-slate-50/60 p-5">
552
- <p className="text-xs font-semibold uppercase tracking-[0.3em] text-slate-500">
553
- Product details
554
- </p>
555
- <div className="mt-3 space-y-2 text-sm text-slate-600">
556
- <p>
557
- <span className="font-medium text-slate-700">Variant:</span> {currentVariant.name}
558
- </p>
559
- <p>
560
- <span className="font-medium text-slate-700">SKU:</span> {variantSku}
561
- </p>
562
- <p>
563
- <span className="font-medium text-slate-700">Status:</span>{' '}
564
- <span className={variantInStock ? 'text-green-600' : 'text-amber-600'}>
565
- {variantInStock ? 'In Stock' : 'Out of Stock'}
566
- </span>
567
- </p>
568
- <p>
569
- <span className="font-medium text-slate-700">Last updated:</span>{' '}
570
- {lastUpdatedLabel}
571
- </p>
572
- <p>
573
- <span className="font-medium text-slate-700">Ships from:</span> Local
574
- pharmacy distribution center
575
- </p>
576
- </div>
576
+ {/* 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"
581
+ onClick={handleAddToCart}
582
+ isLoading={isAddingToCart}
583
+ disabled={!selectedVariant || selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK}
584
+ >
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>
577
603
  </div>
604
+
578
605
  </div>
579
- </section>
580
- </div>
581
606
 
582
- <aside className="space-y-6 lg:sticky lg:top-24">
583
- <div className="rounded-3xl border border-slate-100 bg-white p-8 shadow-lg shadow-primary-100/40">
584
- <div className="flex items-baseline gap-3">
585
- <p className="text-3xl font-bold text-slate-900">
586
- {selectedVariant ? formatPrice(selectedVariant.finalPrice) : formatPrice(product.price)}
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.
587
612
  </p>
588
- {variantComparePrice && variantComparePrice > variantPrice && (
589
- <p className="text-base text-slate-400 line-through">
590
- {formatPrice(variantComparePrice)}
591
- </p>
592
- )}
593
- {discount > 0 && (
594
- <Badge variant="danger" size="sm">
595
- -{discount}%
596
- </Badge>
597
- )}
598
613
  </div>
599
-
600
- <div className="mt-6 space-y-4">
601
- <div className="flex items-center gap-3 rounded-2xl bg-primary-50/80 px-4 py-3 text-sm font-medium text-primary-700">
602
- <ShieldCheck className="h-4 w-4" />
603
- Pharmacist verified product
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
+ ))}
604
623
  </div>
605
-
606
- <div className="flex items-center justify-between rounded-2xl border border-slate-200 px-4 py-3">
607
- <span className="text-sm font-medium text-slate-600">Qty</span>
608
- <div className="flex items-center rounded-full border border-slate-200 bg-slate-50">
609
- <button
610
- type="button"
611
- onClick={() => setQuantity((current) => Math.max(1, current - 1))}
612
- className="rounded-l-full p-2 hover:bg-primary-100/60"
613
- aria-label="Decrease quantity"
614
- >
615
- <Minus className="h-4 w-4" />
616
- </button>
617
- <span className="w-12 text-center text-sm font-semibold text-slate-700">
618
- {quantity}
619
- </span>
620
- <button
621
- type="button"
622
- onClick={() => setQuantity((current) => current + 1)}
623
- className="rounded-r-full p-2 hover:bg-primary-100/60"
624
- aria-label="Increase quantity"
625
- >
626
- <Plus className="h-4 w-4" />
627
- </button>
624
+ <span className="text-sm font-medium text-slate-500">
625
+ Rated 4.8 Patients love the results
626
+ </span>
627
+ </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 }} />
628
633
  </div>
629
- </div>
634
+ )}
630
635
  </div>
636
+ </div>
637
+ </section>
631
638
 
632
- {selectedVariant && (
633
- <div className="mt-4 text-sm">
634
- {selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK ||
635
- selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.LOWSTOCK ? (
636
- <div className="text-red-600 font-medium">Out of Stock</div>
637
- ) : <div className="text-green-600 font-medium">In Stock</div>
638
- }
639
- </div>
640
- )}
641
- <div className="mt-6 space-x-3">
642
- <Button
643
- size="lg"
644
- className="w-full"
645
- onClick={handleAddToCart}
646
- isLoading={isAddingToCart}
647
- disabled={!selectedVariant || selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK}
648
- >
649
- <ShoppingCart className="h-5 w-5" />
650
- {!selectedVariant
651
- ? 'Select a variant'
652
- : selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK
653
- ? 'Out of Stock'
654
- : `Add to Cart`}
655
- </Button>
656
- <Button
657
- size="lg"
658
- variant="outline"
659
- className="w-full"
660
- onClick={handleToggleFavorite}
661
- >
662
- <Heart
663
- className={`h-5 w-5 ${isFavorited ? 'fill-red-500 text-red-500' : 'text-slate-500'
664
- }`}
665
- />
666
- {isFavorited ? 'Saved' : 'Save for later'}
667
- </Button>
668
- </div>
669
639
 
670
- </div>
671
640
 
672
- <div className="rounded-3xl border border-primary-100 bg-primary-50/70 p-6 text-sm text-primary-700 shadow-sm">
673
- <p className="font-semibold uppercase tracking-[0.25em]">Need advice?</p>
674
- <p className="mt-2 leading-relaxed">
675
- Chat with a pharmacist in real time before completing your purchase. We will help
676
- you choose supporting supplements and answer dosing questions.
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
677
665
  </p>
678
666
  </div>
679
- </aside>
680
- </div>
681
-
682
- {relatedProducts.length > 0 && (
683
- <section className="mt-20">
684
- <div className="flex items-center justify-between">
685
- <div>
686
- <h2 className="text-2xl font-semibold text-slate-900">You may also like</h2>
687
- <p className="mt-1 text-sm text-slate-500">
688
- Hand-picked recommendations that pair nicely with this product.
689
- </p>
667
+ </div>
668
+ {relatedProducts.length > 0 && (
669
+ <section className="mt-20">
670
+ <div className="flex items-center justify-between">
671
+ <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.
675
+ </p>
676
+ </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>
690
682
  </div>
691
- <Link href="/shop" className="hidden md:inline-flex">
692
- <Button variant="ghost" className="text-primary-600">
693
- View all products
694
- </Button>
695
- </Link>
696
- </div>
697
- <div className="mt-6 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
698
- {relatedProducts?.map((relatedProduct) => (
699
- <ProductCard key={relatedProduct.id} product={relatedProduct} />
700
- ))}
701
- </div>
702
- </section>
703
- )}
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) => (
685
+ <ProductCard
686
+ key={relatedProduct.id || relatedProduct._id}
687
+ product={relatedProduct}
688
+ onClickProduct={(item) => {
689
+ router.push(buildPath(`/products/${item._id || item.id}`));
690
+ }}
691
+ />
692
+ ))}
693
+ </div>
694
+ </section>
695
+ )}
696
+ </div>
704
697
  </div>
705
698
  </div>
706
699
  </div>