katheryn-website/frontend/src/app/checkout/page.tsx

317 lines
12 KiB
TypeScript

'use client';
import { useState } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { useCart } from '@/context/cart-context';
import { getAssetUrl } from '@/lib/directus';
import { PayPalCheckout } from '@/components/paypal-checkout';
import { getShippingCost } from '@/lib/shipping';
export default function CheckoutPage() {
const { items, subtotal, removeItem, itemCount, clearCart } = useCart();
const router = useRouter();
const [error, setError] = useState<string | null>(null);
const [formValid, setFormValid] = useState(false);
const [formData, setFormData] = useState({
email: '',
firstName: '',
lastName: '',
address: '',
city: '',
postcode: '',
country: 'United Kingdom',
phone: '',
notes: '',
});
if (items.length === 0) {
return (
<div className="min-h-screen flex flex-col items-center justify-center py-20 pt-32">
<h1 className="font-serif text-2xl">Your cart is empty</h1>
<p className="mt-4 text-gray-600">Add some artwork to continue.</p>
<Link href="/store" className="mt-8 btn btn-primary">
Browse Store
</Link>
</div>
);
}
const handleFieldChange = (field: string, value: string) => {
const updated = { ...formData, [field]: value };
setFormData(updated);
const required = ['email', 'firstName', 'lastName', 'address', 'city', 'postcode'] as const;
setFormValid(required.every(f => updated[f].trim() !== ''));
};
const currency = items[0]?.artwork?.currency || 'GBP';
const currencySymbol = currency === 'GBP' ? '\u00a3' : '$';
const shippingCost = getShippingCost(formData.country, currency);
const total = subtotal + shippingCost;
const paypalItems = items.map(item => ({
artworkId: parseInt(item.id),
artworkName: item.title,
price: item.price,
currency: item.artwork.currency || 'GBP',
quantity: item.quantity,
}));
return (
<div className="min-h-screen bg-white pt-24">
{/* Header */}
<div className="border-b border-gray-200">
<div className="mx-auto max-w-7xl px-4 py-8">
<h1 className="font-serif text-3xl">Checkout</h1>
</div>
</div>
<div className="mx-auto max-w-7xl px-4 py-12">
{error && (
<div className="mb-8 bg-red-50 border border-red-200 text-red-700 px-4 py-3 text-sm">
{error}
</div>
)}
<div className="grid gap-12 lg:grid-cols-2">
{/* Order Form */}
<div>
<h2 className="text-lg font-medium uppercase tracking-wider mb-6">
Contact Information
</h2>
<div className="space-y-6">
<div>
<label htmlFor="email" className="block text-sm text-gray-600 mb-2">
Email Address
</label>
<input
type="email"
id="email"
required
value={formData.email}
onChange={(e) => handleFieldChange('email', e.target.value)}
className="w-full border border-gray-300 px-4 py-3 text-sm focus:border-gray-900 focus:outline-none"
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label htmlFor="firstName" className="block text-sm text-gray-600 mb-2">
First Name
</label>
<input
type="text"
id="firstName"
required
value={formData.firstName}
onChange={(e) => handleFieldChange('firstName', e.target.value)}
className="w-full border border-gray-300 px-4 py-3 text-sm focus:border-gray-900 focus:outline-none"
/>
</div>
<div>
<label htmlFor="lastName" className="block text-sm text-gray-600 mb-2">
Last Name
</label>
<input
type="text"
id="lastName"
required
value={formData.lastName}
onChange={(e) => handleFieldChange('lastName', e.target.value)}
className="w-full border border-gray-300 px-4 py-3 text-sm focus:border-gray-900 focus:outline-none"
/>
</div>
</div>
<h2 className="text-lg font-medium uppercase tracking-wider mt-12 mb-6">
Shipping Address
</h2>
<div>
<label htmlFor="address" className="block text-sm text-gray-600 mb-2">
Address
</label>
<input
type="text"
id="address"
required
value={formData.address}
onChange={(e) => handleFieldChange('address', e.target.value)}
className="w-full border border-gray-300 px-4 py-3 text-sm focus:border-gray-900 focus:outline-none"
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label htmlFor="city" className="block text-sm text-gray-600 mb-2">
City
</label>
<input
type="text"
id="city"
required
value={formData.city}
onChange={(e) => handleFieldChange('city', e.target.value)}
className="w-full border border-gray-300 px-4 py-3 text-sm focus:border-gray-900 focus:outline-none"
/>
</div>
<div>
<label htmlFor="postcode" className="block text-sm text-gray-600 mb-2">
Postcode
</label>
<input
type="text"
id="postcode"
required
value={formData.postcode}
onChange={(e) => handleFieldChange('postcode', e.target.value)}
className="w-full border border-gray-300 px-4 py-3 text-sm focus:border-gray-900 focus:outline-none"
/>
</div>
</div>
<div>
<label htmlFor="country" className="block text-sm text-gray-600 mb-2">
Country
</label>
<select
id="country"
value={formData.country}
onChange={(e) => handleFieldChange('country', e.target.value)}
className="w-full border border-gray-300 px-4 py-3 text-sm focus:border-gray-900 focus:outline-none bg-white"
>
<option>United Kingdom</option>
<option>Ireland</option>
<option>France</option>
<option>Germany</option>
<option>United States</option>
<option>Other</option>
</select>
</div>
<div>
<label htmlFor="phone" className="block text-sm text-gray-600 mb-2">
Phone (optional)
</label>
<input
type="tel"
id="phone"
value={formData.phone}
onChange={(e) => handleFieldChange('phone', e.target.value)}
className="w-full border border-gray-300 px-4 py-3 text-sm focus:border-gray-900 focus:outline-none"
/>
</div>
<div>
<label htmlFor="notes" className="block text-sm text-gray-600 mb-2">
Order Notes (optional)
</label>
<textarea
id="notes"
rows={3}
value={formData.notes}
onChange={(e) => handleFieldChange('notes', e.target.value)}
className="w-full border border-gray-300 px-4 py-3 text-sm focus:border-gray-900 focus:outline-none resize-none"
placeholder="Any special instructions..."
/>
</div>
<div className="pt-6">
{formValid ? (
<PayPalCheckout
items={paypalItems}
shipping={formData}
shippingCost={shippingCost}
currency={currency}
onSuccess={(orderId) => {
clearCart();
router.push(`/order-confirmation?id=${orderId}`);
}}
onError={(message) => {
setError(message);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
/>
) : (
<div className="text-center py-4">
<p className="text-sm text-gray-500">
Please fill in all required fields above to proceed to payment.
</p>
</div>
)}
</div>
</div>
</div>
{/* Order Summary */}
<div className="lg:pl-8">
<div className="bg-gray-50 p-8">
<h2 className="text-lg font-medium uppercase tracking-wider mb-6">
Order Summary ({itemCount} {itemCount === 1 ? 'item' : 'items'})
</h2>
<ul className="divide-y divide-gray-200">
{items.map((item) => (
<li key={item.id} className="py-4 flex gap-4">
<div className="relative h-24 w-24 flex-shrink-0 bg-gray-100">
{item.image && (
<Image
src={getAssetUrl(item.image, { width: 200, quality: 80 })}
alt={item.title}
fill
className="object-cover"
/>
)}
</div>
<div className="flex-1">
<h3 className="text-sm font-medium">{item.title}</h3>
{item.artwork.medium && (
<p className="text-xs text-gray-500 mt-1">{item.artwork.medium}</p>
)}
<p className="text-sm font-medium mt-2">
{item.artwork.currency === 'GBP' ? '\u00a3' : '$'}{item.price.toLocaleString()}
</p>
<button
onClick={() => removeItem(item.id)}
className="text-xs text-gray-500 underline mt-2 hover:no-underline"
>
Remove
</button>
</div>
</li>
))}
</ul>
<div className="border-t border-gray-200 mt-6 pt-6 space-y-2">
<div className="flex justify-between text-sm">
<span>Subtotal</span>
<span>{currencySymbol}{subtotal.toLocaleString()}</span>
</div>
<div className="flex justify-between text-sm text-gray-500">
<span>Shipping</span>
<span>{currencySymbol}{shippingCost.toLocaleString()}</span>
</div>
</div>
<div className="border-t border-gray-200 mt-6 pt-6">
<div className="flex justify-between text-lg font-medium">
<span>Total</span>
<span>{currencySymbol}{total.toLocaleString()}</span>
</div>
</div>
</div>
<div className="mt-8 text-center">
<Link href="/store" className="text-sm underline underline-offset-2 hover:no-underline">
Continue Shopping
</Link>
</div>
</div>
</div>
</div>
</div>
);
}