'use client'; import { useState, useEffect } from 'react'; import { RMABLogger } from '@/lib/utils/logger'; import { fetchWithAuth } from '@/lib/utils/api'; import { EVENT_LABELS } from '@/lib/constants/notification-events'; const logger = RMABLogger.create('NotificationsTab'); interface ProviderConfigField { name: string; label: string; type: 'text' | 'password' | 'select' | 'number'; required: boolean; placeholder?: string; defaultValue?: string | number; options?: { label: string; value: string | number }[]; } interface ProviderMetadata { type: string; displayName: string; description: string; iconLabel: string; iconColor: string; configFields: ProviderConfigField[]; } interface NotificationBackend { id: string; type: string; name: string; config: Record; events: string[]; enabled: boolean; createdAt: string; updatedAt: string; } interface ModalState { isOpen: boolean; mode: 'add' | 'edit'; selectedType?: string; backend?: NotificationBackend; } const eventLabels: Record = EVENT_LABELS; export function NotificationsTab() { const [backends, setBackends] = useState([]); const [providerMetadata, setProviderMetadata] = useState([]); const [loading, setLoading] = useState(true); const [modalState, setModalState] = useState({ isOpen: false, mode: 'add', }); const [formData, setFormData] = useState({ name: '', config: {}, events: ['request_available', 'request_error'], enabled: true, }); const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null); const [isTesting, setIsTesting] = useState(false); const [isSaving, setIsSaving] = useState(false); useEffect(() => { fetchBackends(); fetchProviderMetadata(); }, []); const fetchProviderMetadata = async () => { try { const response = await fetchWithAuth('/api/admin/notifications/providers'); if (response.ok) { const data = await response.json(); if (data.success) { setProviderMetadata(data.providers); } } } catch (error) { logger.error('Failed to fetch provider metadata', { error: error instanceof Error ? error.message : String(error) }); } }; const fetchBackends = async () => { try { setLoading(true); const response = await fetchWithAuth('/api/admin/notifications'); if (response.ok) { const data = await response.json(); if (data.success) { setBackends(data.backends); } else { logger.error('Failed to fetch backends', { error: data.error }); } } else { logger.error('Failed to fetch backends', { status: response.status }); } } catch (error) { logger.error('Failed to fetch backends', { error: error instanceof Error ? error.message : String(error) }); } finally { setLoading(false); } }; const getMetadataForType = (type: string): ProviderMetadata | undefined => { return providerMetadata.find((p) => p.type === type); }; const openAddModal = (type: string) => { const meta = getMetadataForType(type); const defaultConfig: Record = {}; if (meta) { for (const field of meta.configFields) { defaultConfig[field.name] = field.defaultValue ?? ''; } } setModalState({ isOpen: true, mode: 'add', selectedType: type }); setFormData({ name: `${meta?.displayName ?? type} Notifications`, config: defaultConfig, events: ['request_available', 'request_error'], enabled: true, }); setTestResult(null); }; const openEditModal = (backend: NotificationBackend) => { setModalState({ isOpen: true, mode: 'edit', selectedType: backend.type, backend }); setFormData({ name: backend.name, config: backend.config, events: backend.events, enabled: backend.enabled, }); setTestResult(null); }; const closeModal = () => { setModalState({ isOpen: false, mode: 'add' }); setTestResult(null); }; const handleTest = async () => { if (!modalState.selectedType) return; try { setIsTesting(true); setTestResult(null); // In edit mode, use backend ID to test with real config (masked values won't work) // In add mode, use the form config directly const testPayload = modalState.mode === 'edit' && modalState.backend ? { backendId: modalState.backend.id } : { type: modalState.selectedType, config: formData.config }; const response = await fetchWithAuth('/api/admin/notifications/test', { method: 'POST', body: JSON.stringify(testPayload), }); const data = await response.json(); if (response.ok && data.success) { setTestResult({ success: true, message: 'Test notification sent successfully!' }); } else { setTestResult({ success: false, message: data.message || 'Failed to send test notification' }); } } catch (error) { setTestResult({ success: false, message: error instanceof Error ? error.message : 'Unknown error' }); } finally { setIsTesting(false); } }; const handleSave = async () => { if (!modalState.selectedType) return; try { setIsSaving(true); const url = modalState.mode === 'add' ? '/api/admin/notifications' : `/api/admin/notifications/${modalState.backend?.id}`; const method = modalState.mode === 'add' ? 'POST' : 'PUT'; const response = await fetchWithAuth(url, { method, body: JSON.stringify({ type: modalState.selectedType, ...formData, }), }); const data = await response.json(); if (response.ok && data.success) { await fetchBackends(); closeModal(); } else { setTestResult({ success: false, message: data.message || 'Failed to save backend' }); } } catch (error) { setTestResult({ success: false, message: error instanceof Error ? error.message : 'Unknown error' }); } finally { setIsSaving(false); } }; const handleDelete = async (id: string) => { if (!confirm('Are you sure you want to delete this notification backend?')) return; try { const response = await fetchWithAuth(`/api/admin/notifications/${id}`, { method: 'DELETE', }); if (response.ok) { const data = await response.json(); if (data.success) { await fetchBackends(); } } } catch (error) { logger.error('Failed to delete backend', { error: error instanceof Error ? error.message : String(error) }); } }; const renderConfigField = (field: ProviderConfigField) => { if (field.type === 'select' && field.options) { return (
); } return (
setFormData({ ...formData, config: { ...formData.config, [field.name]: e.target.value } })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white" placeholder={field.placeholder} />
); }; const currentMeta = modalState.selectedType ? getMetadataForType(modalState.selectedType) : undefined; return (
{/* Header */}

Notifications

Configure notification backends to receive alerts for audiobook request events.

{/* Type Selector */}

Add Notification Backend

{providerMetadata.map((meta) => ( ))}
{/* Configured Backends */}

Configured Backends

{loading ? (

Loading...

) : backends.length === 0 ? (

No notification backends configured.

) : (
{backends.map((backend) => { const meta = getMetadataForType(backend.type); return (
{meta?.iconLabel ?? backend.type.charAt(0).toUpperCase()}
{backend.name}
{meta?.displayName ?? backend.type}
{backend.enabled ? 'Enabled' : 'Disabled'}
{backend.events.length} {backend.events.length === 1 ? 'event' : 'events'} subscribed
); })}
)}
{/* Modal */} {modalState.isOpen && modalState.selectedType && (

{modalState.mode === 'add' ? 'Add' : 'Edit'} {currentMeta?.displayName ?? modalState.selectedType} Notification

{/* Name */}
setFormData({ ...formData, name: e.target.value })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white" placeholder="e.g., Discord - Admins" />
{/* Dynamic Config Fields */} {currentMeta?.configFields.map((field) => renderConfigField(field))} {/* Events */}
{Object.entries(eventLabels).map(([event, label]) => ( ))}
{/* Enabled Toggle */}
{/* Test Result */} {testResult && (
{testResult.message}
)} {/* Actions */}
)}
); }