import React, { useState, useEffect, useRef } from 'react'; import { PlexConnectionSettings, PlexServerConnection, PlexLibrary } from '../types'; import { apiService } from '../services/api'; import { X, Server, Lock, User, Key, Globe, Eye, EyeOff, CheckCircle, Library, ChevronDown, ChevronRight, Settings, Loader2 } from 'lucide-react'; import { useLanguage } from '../LanguageContext'; interface ConnectionModalProps { isOpen: boolean; onClose: () => void; onConnectSuccess: (serverInfo: PlexServerConnection) => void | Promise; onShowMessage: (message: string) => void; initialSettings?: Partial; } const ConnectionModal: React.FC = ({ isOpen, onClose, onConnectSuccess, onShowMessage, initialSettings }) => { const { t } = useLanguage(); const [formData, setFormData] = useState({ protocol: 'http', address: '', port: '32400', token: '', username: '', password: '', timeout: 9, libraryName: '' }); const [isConnecting, setIsConnecting] = useState(false); const [error, setError] = useState(null); const [showPassword, setShowPassword] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false); // Post-connection state const [connectedServerInfo, setConnectedServerInfo] = useState(null); const [libraries, setLibraries] = useState([]); const [selectedLibraryId, setSelectedLibraryId] = useState(''); const abortControllerRef = useRef(null); const prevIsOpenRef = useRef(isOpen); // Reset state when opening useEffect(() => { // Only execute reset logic when modal opens (isOpen changes from false to true) if (isOpen && !prevIsOpenRef.current) { setError(null); setConnectedServerInfo(null); setLibraries([]); setSelectedLibraryId(''); if (initialSettings) { setFormData(prev => ({ ...prev, protocol: initialSettings.protocol || prev.protocol, address: initialSettings.address || prev.address, port: initialSettings.port || prev.port, token: initialSettings.token || prev.token, libraryName: initialSettings.libraryName || prev.libraryName, })); } } // Cleanup when closing if (!isOpen && prevIsOpenRef.current) { if (abortControllerRef.current) { abortControllerRef.current.abort(); } } prevIsOpenRef.current = isOpen; }, [isOpen, initialSettings]); if (!isOpen) return null; const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleTimeoutChange = (e: React.ChangeEvent) => { const val = parseInt(e.target.value) || 0; setFormData(prev => ({ ...prev, timeout: val })); }; const handleLibraryChange = async (e: React.ChangeEvent) => { const newId = e.target.value; setSelectedLibraryId(newId); const lib = libraries.find(l => l.id === newId); if (lib && connectedServerInfo) { const updatedInfo = { ...connectedServerInfo, libraryName: lib.title }; setConnectedServerInfo(updatedInfo); onConnectSuccess(updatedInfo); const saveResult = await apiService.updateLibrary(lib.title); if (saveResult.status !== 'success') { onShowMessage(saveResult.message || t('toasts.librarySaveFailed')); } else { onShowMessage(t('toasts.librarySwitched', { library: lib.title })); } } }; const isTokenProvided = formData.token.trim().length > 0; const disabledInputClass = isTokenProvided ? "bg-gray-700/50 text-gray-500 line-through decoration-gray-500 cursor-not-allowed border-gray-700" : "bg-gray-800 text-gray-100 border-gray-600 focus:border-plex-orange focus:ring-1 focus:ring-plex-orange"; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // If already connecting, this acts as Cancel if (isConnecting) { if (abortControllerRef.current) { abortControllerRef.current.abort(); abortControllerRef.current = null; setIsConnecting(false); setError(t('toasts.connectionCancelled')); } return; } setError(null); setIsConnecting(true); const abortController = new AbortController(); abortControllerRef.current = abortController; const result = await apiService.connectToPlex(formData, abortController.signal); // Only proceed if we weren't aborted/cancelled (though apiService handles error msg) if (abortController.signal.aborted) return; setIsConnecting(false); abortControllerRef.current = null; if (result.status === 'success' && result.data) { setFormData(prev => ({ ...prev, token: result.data.token, username: '', password: '' })); const info = result.data.serverInfo; setConnectedServerInfo(info); onShowMessage(t('toasts.connectedTo', { name: info.name || 'Plex Server' })); const libs = info.libraries || []; const musicLibraries = libs.filter((lib) => lib.type === 'artist').sort((a, b) => a.title.localeCompare(b.title)); setLibraries(musicLibraries); if (musicLibraries.length > 0) { const preferred = info.libraryName || formData.libraryName; const defaultLib = musicLibraries.find(lib => lib.title === preferred) || musicLibraries[0]; setSelectedLibraryId(defaultLib.id); setFormData(prev => ({ ...prev, libraryName: defaultLib.title })); onConnectSuccess({ ...info, libraryName: defaultLib.title }); const saveResult = await apiService.updateLibrary(defaultLib.title); if (saveResult.status !== 'success') { setError(saveResult.message || t('toasts.librarySaveFailed')); } } else { onConnectSuccess(info); } } else { setError(result.message || t('server.connectionFailed')); } }; const isConnected = !!connectedServerInfo; return (
e.stopPropagation()} > {/* Header */}

{isConnected ? t('connection.titleConnected') : t('connection.titleConnect')}

{/* Body */}
{error && (
{error}
)} {/* Server Connection */}
{/* Authentication */}
{/* Token */}
{!isConnected && ( <>
— OR —
{/* Username */}
{/* Password */}
)}
{/* Advanced Options */} {!isConnected && (
{showAdvanced && (
)}
)} {!isConnected ? ( ) : (

{t('connection.connectedSuccess')}

)} {/* Library Selection - Appears after connection */} {isConnected && libraries.length > 0 && (
)}
); }; export default ConnectionModal;