fix(spa): complete checkout flow, UI standardization, and tailwind v4 styling fixes

This commit is contained in:
Your Name committed 2026-06-06 19:10:19 +07:00
1 parent 83df238bab
commit d8bff72e4e
24 files changed
+1169 -402

No files matched your search

+41 -35
View File
@@ -219,61 +219,67 @@ export default function CheckoutPage() {
}
return (
<div className="min-h-screen">
<div className="min-h-screen bg-pop-bg">
<Navbar categories={categories} />
<main className="max-w-6xl mx-auto px-6 pt-32 pb-24">
<main className="max-w-6xl mx-auto px-6 pt-32 pb-24 text-pop-black">
<h1 className="text-4xl font-bold mb-12">Checkout</h1>
<div className="flex flex-col lg:flex-row gap-12">
{/* Form Section */}
<div className="flex-1 space-y-8">
<form id="checkout-form" onSubmit={handleSubmit} className="space-y-6">
<div className="bg-night-900/50 p-8 rounded-2xl border border-white/5 space-y-6">
<h2 className="text-xl font-bold uppercase tracking-widest text-cyan-glow border-b border-white/10 pb-4">Shipping Information</h2>
<div className="bg-white p-8 rounded-2xl border border-spa-stone/10 space-y-6 shadow-sm">
<h2 className="text-xl font-bold uppercase tracking-widest text-spa-forest border-b border-spa-stone/10 pb-4">Shipping Information</h2>
<div className="grid md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-xs uppercase tracking-widest text-slate-400">Full Name *</label>
<label htmlFor="checkout-name" className="text-xs uppercase tracking-widest text-spa-stone/80 font-bold">Full Name *</label>
<input
id="checkout-name"
type="text"
required
value={formData.name}
onChange={e => setFormData({ ...formData, name: e.target.value })}
className="w-full bg-night-950 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-glow focus:ring-1 focus:ring-cyan-glow outline-none transition-all"
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all placeholder:text-spa-stone/50"
placeholder="Jane Doe"
/>
</div>
<div className="space-y-2">
<label className="text-xs uppercase tracking-widest text-slate-400">Phone Number *</label>
<label htmlFor="checkout-phone" className="text-xs uppercase tracking-widest text-spa-stone/80 font-bold">Phone Number *</label>
<input
id="checkout-phone"
type="tel"
required
pattern="[0-9]{9,10}"
title="Please enter a valid phone number (9-10 digits)"
value={formData.phone}
onChange={e => setFormData({ ...formData, phone: e.target.value })}
className="w-full bg-night-950 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-glow focus:ring-1 focus:ring-cyan-glow outline-none transition-all"
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all placeholder:text-spa-stone/50"
placeholder="0812345678"
/>
</div>
<div className="md:col-span-2 space-y-2">
<label className="text-xs uppercase tracking-widest text-slate-400">Email Address *</label>
<label htmlFor="checkout-email" className="text-xs uppercase tracking-widest text-spa-stone/80 font-bold">Email Address *</label>
<input
id="checkout-email"
type="email"
required
value={formData.email}
onChange={e => setFormData({ ...formData, email: e.target.value })}
className="w-full bg-night-950 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-glow focus:ring-1 focus:ring-cyan-glow outline-none transition-all"
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all placeholder:text-spa-stone/50"
placeholder="jane@example.com"
/>
</div>
<div className="md:col-span-2 space-y-2">
<label className="text-xs uppercase tracking-widest text-slate-400">Shipping Address *</label>
<label htmlFor="checkout-address" className="text-xs uppercase tracking-widest text-spa-stone/80 font-bold">Shipping Address *</label>
<textarea
id="checkout-address"
required
rows={3}
value={formData.address}
onChange={e => setFormData({ ...formData, address: e.target.value })}
className="w-full bg-night-950 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-glow focus:ring-1 focus:ring-cyan-glow outline-none transition-all resize-none"
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all resize-none placeholder:text-spa-stone/50"
placeholder="123 Serenity Street..."
/>
</div>
@@ -284,84 +290,84 @@ export default function CheckoutPage() {
{/* Summary Section */}
<div className="lg:w-96 space-y-6">
<div className="bg-night-900/50 p-8 rounded-2xl border border-white/5 sticky top-32">
<h2 className="text-xl font-bold uppercase tracking-widest text-cyan-glow border-b border-white/10 pb-4 mb-6">Order Summary</h2>
<div className="bg-white p-8 rounded-2xl border border-spa-stone/10 shadow-sm sticky top-32">
<h2 className="text-xl font-bold uppercase tracking-widest text-spa-forest border-b border-spa-stone/10 pb-4 mb-6">Order Summary</h2>
{/* Coupon Section */}
<div className="mb-6 pb-6 border-b border-white/10">
<div className="mb-6 pb-6 border-b border-spa-stone/10">
{!appliedCoupon ? (
<div className="space-y-2">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-500">Discount Code</label>
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">Discount Code</label>
<div className="flex gap-2">
<input
type="text"
value={couponCode}
onChange={e => setCouponCode(e.target.value.toUpperCase())}
placeholder="e.g. SUMMER2026"
className="flex-1 bg-night-950 border border-white/10 rounded-lg px-3 text-sm text-white focus:border-cyan-glow outline-none uppercase tracking-widest"
className="flex-1 bg-spa-light border border-spa-stone/20 rounded-lg px-3 text-sm text-pop-black focus:border-spa-forest outline-none uppercase tracking-widest placeholder:text-spa-stone/50"
/>
<button
type="button"
onClick={handleApplyCoupon}
disabled={couponLoading || !couponCode.trim()}
className="px-4 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-xs font-bold uppercase tracking-widest disabled:opacity-50 transition-all"
className="px-4 bg-spa-stone/10 hover:bg-spa-stone/20 border border-spa-stone/10 rounded-lg text-xs font-bold uppercase tracking-widest disabled:opacity-50 transition-all text-pop-black"
>
{couponLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Apply'}
</button>
</div>
{couponError && <p className="text-xs text-rose-500 mt-1">{couponError}</p>}
{couponError && <p className="text-xs text-rose-500 mt-1 font-medium">{couponError}</p>}
</div>
) : (
<div className="flex items-center justify-between bg-cyan-glow/10 border border-cyan-glow/20 rounded-lg px-4 py-3">
<div className="flex items-center justify-between bg-spa-sage/30 border border-spa-forest/20 rounded-lg px-4 py-3">
<div className="flex items-center gap-2">
<svg className="w-4 h-4 text-cyan-glow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg className="w-4 h-4 text-spa-forest" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-sm font-bold tracking-widest text-cyan-glow">{appliedCoupon}</span>
<span className="text-sm font-bold tracking-widest text-spa-forest">{appliedCoupon}</span>
</div>
<button type="button" onClick={removeCoupon} className="text-xs text-slate-400 hover:text-rose-500 uppercase tracking-widest font-bold">Remove</button>
<button type="button" onClick={removeCoupon} className="text-xs text-spa-stone/80 hover:text-rose-500 uppercase tracking-widest font-bold">Remove</button>
</div>
)}
</div>
<div className="space-y-4 mb-6">
{items.map(item => (
<div key={item.cartItemId} className="flex justify-between items-start gap-4 text-sm">
<div key={item.cartItemId} className="flex justify-between items-start gap-4 text-sm text-pop-black">
<div className="flex-1">
<p className="font-bold">{item.productName}</p>
<p className="text-slate-400 text-xs mt-0.5">{item.variantSize} x {item.quantity}</p>
<p className="text-spa-stone/80 text-xs mt-0.5">{item.variantSize} x {item.quantity}</p>
</div>
<p className="font-medium text-cyan-glow">{formatPrice(item.price * item.quantity)}</p>
<p className="font-bold text-rose-500">{formatPrice(item.price * item.quantity)}</p>
</div>
))}
</div>
<div className="border-t border-white/10 pt-4 space-y-3 font-medium">
<div className="flex justify-between text-sm text-slate-300">
<div className="border-t border-spa-stone/10 pt-4 space-y-3 font-medium text-pop-black">
<div className="flex justify-between text-sm text-spa-stone">
<span>Subtotal</span>
<span>{formatPrice(subtotal)}</span>
</div>
<div className="flex justify-between text-sm text-slate-300">
<div className="flex justify-between text-sm text-spa-stone">
<span>Shipping Fee</span>
{loadingSettings ? <Loader2 className="w-4 h-4 animate-spin" /> :
<span className={isFreeShipping ? "line-through text-slate-500" : ""}>{formatPrice(shippingFee)}</span>
<span className={isFreeShipping ? "line-through text-spa-stone/50" : ""}>{formatPrice(shippingFee)}</span>
}
</div>
{isFreeShipping && (
<div className="flex justify-between text-sm text-emerald-400">
<div className="flex justify-between text-sm text-spa-forest">
<span>Shipping Discount</span>
<span>- {formatPrice(shippingFee)}</span>
</div>
)}
{discountAmount > 0 && (
<div className="flex justify-between text-sm text-cyan-glow">
<div className="flex justify-between text-sm text-rose-500">
<span>Discount</span>
<span>- {formatPrice(discountAmount)}</span>
</div>
)}
<div className="border-t border-white/10 pt-3 mt-3 flex justify-between text-lg font-bold">
<div className="border-t border-spa-stone/10 pt-3 mt-3 flex justify-between text-lg font-bold text-pop-black">
<span>Total</span>
<span className="text-cyan-glow">{formatPrice(total)}</span>
<span className="text-rose-500">{formatPrice(total)}</span>
</div>
</div>
@@ -369,7 +375,7 @@ export default function CheckoutPage() {
form="checkout-form"
type="submit"
disabled={loading || loadingSettings}
className="w-full mt-8 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 disabled:opacity-50 disabled:cursor-not-allowed flex justify-center items-center gap-2"
className="w-full mt-8 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 disabled:opacity-50 disabled:cursor-not-allowed flex justify-center items-center gap-2"
>
{loading ? <Loader2 className="w-5 h-5 animate-spin" /> : 'Confirm Order'}
</button>