first deployment of kikomiko with url photos
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
8d98304137
commit
ae3ed7c7b7
Vendored
+101
@@ -0,0 +1,101 @@
|
||||
/** @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)} تومان`;
|
||||
}
|
||||
Reference in New Issue
Block a user