fix(spa): complete checkout flow, UI standardization, and tailwind v4 styling fixes
This commit is contained in:
1 parent
83df238bab
commit
d8bff72e4e
24 files changed
+1169
-402
No files matched your search
@@ -50,16 +50,16 @@ export default function ProductDetailPage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<div className="min-h-screen bg-pop-bg">
|
||||
<Navbar categories={categories} />
|
||||
<div className="pt-32 px-6 max-w-7xl mx-auto animate-pulse space-y-8">
|
||||
<div className="h-6 w-48 bg-white/5 rounded" />
|
||||
<div className="h-6 w-48 bg-spa-stone/10 rounded" />
|
||||
<div className="grid md:grid-cols-2 gap-12">
|
||||
<div className="h-96 bg-white/5 rounded-2xl" />
|
||||
<div className="h-96 bg-spa-stone/10 rounded-2xl" />
|
||||
<div className="space-y-4">
|
||||
<div className="h-8 w-3/4 bg-white/5 rounded" />
|
||||
<div className="h-4 w-1/2 bg-white/5 rounded" />
|
||||
<div className="h-12 w-1/3 bg-white/5 rounded" />
|
||||
<div className="h-8 w-3/4 bg-spa-stone/10 rounded" />
|
||||
<div className="h-4 w-1/2 bg-spa-stone/10 rounded" />
|
||||
<div className="h-12 w-1/3 bg-spa-stone/10 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,12 +70,12 @@ export default function ProductDetailPage() {
|
||||
|
||||
if (error || !product) {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<div className="min-h-screen bg-pop-bg">
|
||||
<Navbar categories={categories} />
|
||||
<div className="pt-40 text-center space-y-4">
|
||||
<p className="text-4xl">⚠️</p>
|
||||
<p className="text-slate-400">{error ?? 'Product not found'}</p>
|
||||
<Link to="/shop" className="text-cyan-glow hover:underline text-sm">
|
||||
<p className="text-spa-stone font-medium">{error ?? 'Product not found'}</p>
|
||||
<Link to="/shop" className="text-spa-forest hover:underline text-sm font-bold">
|
||||
Back to Shop
|
||||
</Link>
|
||||
</div>
|
||||
@@ -100,35 +100,35 @@ export default function ProductDetailPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen pb-24 md:pb-0">
|
||||
<div className="min-h-screen pb-24 md:pb-0 bg-pop-bg">
|
||||
<Navbar categories={categories} />
|
||||
|
||||
<div className="max-w-7xl mx-auto px-6 pt-32 pb-24 space-y-24">
|
||||
<div className="max-w-7xl mx-auto px-6 pt-24 md:pt-32 pb-24 space-y-12 md:space-y-24">
|
||||
{/* Breadcrumb */}
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400 uppercase tracking-widest">
|
||||
<Link to="/" className="hover:text-white transition-colors">Home</Link>
|
||||
<div className="flex items-center flex-wrap gap-2 text-[10px] md:text-xs text-spa-stone/60 uppercase tracking-widest font-semibold mb-6 md:mb-0">
|
||||
<Link to="/" className="hover:text-spa-forest transition-colors">Home</Link>
|
||||
<span>/</span>
|
||||
<Link to="/shop" className="hover:text-white transition-colors">Shop</Link>
|
||||
<Link to="/shop" className="hover:text-spa-forest transition-colors">Shop</Link>
|
||||
<span>/</span>
|
||||
<Link to={`/category/${product.category.id}`} className="hover:text-white transition-colors">
|
||||
<Link to={`/category/${product.category.id}`} className="hover:text-spa-forest transition-colors">
|
||||
{product.category.name}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-cyan-glow">{product.scent.name}</span>
|
||||
<span className="text-spa-forest">{product.scent.name}</span>
|
||||
</div>
|
||||
|
||||
{/* Product Detail */}
|
||||
<div className="grid md:grid-cols-2 gap-16 items-center">
|
||||
<div className="grid md:grid-cols-2 gap-8 md:gap-16 items-start">
|
||||
{/* Image */}
|
||||
<div className="relative rounded-2xl overflow-hidden h-[480px] group">
|
||||
<div className="relative rounded-2xl overflow-hidden h-[480px] group bg-white shadow-sm border border-spa-stone/10">
|
||||
<img
|
||||
src={product.images?.[0]?.url || getCategoryImage(product.category.name)}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
|
||||
alt={product.scent.name}
|
||||
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-night-900/50 to-transparent" />
|
||||
<div className="absolute top-6 left-6 glass px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest text-cyan-glow border border-cyan-glow/20">
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/10 to-transparent" />
|
||||
<div className="absolute top-6 left-6 bg-white/90 backdrop-blur-sm px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest text-spa-forest shadow-sm">
|
||||
{product.category.name}
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,34 +136,34 @@ export default function ProductDetailPage() {
|
||||
{/* Info */}
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-2">
|
||||
<p className="text-cyan-glow uppercase tracking-[0.3em] text-xs font-semibold opacity-80">
|
||||
<p className="text-spa-forest uppercase tracking-[0.3em] text-xs font-bold opacity-80">
|
||||
{product.category.name}
|
||||
</p>
|
||||
<h1 className="text-5xl font-bold leading-tight">
|
||||
<h1 className="text-5xl font-bold leading-tight text-pop-black">
|
||||
{product.scent.name}
|
||||
</h1>
|
||||
<p className="text-slate-400 text-sm uppercase tracking-widest">
|
||||
<p className="text-spa-stone/80 text-sm uppercase tracking-widest font-semibold">
|
||||
Premium Collection
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{product.description && (
|
||||
<p className="text-slate-300 leading-relaxed">{product.description}</p>
|
||||
<p className="text-spa-stone leading-relaxed">{product.description}</p>
|
||||
)}
|
||||
|
||||
{/* Price */}
|
||||
<div className="flex items-baseline gap-3">
|
||||
<span className="text-4xl font-bold text-cyan-glow">
|
||||
<span className="text-4xl font-bold text-rose-500">
|
||||
{formatPrice(currentVariant?.price)}
|
||||
</span>
|
||||
{currentVariant && (
|
||||
<span className="text-slate-400 text-sm">/ {currentVariant.size}</span>
|
||||
<span className="text-spa-stone/60 text-sm font-semibold">/ {currentVariant.size}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Variant Selector */}
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs uppercase tracking-widest text-slate-400 font-semibold">
|
||||
<p className="text-xs uppercase tracking-widest text-spa-stone/80 font-bold">
|
||||
Select Size
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
@@ -171,14 +171,14 @@ export default function ProductDetailPage() {
|
||||
<button
|
||||
key={v.id}
|
||||
onClick={() => setSelectedVariant(i)}
|
||||
className={`px-5 py-2.5 rounded-lg text-sm font-bold uppercase tracking-widest transition-all duration-300 ${
|
||||
className={`px-5 py-2.5 rounded-lg text-sm font-bold uppercase tracking-widest transition-all duration-300 border ${
|
||||
selectedVariant === i
|
||||
? 'bg-cyan-glow text-night-900 shadow-[0_0_15px_rgba(102,252,241,0.4)]'
|
||||
: 'glass hover:bg-white/10'
|
||||
? 'bg-spa-forest text-white border-spa-forest shadow-md'
|
||||
: 'bg-white text-spa-stone border-spa-stone/20 hover:border-spa-forest hover:text-spa-forest shadow-sm'
|
||||
}`}
|
||||
>
|
||||
{v.size}
|
||||
<span className={`block text-[10px] mt-0.5 ${selectedVariant === i ? 'text-night-800' : 'text-slate-400'}`}>
|
||||
<span className={`block text-[10px] mt-0.5 ${selectedVariant === i ? 'text-white/80' : 'text-spa-stone/60'}`}>
|
||||
{formatPrice(v.price)}
|
||||
</span>
|
||||
</button>
|
||||
@@ -187,14 +187,14 @@ export default function ProductDetailPage() {
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-4">
|
||||
<div className="hidden md:flex gap-4">
|
||||
<button
|
||||
onClick={handleAddToCart}
|
||||
className="flex-1 py-4 bg-white text-night-900 font-bold rounded-lg tracking-widest uppercase hover:bg-cyan-glow hover:shadow-[0_0_25px_rgba(102,252,241,0.4)] transition-all duration-500"
|
||||
className="flex-1 py-4 bg-spa-forest text-white font-bold rounded-lg tracking-widest uppercase hover:bg-spa-stone hover:shadow-lg transition-all duration-300"
|
||||
>
|
||||
Add to Cart
|
||||
</button>
|
||||
<button className="glass p-4 rounded-lg hover:bg-white/10 transition-all">
|
||||
<button className="bg-white border border-spa-stone/20 text-spa-stone p-4 rounded-lg hover:border-spa-forest hover:text-spa-forest shadow-sm transition-all">
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
|
||||
</svg>
|
||||
@@ -202,16 +202,16 @@ export default function ProductDetailPage() {
|
||||
</div>
|
||||
|
||||
{/* Meta */}
|
||||
<div className="border-t border-white/5 pt-6 grid grid-cols-2 gap-4 text-sm text-slate-400">
|
||||
<div className="border-t border-spa-stone/10 pt-6 grid grid-cols-2 gap-4 text-sm text-spa-stone/80">
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1">Scent</span>
|
||||
<span className="text-white font-medium">{product.scent.name}</span>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">Scent</span>
|
||||
<span className="text-pop-black font-bold">{product.scent.name}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1">Category</span>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">Category</span>
|
||||
<Link
|
||||
to={`/category/${product.category.id}`}
|
||||
className="text-white font-medium hover:text-cyan-glow transition-colors"
|
||||
className="text-pop-black font-bold hover:text-spa-forest transition-colors"
|
||||
>
|
||||
{product.category.name}
|
||||
</Link>
|
||||
@@ -222,10 +222,10 @@ export default function ProductDetailPage() {
|
||||
|
||||
{/* Full Details Section */}
|
||||
{product.details && (
|
||||
<div className="pt-16 border-t border-white/5 space-y-6">
|
||||
<h2 className="text-3xl font-bold">Product Details</h2>
|
||||
<div className="pt-16 border-t border-spa-stone/10 space-y-6">
|
||||
<h2 className="text-3xl font-bold text-pop-black">Product Details</h2>
|
||||
<div
|
||||
className="prose prose-invert prose-lg max-w-4xl product-editorial"
|
||||
className="prose prose-spa prose-lg max-w-4xl product-editorial text-spa-stone"
|
||||
dangerouslySetInnerHTML={{ __html: product.details }}
|
||||
/>
|
||||
</div>
|
||||
@@ -233,17 +233,17 @@ export default function ProductDetailPage() {
|
||||
|
||||
{/* Related Products */}
|
||||
{related.length > 0 && (
|
||||
<div className="space-y-8 border-t border-white/5 pt-16">
|
||||
<div className="space-y-8 border-t border-spa-stone/10 pt-16">
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<p className="text-cyan-glow uppercase tracking-[0.3em] text-xs font-semibold opacity-70 mb-2">
|
||||
<p className="text-spa-forest uppercase tracking-[0.3em] text-xs font-bold opacity-80 mb-2">
|
||||
More {product.category.name}
|
||||
</p>
|
||||
<h2 className="text-3xl font-bold">Related Products</h2>
|
||||
<h2 className="text-3xl font-bold text-pop-black">Related Products</h2>
|
||||
</div>
|
||||
<Link
|
||||
to={`/category/${product.category.id}`}
|
||||
className="text-cyan-glow text-sm font-bold uppercase tracking-widest hover:underline"
|
||||
className="text-spa-forest text-sm font-bold uppercase tracking-widest hover:underline"
|
||||
>
|
||||
View All →
|
||||
</Link>
|
||||
@@ -260,7 +260,7 @@ export default function ProductDetailPage() {
|
||||
<Footer categories={categories} />
|
||||
|
||||
{/* Mobile Sticky Add to Cart Bar */}
|
||||
<div className="md:hidden fixed bottom-0 left-0 right-0 p-4 bg-night-950/95 backdrop-blur-xl border-t border-white/10 z-40 animate-fade-in flex flex-col gap-3 shadow-[0_-10px_30px_rgba(0,0,0,0.5)]">
|
||||
<div className="md:hidden fixed bottom-0 left-0 right-0 p-4 bg-white/95 backdrop-blur-xl border-t border-spa-stone/10 z-40 animate-fade-in flex flex-col gap-3 shadow-[0_-10px_30px_rgba(0,0,0,0.05)]">
|
||||
|
||||
{/* Mobile Variant Selector */}
|
||||
{product.variants && product.variants.length > 1 && (
|
||||
@@ -271,8 +271,8 @@ export default function ProductDetailPage() {
|
||||
onClick={() => setSelectedVariant(i)}
|
||||
className={`flex-shrink-0 px-4 py-2 rounded-lg text-[10px] font-bold uppercase tracking-widest transition-all border whitespace-nowrap ${
|
||||
selectedVariant === i
|
||||
? 'bg-cyan-glow/10 border-cyan-glow text-cyan-glow'
|
||||
: 'bg-white/5 border-white/10 text-slate-400'
|
||||
? 'bg-spa-forest border-spa-forest text-white'
|
||||
: 'bg-white border-spa-stone/20 text-spa-stone'
|
||||
}`}
|
||||
>
|
||||
{v.size}
|
||||
@@ -282,17 +282,17 @@ export default function ProductDetailPage() {
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[10px] uppercase tracking-widest text-slate-400">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[10px] uppercase tracking-widest text-spa-stone/60 font-semibold">
|
||||
{product.variants && product.variants.length > 1 ? currentVariant?.size : 'Total Price'}
|
||||
</span>
|
||||
<span className="text-xl font-bold text-cyan-glow">
|
||||
<span className="text-xl font-bold text-rose-500">
|
||||
{formatPrice(currentVariant?.price)}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleAddToCart}
|
||||
className="flex-1 py-3.5 bg-cyan-glow text-night-900 font-bold rounded-lg tracking-widest uppercase hover:brightness-110 shadow-[0_0_15px_rgba(102,252,241,0.3)] transition-all flex justify-center items-center gap-2"
|
||||
className="flex-1 py-3.5 bg-spa-forest text-white font-bold rounded-lg tracking-widest uppercase hover:bg-spa-stone shadow-sm transition-all flex justify-center items-center gap-2"
|
||||
>
|
||||
Add to Cart
|
||||
</button>
|
||||
|
||||
Reference in new issue
Block a user