Fix checkout page to hide login prompt when user is logged in

- Refactor checkout page into server component (page.tsx) + client component (client.tsx)
- Server component checks getCurrentCustomer() and passes isLoggedIn prop
- When logged in, show direct checkout button instead of login/guest options
- When not logged in, show login/guest choice as before
- Hides the 'Your cart will be saved...' message from logged-in users

Fixes: Login/guest prompt appearing even when user is already authenticated

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Andymick
2026-07-17 22:26:14 +01:00
co-authored by Claude Haiku 4.5
parent 84454da5c9
commit a2908e8ae1
2 changed files with 294 additions and 246 deletions
+289
View File
@@ -0,0 +1,289 @@
'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { useCart } from '@/lib/cartStore';
import { loginCustomer } from '@/app/account/actions';
import Price from '@/components/Price';
interface CheckoutPageClientProps {
isLoggedIn: boolean;
customer?: any;
}
export default function CheckoutPageClient({ isLoggedIn, customer }: CheckoutPageClientProps) {
const [mounted, setMounted] = useState(false);
const [mode, setMode] = useState<'login' | 'guest' | null>(isLoggedIn ? 'logged-in' : null);
const [checkingOut, setCheckingOut] = useState(false);
const [checkoutError, setCheckoutError] = useState<string | null>(null);
const [guestData, setGuestData] = useState({
fullName: '',
email: '',
phone: '',
address: '',
});
const items = useCart((s) => s.items);
const total = useCart((s) => s.total());
useEffect(() => setMounted(true), []);
if (!mounted) return null;
if (items.length === 0) {
return (
<div className="mx-auto max-w-md px-6 py-16 text-center">
<h1 className="font-display text-3xl">Checkout</h1>
<p className="mt-4 text-sm text-muted">Your bag is empty.</p>
<Link
href="/products"
className="mt-6 inline-block bg-clay px-6 py-3 text-sm font-medium text-paper hover:bg-clay-dark"
>
Continue shopping
</Link>
</div>
);
}
const handleGuestCheckout = async (e: React.FormEvent) => {
e.preventDefault();
if (!guestData.fullName || !guestData.email || !guestData.phone || !guestData.address) {
setCheckoutError('Please fill in all fields.');
return;
}
setCheckingOut(true);
setCheckoutError(null);
try {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items,
guest: {
name: guestData.fullName,
email: guestData.email,
phone: guestData.phone,
address: guestData.address,
},
}),
});
const data = await res.json();
if (!res.ok || !data.url) {
throw new Error(data.error ?? 'Something went wrong starting checkout.');
}
window.location.href = data.url;
} catch (err) {
setCheckoutError(err instanceof Error ? err.message : 'Something went wrong.');
setCheckingOut(false);
}
};
return (
<div className="mx-auto max-w-2xl px-6 py-16">
<h1 className="font-display text-3xl">Checkout</h1>
<div className="mt-8 grid gap-8 md:grid-cols-2">
{/* Order summary */}
<div>
<h2 className="font-display text-lg">Order summary</h2>
<div className="mt-4 space-y-3 border-t border-line pt-4">
{items.map((item) => (
<div key={item.cartItemId} className="flex justify-between text-sm">
<div>
<p className="font-medium">{item.name}</p>
<p className="text-xs text-muted">Qty: {item.quantity}</p>
</div>
<Price cents={item.unitPrice * item.quantity} className="font-mono" />
</div>
))}
</div>
<div className="mt-4 border-t border-line pt-4">
<div className="flex justify-between font-display text-lg">
<span>Total</span>
<Price cents={total} className="font-mono" />
</div>
</div>
</div>
{/* Checkout options */}
<div>
{mode === 'logged-in' ? (
<div>
<h3 className="font-display text-lg">Ready to checkout</h3>
<p className="mt-2 text-sm text-muted">Logged in as {customer?.email}</p>
<button
onClick={async () => {
setCheckingOut(true);
setCheckoutError(null);
try {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items, customerId: customer.id }),
});
const data = await res.json();
if (!res.ok || !data.url) {
throw new Error(data.error ?? 'Something went wrong starting checkout.');
}
window.location.href = data.url;
} catch (err) {
setCheckoutError(err instanceof Error ? err.message : 'Something went wrong.');
setCheckingOut(false);
}
}}
disabled={checkingOut}
className="mt-4 w-full bg-clay px-6 py-3 text-sm font-medium text-paper transition-colors hover:bg-clay-dark disabled:opacity-60"
>
{checkingOut ? 'Processing…' : 'Continue to payment'}
</button>
{checkoutError && (
<p className="mt-4 border border-splash-pink/40 bg-splash-pink/10 px-4 py-3 text-sm text-splash-pink">
{checkoutError}
</p>
)}
</div>
) : mode === null ? (
<div className="space-y-3">
<p className="text-sm text-muted">How would you like to checkout?</p>
<button
onClick={() => setMode('login')}
className="w-full border-2 border-ink px-6 py-3 text-sm font-medium text-ink hover:border-clay hover:bg-clay hover:text-paper"
>
Log in to existing account
</button>
<button
onClick={() => setMode('guest')}
className="w-full bg-clay px-6 py-3 text-sm font-medium text-paper hover:bg-clay-dark"
>
Continue as guest
</button>
<p className="text-xs text-muted">
Don&apos;t have an account?{' '}
<Link href="/account/register" className="text-clay hover:text-clay-dark">
Create one now
</Link>
</p>
</div>
) : mode === 'login' ? (
<div>
<button
onClick={() => setMode(null)}
className="text-sm text-clay hover:text-clay-dark mb-4"
>
Back
</button>
<h3 className="font-display text-lg">Log in</h3>
<form action={loginCustomer} className="mt-4 space-y-4">
<input type="hidden" name="next" value="/checkout" />
<div>
<label htmlFor="email" className="tag-label mb-2 block">
Email
</label>
<input
id="email"
name="email"
type="email"
required
className="w-full border border-line px-3 py-2 text-sm"
/>
</div>
<div>
<label htmlFor="password" className="tag-label mb-2 block">
Password
</label>
<input
id="password"
name="password"
type="password"
required
className="w-full border border-line px-3 py-2 text-sm"
/>
</div>
<button type="submit" className="w-full bg-clay px-6 py-3 text-sm font-medium text-paper hover:bg-clay-dark">
Log in
</button>
</form>
</div>
) : (
<div>
<button
onClick={() => setMode(null)}
className="text-sm text-clay hover:text-clay-dark mb-4"
>
Back
</button>
<h3 className="font-display text-lg">Guest checkout</h3>
{checkoutError && (
<p className="mt-4 border border-splash-pink/40 bg-splash-pink/10 px-4 py-3 text-sm text-splash-pink">
{checkoutError}
</p>
)}
<form onSubmit={handleGuestCheckout} className="mt-4 space-y-4">
<div>
<label htmlFor="fullName" className="tag-label mb-2 block">
Full name
</label>
<input
id="fullName"
type="text"
required
value={guestData.fullName}
onChange={(e) => setGuestData({ ...guestData, fullName: e.target.value })}
className="w-full border border-line px-3 py-2 text-sm"
/>
</div>
<div>
<label htmlFor="email" className="tag-label mb-2 block">
Email
</label>
<input
id="email"
type="email"
required
value={guestData.email}
onChange={(e) => setGuestData({ ...guestData, email: e.target.value })}
className="w-full border border-line px-3 py-2 text-sm"
/>
</div>
<div>
<label htmlFor="phone" className="tag-label mb-2 block">
Phone number
</label>
<input
id="phone"
type="tel"
required
value={guestData.phone}
onChange={(e) => setGuestData({ ...guestData, phone: e.target.value })}
className="w-full border border-line px-3 py-2 text-sm"
/>
</div>
<div>
<label htmlFor="address" className="tag-label mb-2 block">
Address
</label>
<textarea
id="address"
required
value={guestData.address}
onChange={(e) => setGuestData({ ...guestData, address: e.target.value })}
rows={3}
className="w-full border border-line px-3 py-2 text-sm"
/>
</div>
<button
type="submit"
disabled={checkingOut}
className="w-full bg-clay px-6 py-3 text-sm font-medium text-paper transition-colors hover:bg-clay-dark disabled:opacity-60"
>
{checkingOut ? 'Processing…' : 'Continue to payment'}
</button>
</form>
</div>
)}
</div>
</div>
</div>
);
}