/** * Component: Indexer Management Container * Documentation: documentation/frontend/components.md */ 'use client'; import React, { useState, useEffect } from 'react'; import { Button } from '@/components/ui/Button'; import { IndexerCard } from './IndexerCard'; import { IndexerConfigModal } from './IndexerConfigModal'; import { AvailableIndexerRow } from './AvailableIndexerRow'; import { DeleteConfirmModal } from './DeleteConfirmModal'; import { fetchWithAuth } from '@/lib/utils/api'; interface ProwlarrIndexer { id: number; name: string; protocol: string; supportsRss: boolean; } interface SavedIndexerConfig { id: number; name: string; protocol: string; priority: number; seedingTimeMinutes?: number; // Torrents only removeAfterProcessing?: boolean; // Usenet only rssEnabled: boolean; audiobookCategories: number[]; // Categories for audiobook searches ebookCategories: number[]; // Categories for ebook searches } interface IndexerManagementProps { prowlarrUrl: string; prowlarrApiKey: string; mode: 'wizard' | 'settings'; initialIndexers?: SavedIndexerConfig[]; onIndexersChange?: (indexers: SavedIndexerConfig[]) => void; } export function IndexerManagement({ prowlarrUrl, prowlarrApiKey, mode, initialIndexers = [], onIndexersChange, }: IndexerManagementProps) { const [fetchedIndexers, setFetchedIndexers] = useState([]); const [configuredIndexers, setConfiguredIndexers] = useState(initialIndexers); const [modalState, setModalState] = useState<{ isOpen: boolean; mode: 'add' | 'edit'; indexer?: ProwlarrIndexer; currentConfig?: SavedIndexerConfig; }>({ isOpen: false, mode: 'add' }); const [deleteModalState, setDeleteModalState] = useState<{ isOpen: boolean; indexerId?: number; indexerName?: string; }>({ isOpen: false }); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Sync with parent when configuredIndexers changes useEffect(() => { if (onIndexersChange) { onIndexersChange(configuredIndexers); } }, [configuredIndexers, onIndexersChange]); // Sync with initialIndexers prop changes useEffect(() => { setConfiguredIndexers(initialIndexers); }, [initialIndexers]); const fetchIndexers = async () => { setLoading(true); setError(null); try { const endpoint = mode === 'wizard' ? '/api/setup/test-prowlarr' : '/api/admin/settings/test-prowlarr'; // Use fetchWithAuth for settings mode (requires authentication) // Use plain fetch for wizard mode (no auth required) const response = mode === 'settings' ? await fetchWithAuth(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: prowlarrUrl, apiKey: prowlarrApiKey, }), }) : await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: prowlarrUrl, apiKey: prowlarrApiKey, }), }); const data = await response.json(); if (!response.ok || !data.success) { throw new Error(data.error || 'Failed to fetch indexers'); } setFetchedIndexers(data.indexers || []); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch indexers'); setFetchedIndexers([]); } finally { setLoading(false); } }; const openAddModal = (indexer: ProwlarrIndexer) => { setModalState({ isOpen: true, mode: 'add', indexer, }); }; const openEditModal = (config: SavedIndexerConfig) => { // Find the full indexer info from fetched list const indexer = fetchedIndexers.find((idx) => idx.id === config.id); setModalState({ isOpen: true, mode: 'edit', indexer: indexer || { id: config.id, name: config.name, protocol: config.protocol, supportsRss: config.rssEnabled, }, currentConfig: config, }); }; const closeModal = () => { setModalState({ isOpen: false, mode: 'add' }); }; const handleSave = (config: SavedIndexerConfig) => { if (modalState.mode === 'add') { // Add new indexer setConfiguredIndexers([...configuredIndexers, config]); } else { // Update existing indexer setConfiguredIndexers( configuredIndexers.map((idx) => idx.id === config.id ? config : idx ) ); } }; const handleDelete = (id: number) => { const indexer = configuredIndexers.find((idx) => idx.id === id); if (!indexer) return; setDeleteModalState({ isOpen: true, indexerId: id, indexerName: indexer.name, }); }; const confirmDelete = () => { if (deleteModalState.indexerId) { setConfiguredIndexers( configuredIndexers.filter((idx) => idx.id !== deleteModalState.indexerId) ); } }; const isIndexerAdded = (id: number) => { return configuredIndexers.some((idx) => idx.id === id); }; return (
{/* Section 1: Available Indexers */}

Available Indexers

{error && (
{error}
)} {fetchedIndexers.length > 0 && (
{fetchedIndexers.map((indexer) => ( openAddModal(indexer)} /> ))}
)} {!loading && fetchedIndexers.length === 0 && !error && (
{prowlarrUrl && prowlarrApiKey ? 'Click "Fetch Indexers" to load available indexers from Prowlarr.' : 'Enter Prowlarr URL and API key above, then fetch indexers.'}
)}
{/* Section 2: Configured Indexers */}

Configured Indexers ({configuredIndexers.length})

{configuredIndexers.length === 0 ? (

No indexers configured yet

Fetch indexers from Prowlarr and click "Add" to configure them.

) : (
{configuredIndexers.map((config) => ( openEditModal(config)} onDelete={() => handleDelete(config.id)} /> ))}
)}
{/* Config Modal */} {modalState.isOpen && modalState.indexer && ( )} {/* Delete Confirmation Modal */} setDeleteModalState({ isOpen: false })} onConfirm={confirmDelete} indexerName={deleteModalState.indexerName || ''} />
); }