"use client"; import { useEffect, useState, useCallback } from "react"; import { Key, Trash2, Copy, Check } from "lucide-react"; import { Tooltip, TooltipTrigger, TooltipContent } from "@multica/ui/components/ui/tooltip"; import type { PersonalAccessToken } from "@multica/core/types"; import { Input } from "@multica/ui/components/ui/input"; import { Button } from "@multica/ui/components/ui/button"; import { Card, CardContent } from "@multica/ui/components/ui/card"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from "@multica/ui/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@multica/ui/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@multica/ui/components/ui/alert-dialog"; import { Skeleton } from "@multica/ui/components/ui/skeleton"; import { toast } from "sonner"; import { api } from "@multica/core/api"; export function TokensTab() { const [tokens, setTokens] = useState([]); const [tokenName, setTokenName] = useState(""); const [tokenExpiry, setTokenExpiry] = useState("90"); const [tokenCreating, setTokenCreating] = useState(false); const [newToken, setNewToken] = useState(null); const [tokenCopied, setTokenCopied] = useState(false); const [tokenRevoking, setTokenRevoking] = useState(null); const [revokeConfirmId, setRevokeConfirmId] = useState(null); const [tokensLoading, setTokensLoading] = useState(true); const loadTokens = useCallback(async () => { try { const list = await api.listPersonalAccessTokens(); setTokens(list); } catch (e) { toast.error(e instanceof Error ? e.message : "Failed to load tokens"); } finally { setTokensLoading(false); } }, []); useEffect(() => { loadTokens(); }, [loadTokens]); const handleCreateToken = async () => { setTokenCreating(true); try { const expiresInDays = tokenExpiry === "never" ? undefined : Number(tokenExpiry); const result = await api.createPersonalAccessToken({ name: tokenName, expires_in_days: expiresInDays }); setNewToken(result.token); setTokenName(""); setTokenExpiry("90"); await loadTokens(); } catch (e) { toast.error(e instanceof Error ? e.message : "Failed to create token"); } finally { setTokenCreating(false); } }; const handleRevokeToken = async (id: string) => { setTokenRevoking(id); try { await api.revokePersonalAccessToken(id); await loadTokens(); toast.success("Token revoked"); } catch (e) { toast.error(e instanceof Error ? e.message : "Failed to revoke token"); } finally { setTokenRevoking(null); } }; const handleCopyToken = async () => { if (!newToken) return; await navigator.clipboard.writeText(newToken); setTokenCopied(true); setTimeout(() => setTokenCopied(false), 2000); }; return (

API Tokens

Personal access tokens allow the CLI and external integrations to authenticate with your account.

setTokenName(e.target.value)} placeholder="Token name (e.g. My CLI)" />
{tokensLoading ? (
{Array.from({ length: 2 }).map((_, i) => (
))}
) : tokens.length > 0 && (
{tokens.map((t) => (
{t.name}
{t.token_prefix}... · Created {new Date(t.created_at).toLocaleDateString()} · {t.last_used_at ? `Last used ${new Date(t.last_used_at).toLocaleDateString()}` : "Never used"} {t.expires_at && ` · Expires ${new Date(t.expires_at).toLocaleDateString()}`}
setRevokeConfirmId(t.id)} disabled={tokenRevoking === t.id} aria-label={`Revoke ${t.name}`} > } /> Revoke
))}
)}
{ if (!v) setRevokeConfirmId(null); }}> Revoke token This token will be permanently revoked and can no longer be used. This cannot be undone. Cancel { if (revokeConfirmId) await handleRevokeToken(revokeConfirmId); setRevokeConfirmId(null); }} > Revoke { if (!v) { setNewToken(null); setTokenCopied(false); } }}> Token created Copy your personal access token now. You won't be able to see it again.
{newToken} {tokenCopied ? : } } /> Copy token
); }