'use client' import { useState, useCallback, useEffect, useRef } from 'react' import { createPortal } from 'react-dom' import { IconCheck, IconX } from '@tabler/icons-react' import Button from './Button' interface DirectoryModalProps { isOpen: boolean onClose: () => void onSave: (directory: string) => void onDirectoryListRefresh?: () => void } export default function DirectoryModal({ isOpen, onClose, onSave, onDirectoryListRefresh }: DirectoryModalProps) { const [directory, setDirectory] = useState('') const [isValidating, setIsValidating] = useState(false) const [isValid, setIsValid] = useState(null) const [validationTimeout, setValidationTimeout] = useState(null) const [mounted, setMounted] = useState(false) const [suggestions, setSuggestions] = useState([]) const [showSuggestions, setShowSuggestions] = useState(false) const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1) const suggestionRefs = useRef<(HTMLDivElement | null)[]>([]) useEffect(() => { setMounted(true) return () => setMounted(false) }, []) const validateDirectory = useCallback(async (path: string) => { if (!path.trim()) { setIsValid(null) return } setIsValidating(true) try { const response = await fetch('/api/validate-directory', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ directory: path }), }) const result = await response.json() setIsValid(result.valid) setSuggestions(result.suggestions || []) setShowSuggestions(result.suggestions && result.suggestions.length > 0) setSelectedSuggestionIndex(-1) suggestionRefs.current = [] } catch (error) { setIsValid(false) } finally { setIsValidating(false) } }, []) const handleInputChange = (e: React.ChangeEvent) => { const value = e.target.value setDirectory(value) setShowSuggestions(false) // Hide suggestions while typing setSelectedSuggestionIndex(-1) if (validationTimeout) { clearTimeout(validationTimeout) } const timeout = setTimeout(() => { validateDirectory(value) }, 300) setValidationTimeout(timeout) } const handleSuggestionClick = (suggestion: string) => { setDirectory(suggestion) setShowSuggestions(false) setSelectedSuggestionIndex(-1) validateDirectory(suggestion) } const handleKeyDown = (e: React.KeyboardEvent) => { // Handle Tab key to hide suggestions if directory is valid if (e.key === 'Tab' && directory.trim() && isValid) { setShowSuggestions(false) setSelectedSuggestionIndex(-1) return // Allow default Tab behavior } if (!showSuggestions || suggestions.length === 0) return switch (e.key) { case 'ArrowDown': e.preventDefault() setSelectedSuggestionIndex(prev => { const newIndex = prev === -1 ? 0 : (prev < suggestions.length - 1 ? prev + 1 : 0) setTimeout(() => { suggestionRefs.current[newIndex]?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) }, 0) return newIndex }) break case 'ArrowUp': e.preventDefault() setSelectedSuggestionIndex(prev => { const newIndex = prev > 0 ? prev - 1 : suggestions.length - 1 setTimeout(() => { suggestionRefs.current[newIndex]?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) }, 0) return newIndex }) break case 'Enter': e.preventDefault() if (selectedSuggestionIndex >= 0 && selectedSuggestionIndex < suggestions.length) { handleSuggestionClick(suggestions[selectedSuggestionIndex]) } else if (directory.trim() && isValid) { handleSave() } break case 'Escape': setShowSuggestions(false) setSelectedSuggestionIndex(-1) break } } if (!isOpen || !mounted) return null console.log('Modal is rendering with isOpen:', isOpen) const handleSave = async () => { if (directory.trim() && isValid) { try { // Save directory to database await fetch('/api/directories', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ path: directory.trim() }), }) onSave(directory.trim()) onDirectoryListRefresh?.() // Trigger directory list refresh setDirectory('') setIsValid(null) onClose() } catch (error) { console.error('Failed to save directory:', error) // Still proceed with the save even if database save fails onSave(directory.trim()) onDirectoryListRefresh?.() // Trigger directory list refresh setDirectory('') setIsValid(null) onClose() } } } const handleClose = () => { setDirectory('') setIsValid(null) setSuggestions([]) setShowSuggestions(false) setSelectedSuggestionIndex(-1) if (validationTimeout) { clearTimeout(validationTimeout) } onClose() } const modalContent = (
e.stopPropagation()} > {/* Header */}

Select Directory to Scan

{/* Content Area - Scrollable */}
setShowSuggestions(suggestions.length > 0)} placeholder="/path/to/photos" className="w-full p-2 pr-10 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white" autoFocus />
{isValidating && (
)} {!isValidating && isValid === true && ( )} {!isValidating && (isValid === false || (directory && isValid === null)) && ( )}
{/* Suggestions - Now with better scrolling */} {showSuggestions && suggestions.length > 0 && (
{suggestions.map((suggestion, index) => (
{ suggestionRefs.current[index] = el }} onClick={() => handleSuggestionClick(suggestion)} className={`px-3 py-2 cursor-pointer text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-600 last:border-b-0 ${ index === selectedSuggestionIndex ? 'bg-blue-100 dark:bg-blue-900' : 'hover:bg-gray-100 dark:hover:bg-gray-600' }`} >
{suggestion}
))}
)}
{/* Fixed Footer with Buttons */}
) return createPortal(modalContent, document.body) }