cart rework

This commit is contained in:
nomad 2025-06-13 16:35:00 +03:00
parent 9342518a85
commit 174439cc3b
8 changed files with 415 additions and 179 deletions

View file

@ -1,6 +1,6 @@
<script>
import { page } from '$app/stores';
import { cart } from '$lib/stores/cart';
import { cart, cartItems } from '$lib/stores/cart';
import { fly } from 'svelte/transition';
let isMobileMenuOpen = false;
@ -33,16 +33,16 @@
<!-- Cart Icon (Always Visible) -->
<button
class="btn btn-ghost btn-circle text-white"
on:click={() => cart.toggleCart()}
on:click={() => cart.toggle()}
aria-label="Shopping cart"
>
<div class="indicator">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
{#if $cart.length > 0}
{#if $cartItems.length > 0}
<span class="badge badge-sm indicator-item">
{$cart.reduce((sum, item) => sum + item.quantity, 0)}
{$cartItems.reduce((sum, item) => sum + item.quantity, 0)}
</span>
{/if}
</div>

View file

@ -1,5 +1,8 @@
<script lang="ts">
import { cart } from '$lib/stores/cart';
import { cartItems } from '$lib/stores/cart';
import { region } from '$lib/stores/cart';
import { PUBLIC_MEDUSA_KEY } from '$env/static/public';
import { onMount } from 'svelte';
let formData = {
firstName: '',
@ -14,17 +17,75 @@
buildingNumber: ''
};
let shippingOptions: any[] = [];
let selectedShippingOption: string | null = null;
let isLoadingShipping = true;
onMount(async () => {
await loadShippingOptions();
});
async function loadShippingOptions() {
try {
isLoadingShipping = true;
let cartId = localStorage.getItem('cart_id');
const response = await fetch(`http://localhost:9000/store/shipping-options?cart_id=${cartId}`, {
headers: {
'x-publishable-api-key': PUBLIC_MEDUSA_KEY,
},
});
const data = await response.json();
shippingOptions = data.shipping_options;
// If there's only one option, select it automatically
if (shippingOptions.length === 1) {
selectedShippingOption = shippingOptions[0].id;
}
} catch (error) {
console.error('Error loading shipping options:', error);
} finally {
isLoadingShipping = false;
}
}
function handleSubmit() {
if (!selectedShippingOption) {
alert('Please select a shipping option');
return;
}
// Here you'll integrate payment processing later
console.log('Form submitted:', formData);
console.log('Cart items:', $cart);
console.log('Form submitted:', {
...formData,
shippingOptionId: selectedShippingOption
});
console.log('Cart items:', $cartItems);
}
function calculateItemTotal(item: any) {
return (item.unit_price * item.quantity).toFixed(2);
}
function calculateTotal(items: any[]) {
return items.reduce((sum, item) =>
sum + (item.variant.calculated_price.calculated_amount * item.quantity), 0
sum + (item.unit_price * item.quantity), 0
).toFixed(2);
}
function getCurrencySymbol(currency_code: string) {
switch (currency_code?.toLowerCase()) {
case 'eur':
return '€';
case 'usd':
return '$';
case 'kwd':
return 'KWD';
default:
return currency_code?.toUpperCase() || '';
}
}
let currencyCode = $region?.currency_code || 'KWD';
let currencySymbol = getCurrencySymbol(currencyCode);
</script>
<div class="min-h-screen bg-base-100">
@ -34,22 +95,62 @@
<div class="lg:col-span-1 h-fit bg-base-200 rounded-lg p-6">
<h2 class="text-xl font-bold mb-4">Order Summary</h2>
<div class="space-y-3 mb-4">
{#each $cart as item}
{#each $cartItems as item}
<div class="flex justify-between items-start text-sm border-b border-base-300 pb-2">
<div class="flex-1">
<p class="font-medium">{item.product.title}</p>
<p class="font-medium">{item.title}</p>
<p class="text-xs text-base-content/70">
{item.variant.title} × {item.quantity}
{#if item.variant && item.variant.title}
{item.variant.title}
{/if}
× {item.quantity}
</p>
</div>
<p class="font-medium ml-4">{(item.variant.calculated_price.calculated_amount * item.quantity).toFixed(2)}</p>
<p class="font-medium ml-4">{calculateItemTotal(item)} {currencySymbol}</p>
</div>
{/each}
</div>
<!-- Shipping Options -->
<div class="mb-4">
<h3 class="font-medium mb-2">Shipping Method</h3>
{#if isLoadingShipping}
<div class="w-full h-12 flex items-center justify-center">
<span class="loading loading-spinner loading-sm"></span>
</div>
{:else if shippingOptions.length === 0}
<div class="alert alert-warning alert-sm">
No shipping options available
</div>
{:else}
<div class="space-y-2">
{#each shippingOptions as option}
<label class="flex items-center gap-3 p-2 border rounded-lg cursor-pointer hover:bg-base-300 transition-colors {selectedShippingOption === option.id ? 'bg-base-300 border-primary' : 'bg-base-100'}">
<input
type="radio"
name="shipping_option"
value={option.id}
bind:group={selectedShippingOption}
class="radio radio-primary radio-sm"
/>
<div class="flex-1">
<p class="text-sm font-medium">{option.name}</p>
{#if option.data}
<p class="text-xs text-base-content/70">{option.data.id}</p>
{/if}
</div>
<!-- {#if option.insufficient_inventory}
<span class="badge badge-warning badge-sm">Limited Stock</span>
{/if} -->
</label>
{/each}
</div>
{/if}
</div>
<div class="flex justify-between items-center pt-4">
<div class="flex justify-between items-center pt-4 border-t">
<span class="text-lg font-bold">Total:</span>
<span class="text-lg font-bold">{calculateTotal($cart)}</span>
<span class="text-lg font-bold">{calculateTotal($cartItems)} {currencySymbol}</span>
</div>
</div>

View file

@ -1,28 +1,42 @@
<script lang="ts">
import { cart } from '$lib/stores/cart';
import type { CartItem } from '$lib/stores/cart';
import { cart, cartItems, cartIsOpen } from '$lib/stores/cart';
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import { region } from '$lib/stores/cart';
let isOpen: boolean;
cart.subscribeToOpen(value => isOpen = value);
function calculateItemTotal(item: CartItem) {
return (item.variant.calculated_price.calculated_amount * item.quantity).toFixed(2);
function calculateItemTotal(item: any) {
return (item.unit_price * item.quantity).toFixed(2);
}
function calculateTotal(items: CartItem[]) {
function calculateTotal(items: any[]) {
return items.reduce((sum, item) =>
sum + (item.variant.calculated_price.calculated_amount * item.quantity), 0
sum + (item.unit_price * item.quantity), 0
).toFixed(2);
}
function getCurrencySymbol(currency_code: string) {
switch (currency_code?.toLowerCase()) {
case 'eur':
return '€';
case 'usd':
return '$';
case 'kwd':
return 'KWD';
default:
return currency_code?.toUpperCase() || '';
}
}
let currencyCode = $region?.currency_code || 'KWD';
let currencySymbol = getCurrencySymbol(currencyCode);
console.log('currencySymbol', currencySymbol);
</script>
{#if isOpen}
{#if $cartIsOpen}
<!-- Backdrop -->
<div
class="fixed inset-0 bg-black/50 z-40"
on:click={() => cart.toggleCart()}
on:click={() => cart.toggle()}
transition:fly={{ duration: 200, opacity: 0 }}
></div>
@ -35,7 +49,7 @@
<h3 class="font-bold text-lg">Your Cart</h3>
<button
class="btn btn-ghost btn-sm"
on:click={() => cart.toggleCart()}
on:click={() => cart.toggle()}
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@ -44,7 +58,7 @@
</div>
<div class="flex-1 overflow-y-auto p-4">
{#if $cart.length === 0}
{#if $cartItems.length === 0}
<div class="h-full flex flex-col items-center justify-center text-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-base-300 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
@ -53,31 +67,35 @@
<p class="text-base-content/70 mb-4">Add some items to get started</p>
<button
class="btn btn-primary"
on:click={() => cart.toggleCart()}
on:click={() => cart.toggle()}
>
Continue Shopping
</button>
</div>
{:else}
<div class="space-y-4">
{#each $cart as item, i}
{#each $cartItems as item}
<div class="flex gap-4 pb-4 border-b">
{#if item.product.thumbnail}
{#if item.thumbnail}
<img
src={item.product.thumbnail}
alt={item.product.title}
src={item.thumbnail}
alt={item.title}
class="w-20 h-20 object-cover rounded-lg bg-base-200"
/>
{/if}
<div class="flex-1">
<div class="flex justify-between">
<div>
<h3 class="font-medium">{item.product.title}</h3>
<p class="text-sm text-base-content/70">{item.variant.title}</p>
<h3 class="font-medium">{item.title}</h3>
<p class="text-sm text-base-content/70">
{#if item.variant && item.variant.title}
{item.variant.title}
{/if}
</p>
</div>
<button
class="btn btn-ghost btn-sm"
on:click={() => cart.removeItem(i)}
on:click={() => cart.remove(item.id)}
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@ -89,7 +107,7 @@
<div class="join">
<button
class="btn btn-sm join-item"
on:click={() => cart.updateQuantity(i, item.quantity - 1)}
on:click={() => cart.update(item.id, item.quantity - 1)}
>-</button>
<input
type="text"
@ -99,16 +117,16 @@
const target = e.target as HTMLInputElement;
const value = parseInt(target.value) || 1;
if (value >= 1) {
cart.updateQuantity(i, value);
cart.update(item.id, value);
}
}}
/>
<button
class="btn btn-sm join-item"
on:click={() => cart.updateQuantity(i, item.quantity + 1)}
on:click={() => cart.update(item.id, item.quantity + 1)}
>+</button>
</div>
<p class="font-medium">{calculateItemTotal(item)} </p>
<p class="font-medium">{calculateItemTotal(item)} {currencySymbol}</p>
</div>
</div>
</div>
@ -117,11 +135,11 @@
{/if}
</div>
{#if $cart.length > 0}
{#if $cartItems.length > 0}
<div class="border-t p-4 space-y-4">
<div class="flex justify-between items-center">
<span class="text-lg font-bold">Total:</span>
<span class="text-lg font-bold">{calculateTotal($cart)}</span>
<span class="text-lg font-bold">{calculateTotal($cartItems)} {currencySymbol}</span>
</div>
<div class="grid gap-2">
@ -130,7 +148,7 @@
</a>
<button
class="btn btn-ghost btn-sm w-full"
on:click={() => cart.toggleCart()}
on:click={() => cart.toggle()}
>
Continue Shopping
</button>

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { cart } from '$lib/stores/cart';
import type { MedusaProduct, MedusaVariant } from '$lib/types/medusa';
import { region } from '$lib/stores/cart';
export let products: MedusaProduct[];
let selectedVariants = new Map<string, MedusaVariant>(
@ -10,6 +11,22 @@
function handleVariantChange(productId: string, variant: MedusaVariant) {
selectedVariants.set(productId, variant);
}
function getCurrencySymbol(currency_code: string) {
switch (currency_code?.toLowerCase()) {
case 'eur':
return '€';
case 'usd':
return '$';
case 'kwd':
return 'KWD';
default:
return currency_code?.toUpperCase() || '';
}
}
let currencyCode = $region?.currency_code || 'KWD';
let currencySymbol = getCurrencySymbol(currencyCode);
</script>
<section class="container mx-auto px-4 py-8">
@ -38,14 +55,14 @@
}}
>
{#each product.variants as variant}
<option value={variant.id}>{variant.title} {variant.calculated_price.calculated_amount} {variant.calculated_price.currency_code}</option>
<option value={variant.id}>{variant.title} {variant.calculated_price.calculated_amount} {currencySymbol}</option>
{/each}
</select>
<button
class="btn btn-primary btn-sm w-full"
on:click={() => {
const variant = selectedVariants.get(product.id);
if (variant) cart.addToCart(product, variant);
if (variant) cart.add(variant.id);
}}
>
Add to cart

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { cart } from '$lib/stores/cart';
import type { MedusaProduct, MedusaVariant, MedusaOptionValue } from '$lib/types/medusa';
import { region } from '$lib/stores/cart';
export let product: MedusaProduct;
let selectedVariant: MedusaVariant = product.variants[0];
@ -58,81 +59,104 @@
);
}) || [];
}
function getCurrencySymbol(currency_code: string) {
switch (currency_code?.toLowerCase()) {
case 'eur':
return '€';
case 'usd':
return '$';
case 'kwd':
return 'KWD';
default:
return currency_code?.toUpperCase() || '';
}
}
let currencyCode = $region?.currency_code || 'KWD';
let currencySymbol = getCurrencySymbol(currencyCode);
</script>
<svelte:window on:scroll={handleScroll}/>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="space-y-4">
<img
src={product.images[currentImageIndex].url}
alt={product.title}
class="aspect-square rounded-lg object-cover bg-base-200"
/>
<div class="grid grid-cols-4 gap-2">
{#each product.images as image, i}
<button
class="p-0 border-2 rounded-lg {currentImageIndex === i ? 'border-primary' : 'border-transparent'}"
on:click={() => currentImageIndex = i}
>
<img
src={image.url}
alt={product.title}
class="aspect-square rounded-lg object-cover hover:opacity-75"
/>
</button>
{/each}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 py-8">
<!-- Product Images -->
<div class="relative">
<div class="aspect-square overflow-hidden rounded-lg bg-base-200">
<img
src={product.images[currentImageIndex]?.url || product.thumbnail}
alt={product.title}
class="w-full h-full object-cover"
/>
</div>
</div>
<div class="space-y-6">
<div>
<h1 class="text-3xl font-bold">{product.title}</h1>
<h1 class="text-3xl font-bold">{selectedVariant.calculated_price.calculated_amount} {selectedVariant.calculated_price.currency_code}</h1>
</div>
{#each product.options as option}
<div>
<label class="label" for={`option-${option.id}`}>
<span class="label-text">{option.title}</span>
</label>
<select
id={`option-${option.id}`}
class="select select-bordered w-full"
value={selectedOptions.get(option.id)}
on:change={(e) => handleOptionChange(option.id, (e.target as HTMLSelectElement).value)}
>
{#each getAvailableValues(option.id) as value}
<option value={value.value}>
{value.value}
</option>
{/each}
</select>
</div>
{/each}
<p class="text-lg">{product.description}</p>
<div bind:this={cartButton}>
<button
class="btn btn-primary btn-lg w-full {isSticky ? 'sticky-cart' : ''}"
on:click={() => cart.addToCart(product, selectedVariant)}
>
Add to Cart - {selectedVariant.title}
</button>
</div>
{#if selectedVariant.sku || product.weight || product.material}
<div class="prose pb-16 md:pb-0">
<h3 class="text-lg font-semibold">Product Details</h3>
<ul>
{#if selectedVariant.sku}<li>SKU: {selectedVariant.sku}</li>{/if}
{#if product.weight}<li>Weight: {product.weight}g</li>{/if}
{#if product.material}<li>Material: {product.material}</li>{/if}
</ul>
{#if product.images.length > 1}
<div class="grid grid-cols-4 gap-2 mt-2">
{#each product.images as image, i}
<button
class="aspect-square rounded-lg overflow-hidden bg-base-200 hover:opacity-75 transition-opacity"
class:ring-2={currentImageIndex === i}
class:ring-primary={currentImageIndex === i}
on:click={() => currentImageIndex = i}
>
<img
src={image.url}
alt={`${product.title} - View ${i + 1}`}
class="w-full h-full object-cover"
/>
</button>
{/each}
</div>
{/if}
</div>
<!-- Product Info -->
<div>
<h1 class="text-3xl font-bold mb-4">{product.title}</h1>
<p class="text-xl font-semibold mb-6">{selectedVariant.calculated_price.calculated_amount} {currencySymbol}</p>
<div class="prose max-w-none mb-8">
<p>{product.description}</p>
</div>
<!-- Product Options -->
{#if product.options.length > 0}
<div class="space-y-4 mb-8">
{#each product.options as option}
<div>
<label class="block text-sm font-medium mb-2" for={option.id}>
{option.title}
</label>
<select
id={option.id}
class="select select-bordered w-full"
value={selectedOptions.get(option.id)}
on:change={(e) => handleOptionChange(option.id, (e.target as HTMLSelectElement).value)}
>
{#each getAvailableValues(option.id) as value}
<option value={value.value}>
{value.value}
</option>
{/each}
</select>
</div>
{/each}
</div>
{/if}
<!-- Add to Cart -->
<div
class="card bg-base-200 p-4 sticky bottom-2 transition-all duration-300"
class:sticky-cart={isSticky}
bind:this={cartButton}
>
<button
class="btn btn-primary w-full"
on:click={() => cart.add(selectedVariant.id)}
>
Add to Cart
</button>
</div>
</div>
</div>
<style>

View file

@ -1,12 +1,12 @@
// import {MedusaClient} from 'sveltekit-medusa-client'
// import { PUBLIC_MEDUSA_URL, PUBLIC_MEDUSA_KEY } from '$env/static/public'
import {MedusaClient} from 'sveltekit-medusa-client'
import { PUBLIC_MEDUSA_URL, PUBLIC_MEDUSA_KEY } from '$env/static/public'
// export default new MedusaClient(PUBLIC_MEDUSA_URL, {
// headers: {
// 'x-publishable-api-key': PUBLIC_MEDUSA_KEY
// },
// retry: 0,
// timeout: 10000
// });
export default new MedusaClient(PUBLIC_MEDUSA_URL, {
headers: {
'x-publishable-api-key': PUBLIC_MEDUSA_KEY
},
retry: 0,
timeout: 10000
});

View file

@ -1,6 +1,8 @@
import { writable } from 'svelte/store';
import type { MedusaProduct, MedusaVariant } from '$lib/types/medusa';
import toast from 'svelte-french-toast';
import type { Region } from './region';
import { PUBLIC_MEDUSA_KEY } from '$env/static/public';
export interface CartItem {
product: MedusaProduct;
@ -8,75 +10,143 @@ export interface CartItem {
quantity: number;
}
function createCartStore() {
const { subscribe, set, update } = writable<CartItem[]>([]);
const isOpenStore = writable<boolean>(false);
export const cartId = writable<string | null>(null);
export const cartItems = writable<any[]>([]);
export const cartIsOpen = writable(false);
// Load initial state from localStorage
if (typeof window !== 'undefined') {
const savedCart = localStorage.getItem('cart');
if (savedCart) {
set(JSON.parse(savedCart));
function getStoredCartId() {
if (typeof window === 'undefined') return null;
return localStorage.getItem('cart_id');
}
function setStoredCartId(id: string) {
localStorage.setItem('cart_id', id);
cartId.set(id);
}
async function initCart() {
const existing = getStoredCartId();
if (existing) {
cartId.set(existing);
await syncCartFromBackend(existing);
} else {
const res = await fetch('http://localhost:9000/store/carts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-publishable-api-key': PUBLIC_MEDUSA_KEY
}
});
if (res.ok) {
const { cart } = await res.json();
setStoredCartId(cart.id);
}
}
}
async function syncCartFromBackend(id: string) {
const res = await fetch(`http://localhost:9000/store/carts/${id}`, {
headers: {
'x-publishable-api-key': PUBLIC_MEDUSA_KEY
}
});
if (res.ok) {
const { cart } = await res.json();
cartItems.set(cart.items || []);
return cart;
}
return null;
}
function showAddToCartToast(title: string) {
toast(`${title} added to cart`, {
icon: '🛍️',
duration: 2000,
position: 'bottom-center',
style: 'background-color: #000; color: #fff;',
});
}
function showAddToCartToast(product: MedusaProduct, variant: MedusaVariant) {
toast(`${product.title} ${variant.title} added to cart`, {
icon: '🛍️',
duration: 2000,
position: 'bottom-center',
style: 'background-color: #000; color: #fff;',
});
async function addItemToCart(variant_id: string, quantity = 1) {
const id = getStoredCartId();
if (!id) {
await initCart();
return addItemToCart(variant_id, quantity);
}
const res = await fetch(`http://localhost:9000/store/carts/${id}/line-items`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-publishable-api-key': PUBLIC_MEDUSA_KEY
},
body: JSON.stringify({ variant_id, quantity })
});
if (res.ok) {
const cart = await syncCartFromBackend(id);
const addedItem = cart?.items?.find((item: any) => item.variant_id === variant_id);
if (addedItem) {
showAddToCartToast(addedItem.title);
}
}
}
async function updateItemQuantity(item_id: string, quantity: number) {
const id = getStoredCartId();
if (!id || quantity < 1) return;
const res = await fetch(`http://localhost:9000/store/carts/${id}/line-items/${item_id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-publishable-api-key': PUBLIC_MEDUSA_KEY
},
body: JSON.stringify({ quantity })
});
if (res.ok) await syncCartFromBackend(id);
}
async function removeItem(item_id: string) {
const id = getStoredCartId();
if (!id) return;
const res = await fetch(`http://localhost:9000/store/carts/${id}/line-items/${item_id}`, {
method: 'DELETE',
headers: {
'x-publishable-api-key': PUBLIC_MEDUSA_KEY
}
});
if (res.ok) await syncCartFromBackend(id);
}
export const cart = {
init: initCart,
add: addItemToCart,
update: updateItemQuantity,
remove: removeItem,
toggle: () => cartIsOpen.update(v => !v),
open: () => cartIsOpen.set(true),
close: () => cartIsOpen.set(false),
subscribe: cartItems.subscribe,
subscribeToOpen: cartIsOpen.subscribe,
sync: syncCartFromBackend
};
function createRegionStore() {
const { subscribe, set, update } = writable<Region>();
if (typeof window !== 'undefined') {
const savedRegion = localStorage.getItem('selectedRegion');
if (savedRegion) {
set(JSON.parse(savedRegion));
}
}
return {
subscribe,
subscribeToOpen: isOpenStore.subscribe,
toggleCart: () => {
isOpenStore.update(state => !state);
},
addToCart: (product: MedusaProduct, variant: MedusaVariant) => {
update(items => {
const existingItem = items.find(item =>
item.product.id === product.id && item.variant.id === variant.id
);
if (existingItem) {
existingItem.quantity += 1;
return [...items];
}
const newItems = [...items, { product, variant, quantity: 1 }];
localStorage.setItem('cart', JSON.stringify(newItems));
return newItems;
});
// Show toast notification instead of opening cart
showAddToCartToast(product, variant);
},
updateQuantity: (index: number, quantity: number) => {
update(items => {
if (quantity < 1) {
const newItems = items.filter((_, i) => i !== index);
localStorage.setItem('cart', JSON.stringify(newItems));
return newItems;
}
items[index].quantity = quantity;
localStorage.setItem('cart', JSON.stringify(items));
return [...items];
});
},
removeItem: (index: number) => {
update(items => {
const newItems = items.filter((_, i) => i !== index);
localStorage.setItem('cart', JSON.stringify(newItems));
return newItems;
});
}
setRegion: (region: Region) => set(region),
};
}
export const cart = createCartStore();
export const region = createRegionStore();

View file

@ -4,6 +4,12 @@
import Footer from '$lib/components/Footer.svelte';
import { page } from '$app/stores';
import { Toaster } from 'svelte-french-toast';
import { cart } from '$lib/stores/cart';
import { onMount } from 'svelte';
onMount(() => {
cart.init();
});
</script>
<svelte:head>