import { AtSign, BadgeCheck, Bot, ChevronDown, Database, Film, LayoutDashboard, LogOut, Megaphone, MessageSquareText, Settings, ShieldAlert, ShieldCheck, Smile, Stamp, Users, Sticker } from "lucide-react"; import { useEffect, useState, type ReactNode } from "react"; import { api } from "../api"; import { permissionBotVerificationReview, permissionServerManage, permissionVerificationReview, useCan, useThirdPartyVerificationHidden } from "../permissions"; import { type Navigate, type RouteState, routeTitle } from "../routing"; import { ThemeSwitch } from "../theme"; import { AppLink } from "./AppLink"; export function BootScreen() { return (
OwpenGram OwpenGram {"Admin Console"}
); } export function Shell({ actor, build, route, navigate, onLogout, children }: { actor: string; build?: { commit: string; short_commit: string; dirty: boolean; build_time: string }; route: RouteState; navigate: Navigate; onLogout: () => void; children: ReactNode; }) { // The verification queue is hidden for a session without verification.review: // the entry would only lead to a 403 (and the route itself is gated as well). const canReviewVerification = useCan(permissionVerificationReview); // Same reasoning for the third-party queue, which has its own right: the two // sections are granted independently, so one entry can be visible without the other. const canReviewBotVerification = useCan(permissionBotVerificationReview); const canManageServer = useCan(permissionServerManage); // Server identity (name/icon) is admin-editable per Server Settings -> // Server identity, and takes over the sidebar branding when set -- the // operator's own server should look like their server, not like the // "OwpenGram" reference build, once they've bothered to configure it. // Only fetched for sessions that can even see Server Settings; a session // without that permission just gets the default branding. const [identity, setIdentity] = useState<{ name: string; iconExt?: string } | null>(null); useEffect(() => { if (!canManageServer) return; api.serverIdentity() .then((info) => setIdentity({ name: info.name, iconExt: info.icon_ext })) .catch(() => undefined); }, [canManageServer]); const [brandIconFailed, setBrandIconFailed] = useState(false); const brandName = identity?.name?.trim() || "OwpenGram"; const brandIconSrc = identity?.iconExt && !brandIconFailed ? api.serverIconURL() : "/logo.png"; // Third-party verification is additionally hidden by default (not fully // finished) regardless of what the session was granted -- see permissions.tsx. const thirdPartyVerificationHidden = useThirdPartyVerificationHidden(); const messagesActive = route.path.startsWith("/messages"); const [messagesOpen, setMessagesOpen] = useState(messagesActive); useEffect(() => { if (messagesActive) { setMessagesOpen(true); } }, [messagesActive]); async function logout() { await api.logout().catch(() => undefined); onLogout(); } return (

{routeTitle(route.path)}

{`Actor: ${actor}`}
{children}
); } function NavLink({ href, route, navigate, icon, children, activeWhen }: { href: string; route: RouteState; navigate: Navigate; icon?: ReactNode; children: ReactNode; activeWhen?: (path: string) => boolean; }) { const active = activeWhen ? activeWhen(route.path) : href === "/" ? route.path === "/" : route.path.startsWith(href); return ( {icon ?? ); }