"use client"; // Making an API token: a name, how long it lasts, and what it may do where (some of the person's own permissions). import { ChevronRightIcon } from "lucide-react"; import { useActionState, useState } from "react"; import { CopyButton } from "@/components/copy-button"; import { ErrorText } from "@/components/forms"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select"; import type { TokenScope } from "@/lib/api"; import { createToken } from "./actions"; const keyOf = (s: TokenScope["scope"]) => (s.type === "hub" ? "hub" : `${s.type}:${s.id}`); const kindOf = { hub: "Everything", catalog: "Catalog", product: "Product" } as const; type Row = { scope: string; permissions: string[] }; // Permissions are `resource:action`; they're grouped by resource, in this order (new resources go last, by name). const groupNames: Record = { product: "Products", revision: "Revisions", catalog: "Catalog", team: "Team", member: "Members", hosting: "Hosting", import: "Imports", case: "Cases", audit: "Audit Log", stats: "Stats", person: "People", room: "Rooms", settings: "Settings", }; const order = Object.keys(groupNames); /** A scope's permissions, grouped by resource: [group name, permissions]. */ function groupsOf(permissions: string[]): [string, string[]][] { const by = new Map(); for (const p of permissions) { const resource = p.split(":")[0]!; by.set(resource, [...(by.get(resource) ?? []), p]); } const rank = (r: string) => (order.includes(r) ? order.indexOf(r) : order.length); return [...by.entries()] .sort(([a], [b]) => rank(a) - rank(b) || a.localeCompare(b)) .map(([r, list]) => [groupNames[r] ?? r.charAt(0).toUpperCase() + r.slice(1), list]); } export function TokenForm({ scopes, maxDays }: { scopes: TokenScope[]; maxDays: number }) { const [state, formAction, pending] = useActionState(createToken, null); const [rows, setRows] = useState([{ scope: keyOf(scopes[0]!.scope), permissions: [] }]); const byKey = new Map(scopes.map((s) => [keyOf(s.scope), s])); const grants = rows.flatMap((r) => r.permissions.map((permission) => ({ permission, scope: byKey.get(r.scope)!.scope }))); const set = (i: number, row: Row) => setRows(rows.map((r, j) => (j === i ? row : r))); const unused = scopes.find((s) => !rows.some((r) => r.scope === keyOf(s.scope))); return (
{[7, 30, 90, maxDays].map((d) => ( {d === 365 ? "1 year" : `${d} days`} ))}
{rows.map((row, i) => { const scope = byKey.get(row.scope)!; return (
set(i, { scope: e.target.value, permissions: [] })} aria-label="Where" className="min-w-56" > {scopes.map((s) => { const k = keyOf(s.scope); if (k !== row.scope && rows.some((r) => r.scope === k)) return null; return ( {s.scope.type === "hub" ? "Everything on T5DE" : `${kindOf[s.scope.type]}: ${s.name}`} ); })} {rows.length > 1 && ( )}
{groupsOf(scope.permissions).map(([group, list]) => { const chosen = list.filter((p) => row.permissions.includes(p)).length; const toggleAll = (on: boolean) => set(i, { ...row, permissions: on ? [...new Set([...row.permissions, ...list])] : row.permissions.filter((x) => !list.includes(x)) }); return (
{ if (el) el.indeterminate = chosen > 0 && chosen < list.length; }} onChange={(e) => toggleAll(e.target.checked)} /> {group} {chosen > 0 ? `${chosen} of ${list.length}` : list.length === 1 ? "1 permission" : `${list.length} permissions`}
{list.map((p) => ( ))}
); })}
); })}
{unused && ( )}
{state?.error && {state.error}} {state?.token && (

Your token {state.name}. Copy it now: it won't be shown again.

{state.token}

Send it as Authorization: Bearer <token>.

)}
); }