This commit is contained in:
Mohamad 2025-01-13 17:52:16 +01:00
parent ca49ce7730
commit f347f80f1b
9 changed files with 426 additions and 459 deletions

View File

@ -9,10 +9,11 @@
import { authService, cartService } from '$lib/services/api'; import { authService, cartService } from '$lib/services/api';
let isOpen = false; let isOpen = false;
let isUpdating = false; let updatingItemId: string | null = null;
let updateTimeout: NodeJS.Timeout; let updateTimeout: NodeJS.Timeout;
let cartItems: CartItem[] = []; let cartItems: CartItem[] = [];
let isLoading = true; let isLoading = true;
let showClearConfirmation = false;
onMount(async () => { onMount(async () => {
await loadCart(); await loadCart();
@ -41,7 +42,7 @@
// Debounced quantity update // Debounced quantity update
const handleQuantityChange = async (cartItemId: string, quantity: number) => { const handleQuantityChange = async (cartItemId: string, quantity: number) => {
clearTimeout(updateTimeout); clearTimeout(updateTimeout);
isUpdating = true; updatingItemId = cartItemId;
updateTimeout = setTimeout(async () => { updateTimeout = setTimeout(async () => {
try { try {
@ -50,10 +51,21 @@
} catch (error) { } catch (error) {
console.error('Failed to update quantity:', error); console.error('Failed to update quantity:', error);
} }
isUpdating = false; updatingItemId = null;
}, 500); }, 500);
}; };
// Handle increment/decrement buttons
const updateQuantity = async (cartItemId: string, delta: number) => {
const item = cartItems.find((item) => item.id === cartItemId);
if (item) {
const newQuantity = item.quantity + delta;
if (newQuantity >= 1) {
await handleQuantityChange(cartItemId, newQuantity);
}
}
};
const handleClickOutside = () => { const handleClickOutside = () => {
isOpen = false; isOpen = false;
}; };
@ -79,6 +91,7 @@
// Clear entire cart // Clear entire cart
const clearEntireCart = async () => { const clearEntireCart = async () => {
showClearConfirmation = false;
try { try {
const currentUser = authService.getCurrentUser(); const currentUser = authService.getCurrentUser();
if (currentUser) { if (currentUser) {
@ -95,11 +108,22 @@
isOpen = !isOpen; isOpen = !isOpen;
goto('/main'); goto('/main');
}; };
// Keyboard event handling
onMount(() => {
window.addEventListener('keydown', handleKeyDown);
});
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpen) {
isOpen = false;
}
};
</script> </script>
<!-- Cart Icon Button with Badge --> <!-- Cart Icon Button with Badge -->
<div class="relative"> <div class="relative">
<Button on:click={() => (isOpen = !isOpen)} class="relative h-12"> <Button on:click={() => (isOpen = !isOpen)} class="relative h-12" aria-label="Open Cart">
<CartOutline size="lg" /> <CartOutline size="lg" />
{#if cartItems.length > 0} {#if cartItems.length > 0}
<span <span
@ -124,23 +148,32 @@
{#if isLoading} {#if isLoading}
<p class="py-4 text-center text-gray-500">Loading cart...</p> <p class="py-4 text-center text-gray-500">Loading cart...</p>
{:else if cartItems.length === 0} {:else if cartItems.length === 0}
<p class="py-4 text-center text-gray-500">Your cart is empty</p> <div class="flex flex-col items-center py-6">
<img src="/empty-cart.svg" alt="Empty Cart" class="mb-4 h-32 w-32" />
<p class="mb-4 text-center text-gray-500">Your cart is empty</p>
<Button size="xl" on:click={goToShop}> <Button size="xl" on:click={goToShop}>
Shop Now <ArrowRightOutline class="ml-2 h-5 w-5" /> Shop Now <ArrowRightOutline class="ml-2 h-5 w-5" />
</Button> </Button>
</div>
{:else} {:else}
<ul class="max-h-96 space-y-3 overflow-y-auto"> <ul class="max-h-96 space-y-3 overflow-x-hidden overflow-y-scroll">
{#each cartItems as item (item.id)} {#each cartItems as item (item.id)}
<li <li
id="cart-item-{item.id}" id="cart-item-{item.id}"
class="flex items-center justify-between rounded-lg bg-gray-50 p-3 transition-all duration-200 hover:bg-gray-100" class=" flex items-center justify-between rounded-lg bg-gray-50 p-3 transition-all duration-200 hover:scale-105 hover:bg-gray-100"
> >
<div class="flex-grow"> <div class="flex-grow">
<h3 class="font-semibold">{item.expand?.product?.title}</h3> <h3 class="font-semibold">{item.expand?.product?.title}</h3>
<p class="text-sm text-gray-600">${item.expand?.product?.price.toFixed(2)} each</p> <p class="text-sm text-gray-600">${item.expand?.product?.price.toFixed(2)} each</p>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="relative"> <div class="flex items-center gap-2">
<button
on:click={() => updateQuantity(item.id, -1)}
class="rounded bg-gray-200 p-1 hover:bg-gray-300"
>
</button>
<input <input
type="number" type="number"
value={item.quantity} value={item.quantity}
@ -148,11 +181,12 @@
on:input={(e) => handleQuantityChange(item.id, parseInt(e.currentTarget.value))} on:input={(e) => handleQuantityChange(item.id, parseInt(e.currentTarget.value))}
class="w-16 rounded border p-1 text-center" class="w-16 rounded border p-1 text-center"
/> />
{#if isUpdating} <button
<span on:click={() => updateQuantity(item.id, 1)}
class="absolute -top-1 right-0 h-2 w-2 animate-pulse rounded-full bg-blue-500" class="rounded bg-gray-200 p-1 hover:bg-gray-300"
></span> >
{/if}
</button>
</div> </div>
<button <button
on:click={() => removeItem(item.id)} on:click={() => removeItem(item.id)}
@ -173,13 +207,32 @@
</div> </div>
<div class="mt-4 space-y-2"> <div class="mt-4 space-y-2">
<Button href="/checkout" on:click={() => (isOpen = !isOpen)} class="w-full" <Button href="/checkout" on:click={() => (isOpen = !isOpen)} class="w-full">
>Proceed to Checkout</Button Proceed to Checkout
</Button>
<Button
on:click={() => (showClearConfirmation = true)}
color="red"
class="w-full"
variant="outline"
> >
<Button on:click={clearEntireCart} color="red" class="w-full" variant="outline">
Clear Cart Clear Cart
</Button> </Button>
</div> </div>
{/if} {/if}
</div> </div>
{/if} {/if}
<!-- Confirmation Dialog for Clearing Cart -->
{#if showClearConfirmation}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div class="rounded-lg bg-white p-6 shadow-lg">
<h3 class="mb-4 text-lg font-bold">Are you sure?</h3>
<p class="mb-6">This will remove all items from your cart.</p>
<div class="flex justify-end gap-3">
<Button on:click={() => (showClearConfirmation = false)} variant="outline">Cancel</Button>
<Button on:click={clearEntireCart} color="red">Clear Cart</Button>
</div>
</div>
</div>
{/if}

View File

@ -178,6 +178,19 @@ export const favoritesService = {
} catch (error) { } catch (error) {
throw handleError(error); throw handleError(error);
} }
},
clearFavorites: async (userId: string): Promise<void> => {
try {
const favorites = await pb.collection('favorites').getFullList({
filter: `user = "${userId}"`
});
for (const favorite of favorites) {
await pb.collection('favorites').delete(favorite.id);
}
} catch (error) {
throw handleError(error);
}
} }
}; };

View File

@ -24,8 +24,8 @@ export interface CartItem {
user: string; // User ID user: string; // User ID
product: string; // Product ID product: string; // Product ID
quantity: number; quantity: number;
created: string; created?: string;
updated: string; updated?: string;
expand?: { expand?: {
product: Product; // Expanded product details product: Product; // Expanded product details
}; };
@ -39,7 +39,7 @@ export interface Favorite {
created: string; created: string;
updated: string; updated: string;
expand?: { expand?: {
product: Product; // Expanded product details product?: Product[]; // Expanded product details
}; };
} }

View File

@ -1,21 +0,0 @@
const USER_KEY = 'user';
const PASS_KEY = 'username';
export const login = (user: string, password: string) => {
localStorage.setItem(USER_KEY, user);
localStorage.setItem(PASS_KEY, password);
};
export const logout = () => {
localStorage.removeItem(USER_KEY);
localStorage.removeItem(PASS_KEY);
window.location.reload();
};
export const loggedIn = () => localStorage.getItem(USER_KEY) !== null;
export const name = () => localStorage.getItem(PASS_KEY) ?? '';
export const auth = () => localStorage.getItem(USER_KEY);
export default { login, logout, loggedIn, name, auth };

View File

@ -1,47 +0,0 @@
import { writable } from 'svelte/store';
// Define the type for a product
export interface Product {
id: number;
title: string;
description: string;
imageUrl: string;
price: number;
quantity: number;
}
// Initialize the cart store
export const cart = writable<Product[]>([]);
// Add a product to the cart
export const addToCart = (product: Product) => {
cart.update((items) => {
const existingItem = items.find((item) => item.id === product.id);
if (existingItem) {
// If the product already exists, increase the quantity
return items.map((item) =>
item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item
);
} else {
// If the product doesn't exist, add it to the cart
return [...items, { ...product, quantity: 1 }];
}
});
};
// Remove a product from the cart
export const removeFromCart = (productId: number) => {
cart.update((items) => items.filter((item) => item.id !== productId));
};
// Update the quantity of a product in the cart
export const updateQuantity = (productId: number, quantity: number) => {
cart.update((items) =>
items.map((item) => (item.id === productId ? { ...item, quantity } : item))
);
};
// Clear the cart
export const clearCart = () => {
cart.set([]);
};

View File

@ -1,86 +0,0 @@
import { writable } from 'svelte/store';
// Define the type for a product
export interface Product {
id: number;
title: string;
description: string;
imageUrl: string;
price: number; // Added price for cart calculations
quantity?: number; // Optional quantity for cart items
}
// Define the type for the cart and favorites
export interface AppState {
cart: Product[];
favorites: Product[];
}
// Initialize the store
const initialState: AppState = {
cart: [],
favorites: []
};
export const appStore = writable<AppState>(initialState);
export const favorites = writable<Product[]>([]);
// Add a product to the cart
export const addToCart = (product: Product) => {
appStore.update((state) => {
const existingItem = state.cart.find((item) => item.id === product.id);
if (existingItem) {
// If the product already exists, increase the quantity
return {
...state,
cart: state.cart.map((item) =>
item.id === product.id ? { ...item, quantity: (item.quantity || 1) + 1 } : item
)
};
} else {
// If the product doesn't exist, add it to the cart with quantity 1
return { ...state, cart: [...state.cart, { ...product, quantity: 1 }] };
}
});
};
// Remove a product from the cart
export const removeFromCart = (productId: number) => {
appStore.update((state) => ({
...state,
cart: state.cart.filter((item) => item.id !== productId)
}));
};
// Update the quantity of a product in the cart
export const updateCartQuantity = (productId: number, quantity: number) => {
appStore.update((state) => ({
...state,
cart: state.cart.map((item) => (item.id === productId ? { ...item, quantity } : item))
}));
};
// Add a product to favorites
export const addToFavorites = (product: Product) => {
favorites.update((items) => {
if (!items.some((item) => item.id === product.id)) {
return [...items, product];
}
return items;
});
};
// Remove a product from favorites
export const removeFromFavorites = (productId: number) => {
favorites.update((items) => items.filter((item) => item.id !== productId));
};
// Clear the cart
export const clearCart = () => {
appStore.update((state) => ({ ...state, cart: [] }));
};
// Clear favorites
export const clearFavorites = () => {
favorites.update((state) => ({ ...state, favorites: [] }));
};

View File

@ -1,193 +1,170 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte';
import { cartService, orderService, authService } from '$lib/services/api';
import { Button, Input, Card, Badge } from 'flowbite-svelte';
import { ArrowRightOutline, CheckCircleOutline } from 'flowbite-svelte-icons';
import type { CartItem } from '$lib/services/types';
import { goto } from '$app/navigation'; 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 // State variables
let formData = { let cartItems: CartItem[] = [];
name: '', let total = 0;
email: '', let name = '';
address: '', let email = '';
paymentMethod: 'credit_card' let address = '';
}; let paymentMethod = 'credit_card';
let isLoading = false;
let isProcessing = false; // Fetch the user's cart on mount
let error = ''; onMount(async () => {
let success = ''; const user = authService.getCurrentUser();
if (!user) {
// Calculate the total price goto('/login'); // Redirect to login if not authenticated
$: 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; return;
} }
isProcessing = true; try {
error = ''; cartItems = await cartService.getCart(user.id);
total = cartItems.reduce(
(sum, item) => sum + (item.expand?.product.price || 0) * item.quantity,
0
);
} catch (error) {
console.error('Failed to fetch cart:', error);
}
});
const checkoutRequest = { // Handle form submission
...formData, const handleCheckout = async () => {
items: $cart const user = authService.getCurrentUser();
}; if (!user) {
goto('/login'); // Redirect to login if not authenticated
return;
}
isLoading = true;
try { try {
clearCart(); // Prepare order items
const items = cartItems.map((item) => ({
product: item.product,
quantity: item.quantity
}));
goto(`/order-confirmation/12345`); // Create the order
} catch (err) { const order = await orderService.createOrder(user.id, items, total);
error = 'Failed to process checkout. Please try again.';
// Clear the cart
await cartService.clearCart(user.id);
// Redirect to order confirmation page
goto(`/order-confirmation/${order.id}`);
} catch (error) {
console.error('Failed to place order:', error);
alert('Failed to place order. Please try again.');
} finally { } finally {
isProcessing = false; isLoading = false;
} }
}; };
let modalOpen = false; onMount(() => {
let selectedImage = { const elements = document.querySelectorAll('.fade-in');
url: '', elements.forEach((el) => {
title: '' el.classList.add('visible');
}; });
});
const openImageModal = (product: Product) => {
selectedImage = {
url: product.imageUrl,
title: product.title
};
modalOpen = true;
};
const closeImageModal = () => {
modalOpen = false;
};
</script> </script>
<main class="mx-auto max-w-4xl p-6"> <main class="container mx-auto px-4 py-8">
<h1 class="mb-6 text-3xl font-bold">Checkout</h1> <!-- Hero Section -->
<section class="mb-8 rounded-lg bg-gradient-to-r from-pink-100 to-purple-100 py-20">
<div class="container mx-auto px-4 text-center">
<h1 class="mb-4 text-5xl font-bold text-gray-900">Checkout</h1>
<p class="mb-8 text-xl text-gray-600">
Review your order and enter your details to complete the purchase.
</p>
</div>
</section>
{#if $cart.length === 0} <!-- Cart Summary -->
<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"> <Card class="mb-6">
<h2 class="mb-4 text-xl font-semibold">Order Summary</h2> <h2 class="mb-4 text-2xl font-bold">Order Summary</h2>
<ul class="space-y-3"> <ul class="space-y-4">
{#each $cart as item} {#each cartItems as item}
<li class="flex items-center justify-between rounded-lg bg-gray-50 p-3"> <li class="flex items-center justify-between border-b pb-4">
<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> <div>
<h3 class="font-semibold">{item.title}</h3> <h3 class="text-xl font-semibold text-gray-900">{item.expand?.product.title}</h3>
<p class="text-sm text-gray-600">Quantity: {item.quantity}</p> <p class="text-gray-600">x{item.quantity}</p>
</div> </div>
</div> <p class="text-2xl font-bold text-gray-900">
<span class="text-lg font-medium"> ${((item.expand?.product.price || 0) * item.quantity).toFixed(2)}
${(item.price * item.quantity).toFixed(2)} </p>
</span>
</li> </li>
{/each} {/each}
</ul> </ul>
<div class="mt-4 border-t pt-4"> <div class="mt-6 border-t pt-4">
<div class="flex justify-between text-lg"> <p class="text-2xl font-bold text-gray-900">Total: ${total.toFixed(2)}</p>
<strong>Total:</strong>
<span class="font-bold">${total.toFixed(2)}</span>
</div>
</div> </div>
</Card> </Card>
<!-- Customer Details Form --> <!-- Customer Details Form -->
<Card class="mb-6"> <Card class="mb-6">
<h2 class="mb-4 text-xl font-semibold">Customer Details</h2> <h2 class="mb-4 text-2xl font-bold">Customer Details</h2>
<form on:submit|preventDefault={handleCheckout} class="space-y-4"> <form on:submit|preventDefault={handleCheckout} class="space-y-4">
<Input <Input label="Full Name" bind:value={name} placeholder="John Doe" required />
label="Full Name"
bind:value={formData.name}
placeholder="John Doe"
required
error={!formData.name && 'Name is required'}
/>
<Input <Input
label="Email" label="Email"
type="email" type="email"
bind:value={formData.email} bind:value={email}
placeholder="john@example.com" placeholder="john@example.com"
required required
error={formData.email &&
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) &&
'Please enter a valid email'}
/> />
<Input <Input
label="Shipping Address" label="Shipping Address"
bind:value={formData.address} bind:value={address}
placeholder="123 Main St, City, Country" placeholder="123 Main St, City, Country"
required required
error={!formData.address && 'Address is required'}
/> />
<Input
<div> label="Payment Method"
<label class="mb-2 block font-medium">Payment Method</label> bind:value={paymentMethod}
<div class="space-y-2"> options={[
{#each [{ value: 'credit_card', label: 'Credit Card', icon: '💳' }, { value: 'paypal', label: 'PayPal', icon: '🅿️' }, { value: 'cash_on_delivery', label: 'Cash on Delivery', icon: '💵' }] as method} { value: 'credit_card', name: 'Credit Card' },
<label { value: 'paypal', name: 'PayPal' },
class="flex cursor-pointer items-center gap-3 rounded-lg border p-3 transition-colors hover:bg-gray-50" { value: 'cash_on_delivery', name: 'Cash on Delivery' }
> ]}
<input required
type="radio"
bind:group={formData.paymentMethod}
value={method.value}
class="h-4 w-4"
/> />
<span>{method.icon}</span> <Button type="submit" color="blue" class="w-full" disabled={isLoading}>
<span>{method.label}</span> {isLoading ? 'Placing Order...' : 'Place Order'}
</label> <ArrowRightOutline class="ml-2 h-5 w-5" />
{/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> </Button>
</form> </form>
</Card> </Card>
</div>
{/if} <!-- Guarantee Section -->
<section class="rounded-lg bg-gradient-to-r from-pink-100 to-purple-100 py-12 text-center">
<CheckCircleOutline class="mx-auto mb-4 h-12 w-12 text-purple-500" />
<h2 class="mb-4 text-2xl font-bold text-gray-900">Satisfaction Guaranteed</h2>
<p class="text-gray-600">
We stand by our products with a 100% satisfaction guarantee. If you're not happy, we'll make
it right.
</p>
</section>
</main> </main>
<style>
/* Add custom animations */
.fade-in {
opacity: 0;
transform: translateY(20px);
transition:
opacity 0.6s ease-out,
transform 0.6s ease-out;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}
</style>

View File

@ -1,33 +1,85 @@
<script lang="ts"> <script lang="ts">
import { favorites, removeFromFavorites, clearFavorites } from '$lib/stores/store'; import { onMount } from 'svelte';
import { favoritesService, authService } from '$lib/services/api';
import { Button, Card } from 'flowbite-svelte';
import { TrashBinOutline } from 'flowbite-svelte-icons';
import { goto } from '$app/navigation';
import type { Favorite } from '$lib/services/types';
let favorites: Favorite[] = [];
let isLoading = true;
const fetchFavorites = async () => {
const user = authService.getCurrentUser();
if (!user) {
goto('/login');
return;
}
try {
const fetchedFavorites = await favoritesService.getFavorites(user.id);
favorites = fetchedFavorites.map((favorite: Favorite) => ({
...favorite,
expand: favorite.expand ?? { product: [] }
}));
} catch (error) {
console.error('Failed to fetch favorites:', error);
} finally {
isLoading = false;
}
};
const removeFromFavorites = async (favoriteId: string) => {
try {
await favoritesService.removeFromFavorites(favoriteId);
favorites = favorites.filter((favorite) => favorite.id !== favoriteId);
} catch (error) {
console.error('Failed to remove favorite:', error);
}
};
onMount(() => {
fetchFavorites();
});
</script> </script>
<main class="p-6"> <main class="container mx-auto px-4 py-8">
<h1 class="mb-6 text-3xl font-bold">Favorites</h1> <h1 class="mb-8 text-4xl font-bold text-gray-900">Your Favorites</h1>
{#if $favorites.length > 0}
<ul class="space-y-4"> {#if isLoading}
{#each $favorites as item} <p class="text-gray-600">Loading your favorites...</p>
<li class="flex items-center justify-between border-b pb-4"> {:else if favorites.length === 0}
<div> <p class="text-gray-600">You have no favorite products yet.</p>
<h2 class="text-xl font-semibold">{item.title}</h2>
<p class="text-gray-600">{item.description}</p>
</div>
<button
on:click={() => removeFromFavorites(item.id)}
class="text-red-500 hover:text-red-700"
>
🗑️
</button>
</li>
{/each}
</ul>
<button
on:click={clearFavorites}
class="mt-4 w-full rounded bg-red-500 p-2 text-white hover:bg-red-600"
>
Clear Favorites
</button>
{:else} {:else}
<p class="text-gray-600">You have no favorite products.</p> <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{#each favorites as favorite}
<Card class="h-full">
<div class="flex h-full flex-col">
<img
src={favorite.expand?.product?.[0]?.imageUrl || '/fallback-image.jpg'}
alt={favorite.expand?.product?.[0]?.title || 'Product image'}
class="h-48 w-full rounded-t-lg object-cover"
/>
<div class="flex-grow p-4">
<h2 class="mb-2 text-xl font-bold text-gray-900">
{favorite.expand?.product?.[0]?.title || 'Unnamed Product'}
</h2>
<p class="mb-4 text-gray-600">
{favorite.expand?.product?.[0]?.description || 'No description available.'}
</p>
<p class="text-2xl font-bold text-gray-900">
${favorite.expand?.product?.[0]?.price || 'N/A'}
</p>
</div>
<div class="p-4">
<Button color="red" class="w-full" on:click={() => removeFromFavorites(favorite.id)}>
<TrashBinOutline class="mr-2 h-5 w-5" />
Remove from Favorites
</Button>
</div>
</div>
</Card>
{/each}
</div>
{/if} {/if}
</main> </main>

View File

@ -1,53 +1,74 @@
<script lang="ts"> <script lang="ts">
import { favorites } from '$lib/stores/store'; import { authService, orderService } from '$lib/services/api';
import { Card, Button, Badge, Input, Textarea } from 'flowbite-svelte'; import { Card, Button, Badge, Input, Textarea } from 'flowbite-svelte';
import { StarSolid, CheckCircleOutline, PenOutline } from 'flowbite-svelte-icons'; import { StarSolid, CheckCircleOutline, PenOutline } from 'flowbite-svelte-icons';
import { onMount } from 'svelte';
// Example user data // User data
const user = { let user: { id: string; name: string; email: string; address?: string } | null = null;
name: 'John Doe',
email: 'john.doe@example.com',
address: '123 Main St, City, Country'
};
// Example order history // Order history
const orders = [ let orders: Array<{
{ id: string;
id: 1, created: string;
date: '2023-10-01', items: Array<{ product: string; quantity: number }>;
items: ['Tulip Bouquet', 'Rose Bouquet'], total: number;
total: 60, status: string;
status: 'Delivered' }> = [];
},
{
id: 2,
date: '2023-09-25',
items: ['Sunflower Bouquet'],
total: 30,
status: 'Shipped'
}
];
// State for editing profile // State for editing profile
let isEditing = false; let isEditing = false;
let name = user.name; let name = '';
let email = user.email; let email = '';
let address = user.address; let address = '';
// Fetch user and order data on component mount
onMount(async () => {
// Get the current user
user = authService.getCurrentUser();
if (user) {
name = user.name;
email = user.email;
address = user.address || '';
// Fetch the user's orders
try {
const userOrders = await orderService.getOrders(user.id);
orders = userOrders.map((order) => ({
id: order.id,
created: order.created,
items: order.items,
total: order.total,
status: order.status
}));
} catch (error) {
console.error('Failed to fetch orders:', error);
}
}
});
// Handle profile update // Handle profile update
const updateProfile = () => { const updateProfile = async () => {
if (user) {
try {
// Update the user's profile (this is a placeholder; you'll need to implement the API call)
// Example: await authService.updateProfile(user.id, { name, email, address });
user.name = name; user.name = name;
user.email = email; user.email = email;
user.address = address; user.address = address;
isEditing = false; isEditing = false;
// You could add an API call here to update the user's profile console.log('Profile updated successfully');
} catch (error) {
console.error('Failed to update profile:', error);
}
}
}; };
</script> </script>
<!-- Welcome Section --> <!-- Welcome Section -->
<section class="bg-gradient-to-r from-pink-100 to-purple-100 py-12"> <section class="bg-gradient-to-r from-pink-100 to-purple-100 py-12">
<div class="container mx-auto px-4"> <div class="container mx-auto px-4">
<h1 class="text-4xl font-bold text-gray-900">Welcome, {user.name}!</h1> <h1 class="text-4xl font-bold text-gray-900">Welcome, {user?.name || user?.email}!</h1>
<p class="text-lg text-gray-600">Manage your orders, favorites, and account settings here.</p> <p class="text-lg text-gray-600">Manage your orders, favorites, and account settings here.</p>
</div> </div>
</section> </section>
@ -58,34 +79,40 @@
<div class="lg:col-span-2"> <div class="lg:col-span-2">
<h2 class="mb-6 text-2xl font-bold text-gray-900">Order History</h2> <h2 class="mb-6 text-2xl font-bold text-gray-900">Order History</h2>
<div class="space-y-4"> <div class="space-y-4">
{#if orders.length > 0}
{#each orders as order} {#each orders as order}
<Card class="p-6"> <Card class="p-6">
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<h3 class="text-xl font-bold text-gray-900">Order #{order.id}</h3> <h3 class="text-xl font-bold text-gray-900">Order #{order.id}</h3>
<Badge color={order.status === 'Delivered' ? 'green' : 'blue'}> <Badge color={order.status === 'delivered' ? 'green' : 'blue'}>
{order.status} {order.status}
</Badge> </Badge>
</div> </div>
<p class="mb-2 text-gray-600"> <p class="mb-2 text-gray-600">
<strong>Date:</strong> <strong>Date:</strong>
{order.date} {new Date(order.created).toLocaleDateString()}
</p> </p>
<p class="mb-2 text-gray-600"> <p class="mb-2 text-gray-600">
<strong>Items:</strong> <strong>Items:</strong>
{order.items.join(', ')} {#each order.items as item}
{item.product} (x{item.quantity}){@const isLast =
item === order.items[order.items.length - 1]}{#if !isLast},
{/if}
{/each}
</p> </p>
<p class="text-gray-600"> <p class="text-gray-600">
<strong>Total:</strong> ${order.total.toFixed(2)} <strong>Total:</strong> ${order.total.toFixed(2)}
</p> </p>
</Card> </Card>
{/each} {/each}
{:else}
<p class="text-gray-600">No orders found.</p>
{/if}
</div> </div>
</div> </div>
<!-- Account Settings --> <!-- Account Settings -->
<div class="space-y-8"> <div class="space-y-8">
<!-- Account Settings -->
<div>
<h2 class="mb-6 text-2xl font-bold text-gray-900">Account Settings</h2> <h2 class="mb-6 text-2xl font-bold text-gray-900">Account Settings</h2>
<Card class="p-6"> <Card class="p-6">
{#if isEditing} {#if isEditing}
@ -100,9 +127,9 @@
</form> </form>
{:else} {:else}
<div class="space-y-4"> <div class="space-y-4">
<p class="text-gray-600"><strong>Name:</strong> {user.name}</p> <p class="text-gray-600"><strong>Name:</strong> {user?.name}</p>
<p class="text-gray-600"><strong>Email:</strong> {user.email}</p> <p class="text-gray-600"><strong>Email:</strong> {user?.email}</p>
<p class="text-gray-600"><strong>Address:</strong> {user.address}</p> <p class="text-gray-600"><strong>Address:</strong> {user?.address || 'Not provided'}</p>
<Button on:click={() => (isEditing = true)}> <Button on:click={() => (isEditing = true)}>
<PenOutline class="mr-2 h-5 w-5" /> <PenOutline class="mr-2 h-5 w-5" />
Edit Profile Edit Profile
@ -112,4 +139,3 @@
</Card> </Card>
</div> </div>
</div> </div>
</div>