193 lines
4.8 KiB
Svelte
193 lines
4.8 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import { clearCart, cart, type Product } from '$lib/stores/cartStore';
|
|
import { Card, Input, Button, Alert } from 'flowbite-svelte';
|
|
import { fade } from 'svelte/transition';
|
|
|
|
// Form state
|
|
let formData = {
|
|
name: '',
|
|
email: '',
|
|
address: '',
|
|
paymentMethod: 'credit_card'
|
|
};
|
|
|
|
let isProcessing = false;
|
|
let error = '';
|
|
let success = '';
|
|
|
|
// Calculate the total price
|
|
$: total = $cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
|
|
|
// Form validation
|
|
$: isFormValid =
|
|
formData.name.length > 0 &&
|
|
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) &&
|
|
formData.address.length > 0;
|
|
|
|
// Handle form submission
|
|
const handleCheckout = async () => {
|
|
if (!isFormValid) {
|
|
error = 'Please fill in all required fields correctly.';
|
|
return;
|
|
}
|
|
|
|
isProcessing = true;
|
|
error = '';
|
|
|
|
const checkoutRequest = {
|
|
...formData,
|
|
items: $cart
|
|
};
|
|
|
|
try {
|
|
clearCart();
|
|
goto(`/order-confirmation/12345`);
|
|
} catch (err) {
|
|
error = 'Failed to process checkout. Please try again.';
|
|
} finally {
|
|
isProcessing = false;
|
|
}
|
|
};
|
|
|
|
let modalOpen = false;
|
|
let selectedImage = {
|
|
url: '',
|
|
title: ''
|
|
};
|
|
|
|
const openImageModal = (product: Product) => {
|
|
selectedImage = {
|
|
url: product.imageUrl,
|
|
title: product.title
|
|
};
|
|
modalOpen = true;
|
|
};
|
|
|
|
const closeImageModal = () => {
|
|
modalOpen = false;
|
|
};
|
|
</script>
|
|
|
|
<main class="mx-auto max-w-4xl p-6">
|
|
<h1 class="mb-6 text-3xl font-bold">Checkout</h1>
|
|
|
|
{#if $cart.length === 0}
|
|
<Alert color="red" class="mb-6">
|
|
Your cart is empty. Please add items before checking out.
|
|
</Alert>
|
|
{:else}
|
|
<div class="flex gap-10">
|
|
<!-- Order Summary -->
|
|
<Card class="mb-6">
|
|
<h2 class="mb-4 text-xl font-semibold">Order Summary</h2>
|
|
<ul class="space-y-3">
|
|
{#each $cart as item}
|
|
<li class="flex items-center justify-between rounded-lg bg-gray-50 p-3">
|
|
<div class="flex">
|
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
<img
|
|
src={item.imageUrl}
|
|
alt="item"
|
|
class="me-2 h-20 w-20"
|
|
on:click={() => openImageModal(item)}
|
|
on:keydown={(e) => e.key === 'Enter' && openImageModal(item)}
|
|
tabindex="0"
|
|
/>
|
|
<div>
|
|
<h3 class="font-semibold">{item.title}</h3>
|
|
<p class="text-sm text-gray-600">Quantity: {item.quantity}</p>
|
|
</div>
|
|
</div>
|
|
<span class="text-lg font-medium">
|
|
${(item.price * item.quantity).toFixed(2)}
|
|
</span>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
<div class="mt-4 border-t pt-4">
|
|
<div class="flex justify-between text-lg">
|
|
<strong>Total:</strong>
|
|
<span class="font-bold">${total.toFixed(2)}</span>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
<!-- Customer Details Form -->
|
|
<Card class="mb-6">
|
|
<h2 class="mb-4 text-xl font-semibold">Customer Details</h2>
|
|
<form on:submit|preventDefault={handleCheckout} class="space-y-4">
|
|
<Input
|
|
label="Full Name"
|
|
bind:value={formData.name}
|
|
placeholder="John Doe"
|
|
required
|
|
error={!formData.name && 'Name is required'}
|
|
/>
|
|
|
|
<Input
|
|
label="Email"
|
|
type="email"
|
|
bind:value={formData.email}
|
|
placeholder="john@example.com"
|
|
required
|
|
error={formData.email &&
|
|
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) &&
|
|
'Please enter a valid email'}
|
|
/>
|
|
|
|
<Input
|
|
label="Shipping Address"
|
|
bind:value={formData.address}
|
|
placeholder="123 Main St, City, Country"
|
|
required
|
|
error={!formData.address && 'Address is required'}
|
|
/>
|
|
|
|
<div>
|
|
<label class="mb-2 block font-medium">Payment Method</label>
|
|
<div class="space-y-2">
|
|
{#each [{ value: 'credit_card', label: 'Credit Card', icon: '💳' }, { value: 'paypal', label: 'PayPal', icon: '🅿️' }, { value: 'cash_on_delivery', label: 'Cash on Delivery', icon: '💵' }] as method}
|
|
<label
|
|
class="flex cursor-pointer items-center gap-3 rounded-lg border p-3 transition-colors hover:bg-gray-50"
|
|
>
|
|
<input
|
|
type="radio"
|
|
bind:group={formData.paymentMethod}
|
|
value={method.value}
|
|
class="h-4 w-4"
|
|
/>
|
|
<span>{method.icon}</span>
|
|
<span>{method.label}</span>
|
|
</label>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
{#if error}
|
|
<Alert color="red">
|
|
{error}
|
|
</Alert>
|
|
{/if}
|
|
|
|
{#if success}
|
|
<Alert color="green">
|
|
{success}
|
|
</Alert>
|
|
{/if}
|
|
|
|
<Button type="submit" color="blue" class="w-full" disabled={isProcessing || !isFormValid}>
|
|
{#if isProcessing}
|
|
<span class="inline-block animate-spin">⌛</span>
|
|
Processing...
|
|
{:else}
|
|
Place Order (${total.toFixed(2)})
|
|
{/if}
|
|
</Button>
|
|
</form>
|
|
</Card>
|
|
</div>
|
|
{/if}
|
|
</main>
|