'use client'; import { Fragment, useState, useEffect, useCallback } from 'react'; import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild, Listbox, ListboxButton, ListboxOptions, ListboxOption, Button } from '@headlessui/react'; import { useTheme } from '@/contexts/ThemeContext'; import { useConfig } from '@/contexts/ConfigContext'; import { ChevronUpDownIcon, CheckIcon } from './icons/Icons'; import { indexedDBService } from '@/utils/indexedDB'; import { useDocuments } from '@/contexts/DocumentContext'; import { setItem, getItem } from '@/utils/indexedDB'; import { ConfirmDialog } from '@/components/ConfirmDialog'; import { THEMES } from '@/contexts/ThemeContext'; const isDev = process.env.NEXT_PUBLIC_NODE_ENV !== 'production' || process.env.NODE_ENV == null; interface SettingsModalProps { isOpen: boolean; setIsOpen: (isOpen: boolean) => void; } const themes = THEMES.map(id => ({ id, name: id.charAt(0).toUpperCase() + id.slice(1) })); export function SettingsModal({ isOpen, setIsOpen }: SettingsModalProps) { const { theme, setTheme } = useTheme(); const { apiKey, baseUrl, updateConfig } = useConfig(); const { refreshPDFs, refreshEPUBs, clearPDFs, clearEPUBs } = useDocuments(); const [localApiKey, setLocalApiKey] = useState(apiKey); const [localBaseUrl, setLocalBaseUrl] = useState(baseUrl); const [isSyncing, setIsSyncing] = useState(false); const [isLoading, setIsLoading] = useState(false); const selectedTheme = themes.find(t => t.id === theme) || themes[0]; const [showClearLocalConfirm, setShowClearLocalConfirm] = useState(false); const [showClearServerConfirm, setShowClearServerConfirm] = useState(false); // set firstVisit on initial load const checkFirstVist = useCallback(async () => { if (!isDev) return; const firstVisit = await getItem('firstVisit'); if (firstVisit == null) { await setItem('firstVisit', 'true'); setIsOpen(true); } }, [setIsOpen]); useEffect(() => { checkFirstVist(); setLocalApiKey(apiKey); setLocalBaseUrl(baseUrl); }, [apiKey, baseUrl, checkFirstVist]); const handleSync = async () => { try { setIsSyncing(true); await indexedDBService.syncToServer(); } catch (error) { console.error('Sync failed:', error); } finally { setIsSyncing(false); } }; const handleLoad = async () => { try { setIsLoading(true); await indexedDBService.loadFromServer(); await Promise.all([refreshPDFs(), refreshEPUBs()]); } catch (error) { console.error('Load failed:', error); } finally { setIsLoading(false); } }; const handleClearLocal = async () => { await clearPDFs(); await clearEPUBs(); setShowClearLocalConfirm(false); }; const handleClearServer = async () => { try { const response = await fetch('/api/documents', { method: 'DELETE', }); if (!response.ok) { throw new Error('Failed to delete server documents'); } } catch (error) { console.error('Delete failed:', error); } setShowClearServerConfirm(false); }; return ( <> setIsOpen(false)}>
Settings
setTheme(newTheme.id)}> {selectedTheme.name} {themes.map((theme) => ( `relative cursor-pointer select-none py-2 pl-10 pr-4 ${active ? 'bg-accent/10 text-accent' : 'text-foreground' }` } value={theme} > {({ selected }) => ( <> {theme.name} {selected ? ( ) : null} )} ))}
setLocalApiKey(e.target.value)} className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-sm focus:outline-none focus:ring-2 focus:ring-accent" />
setLocalBaseUrl(e.target.value)} className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-sm focus:outline-none focus:ring-2 focus:ring-accent" />
{isDev &&
}
{isDev && }
setShowClearLocalConfirm(false)} onConfirm={handleClearLocal} title="Delete Local Documents" message="Are you sure you want to delete all local documents? This action cannot be undone." confirmText="Delete" isDangerous={true} /> setShowClearServerConfirm(false)} onConfirm={handleClearServer} title="Delete Server Documents" message="Are you sure you want to delete all documents from the server? This action cannot be undone." confirmText="Delete" isDangerous={true} /> ); }