feat(auth): sidebar shows current user + logout button
This commit is contained in:
+60
-13
@@ -7,6 +7,7 @@ import {
|
|||||||
Inbox as InboxIcon,
|
Inbox as InboxIcon,
|
||||||
Layers,
|
Layers,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
|
LogOut,
|
||||||
Receipt,
|
Receipt,
|
||||||
Stethoscope,
|
Stethoscope,
|
||||||
Upload as UploadIcon,
|
Upload as UploadIcon,
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useReconciliation } from "@/hooks/useReconciliation";
|
import { useReconciliation } from "@/hooks/useReconciliation";
|
||||||
|
import { useAuth } from "@/auth/useAuth";
|
||||||
|
|
||||||
const nav = [
|
const nav = [
|
||||||
{ to: "/", label: "Dashboard", icon: LayoutDashboard, end: true },
|
{ to: "/", label: "Dashboard", icon: LayoutDashboard, end: true },
|
||||||
@@ -188,25 +190,70 @@ export function Sidebar() {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Operator card — always at the bottom. Ringed, mono id, the
|
{/* Operator card — always at the bottom. Ringed, mono id, the
|
||||||
account role under the name. */}
|
account role under the name. The avatar initial + username +
|
||||||
|
role all come from `useAuth()` now (replaced the hardcoded
|
||||||
|
"Jordan K. / Administrator"); the Sign out button next to it
|
||||||
|
hits `authApi.logout` and drops the operator back at /login. */}
|
||||||
<div className="px-3 py-4 border-t border-sidebar-border/80">
|
<div className="px-3 py-4 border-t border-sidebar-border/80">
|
||||||
<div className="flex items-center gap-3 rounded-md p-2 hover:bg-muted/40 cursor-default">
|
<CurrentUserCard />
|
||||||
<div className="relative h-8 w-8 rounded-full bg-accent/20 ring-1 ring-inset ring-accent/40 flex items-center justify-center text-[10.5px] font-semibold text-accent mono">
|
|
||||||
JK
|
|
||||||
<span className="absolute -bottom-0.5 -right-0.5 h-2.5 w-2.5 rounded-full bg-[hsl(var(--success))] ring-2 ring-sidebar" />
|
|
||||||
</div>
|
|
||||||
<div className="leading-tight min-w-0">
|
|
||||||
<div className="text-[13px] font-medium truncate">Jordan K.</div>
|
|
||||||
<div className="mono text-[10px] text-muted-foreground tracking-wider uppercase">
|
|
||||||
Administrator
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom-of-sidebar identity card. Driven by `useAuth()` so the
|
||||||
|
* username + role update whenever the operator's session changes
|
||||||
|
* (e.g. an admin re-grants a role via the admin pages, or the
|
||||||
|
* session expires and a fresh login lands them back with a
|
||||||
|
* different username).
|
||||||
|
*/
|
||||||
|
function CurrentUserCard() {
|
||||||
|
const { user, logout } = useAuth();
|
||||||
|
if (!user) {
|
||||||
|
// Shouldn't happen — the route guard ensures the sidebar only
|
||||||
|
// mounts after auth is established. Render a harmless placeholder
|
||||||
|
// so the layout doesn't reflow if the context ever lags.
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-3 rounded-md p-2 text-[12px] text-muted-foreground"
|
||||||
|
aria-hidden
|
||||||
|
>
|
||||||
|
<div className="h-8 w-8 rounded-full bg-muted/40" />
|
||||||
|
<div className="leading-tight">Signed out</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Use the first letter of the username for the avatar monogram;
|
||||||
|
// fall back to "?" if the username is somehow empty.
|
||||||
|
const initial = user.username?.[0]?.toUpperCase() ?? "?";
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-3 rounded-md p-2 hover:bg-muted/40 group">
|
||||||
|
<div className="relative h-8 w-8 rounded-full bg-accent/20 ring-1 ring-inset ring-accent/40 flex items-center justify-center text-[10.5px] font-semibold text-accent mono shrink-0">
|
||||||
|
{initial}
|
||||||
|
<span className="absolute -bottom-0.5 -right-0.5 h-2.5 w-2.5 rounded-full bg-[hsl(var(--success))] ring-2 ring-sidebar" />
|
||||||
|
</div>
|
||||||
|
<div className="leading-tight min-w-0 flex-1">
|
||||||
|
<div className="text-[13px] font-medium truncate">{user.username}</div>
|
||||||
|
<div className="mono text-[10px] text-muted-foreground tracking-wider uppercase">
|
||||||
|
{user.role}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
void logout();
|
||||||
|
}}
|
||||||
|
aria-label="Sign out"
|
||||||
|
title="Sign out"
|
||||||
|
className="shrink-0 inline-flex items-center justify-center h-7 w-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring opacity-60 group-hover:opacity-100"
|
||||||
|
>
|
||||||
|
<LogOut className="h-3.5 w-3.5" strokeWidth={1.5} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function SectionLabel({ children }: { children: React.ReactNode }) {
|
function SectionLabel({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="px-3 mb-1.5 eyebrow text-muted-foreground/60">
|
<div className="px-3 mb-1.5 eyebrow text-muted-foreground/60">
|
||||||
|
|||||||
Reference in New Issue
Block a user