sv_webshop/src/routes/(auth)/checkout/+page.svelte
2025-01-13 14:31:37 +01:00

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>