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}
) : null}
setNewTokenLabel(event.target.value)} placeholder="Personal API token" />
setNewTokenExpires(event.target.value)} />
{capabilitySetsLoading ? ( Loading capability sets… ) : null} {!capabilitySetsLoading && !capabilitySetOptions.length ? ( No capability sets available. ) : null} {!capabilitySetsLoading && capabilitySetMap[newTokenCapabilitySetId]?.capabilities?.length ? ( Includes: {capabilitySetMap[newTokenCapabilitySetId].capabilities.join(', ')} ) : null}
{formError ?

{formError}

: null} {loading && !hasTokens ? (

Loading tokens…

) : null} {!loading && !hasTokens ? (

No API tokens yet.

) : null} {hasTokens ? ( {tokens.map((token) => { const isRevoked = Boolean(token?.revoked_at); const selectedSet = token?.capability_set_id ? capabilitySetMap[token.capability_set_id] : null; const capabilityList = Array.isArray(token?.capabilities) && token.capabilities.length ? token.capabilities : selectedSet?.capabilities || []; const capabilitySetLabel = selectedSet?.label || selectedSet?.slug || token.capability_set_id || '—'; return ( ); })}
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 ) : ( <> )}
) : null}
); }, [ tokens, loading, createdToken, creating, deletingId, regeneratingId, newTokenLabel, newTokenExpires, newTokenCapabilitySetId, formError, capabilitySetOptions, capabilitySetMap, capabilitySetsLoading, formatDateTime, handleCopyToken, handleCreateToken, handleRegenerateToken, handleRefresh, onDelete, handleDismissSecret, handleNewCapabilitySetChange, ]); const renderPasskeysSection = useMemo(() => { const hasPasskeys = Array.isArray(passkeys) && passkeys.length > 0; return (
{passkeysSupported === false ? (

Passkeys are not enabled for this account.

) : ( <>
setNewPasskeyNickname(event.target.value)} disabled={registeringPasskey} />
{passkeysLoading && !hasPasskeys ? (

Loading passkeys…

) : null} {!passkeysLoading && !hasPasskeys ? (

No passkeys registered yet.

) : null} {hasPasskeys ? ( {passkeys.map((passkey) => { const createdAt = passkey.created_at || passkey.createdAt; const lastUsedAt = passkey.last_used_at || passkey.lastUsedAt; const revokedAt = passkey.revoked_at || passkey.revokedAt; const revokedReason = passkey.revoked_reason || passkey.revokedReason; const revoked = Boolean(revokedAt); const transports = Array.isArray(passkey.transports) ? passkey.transports.filter(Boolean) : []; return ( ); })}
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 ) : ( )}
) : null} )}
); }, [ passkeys, passkeysLoading, passkeysSupported, registeringPasskey, revokingPasskeyId, newPasskeyNickname, formatDateTime, handlePasskeyRefresh, handlePasskeyRegister, handlePasskeyRevoke, ]); if (!open) { return null; } return (
Close )} />
{activeSection === 'passkeys' ? renderPasskeysSection : null} {activeSection === 'apiTokens' ? renderApiTokensSection : null} {activeSection !== 'passkeys' && activeSection !== 'apiTokens' ? (

Select a settings section.

) : null}
); }; export default SettingsModal;