const APP_VERSION = "50c23f2";
// ── User dropdown menu ────────────────────────────────────────────────────────
function UserMenu({ username, onLogout }) {
const [open, setOpen] = React.useState(false);
const ref = React.useRef(null);
React.useEffect(() => {
function handleClick(e) {
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
}
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, []);
return (
{open && (
)}
);
}
// ── Hash routing ──────────────────────────────────────────────────────────────
function useRoute() {
const [hash, setHash] = React.useState(window.location.hash || "#/receipts");
React.useEffect(() => {
const h = () => setHash(window.location.hash || "#/receipts");
window.addEventListener("hashchange", h);
return () => window.removeEventListener("hashchange", h);
}, []);
if (hash.startsWith("#/receipts/") && hash.endsWith("/correct")) {
const id = hash.slice(11, -8);
return { page: "correct", id };
}
if (hash.startsWith("#/receipts/"))
return { page: "receipt", id: hash.slice(11) };
if (hash.startsWith("#/new-receipt")) return { page: "new-receipt" };
if (hash.startsWith("#/items")) return { page: "items" };
if (hash.startsWith("#/basket")) return { page: "basket" };
if (hash.startsWith("#/stats")) return { page: "stats" };
if (hash.startsWith("#/item/"))
return { page: "item-details", barcode: hash.slice(7) };
if (hash.startsWith("#/item")) return { page: "item-details", barcode: "" };
return { page: "receipts" };
}
// ── Auth state ────────────────────────────────────────────────────────────────
function useAuth() {
// null = not authed, undefined = loading, object = { user_id, username }
const [auth, setAuth] = React.useState(undefined);
React.useEffect(() => {
fetch("/auth/me")
.then((r) => (r.ok ? r.json() : null))
.then(setAuth)
.catch(() => setAuth(null));
}, []);
async function logout() {
await fetch("/auth/logout", { method: "POST" });
setAuth(null);
}
return { auth, setAuth, logout };
}
// ── App shell ─────────────────────────────────────────────────────────────────
function App() {
const route = useRoute();
const { auth, setAuth, logout } = useAuth();
const onReceipts = route.page === "receipts" || route.page === "receipt";
const onItems = route.page === "items";
const onBasket = route.page === "basket";
const onStats = route.page === "stats";
// Loading — check session before rendering anything
if (auth === undefined) {
return (
);
}
// Not authenticated — show login form
if (auth === null) {
return ;
}
if (route.page === "correct") {
return (
);
}
if (route.page === "new-receipt") {
return (
);
}
return (
{/* Top bar */}
{/* Mobile: brand + logout */}
{/* Desktop: brand + nav */}
{route.page === "receipts" && }
{route.page === "receipt" && }
{route.page === "items" && }
{route.page === "basket" && }
{route.page === "stats" && }
{route.page === "item-details" && (
)}
{/* Footer */}
{/* Mobile bottom tab bar */}
);
}
ReactDOM.createRoot(document.getElementById("root")).render();