import { useState } from 'react'; import { Monitor, QrCode, Store, UserCog, UserRound, Users } from 'lucide-react'; import { StoreQrDrawer } from './StoreQrDrawer'; import { AppShell, IconButton, type MenuEntry, type NavEntry } from '@/components/shell/AppShell'; import { useAuth } from '@/auth/AuthContext'; import { useTenantLocations } from '@/queries/hooks'; import { BranchScopeProvider, useBranchScope } from '@/features/store-admin/BranchScope'; /** * Store user — one branch, and only that branch. * * The same four destinations as the Store Admin, for the same reason: the * questions a shop asks in a day are what did we take, what do we sell, what is * on the shelf, and how did the period go. What changes is the scope, not the * shape — so the pages are the merchant's pages with the branch fixed, rather * than a second set that drifts. * * The old console gave this role five nav entries over a six-tab hub, and one * of them (Store Console) rendered another one (Products) inside itself. Four * pages, each owning one question, replaces it. */ const NAV: readonly NavEntry[] = [ { to: '/store/console', label: 'Console' }, { to: '/store/products', label: 'Products' }, { to: '/store/sales', label: 'Sales' }, { to: '/store/reports', label: 'Reports' }, ]; /** * The account menu. * * Everything here is either about this person, or about the shop's fittings — * reached when something needs checking, not worked from hour to hour. The * merchant's "New branch" entry is deliberately absent: a shop does not * commission outlets, and `createtenantlocation` also spawns a login. * * Customers sits here rather than in the nav because the four nav slots each * answer a question about the trading day, and "who orders from us" is a * lookup. Move it up if the shop uses it hourly. * * The store QR is NOT here — it is an icon in the header, because it opens in * place and closes again. A destination is for somewhere you work; this is * something you glance at and print. */ const MANAGE: readonly MenuEntry[] = [ { to: '/store/customers', label: 'Customers', icon: , note: 'Who orders from this shop', }, { to: '/store/terminals', label: 'Terminals', icon: , note: 'Till health at this counter', }, { to: '/store/staff', label: 'Counter staff', icon: , note: 'Who can open the till', }, { to: '/store/account', label: 'My account', icon: , note: 'Profile and store binding', }, ]; /** * Resolve the outlet this account is bound to. * * Two steps, and no third. `app_users.locationid` is the binding; a tenant with * exactly one outlet needs no id at all. The old console had four more * fallbacks below these — including matching the email's local part against a * store's address, and falling back to `applocationid` — and those are guesses * at an authorisation boundary. `applocationid` is a CITY id: passing one to a * POS read returns an empty board, or another shop's takings if the numbers * happen to collide. */ function useResolvedOutlet(): { locationid: number | null; isLoading: boolean } { const { user } = useAuth(); const { data, isLoading } = useTenantLocations(user?.tenantid || undefined); const branches = data ?? []; if (user?.locationid && user.locationid > 0) return { locationid: user.locationid, isLoading }; if (isLoading) return { locationid: null, isLoading: true }; if (branches.length === 1 && branches[0]) return { locationid: branches[0].locationid, isLoading }; return { locationid: null, isLoading: false }; } export function StoreUserShell() { const { locationid, isLoading } = useResolvedOutlet(); const [isQrOpen, setQrOpen] = useState(false); if (isLoading) return ; if (locationid === null) return ; return ( } manageItems={MANAGE} headerActions={ setQrOpen(true)}> } /> {isQrOpen ? setQrOpen(false)} /> : null} ); } /** * Where the merchant has a branch selector, the shop has a label. * * Same slot, same size, same position — so the two workspaces read as one * product — but not a control, because there is nothing to choose. A disabled * dropdown would say "you may not switch"; a label says "this is your shop", * which is the true statement. */ function BranchPill() { const { current, isLoading } = useBranchScope(); const label = isLoading ? 'Loading…' : (current?.locationname ?? 'Your store'); return ( {label} ); } function Resolving() { return (

Finding your store…

); } /** * The honest refusal. * * Every read on every page below is authorised by `locationid` alone, so a page * that guessed one would not fail loudly — it would quietly show another shop's * takings, or an empty board that reads as a quiet day. */ function NoStore() { const { signOut } = useAuth(); return (

No store assigned

Your account is not linked to a branch yet, so there is nothing to show. Ask your store administrator to allocate you to one.

); }