import { useEffect, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { apiFetch } from '../lib/api'; import { SettingsSkeleton } from './Skeleton'; import type { AIProvider, ScanSchedule, Settings as SettingsType } from '../lib/types'; const PROVIDERS: { value: AIProvider; label: string; models: string[]; needsKey: boolean }[] = [ { value: 'none', label: 'None (scan and widget only, no AI)', models: [], needsKey: false }, { value: 'anthropic', label: 'Anthropic Claude', models: ['claude-haiku-4-5-20251001', 'claude-sonnet-4-20250514'], needsKey: true }, { value: 'openai', label: 'OpenAI', models: ['gpt-4o-mini', 'gpt-4o'], needsKey: true }, { value: 'gemini', label: 'Google Gemini', models: ['gemini-1.5-flash', 'gemini-1.5-pro'], needsKey: true }, { value: 'ollama', label: 'Ollama (local)', models: ['llama3.2', 'mistral', 'phi3'], needsKey: false }, ]; const SCHEDULES: { value: ScanSchedule; label: string }[] = [ { value: 'daily', label: 'Daily' }, { value: 'weekly', label: 'Weekly' }, { value: 'monthly', label: 'Monthly' }, ]; export default function Settings() { const qc = useQueryClient(); const { data: saved, isLoading } = useQuery({ queryKey: ['settings'], queryFn: () => apiFetch('/settings'), retry: false, }); const [provider, setProvider] = useState('none'); const [apiKey, setApiKey] = useState(''); const [showKey, setShowKey] = useState(false); const [model, setModel] = useState(''); const [schedule, setSchedule] = useState('weekly'); const [saveOk, setSaveOk] = useState(false); // Hydrate form from server once loaded. useEffect(() => { if (!saved) return; setProvider(saved.ai_provider); setSchedule(saved.scan_schedule); setModel(saved.ai_model || (PROVIDERS.find((p) => p.value === saved.ai_provider)?.models[0] ?? '')); }, [saved]); const save = useMutation({ mutationFn: () => apiFetch<{ saved: boolean }>('/settings', { method: 'POST', body: JSON.stringify({ ai_provider: provider, ai_key: apiKey, ai_model: model, scan_schedule: schedule }), }), onSuccess: () => { setSaveOk(true); setApiKey(''); qc.invalidateQueries({ queryKey: ['settings'] }); setTimeout(() => setSaveOk(false), 3000); }, }); const testConnection = useMutation({ mutationFn: () => apiFetch<{ text?: string; error?: string }>('/ai/proxy', { method: 'POST', body: JSON.stringify({ task: 'explain', params: { title: 'test', wcag: '1.1.1' } }), }), }); const providerDef = PROVIDERS.find((p) => p.value === provider)!; if (isLoading) return ; return (

Settings

{/* AI Provider */}

AI Provider

AI features are optional. The API key is stored encrypted in{' '} wp_options and never sent to the browser.

Select AI provider {PROVIDERS.map((p) => ( ))}
{providerDef.models.length > 0 && ( )} {providerDef.needsKey && (
{ setApiKey(e.target.value); testConnection.reset(); }} placeholder={saved?.ai_key_set ? '••••••••••••••••' : 'Paste your API key'} autoComplete="off" />
{/* Test connection feedback */} {testConnection.isSuccess && !testConnection.data?.error && (

Connection verified.

)} {(testConnection.isError || testConnection.data?.error) && (

{testConnection.data?.error ?? (testConnection.error as Error)?.message ?? 'Connection failed.'}

)}
)}
{/* Scan schedule */}

Scheduled Scans

{/* Accessibility note */}

Accessibility note: AccessMate uses automated scanning to identify and help fix accessibility issues. Automated tools can detect only a subset of accessibility problems. For full WCAG 2.1 AA compliance, manual testing — including testing with assistive technologies such as screen readers — is recommended.

AccessMate helps you find and fix accessibility issues faster. It does not replace a full manual accessibility audit.

{/* Save */}
{saveOk && Saved.} {save.isError && ( {(save.error as Error).message} )}
); }