import { createStore } from 'zustand/vanilla'; import { useSyncExternalStore } from 'react'; export interface PricingItem { id: string; label: string; price: number; modelPath?: string; } export interface PricingState { items: PricingItem[]; isHydrated: boolean; } export interface PricingActions { updatePrice: (itemId: string, newPrice: number) => void; updateItem: (itemId: string, updates: Partial>) => void; addItem: (item: PricingItem) => void; removeItem: (itemId: string) => void; resetPrices: () => void; hydrate: () => void; } export type PricingStore = PricingState & PricingActions; // Prices in AED. USD → AED at 3.6725 (CBUAE peg). // Basic = Unitree G1 retail ($16k) + $5k markup = $21k ≈ 77,125 AED. // EDU = $40k flat ≈ 146,900 AED. // All persona attire = 5,000 AED each. const DEFAULT_ITEMS: PricingItem[] = [ { id: 'base', label: 'G1 Robot Basic', price: 77125 }, { id: 'edu', label: 'G1 Robot EDU', price: 146900 }, { id: 'emarati-kandura', label: 'Emarati Kandura', price: 5000 }, { id: 'industrial-vest', label: 'Industrial Vest', price: 5000 }, { id: 'business-suit', label: 'Business Suit', price: 5000 }, { id: 'custom-color', label: 'Custom Color', price: 3500 }, { id: 'robot-doctor', label: 'Robot Doctor', price: 5000 }, { id: 'security-guard', label: 'Security Guard', price: 5000 }, ]; // Bump suffix to invalidate any cached localStorage prices from the old pricing scheme. const STORAGE_KEY = 'lootah-pricing-v2'; function loadFromStorage(): PricingItem[] | null { if (typeof window === 'undefined') return null; try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; const parsed = JSON.parse(raw); if (!Array.isArray(parsed)) return null; return parsed; } catch { return null; } } function saveToStorage(items: PricingItem[]) { if (typeof window === 'undefined') return; try { localStorage.setItem(STORAGE_KEY, JSON.stringify(items)); } catch { // Storage full or unavailable } } export const pricingStore = createStore((set, get) => ({ items: DEFAULT_ITEMS, isHydrated: false, updatePrice: (itemId: string, newPrice: number) => { set((state) => { const updated = state.items.map((item) => item.id === itemId ? { ...item, price: newPrice } : item ); saveToStorage(updated); return { items: updated }; }); }, updateItem: (itemId: string, updates: Partial>) => { set((state) => { const updated = state.items.map((item) => item.id === itemId ? { ...item, ...updates } : item ); saveToStorage(updated); return { items: updated }; }); }, resetPrices: () => { saveToStorage(DEFAULT_ITEMS); set({ items: [...DEFAULT_ITEMS] }); }, addItem: (item: PricingItem) => { set((state) => { if (state.items.some((i) => i.id === item.id)) return state; const updated = [...state.items, item]; saveToStorage(updated); return { items: updated }; }); }, removeItem: (itemId: string) => { // Prevent removing the base robot price if (itemId === 'base') return; set((state) => { const updated = state.items.filter((i) => i.id !== itemId); saveToStorage(updated); return { items: updated }; }); }, hydrate: () => { const stored = loadFromStorage(); if (stored && stored.length > 0) { // Use stored items directly (preserves custom labels, prices, added items). // Re-add any default items that were never stored (fresh install gap). const storedIds = new Set(stored.map((s) => s.id)); const missing = DEFAULT_ITEMS.filter((d) => !storedIds.has(d.id)); set({ items: [...stored, ...missing], isHydrated: true }); } else { set({ isHydrated: true }); } // Also fetch from server DB to get the latest pricing (async, non-blocking) if (typeof window !== 'undefined') { fetch('/api/admin/pricing/') .then((r) => r.json()) .then((data) => { const serverItems: PricingItem[] = data.items ?? []; if (serverItems.length > 0) { // Merge: server wins for label/price, but keep local modelPath if server doesn't have one const localItems = get().items; const localMap = new Map(localItems.map((l) => [l.id, l])); const merged: PricingItem[] = serverItems.map((s) => { const local = localMap.get(s.id); return { ...s, modelPath: s.modelPath || local?.modelPath, }; }); // Also keep any local-only items not in server for (const local of localItems) { if (!serverItems.some((s) => s.id === local.id)) { merged.push(local); } } saveToStorage(merged); set({ items: merged }); } }) .catch(() => {}); // silent — use local data as fallback } }, })); export const usePricingStore = (selector: (state: PricingStore) => T): T => { return useSyncExternalStore( pricingStore.subscribe, () => selector(pricingStore.getState()), () => selector(pricingStore.getState()) ); }; export const getPrice = (itemId: string): number => { const item = pricingStore.getState().items.find((i) => i.id === itemId); return item?.price ?? 0; };