Vectrum
Sign In
101 lines · 3.6 KB · TypeScript
import type { Metadata } from "next";
import type { ReactNode } from "react";
import { cookies } from "next/headers";
import Link from "next/link";
import { Doto, Geist, Geist_Mono } from "next/font/google";
import { BRAND, LogoMark } from "@/components/brand";
import { SiteHeader, SiteSidebar, TopBar } from "@/components/header";
import { Providers } from "@/components/providers";
import { ThemeToggle } from "@/components/theme-toggle";
import { THEME_COOKIE, colorwayOf, type Theme } from "@/lib/theme";
import { getViewer } from "@/lib/auth";
import "@unlocalhosted/metalui/styles.css";
import "@unlocalhosted/metalui/icons.css";
import "./globals.css";

const geist = Geist({
  variable: "--font-geist",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

/** MetalUI's dot-matrix face, for its pixel type roles. */
const doto = Doto({
  variable: "--font-doto",
  subsets: ["latin"],
  weight: "700",
});

export const metadata: Metadata = {
  metadataBase: new URL(`https://${BRAND.domain}`),
  title: { default: BRAND.name, template: `%s · ${BRAND.name}` },
  description: "A self-hosted home for your git repositories",
};

export default async function RootLayout({ children }: LayoutProps<"/">) {
  const viewer = await getViewer();
  const stored = (await cookies()).get(THEME_COOKIE)?.value;
  const theme: Theme = stored === "light" || stored === "dark" ? stored : "system";
  return (
    <html
      lang="en"
      data-mu-colorway={colorwayOf(theme)}
      className={`${geist.variable} ${geistMono.variable} ${doto.variable} h-full antialiased`}
    >
      <body className="text-[13px]">
        <Providers>
          {viewer ? (
            <div className="flex min-h-screen">
              <SiteSidebar viewer={viewer} theme={theme} />
              <div className="flex min-w-0 flex-1 flex-col">
                <TopBar viewer={viewer} />
                <Sheet besideSidebar footer={<Footer signedIn />}>{children}</Sheet>
              </div>
            </div>
          ) : (
            <div className="flex min-h-screen flex-col">
              <SiteHeader />
              <Sheet footer={<Footer theme={theme} />}>{children}</Sheet>
            </div>
          )}
        </Providers>
      </body>
    </html>
  );
}

/** The raised plate all page content sits on, inset from the page. */
function Sheet({ children, footer, besideSidebar }: { children: ReactNode; footer: ReactNode; besideSidebar?: boolean }) {
  return (
    <div className={`plate mx-2 mb-2 flex flex-1 flex-col rounded-[24px] ${besideSidebar ? "lg:ml-0" : ""}`}>
      <div className="flex flex-1 flex-col">{children}</div>
      {footer}
    </div>
  );
}

function Footer({ signedIn, theme }: { signedIn?: boolean; theme?: Theme }) {
  return (
    <footer className="rule-t mt-12 px-5 py-5">
      <div className="mx-auto flex max-w-[1400px] flex-wrap items-center gap-x-6 gap-y-3">
        <span className="flex items-center gap-2">
          <LogoMark size={16} />
          <span className="type-label engraved">
            <b>{BRAND.name}</b>
            <span className="hidden sm:inline"> · {BRAND.tagline}</span>
          </span>
        </span>
        <nav className="ml-auto flex items-center gap-5 type-ui text-ink2">
          <Link href="/explore" className="transition-colors hover:text-ink">Explore</Link>
          {signedIn && <Link href="/settings/keys" className="transition-colors hover:text-ink">SSH keys</Link>}
          {signedIn && <Link href="/settings/tokens" className="transition-colors hover:text-ink">API tokens</Link>}
          {theme && <ThemeToggle initial={theme} />}
        </nav>
      </div>
    </footer>
  );
}