Add: Shipping selector component and checkout integration
Added ShippingSelector component that: - Displays country selection dropdown - Fetches available Royal Mail shipping quotes based on cart weight - Shows delivery estimates and international delivery warnings - Lets customers select preferred shipping service - Auto-selects cheapest option by default Integrated into checkout: - Shows shipping selector in both logged-in and guest flows - Displays shipping cost breakdown in order summary - Passes shipping country and service to checkout API - Validates shipping selection before processing payment
This commit is contained in:
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
||||
import { useCart } from '@/lib/cartStore';
|
||||
import { loginCustomer } from '@/app/account/actions';
|
||||
import Price from '@/components/Price';
|
||||
import ShippingSelector from '@/components/ShippingSelector';
|
||||
|
||||
interface CheckoutPageClientProps {
|
||||
isLoggedIn: boolean;
|
||||
@@ -16,6 +17,9 @@ export default function CheckoutPageClient({ isLoggedIn, customer }: CheckoutPag
|
||||
const [mode, setMode] = useState<'logged-in' | 'login' | 'guest' | null>(isLoggedIn ? 'logged-in' : null);
|
||||
const [checkingOut, setCheckingOut] = useState(false);
|
||||
const [checkoutError, setCheckoutError] = useState<string | null>(null);
|
||||
const [shippingCountry, setShippingCountry] = useState('GB');
|
||||
const [shippingService, setShippingService] = useState<string | null>(null);
|
||||
const [shippingCost, setShippingCost] = useState(0);
|
||||
const [guestData, setGuestData] = useState({
|
||||
fullName: '',
|
||||
email: '',
|
||||
@@ -24,7 +28,8 @@ export default function CheckoutPageClient({ isLoggedIn, customer }: CheckoutPag
|
||||
});
|
||||
|
||||
const items = useCart((s) => s.items);
|
||||
const total = useCart((s) => s.total());
|
||||
const subtotal = useCart((s) => s.total());
|
||||
const total = subtotal + shippingCost;
|
||||
|
||||
useEffect(() => setMounted(true), []);
|
||||
|
||||
@@ -51,6 +56,10 @@ export default function CheckoutPageClient({ isLoggedIn, customer }: CheckoutPag
|
||||
setCheckoutError('Please fill in all fields.');
|
||||
return;
|
||||
}
|
||||
if (!shippingService) {
|
||||
setCheckoutError('Please select a shipping method.');
|
||||
return;
|
||||
}
|
||||
|
||||
setCheckingOut(true);
|
||||
setCheckoutError(null);
|
||||
@@ -71,6 +80,8 @@ export default function CheckoutPageClient({ isLoggedIn, customer }: CheckoutPag
|
||||
phone: guestData.phone,
|
||||
address: guestData.address,
|
||||
},
|
||||
shippingCountry,
|
||||
shippingService,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -106,8 +117,18 @@ export default function CheckoutPageClient({ isLoggedIn, customer }: CheckoutPag
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-4 border-t border-line pt-4">
|
||||
<div className="flex justify-between font-display text-lg">
|
||||
<div className="mt-4 border-t border-line pt-4 space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>Subtotal</span>
|
||||
<Price cents={subtotal} className="font-mono" />
|
||||
</div>
|
||||
{shippingCost > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>Shipping ({shippingService})</span>
|
||||
<Price cents={shippingCost} className="font-mono" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between font-display text-lg pt-2 border-t border-line">
|
||||
<span>Total</span>
|
||||
<Price cents={total} className="font-mono" />
|
||||
</div>
|
||||
@@ -117,11 +138,28 @@ export default function CheckoutPageClient({ isLoggedIn, customer }: CheckoutPag
|
||||
{/* 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>
|
||||
<div className="space-y-6">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<ShippingSelector
|
||||
items={items}
|
||||
onShippingSelect={(country, service, cost) => {
|
||||
setShippingCountry(country);
|
||||
setShippingService(service);
|
||||
setShippingCost(cost);
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!shippingService) {
|
||||
setCheckoutError('Please select a shipping method.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('💳 Checkout button clicked, cart items:', items.length);
|
||||
setCheckingOut(true);
|
||||
setCheckoutError(null);
|
||||
@@ -137,7 +175,12 @@ export default function CheckoutPageClient({ isLoggedIn, customer }: CheckoutPag
|
||||
const res = await fetch('/api/checkout', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ items, customerId: customer.id }),
|
||||
body: JSON.stringify({
|
||||
items,
|
||||
customerId: customer.id,
|
||||
shippingCountry,
|
||||
shippingService,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
console.log('📦 Checkout response:', { status: res.status, ok: res.ok, hasUrl: !!data.url });
|
||||
@@ -296,6 +339,16 @@ export default function CheckoutPageClient({ isLoggedIn, customer }: CheckoutPag
|
||||
className="w-full border border-line px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ShippingSelector
|
||||
items={items}
|
||||
onShippingSelect={(country, service, cost) => {
|
||||
setShippingCountry(country);
|
||||
setShippingService(service);
|
||||
setShippingCost(cost);
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={checkingOut}
|
||||
|
||||
Reference in New Issue
Block a user