// API keys — URL `/api-keys`. Drives the framework's BUILT-IN key management // surface (`GET/POST /v1/api-keys*`), which the api exposes when it sets // `apiKeys: true`. Keys are hash-stored; the raw token is shown exactly ONCE at // mint time and is unrecoverable after. // // This surface is admin-scoped and off by default, so the page DEGRADES: a 404 // means the api hasn't enabled `apiKeys`, a 401/403 means the signed-in user // lacks the scope. It tells the operator exactly what to turn on rather than // showing a broken table. (api-saas-starter does not enable it out of the box — // see the README.) import type { ReactNode } from 'react' import { useEffect, useState, type FormEvent } from 'react' import type { PageMeta } from '@voltro/web' import { T, useT } from '@voltro/i18n' import { getCatalog } from '../../../locales/index' import type { ApiKey } from '../../../lib/api' export const renderMode = 'ssr' as const export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({ title: getCatalog(locale)['meta.apiKeys.title'], }) type State = 'loading' | 'ready' | 'notEnabled' | 'noAccess' | 'error' const BASE = '/v1/api-keys' export default function ApiKeys(): ReactNode { const [state, setState] = useState('loading') const [keys, setKeys] = useState>([]) const [name, setName] = useState('') const [minted, setMinted] = useState(null) const [pending, setPending] = useState(false) const nameLabel = useT('apiKeys.name.placeholder') const load = (): void => { void fetch(BASE, { headers: { accept: 'application/json' } }) .then(async (res) => { if (res.ok) { const body = (await res.json()) as { keys: ReadonlyArray } setKeys(body.keys ?? []) setState('ready') } else if (res.status === 404) setState('notEnabled') else if (res.status === 401 || res.status === 403) setState('noAccess') else setState('error') }) .catch(() => setState('error')) } useEffect(load, []) const onIssue = (e: FormEvent): void => { e.preventDefault() const trimmed = name.trim() if (trimmed === '') return setPending(true) setMinted(null) void fetch(`${BASE}/issue`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: trimmed }), }) .then(async (res) => { if (res.ok) { const body = (await res.json()) as { token: string } setMinted(body.token) setName('') load() } }) .finally(() => setPending(false)) } const onRevoke = (id: string): void => { void fetch(`${BASE}/revoke`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id }), }).then(() => load()) } return (

{state === 'loading' ?

: null} {state === 'notEnabled' ?

: null} {state === 'noAccess' ?

: null} {state === 'error' ?

: null} {state === 'ready' ? ( <>
setName(e.target.value)} />
{minted ? (

{minted}
) : null} {keys.length === 0 ? (

) : (
    {keys.map((k) => (
  • {k.name} {k.keyPrefix}… {k.revokedAt ? : }
  • ))}
)} ) : null}
) }