web/app/collab/page.tsxblame
View source
0b4b5821import type { Metadata } from "next";
0b4b5822import { cookies } from "next/headers";
0b4b5823import { headers } from "next/headers";
0b4b5824import Link from "next/link";
0b4b5825import { groveApiUrl } from "@/lib/utils";
0b4b5826import { CollabLogo } from "@/app/components/collab-logo";
0b4b5827import { CollabRepoList } from "./collab-repo-list";
0b4b5828
0b4b5829export const metadata: Metadata = {
0b4b58210 title: "Collab",
0b4b58211};
0b4b58212
0b4b58213interface Repo {
0b4b58214 name: string;
0b4b58215 owner_name: string;
0b4b58216 description: string | null;
0b4b58217 last_commit_ts: number | null;
0b4b58218 updated_at: string | null;
0b4b58219}
0b4b58220
0b4b58221async function getRepos(token: string | undefined): Promise<Repo[]> {
0b4b58222 try {
0b4b58223 const headers: Record<string, string> = token
0b4b58224 ? { Authorization: `Bearer ${token}` }
0b4b58225 : {};
0b4b58226 const res = await fetch(`${groveApiUrl}/api/repos`, {
0b4b58227 headers,
0b4b58228 cache: "no-store",
0b4b58229 });
0b4b58230 if (!res.ok) return [];
0b4b58231 const data = await res.json();
0b4b58232 return data.repos ?? [];
0b4b58233 } catch {
0b4b58234 return [];
0b4b58235 }
0b4b58236}
0b4b58237
0b4b58238export default async function CollabHomePage() {
0b4b58239 const cookieStore = await cookies();
0b4b58240 const token = cookieStore.get("grove_hub_token")?.value;
0b4b58241 const signedIn = !!token;
0b4b58242 const headerStore = await headers();
0b4b58243 const host =
0b4b58244 headerStore.get("x-forwarded-host") ??
0b4b58245 headerStore.get("host") ??
0b4b58246 "";
0b4b58247 const protocol = headerStore.get("x-forwarded-proto") ?? "http";
0b4b58248 const canonicalHost = host.split(",")[0]?.trim() ?? "";
0b4b58249 const groveHost = canonicalHost.replace(/^(canopy|ring|collab)\./, "");
0b4b58250 const groveOrigin = groveHost ? `${protocol}://${groveHost}` : "";
0b4b58251 const loginHref = groveOrigin ? `${groveOrigin}/login` : "/login";
0b4b58252 const exploreHref = groveOrigin ? `${groveOrigin}/` : "/";
0b4b58253
0b4b58254 if (!signedIn) {
0b4b58255 return (
0b4b58256 <div className="max-w-3xl mx-auto px-4 py-8">
0b4b58257 <div
0b4b58258 className="p-4 sm:p-8 text-center"
0b4b58259 style={{
0b4b58260 backgroundColor: "var(--bg-card)",
0b4b58261 border: "1px solid var(--border-subtle)",
0b4b58262 }}
0b4b58263 >
0b4b58264 <div className="mx-auto mb-4 w-fit opacity-70">
0b4b58265 <CollabLogo size={44} />
0b4b58266 </div>
0b4b58267 <h1 className="text-lg mb-1">Collab</h1>
0b4b58268 <p className="text-sm mb-4" style={{ color: "var(--text-faint)" }}>
0b4b58269 Collaborate on Mermaid diagrams for your Grove repositories.
0b4b58270 </p>
0b4b58271 <div className="flex items-center justify-center gap-2">
0b4b58272 <Link
0b4b58273 href={loginHref}
0b4b58274 className="px-3 py-1.5 text-sm"
0b4b58275 style={{
0b4b58276 backgroundColor: "var(--accent)",
0b4b58277 color: "var(--accent-text)",
0b4b58278 }}
0b4b58279 >
0b4b58280 Sign in
0b4b58281 </Link>
0b4b58282 <Link
0b4b58283 href={exploreHref}
0b4b58284 className="px-3 py-1.5 text-sm"
0b4b58285 style={{
0b4b58286 border: "1px solid var(--border-subtle)",
0b4b58287 color: "var(--text-secondary)",
0b4b58288 }}
0b4b58289 >
0b4b58290 Explore Grove
0b4b58291 </Link>
0b4b58292 </div>
0b4b58293 </div>
0b4b58294 </div>
0b4b58295 );
0b4b58296 }
0b4b58297
0b4b58298 const repos = await getRepos(token);
0b4b58299
0b4b582100 return <CollabRepoList repos={repos} />;
0b4b582101}