Skip to content
78 linesCodeBlameRaw
1import { Code2, MessagesSquare } from "lucide-react";
2import { Link, data } from "react-router";
3
4import type { Route } from "./+types/code-access";
5import { page } from "../../lib/meta";
6import { identity } from "../../lib/services.server";
7import { requireUser, roleIn } from "../../lib/session.server";
8
9export function meta({ params, ...args }: Route.MetaArgs) {
10 return page(args, { title: `Code access · ${params.owner} · g1t` });
11}
12
13/**
14 * What a member without Code access sees in place of a repository, an
15 * issue, a pull request or a project list (docs/WORKSPACE.md, "Members
16 * without Code"): what it was, and who to ask.
17 */
18export async function loader({ params, context, request }: Route.LoaderArgs) {
19 const viewer = requireUser(context, request);
20 if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 });
21 const members = await identity.listMembers(params.owner, viewer).catch(() => null);
22 const owners = members?.ok ? members.value.filter((m) => m.role === "owner").slice(0, 4).map((m) => m.username) : [];
23 const from = new URL(request.url).searchParams.get("from");
24 // Only a path on this site, shown as text.
25 return { owners, from: from && /^\/[^/]/.test(from) ? from.split("?")[0] : null };
26}
27
28export default function CodeAccess({ loaderData, params }: Route.ComponentProps) {
29 const { owners, from } = loaderData;
30 const what = from ? from.split("/").filter(Boolean).slice(1).join("/") : null;
31 return (
32 <div className="mx-auto max-w-lg py-10 text-center">
33 <span className="mx-auto flex size-12 items-center justify-center rounded-xl border border-line bg-surface text-muted">
34 <Code2 size={22} />
35 </span>
36 <h1 className="mt-5 text-2xl font-semibold tracking-tight">Ask an owner for Code access</h1>
37 <p className="mt-2 text-[0.9375rem] leading-relaxed text-muted">
38 {what ? (
39 <>
40 <span className="font-mono text-fg-soft">{what}</span> is part of Code in {params.owner}.
41 </>
42 ) : (
43 <>This is part of Code in {params.owner}.</>
44 )}{" "}
45 Your membership includes Chat, Docs, Agents and the Inbox. An owner can turn on Code access for you.
46 </p>
47 {owners.length > 0 && (
48 <p className="mt-4 text-sm text-muted">
49 Owners:{" "}
50 {owners.map((owner, index) => (
51 <span key={owner}>
52 {index > 0 && ", "}
53 <Link to={`/u/${owner}`} className="font-medium text-fg hover:text-accent">
54 @{owner}
55 </Link>
56 </span>
57 ))}
58 </p>
59 )}
60 <div className="mt-8 flex justify-center gap-2">
61 <Link
62 to={`/${params.owner}/-/chat`}
63 className="inline-flex h-9 items-center gap-2 rounded-md bg-accent px-3.5 text-sm font-medium text-bg transition-colors hover:bg-accent-hover"
64 >
65 <MessagesSquare size={15} />
66 Back to Chat
67 </Link>
68 <Link
69 to={`/${params.owner}/-/home`}
70 className="inline-flex h-9 items-center rounded-md border border-line px-3.5 text-sm font-medium text-fg/90 transition-colors hover:border-line-strong hover:bg-surface"
71 >
72 Home
73 </Link>
74 </div>
75 <p className="mt-6 text-xs text-faint">Agents can still explain how things work and what changed; they never show source code.</p>
76 </div>
77 );
78}