g1t/apps/web/app/routes/settings/keys.tsx

67 lines2,467 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.

status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1import { identity } from "../../lib/services.server";
2import { Form } from "react-router";
3
4import type { Route } from "./+types/keys";
5import { page } from "../../lib/meta";
6import { Button, ErrorText, Field, Input } from "../../components/ui";
7import { DeleteButton } from "../../components/account-settings";
8import { assertSameOrigin, requireUser } from "../../lib/session.server";
9
10export function meta(args: Route.MetaArgs) {
11 return page(args, { title: "SSH keys · Settings · g1t" });
12}
13
14export async function loader({ request, context }: Route.LoaderArgs) {
15 const user = requireUser(context, request);
16 return { keys: await identity.listSshKeys(user) };
17}
18
19export async function action({ request, context }: Route.ActionArgs) {
20 assertSameOrigin(request);
21 const user = requireUser(context, request);
22 const form = await request.formData();
23 switch (form.get("intent")) {
24 case "add-key": {
25 const result = await identity.addSshKey(
26 user,
27 String(form.get("title") ?? ""),
28 String(form.get("key") ?? ""),
29 );
30 return result.ok ? null : { keyError: result.error.message };
31 }
32 case "delete-key":
33 await identity.removeSshKey(user, String(form.get("id") ?? ""));
34 return null;
35 }
36 return null;
37}
38
39export default function SshKeySettings({ loaderData, actionData }: Route.ComponentProps) {
40 const { keys } = loaderData;
41 return (
42 <section id="ssh-keys" className="scroll-mt-20">
43 <ul className="divide-y divide-line rounded-md border border-line empty:hidden">
44 {keys.map((key) => (
45 <li key={key.id} className="flex items-center gap-4 px-4 py-3">
46 <div className="min-w-0">
47 <p className="text-sm">{key.title}</p>
48 <p className="truncate font-mono text-xs text-muted">{key.fingerprint}</p>
49 </div>
50 <DeleteButton intent="delete-key" id={key.id} />
51 </li>
52 ))}
53 </ul>
54 <Form method="post" className={`space-y-3 ${keys.length > 0 ? "mt-4" : ""}`}>
55 <input type="hidden" name="intent" value="add-key" />
56 <Field label="Title (optional)">
57 <Input name="title" maxLength={100} />
58 </Field>
59 <Field label="Public key">
60 <Input name="key" placeholder="ssh-ed25519 AAAA…" required />
61 </Field>
62 <ErrorText>{actionData?.keyError}</ErrorText>
63 <Button type="submit">Add SSH key</Button>
64 </Form>
65 </section>
66 );
67}