import type { Metadata } from "next";
import { headers } from "next/headers";
import { Label as MuLabel } from "@unlocalhosted/metalui";
import { deleteTokenAction } from "@/app/actions/settings";
import { ConfirmButton } from "@/components/confirm-button";
import { Expiry } from "@/components/expiry";
import { ShieldLockIcon } from "@/components/icons";
import { SettingsSection } from "@/components/metal";
import { ListRow, PlateSection, RowGlyph } from "@/components/settings-plate";
import { Blankslate, Label, PageTitle, RefChip } from "@/components/ui";
import { requireViewer } from "@/lib/auth";
import { timeAgo } from "@/lib/format";
import { canWrite, listReposForUser, repoAccess } from "@/lib/repos";
import { listTokens, TOKEN_PREFIX } from "@/lib/tokens";
import { NewTokenForm } from "./form";
export const metadata: Metadata = { title: "API tokens" };
export default async function TokensPage() {
const viewer = await requireViewer();
const tokens = await listTokens(viewer.id);
const repos = await Promise.all(
(await listReposForUser(viewer.id)).map(async (r) => ({
id: r.id,
fullName: `${r.owner}/${r.name}`,
writable: canWrite(await repoAccess(r, viewer.id)),
})),
);
const h = await headers();
const origin = `${h.get("x-forwarded-proto") ?? "http"}://${h.get("host") ?? "localhost:3000"}`;
return (
<div>
<PageTitle
title="API tokens"
description="Tokens work like passwords for git over HTTPS. Use one as the password when git asks for credentials."
/>
<div className="flex flex-col gap-7">
{tokens.length === 0 ? (
<PlateSection title="Your tokens">
<Blankslate icon={<ShieldLockIcon />} title="No tokens yet">
Generate a token below to clone private repositories or push over HTTPS.
</Blankslate>
</PlateSection>
) : (
<SettingsSection title={`Your tokens · ${tokens.length}`}>
{tokens.map((t) => (
<ListRow
key={t.id}
lead={
<RowGlyph>
<ShieldLockIcon />
</RowGlyph>
}
title={
<>
<span className="truncate">{t.name}</span>
<Label tone={t.permission === "write" ? "accent" : "default"}>
{t.permission === "write" ? "Read & write" : "Read-only"}
</Label>
{t.expired && <Label tone="danger">Expired</Label>}
<RefChip>
{TOKEN_PREFIX}…{t.hint}
</RefChip>
</>
}
meta={
<>
<span className="truncate type-body text-ink2">
{t.allRepos ? "All repositories" : t.repos.map((r) => r.fullName).join(", ") || "No repositories (deleted)"}
</span>
<MuLabel variant="engraved">
<Expiry expiresAt={t.expiresAt} expired={t.expired} /> ·{" "}
{t.lastUsedAt ? (
<>
Last used <b>{timeAgo(t.lastUsedAt)}</b>
</>
) : (
"Never used"
)}
</MuLabel>
</>
}
>
<ConfirmButton
action={deleteTokenAction.bind(null, t.id)}
message={`Delete the token “${t.name}”? Anything using it will stop working immediately.`}
variant="default"
>
Delete Token
</ConfirmButton>
</ListRow>
))}
</SettingsSection>
)}
<PlateSection title="Generate new token">
<NewTokenForm repos={repos} origin={origin} username={viewer.username} />
</PlateSection>
</div>
</div>
);
}