flagon-io/g1t

public

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

g1t/apps/web/app/routes/workspace/people.tsx

76 lines2,714 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Agents as a team: lifecycle, merge queue, billing and a new shell1import { Form } from "react-router";
2
3import type { Route } from "./+types/people";
Social cards for every page: og.g1t.sh4import { page } from "../../lib/meta";
Agents as a team: lifecycle, merge queue, billing and a new shell5import { Avatar, Button, ErrorText, Field, Input, Pill } from "../../components/ui";
6import { identity } from "../../lib/services.server";
7import {
8 assertSameOrigin,
9 getViewer,
10 requireUser,
11 roleIn,
12 unwrap,
13} from "../../lib/session.server";
14
Social cards for every page: og.g1t.sh15export function meta({ params, ...args }: Route.MetaArgs) {
16 return page(args, { title: `People · ${params.owner} · g1t` });
Agents as a team: lifecycle, merge queue, billing and a new shell17}
18
19export async function loader({ params, context }: Route.LoaderArgs) {
20 const viewer = getViewer(context);
21 return {
22 role: roleIn(viewer, params.owner),
23 members: unwrap(await identity.listMembers(params.owner, viewer)),
24 };
25}
26
27export async function action({ request, params, context }: Route.ActionArgs) {
28 assertSameOrigin(request);
29 const user = requireUser(context, request);
30 const form = await request.formData();
31 const username = String(form.get("member") ?? "");
32 const result =
33 form.get("action") === "remove"
34 ? await identity.removeMember(user, params.owner, username)
35 : await identity.addMember(user, params.owner, username);
36 return result.ok ? null : { error: result.error.message };
37}
38
39export default function WorkspacePeople({ loaderData, actionData }: Route.ComponentProps) {
40 const { role, members } = loaderData;
41 return (
42 <div className="max-w-2xl">
A catalogue of model providers, and settings that feel like settings43 <ul className="divide-y divide-line rounded-xl border border-line">
Agents as a team: lifecycle, merge queue, billing and a new shell44 {members.map((member) => (
45 <li key={member.username} className="flex items-center gap-3 px-4 py-3">
46 <Avatar name={member.username} size={28} />
47 <span className="grow font-mono text-sm">{member.username}</span>
48 <Pill>{member.role}</Pill>
49 {role === "owner" && member.role !== "owner" && (
50 <Form method="post">
51 <input type="hidden" name="action" value="remove" />
52 <input type="hidden" name="member" value={member.username} />
53 <Button variant="quiet" type="submit">
54 Remove
55 </Button>
56 </Form>
57 )}
58 </li>
59 ))}
60 </ul>
61 {role === "owner" && (
62 <Form method="post" className="mt-6 flex items-end gap-3">
63 <div className="grow">
64 <Field label="Add a member" hint="Their g1t username. They join as a member.">
65 <Input name="member" required maxLength={39} />
66 </Field>
67 </div>
68 <div className="pb-6">
69 <Button type="submit">Add</Button>
70 </div>
71 </Form>
72 )}
73 <ErrorText>{actionData?.error}</ErrorText>
74 </div>
75 );
76}