"use client"; import { useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, FormSelect, FormRow, Icon, toast, Toaster } from "@/app/components/ui"; import { Settings, CacheStats, ClearCacheResponse, ThumbnailStats, UserDto } from "@/lib/api"; import { useTranslation } from "@/lib/i18n/context"; import type { Locale } from "@/lib/i18n/types"; import { MetadataProvidersCard } from "./components/MetadataProvidersCard"; import { StatusMappingsCard } from "./components/StatusMappingsCard"; import { ProwlarrCard } from "./components/ProwlarrCard"; import { QBittorrentCard } from "./components/QBittorrentCard"; import { TelegramCard } from "./components/TelegramCard"; import { KomgaSyncCard } from "./components/KomgaSyncCard"; import { AnilistTab } from "./components/AnilistTab"; interface SettingsPageProps { initialSettings: Settings; initialCacheStats: CacheStats; initialThumbnailStats: ThumbnailStats; users: UserDto[]; initialTab?: string; } export default function SettingsPage({ initialSettings, initialCacheStats, initialThumbnailStats, users, initialTab }: SettingsPageProps) { const { t, locale, setLocale } = useTranslation(); const router = useRouter(); const searchParams = useSearchParams(); const [settings, setSettings] = useState({ ...initialSettings, thumbnail: initialSettings.thumbnail || { enabled: true, width: 300, height: 400, quality: 80, format: "webp", directory: "/data/thumbnails" } }); const [cacheStats, setCacheStats] = useState(initialCacheStats); const [thumbnailStats, setThumbnailStats] = useState(initialThumbnailStats); const [isClearing, setIsClearing] = useState(false); const [clearResult, setClearResult] = useState(null); const [isSaving, setIsSaving] = useState(false); const VALID_TABS = ["general", "downloadTools", "metadata", "readingStatus", "notifications"] as const; type TabId = typeof VALID_TABS[number]; function resolveTab(tab: string | null | undefined): TabId { if (tab && (VALID_TABS as readonly string[]).includes(tab)) return tab as TabId; return "general"; } const [activeTab, setActiveTab] = useState( resolveTab(searchParams.get("tab") ?? initialTab) ); function handleTabChange(tab: TabId) { setActiveTab(tab); router.replace(`?tab=${tab}`, { scroll: false }); } function hasEmptyValue(v: unknown): boolean { if (v === null || v === "") return true; if (typeof v === "object" && v !== null) { return Object.values(v).some((val) => val !== undefined && hasEmptyValue(val)); } return false; } async function handleUpdateSetting(key: string, value: unknown) { if (hasEmptyValue(value)) return; setIsSaving(true); try { const response = await fetch(`/api/settings/${key}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ value }) }); if (response.ok) { toast(t("settings.savedSuccess"), "success"); } else { toast(t("settings.savedError"), "error"); } } catch { toast(t("settings.saveError"), "error"); } finally { setIsSaving(false); } } async function handleClearCache() { setIsClearing(true); setClearResult(null); try { const response = await fetch("/api/settings/cache/clear", { method: "POST" }); const result = await response.json(); setClearResult(result); // Refresh cache stats const statsResponse = await fetch("/api/settings/cache/stats"); if (statsResponse.ok) { const stats = await statsResponse.json(); setCacheStats(stats); } } catch { setClearResult({ success: false, message: t("settings.cacheClearError") }); } finally { setIsClearing(false); } } const tabs = [ { id: "general" as const, label: t("settings.general"), icon: "settings" as const }, { id: "downloadTools" as const, label: t("settings.downloadTools"), icon: "play" as const }, { id: "metadata" as const, label: t("settings.metadata"), icon: "tag" as const }, { id: "readingStatus" as const, label: t("settings.readingStatus"), icon: "eye" as const }, { id: "notifications" as const, label: t("settings.notifications"), icon: "bell" as const }, ]; return ( <>

{t("settings.title")}

{/* Tab Navigation */}
{tabs.map((tab) => ( ))}
{activeTab === "general" && (<> {/* Language Selector */} {t("settings.language")} {t("settings.languageDesc")} setLocale(e.target.value as Locale)} > {/* Image Processing Settings */} {t("settings.imageProcessing")}
{ const newSettings = { ...settings, image_processing: { ...settings.image_processing, format: e.target.value } }; setSettings(newSettings); handleUpdateSetting("image_processing", newSettings.image_processing); }} > { const quality = parseInt(e.target.value) || 85; const newSettings = { ...settings, image_processing: { ...settings.image_processing, quality } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("image_processing", settings.image_processing)} /> { const newSettings = { ...settings, image_processing: { ...settings.image_processing, filter: e.target.value } }; setSettings(newSettings); handleUpdateSetting("image_processing", newSettings.image_processing); }} > { const max_width = parseInt(e.target.value) || 2160; const newSettings = { ...settings, image_processing: { ...settings.image_processing, max_width } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("image_processing", settings.image_processing)} />
{/* Cache Settings */} {t("settings.cache")} {t("settings.cacheDesc")}

{t("settings.cacheSize")}

{cacheStats.total_size_mb.toFixed(2)} MB

{t("settings.files")}

{cacheStats.file_count}

{t("settings.directory")}

{cacheStats.directory}

{clearResult && (
{clearResult.message}
)} { const newSettings = { ...settings, cache: { ...settings.cache, directory: e.target.value } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("cache", settings.cache)} /> { const max_size_mb = parseInt(e.target.value) || 10000; const newSettings = { ...settings, cache: { ...settings.cache, max_size_mb } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("cache", settings.cache)} />
{/* Limits Settings */} {t("settings.performanceLimits")} {t("settings.performanceDesc")}
{ const concurrent_renders = parseInt(e.target.value) || 4; const newSettings = { ...settings, limits: { ...settings.limits, concurrent_renders } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("limits", settings.limits)} />

{t("settings.concurrentRendersHelp")}

{ const timeout_seconds = parseInt(e.target.value) || 12; const newSettings = { ...settings, limits: { ...settings.limits, timeout_seconds } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("limits", settings.limits)} /> { const rate_limit_per_second = parseInt(e.target.value) || 120; const newSettings = { ...settings, limits: { ...settings.limits, rate_limit_per_second } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("limits", settings.limits)} />

{t("settings.limitsNote")}

{/* Thumbnail Settings */} {t("settings.thumbnails")} {t("settings.thumbnailsDesc")}
{ const newSettings = { ...settings, thumbnail: { ...settings.thumbnail, enabled: e.target.value === "true" } }; setSettings(newSettings); handleUpdateSetting("thumbnail", newSettings.thumbnail); }} > { const newSettings = { ...settings, thumbnail: { ...settings.thumbnail, format: e.target.value } }; setSettings(newSettings); handleUpdateSetting("thumbnail", newSettings.thumbnail); }} >

{settings.thumbnail.format === "original" ? t("settings.formatOriginalDesc") : t("settings.formatReencodeDesc")}

{ const width = parseInt(e.target.value) || 300; const newSettings = { ...settings, thumbnail: { ...settings.thumbnail, width } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("thumbnail", settings.thumbnail)} /> { const height = parseInt(e.target.value) || 400; const newSettings = { ...settings, thumbnail: { ...settings.thumbnail, height } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("thumbnail", settings.thumbnail)} /> { const quality = parseInt(e.target.value) || 80; const newSettings = { ...settings, thumbnail: { ...settings.thumbnail, quality } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("thumbnail", settings.thumbnail)} /> { const newSettings = { ...settings, thumbnail: { ...settings.thumbnail, directory: e.target.value } }; setSettings(newSettings); }} onBlur={() => handleUpdateSetting("thumbnail", settings.thumbnail)} />

{t("settings.totalSize")}

{thumbnailStats.total_size_mb.toFixed(2)} MB

{t("settings.files")}

{thumbnailStats.file_count}

{t("settings.directory")}

{thumbnailStats.directory}

{t("settings.thumbnailsNote")}

)} {activeTab === "metadata" && (<> {/* Metadata Providers */} {/* Status Mappings */} )} {activeTab === "downloadTools" && (<> {/* Prowlarr */} {/* qBittorrent */} )} {activeTab === "notifications" && (<> {/* Telegram Notifications */} )} {activeTab === "readingStatus" && (<> )} ); }