);
}
// Shared visual baseline for every clickable icon button in the sidebar foot.
// Styled with a subtle border and background to make them feel like tangible widgets,
// resolving the flat "floating icons in empty space" visual issue.
const FOOT_ICON_BTN =
"flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border/40 bg-muted/20 text-muted-foreground hover:bg-accent hover:text-foreground focus:outline-none transition-all duration-150 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed";
function UserStrip() {
// The sidebar foot identifies the principal and exposes one action — sign out.
// identity?.email is the only display-safe field (user_id is a UUID, role is
// server-truth not display). While /me is loading or has failed we render a
// muted placeholder rather than a broken row, so the strip never flashes empty.
const { identity, refresh } = useTier();
const { t } = useTranslation("account");
const [signingOut, setSigningOut] = useState(false);
// Mirrors Account.tsx#signOut: idempotent on the server; refresh() flips
// unauthenticated → RequireAuth bounces to /login. No navigate() needed.
async function signOut() {
if (signingOut) return;
setSigningOut(true);
try {
await api.logout();
} finally {
await refresh();
}
}
return (
{identity?.email ?? —}
{identity?.role ?? "user"}
);
}
function LangToggle() {
const { i18n } = useTranslation();
return (
);
}
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
const { t } = useTranslation("common");
return (
);
}
export function AppShell() {
const { isAdmin } = useTier();
const { view } = useViewMode();
const { t, i18n } = useTranslation("navigation");
// Sections are derived from is_admin AND the chosen home: visibleSections drops
// every admin-gated section for a non-admin (fail-closed — false while /me loads or
// on failure), then the role-switcher's view narrows further to the home the admin
// is currently viewing. sectionsForView can only ever narrow, never widen, so an
// admin who steps down to the User-home sees a plain user's sidebar.
const sections = sectionsForView(view, isAdmin);
// Sync document metadata with the active language.
useEffect(() => {
document.documentElement.lang = i18n.language;
document.title = t("common:page_title");
}, [i18n.language, t]);
return (