Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
This commit is contained in:
commit
aa5e262f3a
880 changed files with 111042 additions and 0 deletions
77
apps/web/src/components/shell/sidebar.tsx
Normal file
77
apps/web/src/components/shell/sidebar.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useAuth } from "../../lib/auth-store";
|
||||
import { useApiResource } from "../../lib/use-api";
|
||||
import type { DraftRecord } from "../../lib/api-types";
|
||||
import { LogoMark } from "../brand/logo";
|
||||
import { NavIcon } from "../brand";
|
||||
|
||||
const NAV = [
|
||||
{ href: "/streams", label: "Streams" },
|
||||
{ href: "/compose", label: "Compose" },
|
||||
{ href: "/calendar", label: "Calendar" },
|
||||
{ href: "/video", label: "Video" },
|
||||
{ href: "/control-room", label: "Control Room" },
|
||||
{ href: "/inbox", label: "Inbox" },
|
||||
{ href: "/approvals", label: "Approvals" },
|
||||
{ href: "/archive", label: "Archive" },
|
||||
{ href: "/analytics", label: "Analytics" },
|
||||
{ href: "/connections", label: "Channels" },
|
||||
{ href: "/team", label: "Team" },
|
||||
{ href: "/audit", label: "Audit" },
|
||||
{ href: "/onboarding", label: "Onboarding" },
|
||||
{ href: "/settings", label: "Settings" },
|
||||
];
|
||||
|
||||
const identity = (drafts: DraftRecord[]): DraftRecord[] => drafts;
|
||||
const emptyDrafts: DraftRecord[] = [];
|
||||
|
||||
export function Sidebar(): React.ReactElement {
|
||||
const pathname = usePathname();
|
||||
const { session, can } = useAuth();
|
||||
const drafts = useApiResource<DraftRecord[], DraftRecord[]>("/publishing/drafts", identity, emptyDrafts, { state: "in_review", limit: 100 });
|
||||
// Real in-review count from the API; hidden while loading/offline and for non-approvers.
|
||||
const approvals = can("approval.grant") && drafts.source === "live" ? drafts.data.length : 0;
|
||||
const workspaceName = session?.workspace.name ?? "Studio";
|
||||
return (
|
||||
<aside className="hidden h-screen w-52 shrink-0 flex-col border-r border-border-subtle bg-surface lg:flex">
|
||||
<div className="flex items-center gap-2.5 border-b border-border-subtle px-4 py-4">
|
||||
<LogoMark size={30} title="Tactical Media Broadcast Hub" />
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-sm font-semibold tracking-tight">Tactical Media Broadcast Hub</div>
|
||||
<div className="truncate text-2xs text-text-faint" title={workspaceName}>
|
||||
{workspaceName} ▾
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex-1 overflow-y-auto scrollbar-thin p-2">
|
||||
{NAV.map((item) => {
|
||||
const active = pathname.startsWith(item.href);
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`group flex items-center justify-between rounded-md px-3 py-1.5 text-sm transition-colors duration-150 ${item.href === "/control-room" ? "min-h-11" : ""} ${
|
||||
active ? "bg-accent-dim text-accent" : "text-text-dim hover:bg-surface-2 hover:text-text"
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center gap-2.5">
|
||||
{item.href === "/video" || item.href === "/control-room" ? (
|
||||
<NavIcon name="video" size={14} />
|
||||
) : (
|
||||
<span className={`h-1.5 w-1.5 rounded-full ${active ? "bg-accent" : "bg-border group-hover:bg-text-faint"}`} />
|
||||
)}
|
||||
{item.label}
|
||||
</span>
|
||||
{item.href === "/approvals" && approvals > 0 && (
|
||||
<span className="text-2xs text-text-faint">{approvals}</span>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue