import React, { useMemo, useState, useCallback, useEffect } from 'react'; import PanelHeader from '../ui/PanelHeader'; const SECTIONS = [ { id: 'passkeys', label: 'Passkeys', }, { id: 'apiTokens', label: 'API tokens', }, ]; const SettingsModal = ({ open, onClose, tokens = [], loading = false, creating = false, deletingId = null, regeneratingId = null, capabilitySets = [], capabilitySetsLoading = false, onRefresh, onCreate, onDelete, onRegenerate, createdToken = null, onDismissCreatedToken, passkeys = [], passkeysSupported = null, passkeysLoading = false, registeringPasskey = false, revokingPasskeyId = null, onRefreshPasskeys, onRegisterPasskey, onRevokePasskey, }) => { const defaultSection = SECTIONS[0]?.id || 'passkeys'; const [activeSection, setActiveSection] = useState(defaultSection); const [newTokenLabel, setNewTokenLabel] = useState(''); const [newTokenExpires, setNewTokenExpires] = useState(''); const [newTokenCapabilitySetId, setNewTokenCapabilitySetId] = useState(''); const [formError, setFormError] = useState(null); const [newPasskeyNickname, setNewPasskeyNickname] = useState(''); const handleBackdropClick = useCallback(() => { onClose?.(); }, [onClose]); const handleInnerClick = useCallback((event) => { event.stopPropagation(); }, []); useEffect(() => { if (!open) { setActiveSection(defaultSection); setNewTokenLabel(''); setNewTokenExpires(''); setNewTokenCapabilitySetId(''); setFormError(null); setNewPasskeyNickname(''); } }, [open, defaultSection]); const formatDateTime = useCallback((value) => { if (!value) { return '—'; } const timestamp = new Date(value); if (Number.isNaN(timestamp.getTime())) { return value; } return timestamp.toLocaleString(); }, []); const handleRefresh = useCallback(() => { onRefresh?.(); }, [onRefresh]); const handleCopyToken = useCallback(() => { if (!createdToken) { return; } if (navigator?.clipboard?.writeText) { navigator.clipboard.writeText(createdToken).catch(() => {}); } }, [createdToken]); const handleDismissSecret = useCallback(() => { onDismissCreatedToken?.(); }, [onDismissCreatedToken]); const handlePasskeyRefresh = useCallback(() => { onRefreshPasskeys?.(); }, [onRefreshPasskeys]); const handlePasskeyRegister = useCallback( async (event) => { event.preventDefault(); const nickname = newPasskeyNickname.trim(); const result = await onRegisterPasskey?.({ nickname }); if (result?.ok) { setNewPasskeyNickname(''); } }, [newPasskeyNickname, onRegisterPasskey], ); const handlePasskeyRevoke = useCallback( async (passkey) => { if (!passkey?.id) { return; } const reasonInput = window.prompt('Optional reason for revoking this passkey:', ''); const reason = reasonInput ? reasonInput.trim() : undefined; await onRevokePasskey?.(passkey.id, reason); }, [onRevokePasskey], ); const capabilitySetOptions = useMemo( () => capabilitySets.map((set) => ({ value: set.id, label: set.label || set.slug || set.id, capabilities: Array.isArray(set.capabilities) ? set.capabilities : [], })), [capabilitySets], ); const capabilitySetMap = useMemo( () => Object.fromEntries(capabilitySetOptions.map((option) => [option.value, option])), [capabilitySetOptions], ); useEffect(() => { if (!capabilitySetOptions.length) { setNewTokenCapabilitySetId(''); return; } if (!newTokenCapabilitySetId || !capabilitySetOptions.some((option) => option.value === newTokenCapabilitySetId)) { setNewTokenCapabilitySetId(capabilitySetOptions[0].value); } }, [capabilitySetOptions, newTokenCapabilitySetId]); const handleNewCapabilitySetChange = useCallback((event) => { setFormError(null); setNewTokenCapabilitySetId(event.target.value); }, []); const handleCreateToken = useCallback( async (event) => { event.preventDefault(); setFormError(null); let normalizedLabel = newTokenLabel.trim(); if (normalizedLabel.length === 0) { normalizedLabel = undefined; } let normalizedExpires; if (newTokenExpires) { const parsed = new Date(newTokenExpires); if (Number.isNaN(parsed.getTime())) { setFormError('Enter a valid expiration date.'); return; } normalizedExpires = parsed.toISOString(); } if (!capabilitySetOptions.length) { setFormError('Capability sets are still loading.'); return; } const selectedCapabilitySetId = newTokenCapabilitySetId || capabilitySetOptions[0]?.value; if (!selectedCapabilitySetId) { setFormError('Select a capability set.'); return; } const result = await onCreate?.({ label: normalizedLabel, expires_at: normalizedExpires, capability_set_id: selectedCapabilitySetId, }); if (result !== false) { setNewTokenLabel(''); setNewTokenExpires(''); setNewTokenCapabilitySetId(capabilitySetOptions[0]?.value || ''); setFormError(null); } }, [ capabilitySetOptions, newTokenCapabilitySetId, newTokenExpires, newTokenLabel, onCreate, ], ); const handleRegenerateToken = useCallback( async (token) => { if (!token?.id) { return; } await onRegenerate?.(token.id); }, [onRegenerate], ); const renderApiTokensSection = useMemo(() => { const hasTokens = Array.isArray(tokens) && tokens.length > 0; return (
API tokens use predefined capability sets. Choose the set that matches the access you need when creating or updating a token.
{createdToken ? (Copy this token now; you will not be able to view it again after closing this window.
{createdToken}
{formError}
: null} {loading && !hasTokens ? (Loading tokens…
) : null} {!loading && !hasTokens ? (No API tokens yet.
) : null} {hasTokens ? (| Label | Created | Last used | Expires | Capability set | Actions |
|---|---|---|---|---|---|
| {token.label || '—'} | {formatDateTime(token.created_at)} | {formatDateTime(token.last_used_at)} | {formatDateTime(token.expires_at)} |
{capabilitySetLabel}
{capabilitySetsLoading ? (
Loading capability sets…
) : null}
{!capabilitySetsLoading && capabilityList.length ? (
Includes: {capabilityList.join(', ')}
) : null}
|
{isRevoked ? ( Revoked ) : ( <> > )} |
Passkeys are not enabled for this account.
) : ( <> {passkeysLoading && !hasPasskeys ? (Loading passkeys…
) : null} {!passkeysLoading && !hasPasskeys ? (No passkeys registered yet.
) : null} {hasPasskeys ? (| Nickname | Created | Last used | Transports | Status | Actions |
|---|---|---|---|---|---|
| {passkey.nickname || '—'} | {formatDateTime(createdAt)} | {formatDateTime(lastUsedAt)} | {transports.length ? transports.join(', ') : '—'} | {revoked ? revokedReason ? `Revoked (${revokedReason})` : 'Revoked' : 'Active'} | {revoked ? ( Revoked ) : ( )} |
Select a settings section.
) : null}