/* ============================================================ GlowMerald — App shell: router, cart, wishlist, toasts ============================================================ */ const { useState:useStateA, useEffect:useEffectA, useCallback } = React; const ROUTE_COMPONENTS = { home:'HomePage', shop:'ShopPage', product:'ProductPage', insight:'InsightPage', account:'AccountPage', cart:'CartPage', checkout:'CheckoutPage', confirm:'ConfirmPage', about:'AboutPage', contact:'ContactPage', }; function priceOf(id, vi=0){ const it=findItem(id); if(it&&it.variants&&it.variants[vi]) return it.variants[vi].price; return it?it.price:0; } function labelOf(id, vi=0){ const it=findItem(id); if(it&&it.variants&&it.variants.length>1&&it.variants[vi]) return it.variants[vi].label; return it?(it.size||''):''; } function readHash(){ const h = (location.hash||'').replace(/^#\/?/,''); if(!h) return { route:'home', param:null }; const [route,param] = h.split('/'); return { route: ROUTE_COMPONENTS[route]?route:'home', param: param||null }; } function App(){ const init = readHash(); const [route,setRoute] = useStateA(init.route); const [param,setParam] = useStateA(init.param); const [cart,setCart] = useStateA(()=>{ try{ return JSON.parse(localStorage.getItem('gm_cart'))||[]; }catch(e){ return []; } }); const [wishlist,setWishlist] = useStateA(()=>{ try{ return JSON.parse(localStorage.getItem('gm_wish'))||['mask']; }catch(e){ return []; } }); const [toasts,setToasts] = useStateA([]); const [searchOpen,setSearchOpen] = useStateA(false); useEffectA(()=>{ try{ localStorage.setItem('gm_cart', JSON.stringify(cart)); }catch(e){} },[cart]); useEffectA(()=>{ try{ localStorage.setItem('gm_wish', JSON.stringify(wishlist)); }catch(e){} },[wishlist]); useEffectA(()=>{ const onHash=()=>{ const r=readHash(); setRoute(r.route); setParam(r.param); }; window.addEventListener('hashchange',onHash); return ()=>window.removeEventListener('hashchange',onHash); },[]); const navigate = useCallback((r, p=null)=>{ setRoute(r); setParam(p); const hash = '#/'+r+(p?('/'+p):''); if(location.hash!==hash) history.pushState(null,'',hash); window.scrollTo({ top:0, behavior:'instant' in document.documentElement.style ? 'instant':'auto' }); },[]); const toast = useCallback((msg, icon='check')=>{ const id = Math.random().toString(36).slice(2); setToasts(t=>[...t,{ id, msg, icon }]); setTimeout(()=>setToasts(t=>t.filter(x=>x.id!==id)), 2600); },[]); const addToCart = useCallback((id, qty=1, vi=0)=>{ const key = id+'::'+vi; setCart(c=>{ const ex = c.find(x=>x.key===key); if(ex) return c.map(x=>x.key===key?{...x,qty:x.qty+qty}:x); return [...c,{ key, id, vi, qty }]; }); const it = findItem(id); toast('Ditambahkan ke keranjang — '+(it?it.name:''),'cart'); },[toast]); const removeFromCart = useCallback((key)=> setCart(c=>c.filter(x=>x.key!==key)),[]); const updateQty = useCallback((key,qty)=> setCart(c=>c.map(x=>x.key===key?{...x,qty:Math.max(1,qty)}:x).filter(x=>x.qty>0)),[]); const clearCart = useCallback(()=>setCart([]),[]); const toggleWishlist = useCallback((id)=>{ setWishlist(w=>{ if(w.includes(id)){ toast('Dihapus dari wishlist'); return w.filter(x=>x!==id); } toast('Disimpan ke wishlist','heart'); return [...w,id]; }); },[toast]); const cartCount = cart.reduce((s,x)=>s+x.qty,0); const cartTotal = cart.reduce((s,x)=>s+priceOf(x.id,x.vi)*x.qty,0); const ctx = { route, param, navigate, cart, cartCount, cartTotal, addToCart, removeFromCart, updateQty, clearCart, wishlist, toggleWishlist, toast, priceOf, labelOf, openSearch:()=>setSearchOpen(true), }; const CompName = ROUTE_COMPONENTS[route] || 'HomePage'; const Comp = window[CompName]; return (
{Comp ? : }
); } function StubPage({ name }){ const { navigate } = useApp(); return (
GlowMerald

{name}

Halaman ini sedang dipersiapkan.

); } /* ---- Search overlay ---- */ function SearchOverlay({ close }){ const { navigate } = useApp(); const [q,setQ] = useStateA(''); const ref = React.useRef(); useEffectA(()=>{ ref.current && ref.current.focus(); },[]); const results = q.trim() ? ALL_ITEMS.filter(p=>p.name.toLowerCase().includes(q.toLowerCase())) : ALL_ITEMS.slice(0,3); const go=(id)=>{ navigate('product',id); close(); }; return (
e.stopPropagation()}>
setQ(e.target.value)} placeholder="Cari produk…"/>
{q.trim()?'Hasil':'Populer'}
{results.length===0 &&

Tidak ada hasil untuk “{q}”.

} {results.map(p=>(
go(p.id)} style={{ display:'flex', alignItems:'center', gap:14, padding:'10px 0', cursor:'pointer', borderBottom:'1px solid var(--line)' }}>
{p.name}
{p.type} · {formatRp(p.price)}
))}
); } window.App = App; ReactDOM.createRoot(document.getElementById('root')).render();