create-ecom-app 1.0.0

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 (69) hide show
  1. package/index.js +101 -0
  2. package/package.json +25 -0
  3. package/templates/ecommerce/app/about/page.tsx +152 -0
  4. package/templates/ecommerce/app/cart/page.tsx +103 -0
  5. package/templates/ecommerce/app/checkout/page.tsx +126 -0
  6. package/templates/ecommerce/app/contact/page.tsx +124 -0
  7. package/templates/ecommerce/app/globals.css +137 -0
  8. package/templates/ecommerce/app/layout.tsx +31 -0
  9. package/templates/ecommerce/app/login/page.tsx +224 -0
  10. package/templates/ecommerce/app/not-found.tsx +14 -0
  11. package/templates/ecommerce/app/orders/[id]/page.tsx +138 -0
  12. package/templates/ecommerce/app/page.tsx +215 -0
  13. package/templates/ecommerce/app/product/[id]/page.tsx +417 -0
  14. package/templates/ecommerce/app/products/page.tsx +244 -0
  15. package/templates/ecommerce/app/profile/page.tsx +273 -0
  16. package/templates/ecommerce/app/register/page.tsx +221 -0
  17. package/templates/ecommerce/app/wishlist/page.tsx +89 -0
  18. package/templates/ecommerce/components/CartItem.tsx +74 -0
  19. package/templates/ecommerce/components/CheckoutForm.tsx +109 -0
  20. package/templates/ecommerce/components/Filters.tsx +142 -0
  21. package/templates/ecommerce/components/Footer.tsx +100 -0
  22. package/templates/ecommerce/components/Header.tsx +194 -0
  23. package/templates/ecommerce/components/ProductCard.tsx +158 -0
  24. package/templates/ecommerce/components/SearchBar.tsx +41 -0
  25. package/templates/ecommerce/data/products.json +427 -0
  26. package/templates/ecommerce/lib/localStorage.ts +27 -0
  27. package/templates/ecommerce/lib/utils.ts +25 -0
  28. package/templates/ecommerce/next-env.d.ts +5 -0
  29. package/templates/ecommerce/next.config.js +13 -0
  30. package/templates/ecommerce/next.config.ts +14 -0
  31. package/templates/ecommerce/package.json +30 -0
  32. package/templates/ecommerce/postcss.config.js +7 -0
  33. package/templates/ecommerce/public/images/about-2.jpg +0 -0
  34. package/templates/ecommerce/public/images/contact-bg.jpg +0 -0
  35. package/templates/ecommerce/public/images/hero-bg.jpg +0 -0
  36. package/templates/ecommerce/public/images/products/product-1.jpg +0 -0
  37. package/templates/ecommerce/public/images/products/product-10.jpg +0 -0
  38. package/templates/ecommerce/public/images/products/product-11.jpg +0 -0
  39. package/templates/ecommerce/public/images/products/product-12.jpg +0 -0
  40. package/templates/ecommerce/public/images/products/product-13.jpg +0 -0
  41. package/templates/ecommerce/public/images/products/product-14.jpg +0 -0
  42. package/templates/ecommerce/public/images/products/product-15.jpg +0 -0
  43. package/templates/ecommerce/public/images/products/product-16.jpg +0 -0
  44. package/templates/ecommerce/public/images/products/product-17.jpg +0 -0
  45. package/templates/ecommerce/public/images/products/product-18.jpg +0 -0
  46. package/templates/ecommerce/public/images/products/product-19.jpg +0 -0
  47. package/templates/ecommerce/public/images/products/product-2.jpg +0 -0
  48. package/templates/ecommerce/public/images/products/product-20.jpg +0 -0
  49. package/templates/ecommerce/public/images/products/product-21.jpg +0 -0
  50. package/templates/ecommerce/public/images/products/product-22.jpg +0 -0
  51. package/templates/ecommerce/public/images/products/product-23.jpg +0 -0
  52. package/templates/ecommerce/public/images/products/product-24.jpg +0 -0
  53. package/templates/ecommerce/public/images/products/product-25.jpg +0 -0
  54. package/templates/ecommerce/public/images/products/product-3.jpg +0 -0
  55. package/templates/ecommerce/public/images/products/product-4.jpg +0 -0
  56. package/templates/ecommerce/public/images/products/product-5.jpg +0 -0
  57. package/templates/ecommerce/public/images/products/product-6.jpg +0 -0
  58. package/templates/ecommerce/public/images/products/product-7.jpg +0 -0
  59. package/templates/ecommerce/public/images/products/product-8.jpg +0 -0
  60. package/templates/ecommerce/public/images/products/product-9.jpg +0 -0
  61. package/templates/ecommerce/public/service-worker.js +1 -0
  62. package/templates/ecommerce/store/useAuthStore.ts +56 -0
  63. package/templates/ecommerce/store/useCartStore.ts +63 -0
  64. package/templates/ecommerce/store/useOrderStore.ts +43 -0
  65. package/templates/ecommerce/store/useReviewStore.ts +60 -0
  66. package/templates/ecommerce/store/useWishlistStore.ts +43 -0
  67. package/templates/ecommerce/tailwind.config.ts +44 -0
  68. package/templates/ecommerce/tsconfig.json +22 -0
  69. package/templates/ecommerce/types/index.ts +69 -0
@@ -0,0 +1,215 @@
1
+ import type { Metadata } from 'next';
2
+ import Link from 'next/link';
3
+ import { ArrowRight, Zap, Shield, RefreshCw, Truck, Shirt, Watch, Laptop, ShoppingBag, Leaf, Headphones } from 'lucide-react';
4
+ import ProductCard from '@/components/ProductCard';
5
+ import productsData from '@/data/products.json';
6
+ import type { Product } from '@/types';
7
+
8
+ export const metadata: Metadata = {
9
+ title: 'GenZ Shop — Shop Different. Shop Smarter.',
10
+ description: 'Discover curated fashion, electronics and lifestyle products at GenZ Shop.',
11
+ };
12
+
13
+ const products = productsData as Product[];
14
+ const featured = products.filter((p) => p.inStock).slice(0, 8);
15
+ const bestSellers = [...products].sort((a, b) => b.reviewCount - a.reviewCount).slice(0, 4);
16
+
17
+ const usps = [
18
+ { icon: Truck, label: 'Free Shipping', desc: 'On orders above ₹999' },
19
+ { icon: Shield, label: 'Secure Payment', desc: '100% safe checkout' },
20
+ { icon: RefreshCw, label: '30-Day Returns', desc: 'Hassle-free returns' },
21
+ { icon: Zap, label: 'Fast Delivery', desc: '2-3 business days' },
22
+ ];
23
+
24
+ export default function HomePage() {
25
+ return (
26
+ <div>
27
+ {/* ── HERO (Mixtas Design) ── */}
28
+ <section className="relative w-full h-[600px] md:h-[700px] flex items-center bg-gray-100 overflow-hidden">
29
+ {/* Background Image */}
30
+ <div
31
+ className="absolute inset-0 bg-cover bg-center bg-no-repeat"
32
+ style={{
33
+ backgroundImage: `url('/images/hero-bg.jpg')`,
34
+ }}
35
+ />
36
+ {/* Slight gradient overlay for left text readability */}
37
+ <div className="absolute inset-0 bg-gradient-to-r from-black/40 via-black/10 to-transparent" />
38
+
39
+ <div className="container-main relative z-10 w-full flex items-center">
40
+
41
+ {/* Left Arrow */}
42
+ <button className="hidden md:flex w-10 h-10 bg-white/90 backdrop-blur rounded-full items-center justify-center hover:bg-white transition-colors absolute -left-4 xl:-left-12 shadow-sm">
43
+ <span className="text-gray-900 border-b-2 border-r-2 border-gray-900 w-2.5 h-2.5 rotate-[135deg] ml-1" />
44
+ </button>
45
+
46
+ {/* Text Content */}
47
+ <div className="max-w-xl text-white px-4 md:px-12 animate-fade-in py-10">
48
+ <p className="text-sm font-medium tracking-[0.15em] uppercase mb-4 opacity-90 drop-shadow-sm">
49
+ Urban Edge
50
+ </p>
51
+ <h1 className="text-5xl md:text-6xl font-bold leading-[1.1] mb-8 drop-shadow-md tracking-tight">
52
+ Jackets for the<br />Modern Man
53
+ </h1>
54
+ <Link href="/products" className="inline-flex bg-white text-gray-900 font-semibold px-8 py-3.5 hover:bg-gray-100 transition-colors text-sm rounded-none tracking-wide">
55
+ Discovery Now
56
+ </Link>
57
+ </div>
58
+
59
+ {/* Right Arrow */}
60
+ <button className="hidden md:flex w-10 h-10 bg-white/90 backdrop-blur rounded-full items-center justify-center hover:bg-white transition-colors absolute -right-4 xl:-right-12 shadow-sm">
61
+ <span className="text-gray-900 border-b-2 border-r-2 border-gray-900 w-2.5 h-2.5 -rotate-45 mr-1" />
62
+ </button>
63
+ </div>
64
+ </section>
65
+
66
+
67
+ {/* ── USP STRIP ── */}
68
+ <section className="bg-white border-b border-gray-100">
69
+ <div className="container-main py-5">
70
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-6">
71
+ {usps.map(({ icon: Icon, label, desc }) => (
72
+ <div key={label} className="flex items-center gap-3">
73
+ <div className="w-10 h-10 bg-gray-900 rounded-xl flex items-center justify-center flex-shrink-0">
74
+ <Icon className="w-5 h-5 text-white" />
75
+ </div>
76
+ <div>
77
+ <p className="text-sm font-bold text-gray-900">{label}</p>
78
+ <p className="text-xs text-gray-500">{desc}</p>
79
+ </div>
80
+ </div>
81
+ ))}
82
+ </div>
83
+ </div>
84
+ </section>
85
+
86
+ {/* ── CATEGORY BENTO GRID (PHLOX inspired) ── */}
87
+ <section className="container-main py-16">
88
+ <div className="mb-8">
89
+ <h2 className="section-title">Shop by Category</h2>
90
+ <p className="text-gray-500 mt-1 text-sm">Find what you&apos;re looking for</p>
91
+ </div>
92
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 auto-rows-[160px]">
93
+ {/* Clothing — large */}
94
+ <Link href="/products?category=Clothing" className="col-span-2 group relative rounded-2xl overflow-hidden bg-gray-900 flex items-end p-6 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
95
+ <div className="absolute top-5 right-5 w-12 h-12 bg-white/10 rounded-xl flex items-center justify-center">
96
+ <Shirt className="w-6 h-6 text-white" />
97
+ </div>
98
+ <div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
99
+ <div className="relative z-10">
100
+ <p className="text-white/60 text-xs font-semibold uppercase tracking-wider">Browse</p>
101
+ <h3 className="text-white text-2xl font-black mt-0.5">Clothing</h3>
102
+ </div>
103
+ <ArrowRight className="absolute bottom-6 right-6 w-5 h-5 text-white opacity-0 group-hover:opacity-100 transition-opacity z-10" />
104
+ </Link>
105
+
106
+ <Link href="/products?category=Electronics" className="group relative rounded-2xl overflow-hidden bg-amber-400 flex items-end p-5 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
107
+ <div className="absolute top-4 right-4 w-10 h-10 bg-black/15 rounded-xl flex items-center justify-center">
108
+ <Laptop className="w-5 h-5 text-gray-900" />
109
+ </div>
110
+ <div>
111
+ <p className="text-gray-800/70 text-xs font-semibold uppercase tracking-wider">Trend</p>
112
+ <h3 className="text-gray-900 text-lg font-black">Electronics</h3>
113
+ </div>
114
+ </Link>
115
+
116
+ <Link href="/products?category=Footwear" className="group relative rounded-2xl overflow-hidden bg-rose-500 flex items-end p-5 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
117
+ <div className="absolute top-4 right-4 w-10 h-10 bg-white/20 rounded-xl flex items-center justify-center">
118
+ <ShoppingBag className="w-5 h-5 text-white" />
119
+ </div>
120
+ <div>
121
+ <p className="text-white/70 text-xs font-semibold uppercase tracking-wider">New</p>
122
+ <h3 className="text-white text-lg font-black">Footwear</h3>
123
+ </div>
124
+ </Link>
125
+
126
+ <Link href="/products?category=Accessories" className="group relative rounded-2xl overflow-hidden bg-slate-700 flex items-end p-5 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
127
+ <div className="absolute top-4 right-4 w-10 h-10 bg-white/10 rounded-xl flex items-center justify-center">
128
+ <Watch className="w-5 h-5 text-white" />
129
+ </div>
130
+ <div>
131
+ <p className="text-white/70 text-xs font-semibold uppercase tracking-wider">Style</p>
132
+ <h3 className="text-white text-lg font-black">Accessories</h3>
133
+ </div>
134
+ </Link>
135
+
136
+ <Link href="/products?category=Lifestyle" className="col-span-2 group relative rounded-2xl overflow-hidden bg-emerald-500 flex items-end p-6 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
137
+ <div className="absolute top-5 right-5 w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center">
138
+ <Leaf className="w-6 h-6 text-white" />
139
+ </div>
140
+ <div>
141
+ <p className="text-white/70 text-xs font-semibold uppercase tracking-wider">Live Better</p>
142
+ <h3 className="text-white text-2xl font-black mt-0.5">Lifestyle</h3>
143
+ </div>
144
+ <ArrowRight className="absolute bottom-6 right-6 w-5 h-5 text-white opacity-0 group-hover:opacity-100 transition-opacity" />
145
+ </Link>
146
+
147
+ <Link href="/products" className="group relative rounded-2xl overflow-hidden bg-violet-600 flex items-end p-5 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
148
+ <div className="absolute top-4 right-4 w-10 h-10 bg-white/20 rounded-xl flex items-center justify-center">
149
+ <Headphones className="w-5 h-5 text-white" />
150
+ </div>
151
+ <div>
152
+ <p className="text-white/70 text-xs font-semibold uppercase tracking-wider">Play</p>
153
+ <h3 className="text-white text-lg font-black">Audio</h3>
154
+ </div>
155
+ </Link>
156
+ </div>
157
+ </section>
158
+
159
+ {/* ── FEATURED PRODUCTS ── */}
160
+ <section className="container-main pb-16">
161
+ <div className="flex items-center justify-between mb-8">
162
+ <div>
163
+ <h2 className="section-title">Featured Products</h2>
164
+ <p className="text-gray-500 text-sm mt-1">Handpicked just for you</p>
165
+ </div>
166
+ <Link href="/products" className="flex items-center gap-1.5 text-sm font-bold text-gray-900 hover:underline">
167
+ View all <ArrowRight className="w-4 h-4" />
168
+ </Link>
169
+ </div>
170
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
171
+ {featured.map((product) => (
172
+ <ProductCard key={product.id} product={product} />
173
+ ))}
174
+ </div>
175
+ </section>
176
+
177
+ {/* ── PROMO BANNER ── */}
178
+ <section className="container-main pb-16">
179
+ <div className="relative rounded-3xl overflow-hidden bg-gray-900 py-14 px-10 md:px-16 text-white">
180
+ <div className="absolute inset-0" style={{
181
+ backgroundImage: `radial-gradient(circle at 0% 50%, rgba(124,58,237,0.4) 0%, transparent 50%),
182
+ radial-gradient(circle at 100% 50%, rgba(37,99,235,0.3) 0%, transparent 50%)`,
183
+ }} />
184
+ <div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-6">
185
+ <div>
186
+ <p className="text-sm font-bold uppercase tracking-widest text-violet-400 mb-2">Limited Time</p>
187
+ <h2 className="text-3xl md:text-4xl font-black mb-2">Get 30% off your first order</h2>
188
+ <p className="text-gray-400">Sign up and unlock exclusive deals, early access & free shipping</p>
189
+ </div>
190
+ <Link href="/register" className="flex-shrink-0 flex items-center gap-2 bg-white text-gray-900 font-bold px-8 py-3.5 rounded-xl hover:bg-gray-100 transition-all active:scale-95 text-sm whitespace-nowrap">
191
+ Get Started Free <ArrowRight className="w-4 h-4" />
192
+ </Link>
193
+ </div>
194
+ </div>
195
+ </section>
196
+
197
+ {/* ── BEST SELLERS ── */}
198
+ <section className="bg-gray-50 py-16">
199
+ <div className="container-main">
200
+ <div className="flex items-center justify-between mb-8">
201
+ <div>
202
+ <h2 className="section-title">Best Sellers</h2>
203
+ <p className="text-gray-500 text-sm mt-1">Most loved by our community</p>
204
+ </div>
205
+ </div>
206
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
207
+ {bestSellers.map((product) => (
208
+ <ProductCard key={product.id} product={product} />
209
+ ))}
210
+ </div>
211
+ </div>
212
+ </section>
213
+ </div>
214
+ );
215
+ }
@@ -0,0 +1,417 @@
1
+ 'use client';
2
+
3
+ import { notFound } from 'next/navigation';
4
+ import Image from 'next/image';
5
+ import Link from 'next/link';
6
+ import { useState } from 'react';
7
+ import { motion, AnimatePresence } from 'framer-motion';
8
+ import { ShoppingCart, Heart, Star, ThumbsUp, ChevronDown } from 'lucide-react';
9
+ import { useCartStore } from '@/store/useCartStore';
10
+ import { useWishlistStore } from '@/store/useWishlistStore';
11
+ import { useAuthStore } from '@/store/useAuthStore';
12
+ import { useReviewStore } from '@/store/useReviewStore';
13
+ import productsData from '@/data/products.json';
14
+ import type { Product, Review } from '@/types';
15
+ import { formatPrice, getDiscount, generateId } from '@/lib/utils';
16
+
17
+ const products = productsData as Product[];
18
+
19
+ type SortKey = 'recent' | 'rating' | 'helpful';
20
+
21
+ // ⚠️ Defined OUTSIDE to avoid focus loss bug
22
+ interface StarInputProps {
23
+ value: number;
24
+ onChange: (v: number) => void;
25
+ }
26
+ function StarInput({ value, onChange }: StarInputProps) {
27
+ const [hover, setHover] = useState(0);
28
+ return (
29
+ <div className="flex gap-1">
30
+ {[1, 2, 3, 4, 5].map((s) => (
31
+ <button
32
+ key={s}
33
+ type="button"
34
+ onClick={() => onChange(s)}
35
+ onMouseEnter={() => setHover(s)}
36
+ onMouseLeave={() => setHover(0)}
37
+ className="p-0.5"
38
+ >
39
+ <Star
40
+ className={`w-6 h-6 transition-colors ${
41
+ s <= (hover || value) ? 'text-amber-400 fill-amber-400' : 'text-gray-300 fill-gray-300'
42
+ }`}
43
+ />
44
+ </button>
45
+ ))}
46
+ </div>
47
+ );
48
+ }
49
+
50
+ export default function ProductDetailPage({ params }: { params: { id: string } }) {
51
+ const product = products.find((p) => p.id === params.id);
52
+ if (!product) notFound();
53
+
54
+ const addItem = useCartStore((s) => s.addItem);
55
+ const { toggle, isWishlisted } = useWishlistStore();
56
+ const { user, isAuthenticated } = useAuthStore();
57
+ const { addReview, getProductReviews, hasReviewed, markHelpful } = useReviewStore();
58
+ const wishlisted = isWishlisted(product.id);
59
+ const [added, setAdded] = useState(false);
60
+ const [quantity, setQuantity] = useState(1);
61
+
62
+ // Review state
63
+ const [reviewRating, setReviewRating] = useState(0);
64
+ const [reviewTitle, setReviewTitle] = useState('');
65
+ const [reviewBody, setReviewBody] = useState('');
66
+ const [reviewSort, setReviewSort] = useState<SortKey>('recent');
67
+ const [reviewError, setReviewError] = useState('');
68
+ const [reviewSubmitted, setReviewSubmitted] = useState(false);
69
+
70
+ const reviews = getProductReviews(product.id);
71
+ const alreadyReviewed = user ? hasReviewed(product.id, user.id) : false;
72
+
73
+ const sortedReviews = [...reviews].sort((a: Review, b: Review) => {
74
+ if (reviewSort === 'rating') return b.rating - a.rating;
75
+ if (reviewSort === 'helpful') return b.helpful - a.helpful;
76
+ return new Date(b.date).getTime() - new Date(a.date).getTime();
77
+ });
78
+
79
+ const avgRating =
80
+ reviews.length > 0
81
+ ? reviews.reduce((s, r) => s + r.rating, 0) / reviews.length
82
+ : product.rating;
83
+
84
+ const related = products
85
+ .filter((p) => p.category === product.category && p.id !== product.id)
86
+ .slice(0, 4);
87
+
88
+ const discount = product.originalPrice ? getDiscount(product.originalPrice, product.price) : null;
89
+
90
+ const handleAddToCart = () => {
91
+ for (let i = 0; i < quantity; i++) addItem(product);
92
+ setAdded(true);
93
+ setTimeout(() => setAdded(false), 2000);
94
+ };
95
+
96
+ const handleSubmitReview = (e: React.FormEvent) => {
97
+ e.preventDefault();
98
+ if (!isAuthenticated || !user) { setReviewError('Please login to write a review.'); return; }
99
+ if (reviewRating === 0) { setReviewError('Please select a star rating.'); return; }
100
+ if (!reviewTitle.trim()) { setReviewError('Please add a review title.'); return; }
101
+ if (!reviewBody.trim()) { setReviewError('Please write your review.'); return; }
102
+ addReview({
103
+ productId: product.id,
104
+ userId: user.id,
105
+ userName: user.name,
106
+ rating: reviewRating,
107
+ title: reviewTitle.trim(),
108
+ body: reviewBody.trim(),
109
+ });
110
+ setReviewTitle('');
111
+ setReviewBody('');
112
+ setReviewRating(0);
113
+ setReviewError('');
114
+ setReviewSubmitted(true);
115
+ };
116
+
117
+ return (
118
+ <motion.div
119
+ className="container-main py-10"
120
+ initial={{ opacity: 0, y: 16 }}
121
+ animate={{ opacity: 1, y: 0 }}
122
+ transition={{ duration: 0.4 }}
123
+ >
124
+ {/* Breadcrumb */}
125
+ <nav className="mb-8 flex items-center gap-2 text-sm text-gray-400">
126
+ <Link href="/" className="hover:text-gray-900 transition-colors">Home</Link>
127
+ <span>/</span>
128
+ <Link href="/products" className="hover:text-gray-900 transition-colors">Products</Link>
129
+ <span>/</span>
130
+ <span className="text-gray-700 font-medium truncate">{product.name}</span>
131
+ </nav>
132
+
133
+ {/* Product Main */}
134
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 mb-20">
135
+ {/* Image */}
136
+ <div className="relative aspect-square rounded-3xl overflow-hidden bg-gray-100">
137
+ <Image src={product.image} alt={product.name} fill className="object-cover" sizes="(max-width: 1024px) 100vw, 50vw" priority />
138
+ {discount && (
139
+ <div className="absolute top-5 left-5 bg-red-500 text-white font-bold px-3 py-1.5 rounded-full text-sm">
140
+ -{discount}% OFF
141
+ </div>
142
+ )}
143
+ </div>
144
+
145
+ {/* Info */}
146
+ <div className="flex flex-col justify-center">
147
+ <p className="text-sm text-gray-400 uppercase tracking-wider mb-2">{product.category}</p>
148
+ <h1 className="text-3xl font-bold text-gray-900 mb-3">{product.name}</h1>
149
+
150
+ {/* Rating summary */}
151
+ <div className="flex items-center gap-2 mb-5">
152
+ <div className="flex">
153
+ {[1, 2, 3, 4, 5].map((s) => (
154
+ <Star key={s} className={`w-5 h-5 ${s <= Math.floor(avgRating) ? 'text-amber-400 fill-amber-400' : 'text-gray-200 fill-gray-200'}`} />
155
+ ))}
156
+ </div>
157
+ <span className="text-sm font-semibold text-gray-900">{avgRating.toFixed(1)}</span>
158
+ <span className="text-sm text-gray-400">({reviews.length || product.reviewCount} reviews)</span>
159
+ </div>
160
+
161
+ {/* Price */}
162
+ <div className="flex items-center gap-3 mb-6">
163
+ <span className="text-4xl font-bold text-gray-900">{formatPrice(product.price)}</span>
164
+ {product.originalPrice && (
165
+ <span className="text-xl text-gray-400 line-through">{formatPrice(product.originalPrice)}</span>
166
+ )}
167
+ </div>
168
+
169
+ <p className="text-gray-600 leading-relaxed mb-8">{product.description}</p>
170
+
171
+ {/* Stock */}
172
+ <div className="flex items-center gap-2 mb-6">
173
+ <div className={`w-2.5 h-2.5 rounded-full ${product.inStock ? 'bg-green-400' : 'bg-red-400'}`} />
174
+ <span className={`text-sm font-medium ${product.inStock ? 'text-green-600' : 'text-red-500'}`}>
175
+ {product.inStock ? 'In Stock' : 'Out of Stock'}
176
+ </span>
177
+ </div>
178
+
179
+ {/* Quantity */}
180
+ {product.inStock && (
181
+ <div className="flex items-center gap-3 mb-6">
182
+ <span className="text-sm font-medium text-gray-700">Quantity:</span>
183
+ <div className="flex items-center border border-gray-200 rounded-xl overflow-hidden">
184
+ <button onClick={() => setQuantity(Math.max(1, quantity - 1))} className="px-4 py-2.5 text-gray-600 hover:bg-gray-50 font-medium">−</button>
185
+ <span className="px-4 py-2.5 font-semibold text-gray-900 min-w-[3rem] text-center">{quantity}</span>
186
+ <button onClick={() => setQuantity(quantity + 1)} className="px-4 py-2.5 text-gray-600 hover:bg-gray-50 font-medium">+</button>
187
+ </div>
188
+ </div>
189
+ )}
190
+
191
+ {/* Actions */}
192
+ <div className="flex gap-3">
193
+ <motion.button
194
+ onClick={handleAddToCart}
195
+ disabled={!product.inStock}
196
+ whileTap={{ scale: 0.96 }}
197
+ className={`flex-1 py-3.5 rounded-xl font-semibold text-base flex items-center justify-center gap-2 transition-all duration-200
198
+ ${added ? 'bg-green-500 text-white' : product.inStock ? 'bg-gray-900 text-white hover:bg-gray-700' : 'bg-gray-100 text-gray-400 cursor-not-allowed'}`}
199
+ id="add-to-cart-detail"
200
+ >
201
+ <ShoppingCart className="w-5 h-5" />
202
+ {added ? 'Added to Cart!' : product.inStock ? 'Add to Cart' : 'Out of Stock'}
203
+ </motion.button>
204
+ <motion.button
205
+ onClick={() => toggle(product)}
206
+ whileTap={{ scale: 0.9 }}
207
+ className={`p-3.5 rounded-xl border-2 transition-all
208
+ ${wishlisted ? 'border-red-400 bg-red-50 text-red-500' : 'border-gray-200 text-gray-400 hover:border-gray-400'}`}
209
+ aria-label="Wishlist toggle"
210
+ id="wishlist-toggle"
211
+ >
212
+ <Heart className={`w-5 h-5 ${wishlisted ? 'fill-red-500' : ''}`} />
213
+ </motion.button>
214
+ </div>
215
+
216
+ {product.tags && (
217
+ <div className="flex flex-wrap gap-2 mt-6">
218
+ {product.tags.map((tag) => (
219
+ <span key={tag} className="bg-gray-100 text-gray-600 text-xs px-3 py-1 rounded-full">#{tag}</span>
220
+ ))}
221
+ </div>
222
+ )}
223
+ </div>
224
+ </div>
225
+
226
+ {/* ===== REVIEW SYSTEM ===== */}
227
+ <section className="mb-20">
228
+ <div className="flex items-center justify-between mb-6">
229
+ <h2 className="section-title">Customer Reviews</h2>
230
+ <div className="flex items-center gap-2">
231
+ <span className="text-sm text-gray-500">Sort by:</span>
232
+ <select
233
+ value={reviewSort}
234
+ onChange={(e) => setReviewSort(e.target.value as SortKey)}
235
+ className="input w-36 text-sm py-1.5"
236
+ id="review-sort"
237
+ >
238
+ <option value="recent">Most Recent</option>
239
+ <option value="rating">Top Rated</option>
240
+ <option value="helpful">Most Helpful</option>
241
+ </select>
242
+ </div>
243
+ </div>
244
+
245
+ {/* Rating Overview */}
246
+ <div className="bg-white rounded-2xl shadow-sm p-6 mb-6 flex items-center gap-8">
247
+ <div className="text-center flex-shrink-0">
248
+ <p className="text-5xl font-bold text-gray-900">{avgRating.toFixed(1)}</p>
249
+ <div className="flex mt-1">
250
+ {[1,2,3,4,5].map((s) => (
251
+ <Star key={s} className={`w-4 h-4 ${s <= Math.floor(avgRating) ? 'text-amber-400 fill-amber-400' : 'text-gray-200 fill-gray-200'}`} />
252
+ ))}
253
+ </div>
254
+ <p className="text-xs text-gray-400 mt-1">{reviews.length} review{reviews.length !== 1 ? 's' : ''}</p>
255
+ </div>
256
+ <div className="flex-1 space-y-1.5">
257
+ {[5, 4, 3, 2, 1].map((star) => {
258
+ const count = reviews.filter((r) => r.rating === star).length;
259
+ const pct = reviews.length > 0 ? (count / reviews.length) * 100 : 0;
260
+ return (
261
+ <div key={star} className="flex items-center gap-2">
262
+ <span className="text-xs text-gray-500 w-6">{star}★</span>
263
+ <div className="flex-1 bg-gray-100 rounded-full h-2 overflow-hidden">
264
+ <motion.div
265
+ className="bg-amber-400 h-full rounded-full"
266
+ initial={{ width: 0 }}
267
+ animate={{ width: `${pct}%` }}
268
+ transition={{ duration: 0.5, delay: 0.1 }}
269
+ />
270
+ </div>
271
+ <span className="text-xs text-gray-400 w-6">{count}</span>
272
+ </div>
273
+ );
274
+ })}
275
+ </div>
276
+ </div>
277
+
278
+ {/* Reviews List */}
279
+ <div className="space-y-4 mb-8">
280
+ {sortedReviews.length === 0 ? (
281
+ <div className="text-center py-12 text-gray-400">
282
+ <Star className="w-12 h-12 mx-auto mb-3 text-gray-200" />
283
+ <p className="font-medium text-gray-700">No reviews yet</p>
284
+ <p className="text-sm mt-1">Be the first to share your experience!</p>
285
+ </div>
286
+ ) : (
287
+ sortedReviews.map((review) => (
288
+ <motion.div
289
+ key={review.id}
290
+ initial={{ opacity: 0, y: 10 }}
291
+ animate={{ opacity: 1, y: 0 }}
292
+ className="bg-white rounded-2xl shadow-sm p-5"
293
+ >
294
+ <div className="flex items-start justify-between mb-2">
295
+ <div>
296
+ <div className="flex items-center gap-2 mb-1">
297
+ <div className="w-8 h-8 bg-gray-900 rounded-full flex items-center justify-center">
298
+ <span className="text-white text-xs font-bold">{review.userName.charAt(0).toUpperCase()}</span>
299
+ </div>
300
+ <span className="font-semibold text-gray-900 text-sm">{review.userName}</span>
301
+ </div>
302
+ <div className="flex gap-0.5 mb-1">
303
+ {[1,2,3,4,5].map((s) => (
304
+ <Star key={s} className={`w-3.5 h-3.5 ${s <= review.rating ? 'text-amber-400 fill-amber-400' : 'text-gray-200 fill-gray-200'}`} />
305
+ ))}
306
+ </div>
307
+ <p className="font-semibold text-gray-900">{review.title}</p>
308
+ </div>
309
+ <span className="text-xs text-gray-400 flex-shrink-0 ml-4">
310
+ {new Date(review.date).toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' })}
311
+ </span>
312
+ </div>
313
+ <p className="text-sm text-gray-600 leading-relaxed mb-3">{review.body}</p>
314
+ <button
315
+ onClick={() => user && markHelpful(review.id, user.id)}
316
+ className={`flex items-center gap-1.5 text-xs font-medium transition-colors
317
+ ${user && review.helpfulBy.includes(user.id) ? 'text-gray-900' : 'text-gray-400 hover:text-gray-700'}`}
318
+ id={`helpful-${review.id}`}
319
+ >
320
+ <ThumbsUp className={`w-3.5 h-3.5 ${user && review.helpfulBy.includes(user.id) ? 'fill-gray-900' : ''}`} />
321
+ Helpful ({review.helpful})
322
+ </button>
323
+ </motion.div>
324
+ ))
325
+ )}
326
+ </div>
327
+
328
+ {/* Write Review Form */}
329
+ {!alreadyReviewed ? (
330
+ <div className="bg-white rounded-2xl shadow-sm p-6">
331
+ <h3 className="font-bold text-gray-900 mb-4">Write a Review</h3>
332
+ {!isAuthenticated ? (
333
+ <div className="text-center py-8">
334
+ <p className="text-gray-500 mb-3">Please login to write a review.</p>
335
+ <Link href="/login" className="btn-primary inline-block">Login</Link>
336
+ </div>
337
+ ) : (
338
+ <form onSubmit={handleSubmitReview} className="space-y-4">
339
+ <div>
340
+ <label className="block text-sm font-medium text-gray-700 mb-2">Your Rating *</label>
341
+ <StarInput value={reviewRating} onChange={setReviewRating} />
342
+ </div>
343
+ <div>
344
+ <label htmlFor="review-title" className="block text-sm font-medium text-gray-700 mb-1.5">Review Title *</label>
345
+ <input
346
+ id="review-title"
347
+ type="text"
348
+ value={reviewTitle}
349
+ onChange={(e) => setReviewTitle(e.target.value)}
350
+ placeholder="Summarize your experience"
351
+ className="input"
352
+ />
353
+ </div>
354
+ <div>
355
+ <label htmlFor="review-body" className="block text-sm font-medium text-gray-700 mb-1.5">Your Review *</label>
356
+ <textarea
357
+ id="review-body"
358
+ value={reviewBody}
359
+ onChange={(e) => setReviewBody(e.target.value)}
360
+ placeholder="Share details of your experience..."
361
+ rows={4}
362
+ className="input resize-none"
363
+ />
364
+ </div>
365
+ {reviewError && (
366
+ <p className="text-sm text-red-500 bg-red-50 px-4 py-2.5 rounded-xl">{reviewError}</p>
367
+ )}
368
+ <AnimatePresence>
369
+ {reviewSubmitted && (
370
+ <motion.p
371
+ initial={{ opacity: 0, y: -5 }}
372
+ animate={{ opacity: 1, y: 0 }}
373
+ exit={{ opacity: 0 }}
374
+ className="text-sm text-green-600 bg-green-50 px-4 py-2.5 rounded-xl"
375
+ >
376
+ ✓ Review submitted! Thank you.
377
+ </motion.p>
378
+ )}
379
+ </AnimatePresence>
380
+ <button type="submit" className="btn-primary" id="submit-review-btn">
381
+ Submit Review
382
+ </button>
383
+ </form>
384
+ )}
385
+ </div>
386
+ ) : (
387
+ <div className="bg-green-50 border border-green-200 rounded-2xl p-5 text-center">
388
+ <p className="text-green-700 font-medium">✓ You&apos;ve already reviewed this product. Thank you!</p>
389
+ </div>
390
+ )}
391
+ </section>
392
+
393
+ {/* Related Products */}
394
+ {related.length > 0 && (
395
+ <section>
396
+ <h2 className="section-title mb-6">You Might Also Like</h2>
397
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
398
+ {related.map((p) => (
399
+ <Link key={p.id} href={`/product/${p.id}`} className="group block">
400
+ <div className="card overflow-hidden">
401
+ <div className="relative aspect-square overflow-hidden bg-gray-100">
402
+ <Image src={p.image} alt={p.name} fill className="object-cover group-hover:scale-105 transition-transform duration-500" sizes="25vw" />
403
+ </div>
404
+ <div className="p-4">
405
+ <p className="text-xs text-gray-400 uppercase tracking-wider">{p.category}</p>
406
+ <h3 className="font-semibold text-sm text-gray-900 mt-1 line-clamp-2">{p.name}</h3>
407
+ <p className="font-bold text-gray-900 mt-2">{formatPrice(p.price)}</p>
408
+ </div>
409
+ </div>
410
+ </Link>
411
+ ))}
412
+ </div>
413
+ </section>
414
+ )}
415
+ </motion.div>
416
+ );
417
+ }