102 lines
2.4 KiB
JavaScript
102 lines
2.4 KiB
JavaScript
/** @typedef {{ productId: string; slug: string; name: string; price: number; image?: string | null; category?: string | null; quantity: number }} CartItem */
|
|
|
|
const STORAGE_KEY = "kikomiko-cart-v1";
|
|
|
|
/** @type {CartItem[]} */
|
|
let items = [];
|
|
let hydrated = false;
|
|
const listeners = new Set();
|
|
|
|
function notify() {
|
|
for (const fn of listeners) fn(getSnapshot());
|
|
}
|
|
|
|
function persist() {
|
|
if (!hydrated) return;
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
export function hydrateCart() {
|
|
if (hydrated) return;
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
items = raw ? JSON.parse(raw) : [];
|
|
if (!Array.isArray(items)) items = [];
|
|
} catch {
|
|
items = [];
|
|
}
|
|
hydrated = true;
|
|
notify();
|
|
}
|
|
|
|
export function subscribe(fn) {
|
|
listeners.add(fn);
|
|
return () => listeners.delete(fn);
|
|
}
|
|
|
|
export function getSnapshot() {
|
|
const itemCount = items.reduce((n, i) => n + i.quantity, 0);
|
|
const subtotal = items.reduce((n, i) => n + i.price * i.quantity, 0);
|
|
return { items: [...items], itemCount, subtotal, hydrated };
|
|
}
|
|
|
|
export function addItem(item) {
|
|
hydrateCart();
|
|
const qty = Math.max(1, item.quantity ?? 1);
|
|
const existing = items.find((i) => i.productId === item.productId);
|
|
if (existing) {
|
|
existing.quantity += qty;
|
|
if (item.category && !existing.category) existing.category = item.category;
|
|
if (item.image && !existing.image) existing.image = item.image;
|
|
} else {
|
|
items.push({
|
|
productId: item.productId,
|
|
slug: item.slug,
|
|
name: item.name,
|
|
price: item.price,
|
|
image: item.image ?? null,
|
|
category: item.category ?? null,
|
|
quantity: qty,
|
|
});
|
|
}
|
|
persist();
|
|
notify();
|
|
}
|
|
|
|
export function updateQuantity(productId, quantity) {
|
|
hydrateCart();
|
|
if (quantity <= 0) {
|
|
items = items.filter((i) => i.productId !== productId);
|
|
} else {
|
|
const row = items.find((i) => i.productId === productId);
|
|
if (row) row.quantity = quantity;
|
|
}
|
|
persist();
|
|
notify();
|
|
}
|
|
|
|
export function removeItem(productId) {
|
|
hydrateCart();
|
|
items = items.filter((i) => i.productId !== productId);
|
|
persist();
|
|
notify();
|
|
}
|
|
|
|
export function clearCart() {
|
|
hydrateCart();
|
|
items = [];
|
|
persist();
|
|
notify();
|
|
}
|
|
|
|
export function formatPrice(price) {
|
|
const amount = Number.isFinite(price) ? Math.round(price) : 0;
|
|
return `${new Intl.NumberFormat("fa-IR", {
|
|
maximumFractionDigits: 0,
|
|
}).format(amount)} تومان`;
|
|
}
|