Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

92 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";
8import { Unavailable } from "../../components/boundary";
9import { isFailed, part } from "../../lib/partial";
10
11export function meta({ params, ...args }: Route.MetaArgs) {
12 return page(args, { title: `Code access · ${params.owner} · g1t` });
13}
14
15/**
16 * What a member without Code access sees in place of a repository, an
17 * work item, a pull request or a project list: what it was, and who to ask.
18 */
19export async function loader({ params, context, request }: Route.LoaderArgs) {
20 const viewer = requireUser(context, request);
21 if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 });
22 // Who to ask is beside what the page says: without it, the page says that much.
23 const members = await part(identity.listMembers(params.owner, viewer), "The workspace's owners");
24 const owners = isFailed(members)
25 ? members
26 : members.ok
27 ? members.value
28 .filter((m) => m.role === "owner")
29 .slice(0, 4)
30 .map((m) => m.username)
31 : [];
32 const from = new URL(request.url).searchParams.get("from");
33 // Only a path on this site, shown as text.
34 return { owners, from: from && /^\/[^/]/.test(from) ? from.split("?")[0] : null };
35}
36
37export default function CodeAccess({ loaderData, params }: Route.ComponentProps) {
38 const { owners, from } = loaderData;
39 const what = from ? from.split("/").filter(Boolean).slice(1).join("/") : null;
40 return (
41 <div className="mx-auto max-w-lg py-10 text-center">
42 <span className="mx-auto flex size-12 items-center justify-center rounded-xl border border-line bg-surface text-muted">
43 <Code2 size={22} />
44 </span>
45 <h1 className="mt-5 text-2xl font-semibold tracking-tight">Ask an owner for Code access</h1>
46 <p className="mt-2 text-[0.9375rem] leading-relaxed text-muted">
47 {what ? (
48 <>
49 <span className="font-mono text-fg-soft">{what}</span> is part of Code in {params.owner}.
50 </>
51 ) : (
52 <>This is part of Code in {params.owner}.</>
53 )}{" "}
54 Your membership includes Chat, Artifacts and Notifications. An owner can turn on Code access for you.
55 </p>
56 {isFailed(owners) ? (
57 <div className="mt-4 flex justify-center">
58 <Unavailable what={owners.what} compact />
59 </div>
60 ) : (
61 owners.length > 0 && (
62 <p className="mt-4 text-sm text-muted">
63 Owners:{" "}
64 {owners.map((owner, index) => (
65 <span key={owner}>
66 {index > 0 && ", "}
67 <Link to={`/u/${owner}`} className="font-medium text-fg hover:text-accent">
68 @{owner}
69 </Link>
70 </span>
71 ))}
72 </p>
73 )
74 )}
75 <div className="mt-8 flex justify-center gap-2">
76 <Link
77 to={`/${params.owner}/-/chat`}
78 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"
79 >
80 <MessagesSquare size={15} />
81 Back to Chat
82 </Link>
83 <Link
84 to={`/${params.owner}/-/home`}
85 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"
86 >
87 Home
88 </Link>
89 </div>
90 </div>
91 );
92}