'use client'; import { useCallback, useEffect, useState } from 'react'; import { useTranslations } from 'next-intl'; import { type SearchProviderResponse, addSearchProvider, fetchSearchProviders, removeSearchProvider, } from '@/lib/dashboard-api'; /** * SearchProviderForm (D-15). * Lists current search providers (defaults read-only, custom deletable) * plus an add form for creating custom providers with client-side * validation that urlTemplate contains {query}. */ export function SearchProviderForm() { const t = useTranslations('widgets'); const [providers, setProviders] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); // Add form state const [newName, setNewName] = useState(''); const [newUrlTemplate, setNewUrlTemplate] = useState(''); const [validationError, setValidationError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const loadProviders = useCallback(async () => { try { const data = await fetchSearchProviders(); setProviders(data); setError(null); } catch { setError('Failed to load search providers'); } finally { setIsLoading(false); } }, []); useEffect(() => { loadProviders(); }, [loadProviders]); const handleAdd = useCallback(async () => { // Client-side validation: urlTemplate must contain {query} if (!newName.trim()) { setValidationError('Name is required'); return; } if (!newUrlTemplate.includes('{query}')) { setValidationError('URL template must contain {query}'); return; } setValidationError(null); setIsSubmitting(true); try { const created = await addSearchProvider({ name: newName.trim(), urlTemplate: newUrlTemplate.trim(), }); setProviders((prev) => [...prev, created]); setNewName(''); setNewUrlTemplate(''); } catch { setValidationError('Failed to add provider'); } finally { setIsSubmitting(false); } }, [newName, newUrlTemplate]); const handleDelete = useCallback(async (id: string) => { try { await removeSearchProvider(id); setProviders((prev) => prev.filter((p) => p.id !== id)); } catch { // Silent fail — provider stays in list } }, []); if (isLoading) { return (

Loading providers...

); } if (error) { return

{error}

; } return (
{/* Existing providers list */}

{t('search.providers')}

{providers.length === 0 ? (

No providers found.

) : (
    {providers.map((provider) => (
  • {provider.name} {provider.isDefault && ( (Default) )}

    {provider.urlTemplate}

    {/* Only custom (non-default) providers can be deleted */} {!provider.isDefault && ( )}
  • ))}
)}
{/* Add new provider form */}

{t('search.addProvider')}

{ setNewName(e.target.value); setValidationError(null); }} />
{ setNewUrlTemplate(e.target.value); setValidationError(null); }} />

Must contain {'{query}'} as placeholder for the search term.

{validationError && (

{validationError}

)}
); }