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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user