// ============================================================ // App root (live): auth, data loading, API handlers, routing // ============================================================ (function () { const { Icon, BrandDot, Btn, useToasts } = window; const { useState, useEffect, useCallback } = React; const API = window.API; function App() { const [booting, setBooting] = useState(true); const [bootError, setBootError] = useState(null); const [user, setUser] = useState(null); const [products, setProducts] = useState([]); const [history, setHistory] = useState([]); const [stats, setStats] = useState({}); const [printer, setPrinter] = useState(API.toPrinter({})); const [users, setUsers] = useState([]); const [route, setRoute] = useState('dash'); const [queue, setQueue] = useState([]); const { push: toast, node: toastNode } = useToasts(); const loadAll = useCallback(async () => { const [prods, hist, conf] = await Promise.all([API.products(), API.history(), API.settings()]); setProducts(prods); setHistory(hist.jobs || []); setStats(hist.stats || {}); setUsers(conf.users || []); setPrinter(p => ({ ...API.toPrinter(conf.settings), connected: p.connected })); }, []); // boot: check session useEffect(() => { (async () => { try { const u = await API.me(); if (u) { setUser(u); await loadAll(); } } catch (e) { setBootError(e.message); } setBooting(false); })(); }, [loadAll]); const refreshHistory = useCallback(async () => { const hist = await API.history(); setHistory(hist.jobs || []); setStats(hist.stats || {}); }, []); const addToQueue = (p) => setQueue(qq => qq.find(x => x.sku === p.sku) ? qq : [...qq, { qty: 50, mfg: p.mfg || '05/2026', exp: p.exp || '04/2028', batch: p.batch || '34011542', ...p }]); const onPrint = async (items, status = 'printed') => { const r = await API.print(items, status); await refreshHistory(); return r; }; const onSaveProduct = async (prod) => { if (prod.__new) { const np = await API.createProduct(prod); setProducts(ps => [np, ...ps]); toast('Product added · ' + np.sku); } else { const up = await API.updateProduct(prod); setProducts(ps => ps.map(x => x.id === up.id ? up : x)); toast('Product updated · ' + up.sku); } }; const onDeleteProduct = async (prod) => { await API.deleteProduct(prod.id); setProducts(ps => ps.filter(x => x.id !== prod.id)); }; const onSaveSettings = async (p) => { await API.saveSettings(p); setPrinter(p); }; const onTestPrinter = (p) => API.testPrinter(p); const onDetect = (p) => API.detectPrinter(p); const onTestPrint = () => API.testPrint(); const doLogin = async (u) => { setUser(u); await loadAll(); }; const logout = async () => { try { await API.logout(); } catch (e) {} setUser(null); setRoute('dash'); setQueue([]); }; if (booting) return React.createElement(Boot, null); if (bootError) return React.createElement(InstallNeeded, { message: bootError }); if (!user) return React.createElement(Login, { onLogin: doLogin }); const go = setRoute; const props = { products, history, stats, queue, setQueue, printer, users, addToQueue, onPrint, onSaveProduct, onDeleteProduct, onSaveSettings, onTestPrinter, onDetect, onTestPrint, go, toast, user }; return React.createElement('div', { className: 'app' }, React.createElement(Sidebar, { route, go, user, onLogout: logout, printer }), React.createElement('main', { className: 'main' }, route === 'dash' && React.createElement(window.Dashboard, props), route === 'products' && React.createElement(window.Products, props), route === 'print' && React.createElement(window.PrintStudio, props), route === 'history' && React.createElement(window.History, props), route === 'settings' && React.createElement(window.Settings, props) ), toastNode ); } function Boot() { return React.createElement('div', { className: 'boot' }, React.createElement('div', { className: 'brand-mark', style: { width: 52, height: 52 } }, React.createElement(Icon, { name: 'barcode', size: 28, color: '#0a0b0e', stroke: 2.2 })), React.createElement('div', { className: 'boot-spin' }) ); } function InstallNeeded({ message }) { return React.createElement('div', { className: 'login' }, React.createElement('div', { className: 'login-form', style: { maxWidth: 'none', alignItems: 'flex-start' } }, React.createElement('div', { className: 'brand big' }, React.createElement('div', { className: 'brand-mark' }, React.createElement(Icon, { name: 'barcode', size: 24, color: '#0a0b0e', stroke: 2.2 })), React.createElement('div', null, React.createElement('b', null, 'Barcode'), React.createElement('span', null, 'Label CRM')) ), React.createElement('h1', { style: { marginTop: 20 } }, 'Setup needed'), React.createElement('p', { className: 'login-sub' }, message), React.createElement('div', { className: 'hint-box', style: { maxWidth: 460 } }, React.createElement('b', null, 'First time? '), 'Open ', React.createElement('a', { href: 'install.php', style: { color: 'var(--amber)' } }, 'install.php'), ' to create the database and seed your products, then return here.'), React.createElement('a', { className: 'btn btn-primary', href: 'install.php', style: { marginTop: 18 } }, 'Open installer →') ) ); } function Sidebar({ route, go, user, onLogout, printer }) { const [open, setOpen] = useState(false); const nav = [ { id: 'dash', label: 'Dashboard', icon: 'dash' }, { id: 'print', label: 'Print Studio', icon: 'print' }, { id: 'products', label: 'Products', icon: 'box' }, { id: 'history', label: 'History', icon: 'history' }, { id: 'settings', label: 'Settings', icon: 'settings' } ]; return React.createElement(React.Fragment, null, React.createElement('button', { className: 'mobile-nav-btn', onClick: () => setOpen(o => !o) }, React.createElement(Icon, { name: open ? 'x' : 'list', size: 20 })), open && React.createElement('div', { className: 'nav-scrim', onClick: () => setOpen(false) }), React.createElement('aside', { className: 'sidebar' + (open ? ' open' : '') }, React.createElement('div', { className: 'brand' }, React.createElement('div', { className: 'brand-mark' }, React.createElement(Icon, { name: 'barcode', size: 22, color: '#0a0b0e', stroke: 2.2 })), React.createElement('div', null, React.createElement('b', null, 'Barcode'), React.createElement('span', null, 'Label CRM')) ), React.createElement('nav', { className: 'nav' }, nav.map(n => React.createElement('button', { key: n.id, className: 'nav-item' + (route === n.id ? ' on' : ''), onClick: () => { go(n.id); setOpen(false); } }, React.createElement(Icon, { name: n.icon, size: 19, stroke: 1.8 }), React.createElement('span', null, n.label))) ), React.createElement('div', { className: 'side-printer' }, React.createElement('span', { className: 'pulse-dot ' + (printer.connected ? 'live' : 'off') }), React.createElement('div', null, React.createElement('b', null, printer.connected ? 'Printer connected' : 'Printer not tested'), React.createElement('span', { className: 'mono' }, printer.model) ) ), React.createElement('div', { className: 'side-user' }, React.createElement('div', { className: 'avatar', style: { background: user.color } }, user.name.split(' ').map(s => s[0]).join('').slice(0, 2)), React.createElement('div', { className: 'su-main' }, React.createElement('b', null, user.name), React.createElement('span', null, user.role)), React.createElement('button', { className: 'icon-btn', title: 'Log out', onClick: onLogout }, React.createElement(Icon, { name: 'logout', size: 17 })) ) ) ); } const DEMO = [ { name: 'Aarav Mehta', email: 'admin@barcode.crm', role: 'admin', pass: 'admin123', color: '#cda653' }, { name: 'Priya Nair', email: 'operator@barcode.crm', role: 'operator', pass: 'operator123', color: '#48a979' } ]; function Login({ onLogin }) { const [email, setEmail] = useState('admin@barcode.crm'); const [pass, setPass] = useState(''); const [err, setErr] = useState(''); const [busy, setBusy] = useState(false); const submit = async (e) => { e && e.preventDefault(); setBusy(true); setErr(''); try { const u = await API.login(email, pass); await onLogin(u); } catch (ex) { setErr(ex.message); setBusy(false); } }; return React.createElement('div', { className: 'login' }, React.createElement('div', { className: 'login-art' }, React.createElement('div', { className: 'login-art-inner' }, React.createElement('div', { className: 'brand big' }, React.createElement('div', { className: 'brand-mark' }, React.createElement(Icon, { name: 'barcode', size: 30, color: '#0a0b0e', stroke: 2.2 })), React.createElement('div', null, React.createElement('b', null, 'Barcode'), React.createElement('span', null, 'Label CRM')) ), React.createElement('h2', null, 'Generate, batch & print product labels — your whole catalog, one console.'), React.createElement('div', { className: 'login-brands' }, React.createElement('div', { className: 'lb' }, React.createElement(BrandDot, { brand: 'LUXURIATE', size: 11 }), 'LUXURIATE', React.createElement('em', null, '122 SKUs')), React.createElement('div', { className: 'lb' }, React.createElement(BrandDot, { brand: 'VITMINVEDA', size: 11 }), 'VITMINVEDA', React.createElement('em', null, '91 SKUs')) ), React.createElement('div', { className: 'login-strip' }, [...Array(40)].map((_, i) => React.createElement('span', { key: i, style: { width: (1 + (i * 7) % 4) + 'px' } }))) ) ), React.createElement('form', { className: 'login-form', onSubmit: submit }, React.createElement('h1', null, 'Sign in'), React.createElement('p', { className: 'login-sub' }, 'Use a demo account below, or your own credentials.'), React.createElement('label', { className: 'field' }, React.createElement('span', { className: 'field-label' }, 'Email'), React.createElement('input', { className: 'input', value: email, onChange: e => { setEmail(e.target.value); setErr(''); } })), React.createElement('label', { className: 'field' }, React.createElement('span', { className: 'field-label' }, 'Password'), React.createElement('input', { className: 'input', type: 'password', value: pass, placeholder: '••••••', onChange: e => { setPass(e.target.value); setErr(''); } })), err && React.createElement('div', { className: 'login-err' }, React.createElement(Icon, { name: 'alert', size: 15 }), err), React.createElement(Btn, { variant: 'primary', full: true, icon: 'logout', disabled: busy, onClick: submit }, busy ? 'Signing in…' : 'Enter console'), React.createElement('div', { className: 'demo-accts' }, DEMO.map(u => React.createElement('button', { type: 'button', key: u.email, className: 'demo-acct', onClick: () => { setEmail(u.email); setPass(u.pass); setErr(''); } }, React.createElement('div', { className: 'avatar sm', style: { background: u.color } }, u.name.split(' ').map(s => s[0]).join('').slice(0, 2)), React.createElement('div', null, React.createElement('b', null, u.name), React.createElement('span', null, u.role, ' · ', u.pass)) )) ) ) ); } window.__BarcodeApp = App; })();