Repository Public
1import Link from "next/link";2import { Logo } from "@/components/Logo";3import { ThemeToggle } from "@/components/Theme";4import { requireRole } from "@/lib/auth";5import { getPolicy } from "@/lib/budget";6import { killSwitch, logout } from "../actions";78const NAV: [string, string][] = [["Dashboard", "/studio"], ["Projects", "/studio/projects"], ["Director", "/studio/director"], ["Concept Lab", "/studio/projects/new"], ["Jobs", "/studio/jobs"], ["Films", "/studio/films"], ["Programme", "/studio/schedule"], ["Costs", "/studio/costs"], ["Reports", "/studio/reports"], ["Settings", "/studio/settings"], ["Audit log", "/studio/audit"]];910export default async function StudioLayout({ children }: { children: React.ReactNode }) {11 const user = await requireRole("viewer", { allowPasswordChange: true });12 const policy = await getPolicy();13 return (14 <div className="min-h-dvh lg:grid lg:grid-cols-[230px_1fr]">15 <aside className="border-b border-[var(--color-line-soft)] bg-[var(--color-stage)] lg:sticky lg:top-0 lg:h-dvh lg:border-b-0 lg:border-r">16 <div className="flex items-center justify-between px-5 py-4"><Link href="/studio" aria-label="CLIP Studio"><Logo size={20} /></Link><span className="flex items-center gap-1"><span className="label">Studio</span><ThemeToggle /></span></div>17 <nav aria-label="Studio" className="flex gap-1 overflow-x-auto px-3 pb-3 text-sm lg:block lg:space-y-0.5">18 {NAV.map(([label, href]) => <Link key={href} href={href} className="block whitespace-nowrap rounded-lg px-3 py-2 text-[var(--color-muted)] hover:bg-[var(--color-panel)] hover:text-[var(--color-text)]">{label}</Link>)}19 </nav>20 <div className="hidden px-5 py-4 text-xs text-[var(--color-faint)] lg:absolute lg:inset-x-0 lg:bottom-0 lg:block">21 <form action={killSwitch} className="mb-4">22 <input type="hidden" name="on" value={policy.kill_switch ? "0" : "1"} /><input type="hidden" name="back" value="/studio" />23 <button className={`btn btn-sm w-full ${policy.kill_switch ? "btn-primary" : "btn-danger"}`} disabled={user.role === "viewer"}>{policy.kill_switch ? "Release kill switch" : "Kill switch"}</button>24 </form>25 <p className="truncate text-[var(--color-muted)]">{user.email}</p>26 <p className="mt-0.5 capitalize">{user.role}{user.totp_enabled ? " · 2FA on" : " · 2FA off"}</p>27 <div className="mt-2 flex gap-3"><Link href="/studio/account" className="underline">Account</Link><Link href="/" className="underline">Public site</Link><form action={logout}><button className="underline">Sign out</button></form></div>28 </div>29 </aside>30 <div className="min-w-0">31 {policy.kill_switch && <div role="alert" className="notice-err border-x-0 border-t-0 px-6 py-2 text-sm">Kill switch is ON — no new paid job will start. Jobs already submitted to the provider are tracked to completion.</div>}32 <main id="main" className="mx-auto max-w-[1300px] px-4 py-6 sm:px-6 lg:px-8">{children}</main>33 <div className="flex gap-4 px-6 pb-6 text-xs text-[var(--color-faint)] lg:hidden"><Link href="/studio/account" className="underline">Account</Link><form action={logout}><button className="underline">Sign out</button></form></div>34 </div>35 </div>36 );37}38