first commit

This commit is contained in:
Matt Kane
2026-04-01 10:44:22 +01:00
commit 43fcb9a131
1789 changed files with 395041 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
import { Button, LinkButton, Popover } from "@cloudflare/kumo";
import { SignOut, Shield, Gear, ArrowSquareOut } from "@phosphor-icons/react";
import { Link } from "@tanstack/react-router";
import * as React from "react";
import { apiFetch } from "../lib/api/client";
import { useCurrentUser } from "../lib/api/current-user";
import { Sidebar } from "./Sidebar";
import { ThemeToggle } from "./ThemeToggle";
export type { CurrentUser } from "../lib/api/current-user";
async function handleLogout() {
const res = await apiFetch("/_emdash/api/auth/logout?redirect=/_emdash/admin/login", {
method: "POST",
credentials: "same-origin",
});
if (res.redirected) {
window.location.href = res.url;
} else {
window.location.href = "/_emdash/admin/login";
}
}
/**
* Admin header with mobile menu toggle and user actions.
* Uses useSidebar() hook from kumo Sidebar.Provider context.
*/
export function Header() {
const [userMenuOpen, setUserMenuOpen] = React.useState(false);
const { data: user } = useCurrentUser();
// Get display name and initials
const displayName = user?.name || user?.email || "User";
const initialsSource = user?.name || user?.email || "U";
const initials = (initialsSource[0] ?? "U").toUpperCase();
return (
<header className="sticky top-0 z-10 flex h-16 items-center justify-between border-b bg-kumo-base px-4">
{/* Sidebar toggle — collapses to icon mode on desktop, opens drawer on mobile */}
<Sidebar.Trigger />
{/* Right side actions */}
<div className="flex items-center space-x-2">
{/* View site link */}
<LinkButton variant="ghost" size="sm" href="/" external>
<ArrowSquareOut className="h-4 w-4 mr-1" />
View Site
</LinkButton>
{/* Theme toggle */}
<ThemeToggle />
{/* User menu */}
<Popover open={userMenuOpen} onOpenChange={setUserMenuOpen}>
<Popover.Trigger asChild>
<Button variant="ghost" size="sm" className="gap-2">
{user?.avatarUrl ? (
<img src={user.avatarUrl} alt="" className="h-6 w-6 rounded-full object-cover" />
) : (
<div className="h-6 w-6 rounded-full bg-kumo-brand/10 flex items-center justify-center text-xs font-medium">
{initials}
</div>
)}
<span className="hidden sm:inline max-w-[120px] truncate">{displayName}</span>
</Button>
</Popover.Trigger>
<Popover.Content className="w-56 p-2" align="end">
{/* User info */}
<div className="px-3 py-2 border-b mb-1">
<div className="font-medium truncate">{user?.name || "User"}</div>
<div className="text-xs text-kumo-subtle truncate">{user?.email}</div>
</div>
<div className="grid gap-1">
<Link
to="/settings/security"
onClick={() => setUserMenuOpen(false)}
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-kumo-tint"
>
<Shield className="h-4 w-4" />
Security Settings
</Link>
<Link
to="/settings"
onClick={() => setUserMenuOpen(false)}
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-kumo-tint"
>
<Gear className="h-4 w-4" />
Settings
</Link>
<hr className="my-1" />
<button
onClick={handleLogout}
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-kumo-danger hover:bg-kumo-danger/10 w-full text-left"
>
<SignOut className="h-4 w-4" />
Log out
</button>
</div>
</Popover.Content>
</Popover>
</div>
</header>
);
}