fix: thank you page 401 error
- Add public /checkout/order/{id} endpoint with order_key validation
- Update checkout redirect to include order_key parameter
- Update ThankYou page to use new public endpoint with key
- Support both guest (via key) and logged-in (via customer_id) access
This commit is contained in:
@@ -74,7 +74,7 @@ export default function Checkout() {
|
||||
const [shipToDifferentAddress, setShipToDifferentAddress] = useState(false);
|
||||
const [orderNotes, setOrderNotes] = useState('');
|
||||
const [paymentMethod, setPaymentMethod] = useState(isVirtualOnly ? 'bacs' : 'cod');
|
||||
|
||||
|
||||
// Saved addresses
|
||||
const [savedAddresses, setSavedAddresses] = useState<SavedAddress[]>([]);
|
||||
const [selectedBillingAddressId, setSelectedBillingAddressId] = useState<number | null>(null);
|
||||
@@ -92,15 +92,15 @@ export default function Checkout() {
|
||||
setLoadingAddresses(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const addresses = await api.get<SavedAddress[]>('/account/addresses');
|
||||
setSavedAddresses(addresses);
|
||||
|
||||
|
||||
// Auto-select default addresses
|
||||
const defaultBilling = addresses.find(a => a.is_default && (a.type === 'billing' || a.type === 'both'));
|
||||
const defaultShipping = addresses.find(a => a.is_default && (a.type === 'shipping' || a.type === 'both'));
|
||||
|
||||
|
||||
if (defaultBilling) {
|
||||
setSelectedBillingAddressId(defaultBilling.id);
|
||||
fillBillingFromAddress(defaultBilling);
|
||||
@@ -117,10 +117,10 @@ export default function Checkout() {
|
||||
setLoadingAddresses(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
loadAddresses();
|
||||
}, [user, isVirtualOnly]);
|
||||
|
||||
|
||||
// Helper functions to fill forms from saved addresses
|
||||
const fillBillingFromAddress = (address: SavedAddress) => {
|
||||
setBillingData({
|
||||
@@ -135,7 +135,7 @@ export default function Checkout() {
|
||||
country: address.country,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const fillShippingFromAddress = (address: SavedAddress) => {
|
||||
setShippingData({
|
||||
firstName: address.first_name,
|
||||
@@ -147,13 +147,13 @@ export default function Checkout() {
|
||||
country: address.country,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handleSelectBillingAddress = (address: SavedAddress) => {
|
||||
setSelectedBillingAddressId(address.id);
|
||||
fillBillingFromAddress(address);
|
||||
setShowBillingForm(false); // Hide form when address is selected
|
||||
};
|
||||
|
||||
|
||||
const handleSelectShippingAddress = (address: SavedAddress) => {
|
||||
setSelectedShippingAddressId(address.id);
|
||||
fillShippingFromAddress(address);
|
||||
@@ -235,15 +235,15 @@ export default function Checkout() {
|
||||
// Submit order
|
||||
const response = await apiClient.post('/checkout/submit', orderData);
|
||||
const data = (response as any).data || response;
|
||||
|
||||
|
||||
if (data.ok && data.order_id) {
|
||||
// Clear cart
|
||||
cart.items.forEach(item => {
|
||||
useCartStore.getState().removeItem(item.key);
|
||||
});
|
||||
|
||||
|
||||
toast.success('Order placed successfully!');
|
||||
navigate(`/order-received/${data.order_id}`);
|
||||
navigate(`/order-received/${data.order_id}?key=${data.order_key}`);
|
||||
} else {
|
||||
throw new Error(data.error || 'Failed to create order');
|
||||
}
|
||||
@@ -307,7 +307,7 @@ export default function Checkout() {
|
||||
Change Address
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
{selectedBillingAddressId ? (
|
||||
(() => {
|
||||
const selected = savedAddresses.find(a => a.id === selectedBillingAddressId);
|
||||
@@ -344,7 +344,7 @@ export default function Checkout() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Billing Address Modal */}
|
||||
<AddressSelector
|
||||
isOpen={showBillingModal}
|
||||
@@ -354,204 +354,19 @@ export default function Checkout() {
|
||||
onSelectAddress={handleSelectBillingAddress}
|
||||
type="billing"
|
||||
/>
|
||||
|
||||
|
||||
{/* Billing Details Form - Only show if no saved address selected or user wants to enter manually */}
|
||||
{(savedAddresses.length === 0 || !selectedBillingAddressId || showBillingForm) && (
|
||||
<div className="bg-white border rounded-lg p-6">
|
||||
<h2 className="text-xl font-bold mb-4">Billing Details</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">First Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.firstName}
|
||||
onChange={(e) => setBillingData({ ...billingData, firstName: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Last Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.lastName}
|
||||
onChange={(e) => setBillingData({ ...billingData, lastName: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-2">Email Address *</label>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
value={billingData.email}
|
||||
onChange={(e) => setBillingData({ ...billingData, email: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-2">Phone *</label>
|
||||
<input
|
||||
type="tel"
|
||||
required
|
||||
value={billingData.phone}
|
||||
onChange={(e) => setBillingData({ ...billingData, phone: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Address fields - only for physical products */}
|
||||
{!isVirtualOnly && (
|
||||
<>
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-2">Street Address *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.address}
|
||||
onChange={(e) => setBillingData({ ...billingData, address: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">City *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.city}
|
||||
onChange={(e) => setBillingData({ ...billingData, city: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">State / Province *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.state}
|
||||
onChange={(e) => setBillingData({ ...billingData, state: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Postcode / ZIP *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.postcode}
|
||||
onChange={(e) => setBillingData({ ...billingData, postcode: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Country *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.country}
|
||||
onChange={(e) => setBillingData({ ...billingData, country: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Ship to Different Address - only for physical products */}
|
||||
{!isVirtualOnly && (
|
||||
<div className="bg-white border rounded-lg p-6">
|
||||
<label className="flex items-center gap-2 mb-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={shipToDifferentAddress}
|
||||
onChange={(e) => setShipToDifferentAddress(e.target.checked)}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span className="font-medium">Ship to a different address?</span>
|
||||
</label>
|
||||
|
||||
{shipToDifferentAddress && (
|
||||
<>
|
||||
{/* Selected Shipping Address Summary */}
|
||||
{!loadingAddresses && savedAddresses.length > 0 && savedAddresses.some(a => a.type === 'shipping' || a.type === 'both') && (
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<MapPin className="w-4 h-4" />
|
||||
Shipping Address
|
||||
</h3>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowShippingModal(true)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Edit2 className="w-4 h-4" />
|
||||
Change Address
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{selectedShippingAddressId ? (
|
||||
(() => {
|
||||
const selected = savedAddresses.find(a => a.id === selectedShippingAddressId);
|
||||
return selected ? (
|
||||
<div>
|
||||
<div className="bg-primary/5 border-2 border-primary rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<p className="font-semibold">{selected.label}</p>
|
||||
{selected.is_default && (
|
||||
<span className="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded">Default</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm font-medium text-gray-900">{selected.first_name} {selected.last_name}</p>
|
||||
{selected.phone && <p className="text-sm text-gray-600">{selected.phone}</p>}
|
||||
<p className="text-sm text-gray-600 mt-2">{selected.address_1}</p>
|
||||
{selected.address_2 && <p className="text-sm text-gray-600">{selected.address_2}</p>}
|
||||
<p className="text-sm text-gray-600">{selected.city}, {selected.state} {selected.postcode}</p>
|
||||
<p className="text-sm text-gray-600">{selected.country}</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowShippingForm(true)}
|
||||
className="mt-3 text-primary hover:text-primary"
|
||||
>
|
||||
Use a different address
|
||||
</Button>
|
||||
</div>
|
||||
) : null;
|
||||
})()
|
||||
) : (
|
||||
<p className="text-gray-500 text-sm">No address selected</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Shipping Address Modal */}
|
||||
<AddressSelector
|
||||
isOpen={showShippingModal}
|
||||
onClose={() => setShowShippingModal(false)}
|
||||
addresses={savedAddresses}
|
||||
selectedAddressId={selectedShippingAddressId}
|
||||
onSelectAddress={handleSelectShippingAddress}
|
||||
type="shipping"
|
||||
/>
|
||||
|
||||
{/* Shipping Form - Only show if no saved address selected or user wants to enter manually */}
|
||||
{(!selectedShippingAddressId || showShippingForm) && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="bg-white border rounded-lg p-6">
|
||||
<h2 className="text-xl font-bold mb-4">Billing Details</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">First Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.firstName}
|
||||
onChange={(e) => setShippingData({ ...shippingData, firstName: e.target.value })}
|
||||
value={billingData.firstName}
|
||||
onChange={(e) => setBillingData({ ...billingData, firstName: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
@@ -560,66 +375,251 @@ export default function Checkout() {
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.lastName}
|
||||
onChange={(e) => setShippingData({ ...shippingData, lastName: e.target.value })}
|
||||
value={billingData.lastName}
|
||||
onChange={(e) => setBillingData({ ...billingData, lastName: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-2">Street Address *</label>
|
||||
<label className="block text-sm font-medium mb-2">Email Address *</label>
|
||||
<input
|
||||
type="text"
|
||||
type="email"
|
||||
required
|
||||
value={shippingData.address}
|
||||
onChange={(e) => setShippingData({ ...shippingData, address: e.target.value })}
|
||||
value={billingData.email}
|
||||
onChange={(e) => setBillingData({ ...billingData, email: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">City *</label>
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-2">Phone *</label>
|
||||
<input
|
||||
type="text"
|
||||
type="tel"
|
||||
required
|
||||
value={shippingData.city}
|
||||
onChange={(e) => setShippingData({ ...shippingData, city: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">State / Province *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.state}
|
||||
onChange={(e) => setShippingData({ ...shippingData, state: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Postcode / ZIP *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.postcode}
|
||||
onChange={(e) => setShippingData({ ...shippingData, postcode: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Country *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.country}
|
||||
onChange={(e) => setShippingData({ ...shippingData, country: e.target.value })}
|
||||
value={billingData.phone}
|
||||
onChange={(e) => setBillingData({ ...billingData, phone: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Address fields - only for physical products */}
|
||||
{!isVirtualOnly && (
|
||||
<>
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-2">Street Address *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.address}
|
||||
onChange={(e) => setBillingData({ ...billingData, address: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">City *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.city}
|
||||
onChange={(e) => setBillingData({ ...billingData, city: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">State / Province *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.state}
|
||||
onChange={(e) => setBillingData({ ...billingData, state: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Postcode / ZIP *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.postcode}
|
||||
onChange={(e) => setBillingData({ ...billingData, postcode: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Country *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingData.country}
|
||||
onChange={(e) => setBillingData({ ...billingData, country: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Ship to Different Address - only for physical products */}
|
||||
{!isVirtualOnly && (
|
||||
<div className="bg-white border rounded-lg p-6">
|
||||
<label className="flex items-center gap-2 mb-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={shipToDifferentAddress}
|
||||
onChange={(e) => setShipToDifferentAddress(e.target.checked)}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span className="font-medium">Ship to a different address?</span>
|
||||
</label>
|
||||
|
||||
{shipToDifferentAddress && (
|
||||
<>
|
||||
{/* Selected Shipping Address Summary */}
|
||||
{!loadingAddresses && savedAddresses.length > 0 && savedAddresses.some(a => a.type === 'shipping' || a.type === 'both') && (
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<MapPin className="w-4 h-4" />
|
||||
Shipping Address
|
||||
</h3>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowShippingModal(true)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Edit2 className="w-4 h-4" />
|
||||
Change Address
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{selectedShippingAddressId ? (
|
||||
(() => {
|
||||
const selected = savedAddresses.find(a => a.id === selectedShippingAddressId);
|
||||
return selected ? (
|
||||
<div>
|
||||
<div className="bg-primary/5 border-2 border-primary rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<p className="font-semibold">{selected.label}</p>
|
||||
{selected.is_default && (
|
||||
<span className="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded">Default</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm font-medium text-gray-900">{selected.first_name} {selected.last_name}</p>
|
||||
{selected.phone && <p className="text-sm text-gray-600">{selected.phone}</p>}
|
||||
<p className="text-sm text-gray-600 mt-2">{selected.address_1}</p>
|
||||
{selected.address_2 && <p className="text-sm text-gray-600">{selected.address_2}</p>}
|
||||
<p className="text-sm text-gray-600">{selected.city}, {selected.state} {selected.postcode}</p>
|
||||
<p className="text-sm text-gray-600">{selected.country}</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowShippingForm(true)}
|
||||
className="mt-3 text-primary hover:text-primary"
|
||||
>
|
||||
Use a different address
|
||||
</Button>
|
||||
</div>
|
||||
) : null;
|
||||
})()
|
||||
) : (
|
||||
<p className="text-gray-500 text-sm">No address selected</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Shipping Address Modal */}
|
||||
<AddressSelector
|
||||
isOpen={showShippingModal}
|
||||
onClose={() => setShowShippingModal(false)}
|
||||
addresses={savedAddresses}
|
||||
selectedAddressId={selectedShippingAddressId}
|
||||
onSelectAddress={handleSelectShippingAddress}
|
||||
type="shipping"
|
||||
/>
|
||||
|
||||
{/* Shipping Form - Only show if no saved address selected or user wants to enter manually */}
|
||||
{(!selectedShippingAddressId || showShippingForm) && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">First Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.firstName}
|
||||
onChange={(e) => setShippingData({ ...shippingData, firstName: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Last Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.lastName}
|
||||
onChange={(e) => setShippingData({ ...shippingData, lastName: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-2">Street Address *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.address}
|
||||
onChange={(e) => setShippingData({ ...shippingData, address: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">City *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.city}
|
||||
onChange={(e) => setShippingData({ ...shippingData, city: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">State / Province *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.state}
|
||||
onChange={(e) => setShippingData({ ...shippingData, state: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Postcode / ZIP *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.postcode}
|
||||
onChange={(e) => setShippingData({ ...shippingData, postcode: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Country *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingData.country}
|
||||
onChange={(e) => setShippingData({ ...shippingData, country: e.target.value })}
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Order Notes */}
|
||||
@@ -722,30 +722,30 @@ export default function Checkout() {
|
||||
{/* Hide COD for virtual-only products */}
|
||||
{!isVirtualOnly && (
|
||||
<label className="flex items-center gap-2 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
|
||||
<input
|
||||
type="radio"
|
||||
name="payment"
|
||||
value="cod"
|
||||
<input
|
||||
type="radio"
|
||||
name="payment"
|
||||
value="cod"
|
||||
checked={paymentMethod === 'cod'}
|
||||
onChange={(e) => setPaymentMethod(e.target.value)}
|
||||
className="w-4 h-4"
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span>Cash on Delivery</span>
|
||||
</label>
|
||||
)}
|
||||
<label className="flex items-center gap-2 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
|
||||
<input
|
||||
type="radio"
|
||||
name="payment"
|
||||
value="bacs"
|
||||
<input
|
||||
type="radio"
|
||||
name="payment"
|
||||
value="bacs"
|
||||
checked={paymentMethod === 'bacs'}
|
||||
onChange={(e) => setPaymentMethod(e.target.value)}
|
||||
className="w-4 h-4"
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span>Bank Transfer</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Payment Icons */}
|
||||
{elements.payment_icons && (
|
||||
<div className="flex items-center gap-2 mt-3 pt-3 border-t">
|
||||
|
||||
Reference in New Issue
Block a user