import { useState, useEffect, useCallback } from 'react'; import type { Secret } from '../types'; import { t } from '../i18n'; const PRESETS: { name: string; description: string; category: Secret['category'] }[] = [ { name: 'OPENAI_API_KEY', description: 'OpenAI API key (for /image-gen, /speech-to-text)', category: 'api' }, { name: 'THREADS_TOKEN', description: 'Meta Threads publishing token', category: 'social' }, { name: 'SLACK_TOKEN', description: 'Slack bot token (for /slack-ops)', category: 'social' }, { name: 'TRELLO_API_KEY', description: 'Trello API key', category: 'api' }, { name: 'TRELLO_TOKEN', description: 'Trello auth token', category: 'api' }, { name: 'NOTION_API_KEY', description: 'Notion integration token', category: 'api' }, { name: 'GOOGLE_PLACES_API_KEY', description: 'Google Places API key', category: 'api' }, { name: 'ELEVENLABS_API_KEY', description: 'ElevenLabs TTS API key', category: 'api' }, { name: 'CF_ACCOUNT_ID', description: 'Cloudflare Account ID', category: 'mcp' }, { name: 'CF_API_TOKEN', description: 'Cloudflare API token', category: 'mcp' }, { name: 'GH_TOKEN', description: 'GitHub personal access token', category: 'api' }, ]; const CATEGORY_BADGES: Record = { general: 'badge-gray', social: 'badge-blue', api: 'badge-green', mcp: 'badge-purple', }; const CATEGORY_LABELS: Record = { general: 'general', social: 'social', api: 'api', mcp: 'mcp', }; interface EditForm { value: string; description: string; category: Secret['category']; } export default function SecretsPage() { const [secrets, setSecrets] = useState([]); const [search, setSearch] = useState(''); const [showAdd, setShowAdd] = useState(false); const [editingId, setEditingId] = useState(null); const [revealedIds, setRevealedIds] = useState>(new Set()); const [status, setStatus] = useState(''); // Add form state const [addName, setAddName] = useState(''); const [addValue, setAddValue] = useState(''); const [addDesc, setAddDesc] = useState(''); const [addCategory, setAddCategory] = useState('general'); // Edit form state (keyed by id) const [editForms, setEditForms] = useState>({}); const fetchSecrets = useCallback(async () => { try { const res = await fetch('/api/secrets'); const data = await res.json(); if (Array.isArray(data)) setSecrets(data); } catch { setSecrets([]); } }, []); useEffect(() => { fetchSecrets(); }, [fetchSecrets]); const showStatus = (msg: string) => { setStatus(msg); setTimeout(() => setStatus(''), 3000); }; const handleCreate = async () => { if (!addName.trim() || !addValue.trim()) return; try { await fetch('/api/secrets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: addName.trim(), value: addValue.trim(), description: addDesc.trim(), category: addCategory, }), }); setShowAdd(false); setAddName(''); setAddValue(''); setAddDesc(''); setAddCategory('general'); fetchSecrets(); showStatus('Secret saved.'); } catch (err) { showStatus(`Error: ${(err as Error).message}`); } }; const handleStartEdit = (secret: Secret) => { setEditingId(secret.id); setEditForms(prev => ({ ...prev, [secret.id]: { value: '', description: secret.description, category: secret.category }, })); }; const handleUpdate = async (id: string) => { const form = editForms[id]; if (!form) return; try { const body: Partial = { description: form.description, category: form.category, }; if (form.value.trim()) body.value = form.value.trim(); await fetch(`/api/secrets/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); setEditingId(null); fetchSecrets(); showStatus('Secret updated.'); } catch (err) { showStatus(`Error: ${(err as Error).message}`); } }; const handleDelete = async (id: string, name: string) => { if (!confirm(`Delete secret "${name}"?`)) return; try { await fetch(`/api/secrets/${id}`, { method: 'DELETE' }); fetchSecrets(); showStatus(`Deleted: ${name}`); } catch (err) { showStatus(`Error: ${(err as Error).message}`); } }; const toggleReveal = (id: string) => { setRevealedIds(prev => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; const handlePresetClick = (preset: typeof PRESETS[number]) => { setAddName(preset.name); setAddDesc(preset.description); setAddCategory(preset.category); setAddValue(''); setShowAdd(true); }; const handleNameInput = (val: string) => { setAddName(val.toUpperCase().replace(/\s+/g, '_')); }; const filtered = search.trim() ? secrets.filter(s => s.name.toLowerCase().includes(search.toLowerCase()) || s.description.toLowerCase().includes(search.toLowerCase()) ) : secrets; const existingNames = new Set(secrets.map(s => s.name)); const availablePresets = PRESETS.filter(p => !existingNames.has(p.name)); return (
{/* Header */}

{t('secrets.title')}

{t('secrets.subtitle')}

{/* Status banner */} {status && (

{status}

)} {/* Injected note */}

{t('secrets.injected')}

{/* Add form */} {showAdd && (
handleNameInput(e.target.value)} placeholder={t('secrets.namePlaceholder')} className="input-base font-mono" /> setAddValue(e.target.value)} placeholder={t('secrets.valuePlaceholder')} className="input-base col-span-2" /> setAddDesc(e.target.value)} placeholder={t('secrets.descPlaceholder')} className="input-base col-span-2" />
)} {/* Main scrollable area */}
{/* Search */} setSearch(e.target.value)} placeholder={t('secrets.search')} className="input-base w-full" /> {/* Secrets list */} {filtered.length === 0 ? (

{t('secrets.noSecrets')}

) : (
{filtered.map(secret => { const isEditing = editingId === secret.id; const isRevealed = revealedIds.has(secret.id); const form = editForms[secret.id]; return (
{/* Top row: name + category + actions */}
{secret.name} {CATEGORY_LABELS[secret.category]}
{!isEditing && ( <> )}
{/* Description */} {secret.description && !isEditing && (

{secret.description}

)} {/* Value row (view mode) */} {!isEditing && (
{isRevealed ? secret.value : '••••••••••••'}
)} {/* Edit form (inline) */} {isEditing && form && (
setEditForms(prev => ({ ...prev, [secret.id]: { ...prev[secret.id], value: e.target.value } }))} placeholder="New value (leave blank to keep existing)" className="input-base w-full" /> setEditForms(prev => ({ ...prev, [secret.id]: { ...prev[secret.id], description: e.target.value } }))} placeholder={t('secrets.descPlaceholder')} className="input-base w-full" />
)}
); })}
)} {/* Preset suggestions */} {availablePresets.length > 0 && (

{t('secrets.presets')}

{t('secrets.presetsHint')}

{availablePresets.map(preset => (
{preset.name}

{preset.description}

))}
)}
); }