Skip to content
66 linesCodeBlameRaw
1import { Plus } from "lucide-react";
2import { Link, redirect } from "react-router";
3
4import type { Route } from "./+types/tokens";
5import { page } from "../../lib/meta";
6import { ButtonLink, EmptyState } from "../../components/ui";
7import { TokenList } from "../../components/token-list";
8import { currentTokensPath } from "../../lib/access-tokens";
9import { identity } from "../../lib/services.server";
10import { requireUser } from "../../lib/session.server";
11
12// Your access tokens: one list, each a link to its page. Tokens used to
13// come in two kinds, on two tabs (`?tab=classic`, `?edit=<id>`); those
14// addresses lead here and to a token's page now.
15
16export function meta(args: Route.MetaArgs) {
17 return page(args, { title: "Access tokens · Settings · g1t" });
18}
19
20export async function loader({ request, context }: Route.LoaderArgs) {
21 const url = new URL(request.url);
22 const moved = currentTokensPath("/settings/tokens", url.searchParams);
23 if (moved) throw redirect(moved, 301);
24 const user = requireUser(context, request);
25 const tokens = await identity.listAccessTokens(user);
26 return { tokens, workspaces: (user.workspaces ?? []).map((membership) => membership.slug) };
27}
28
29export default function TokenSettings({ loaderData }: Route.ComponentProps) {
30 const { tokens, workspaces } = loaderData;
31 return (
32 <section className="space-y-5">
33 <div className="flex flex-wrap items-start justify-between gap-3">
34 <p className="max-w-lg text-sm text-muted">
35 Each token has permissions, a level for each resource, and reaches all your workspaces, one of them, or only your
36 account. It never does more than you can.
37 </p>
38 <ButtonLink to="/settings/tokens/new">
39 <Plus size={15} />
40 New token
41 </ButtonLink>
42 </div>
43 {tokens.length === 0 ? (
44 <EmptyState title="No access tokens yet">
45 Make one for git over HTTPS, the API, the MCP server or an agent.
46 </EmptyState>
47 ) : (
48 <TokenList tokens={tokens} href={(token) => `/settings/tokens/${token.id}`} />
49 )}
50 {workspaces.length > 0 && (
51 <p className="text-xs text-faint">
52 For CI and integrations that work for a team, use a workspace's own tokens, which keep working when you leave:{" "}
53 {workspaces.map((slug, i) => (
54 <span key={slug}>
55 {i > 0 && ", "}
56 <Link to={`/${slug}/-/tokens`} className="font-mono text-muted underline underline-offset-4 hover:text-fg">
57 {slug}
58 </Link>
59 </span>
60 ))}
61 .
62 </p>
63 )}
64 </section>
65 );
66}