// VMAX POS app — PIN sign-in (multi-user), identity session (active user + idle/stale),
// chrome, routing between order + queue, Tweaks dock. Landscape touchscreen.
(function () {
const DS = window.VMAX365DesignSystem_0f78b2;
const { SegmentedControl } = DS;
const { Icon, NetBadge } = window.VMAXUI;
const { Avatar, PinDialog, Toaster, pushToast, NotifBadge } = window.VMAXW;
const { useVMAX, notifications } = window.VMAXStore;
const { OrderSurface } = window.VMAXORDER;
const { MakerQueueSurface } = window.VMAXQUEUE;
const { RequestStockSheet, CashierClose } = window.VMAXEXTRA;
const { TweakDock } = window.VMAXTWEAKS;
const IDLE_MS = 180000;
const SURFACE_LABEL = { pos: 'Point of sale', queue: 'Order queue' };
function surfacesFor(p) {
if (!p) return ['pos'];
const r = p.roles, out = [];
if (r.includes('cashier') || r.includes('owner') || r.includes('manager')) out.push('pos');
if (r.includes('maker') || r.includes('owner') || r.includes('manager')) out.push('queue');
return out.length ? out : ['pos'];
}
function useIdentity() {
const [s, a] = useVMAX();
const [signedIds, setSignedIds] = React.useState([]);
const [activeId, setActiveId] = React.useState(null);
const [stale, setStale] = React.useState(false);
const lastRef = React.useRef(Date.now());
const people = s.people;
const signedIn = signedIds.map((id) => people.find((p) => p.id === id)).filter(Boolean);
const active = people.find((p) => p.id === activeId) || signedIn[0] || null;
React.useEffect(() => { const t = setInterval(() => { if (Date.now() - lastRef.current > IDLE_MS) setStale(true); }, 5000); return () => clearInterval(t); }, []);
const markFresh = () => { lastRef.current = Date.now(); setStale(false); };
return {
signedIn, active, stale, people,
verify: (id, pin) => a.verifyPin(id, pin),
markFresh,
signIn: (p) => { setSignedIds((ids) => (ids.includes(p.id) ? ids : [...ids, p.id])); setActiveId(p.id); markFresh(); },
setActive: (p) => { setActiveId(p.id); markFresh(); },
signOut: (id) => { setSignedIds((ids) => ids.filter((x) => x !== id)); if (activeId === id) setActiveId(null); },
signOutAll: () => { setSignedIds([]); setActiveId(null); },
simulateIdle: () => { lastRef.current = Date.now() - IDLE_MS - 1; setStale(true); pushToast('Till marked idle — next print will re-verify', 'warn'); },
};
}
// ---------- Sign in (PIN) ----------
function SignIn({ session }) {
const [s, a] = useVMAX();
const [pinFor, setPinFor] = React.useState(null);
const roster = s.people.filter((p) => p.roles.some((r) => ['cashier', 'maker', 'owner', 'manager'].includes(r)));
const setMode = pinFor && (pinFor.pin == null || pinFor.resetPending);
return (
Heritage Community Park · enter your PIN to start a shift
Demo PINs — Clara 1111 · Joseph 2222 · Maria 3333 · Owner 2468 · Bright sets a new one. Or open Tweaks (⚙ lower-right) to jump in.
{roster.map((p) => (
))}
session.verify(pinFor.id, pin)} onClose={() => setPinFor(null)}
onSuccess={(pin) => { if (setMode) a.setPin(pinFor.id, pin); const p = { ...pinFor, pin }; session.signIn(p); pushToast('Signed in · ' + pinFor.name.split(' ')[0]); setPinFor(null); }} />
);
}
// ---------- Chrome user cluster ----------
function UserCluster({ session }) {
const [open, setOpen] = React.useState(false);
const [addOpen, setAddOpen] = React.useState(false);
const [switchTo, setSwitchTo] = React.useState(null);
const [reqOpen, setReqOpen] = React.useState(false);
const [cashOpen, setCashOpen] = React.useState(false);
const [s] = useVMAX();
const active = session.active;
const others = s.people.filter((p) => p.roles.some((r) => ['cashier', 'maker', 'owner', 'manager'].includes(r)) && p.pin && !session.signedIn.find((x) => x.id === p.id));
return (
{open && (
setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 79 }} />
Signed in ({session.signedIn.length})
{session.signedIn.map((p) => { const isA = active && p.id === active.id; return (
); })}
)}
{/* switch active (PIN) */}
session.verify(switchTo.id, pin)} onClose={() => setSwitchTo(null)} onSuccess={() => { session.setActive(switchTo); pushToast('Active user · ' + switchTo.name.split(' ')[0]); setSwitchTo(null); }} />
{/* add another (name -> PIN) */}
{addOpen && setAddOpen(false)} />}
setReqOpen(false)} who={active ? active.name : ''} />
setCashOpen(false)} who={active ? active.name : ''} />
);
}
const menuRow = { width: '100%', display: 'flex', alignItems: 'center', gap: 10, padding: '9px 10px', border: 'none', background: 'transparent', cursor: 'pointer', borderRadius: 'var(--radius-md)', color: 'var(--vmax-ink)', fontWeight: 600, fontSize: 13, fontFamily: 'var(--font-body)' };
function AddUserFlow({ session, others, onClose }) {
const [pinFor, setPinFor] = React.useState(null);
const { Sheet } = window.VMAXUI;
if (pinFor) return session.verify(pinFor.id, pin)} onClose={onClose} onSuccess={() => { session.signIn(pinFor); pushToast(pinFor.name.split(' ')[0] + ' signed in'); onClose(); }} />;
return (
Add another user
{others.map((p) =>
)}
{others.length === 0 &&
Everyone's already signed in.
}
);
}
function App() {
const [s, a] = useVMAX();
const session = useIdentity();
const notif = notifications(s);
const surfaces = surfacesFor(session.active);
const [surface, setSurface] = React.useState('pos');
React.useEffect(() => { if (!surfaces.includes(surface)) setSurface(surfaces[0]); }, [surfaces.join()]);
const pick = (id) => s.people.find((p) => p.id === id);
const roleJump = [
pick('u_clara') && { label: 'Cashier', person: pick('u_clara') },
pick('u_joseph') && { label: 'Maker', person: pick('u_joseph') },
pick('u_owner') && { label: 'Owner', person: pick('u_owner') },
].filter(Boolean);
const dock = session.signIn(p)} onSimulateIdle={session.active ? session.simulateIdle : null} />;
if (!session.active) return {dock};
return (
{surface === 'pos' && }
{surface === 'queue' && }
{dock}
);
}
window.VMAXPosApp = App;
})();