feat: SSR pour toutes les cards de la page Settings
Toutes les configurations (Prowlarr, qBittorrent, Telegram, Anilist, Komga, metadata providers, status mappings) sont maintenant récupérées côté serveur dans page.tsx et passées en props aux cards. Supprime ~10 fetchs client useEffect au chargement, élimine les layout shifts et réduit le temps de rendu initial. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,9 +20,19 @@ interface SettingsPageProps {
|
|||||||
initialThumbnailStats: ThumbnailStats;
|
initialThumbnailStats: ThumbnailStats;
|
||||||
users: UserDto[];
|
users: UserDto[];
|
||||||
initialTab?: string;
|
initialTab?: string;
|
||||||
|
initialProwlarr: Record<string, unknown> | null;
|
||||||
|
initialQbittorrent: Record<string, unknown> | null;
|
||||||
|
initialTorrentImport: Record<string, unknown> | null;
|
||||||
|
initialTelegram: Record<string, unknown> | null;
|
||||||
|
initialAnilist: Record<string, unknown> | null;
|
||||||
|
initialKomga: Record<string, unknown> | null;
|
||||||
|
initialMetadataProviders: Record<string, unknown> | null;
|
||||||
|
initialStatusMappings: Record<string, unknown>[];
|
||||||
|
initialSeriesStatuses: string[];
|
||||||
|
initialProviderStatuses: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SettingsPage({ initialSettings, initialCacheStats, initialThumbnailStats, users, initialTab }: SettingsPageProps) {
|
export default function SettingsPage({ initialSettings, initialCacheStats, initialThumbnailStats, users, initialTab, initialProwlarr, initialQbittorrent, initialTorrentImport, initialTelegram, initialAnilist, initialKomga, initialMetadataProviders, initialStatusMappings, initialSeriesStatuses, initialProviderStatuses }: SettingsPageProps) {
|
||||||
const { t, locale, setLocale } = useTranslation();
|
const { t, locale, setLocale } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -521,28 +531,28 @@ export default function SettingsPage({ initialSettings, initialCacheStats, initi
|
|||||||
|
|
||||||
{activeTab === "metadata" && (<>
|
{activeTab === "metadata" && (<>
|
||||||
{/* Metadata Providers */}
|
{/* Metadata Providers */}
|
||||||
<MetadataProvidersCard handleUpdateSetting={handleUpdateSetting} />
|
<MetadataProvidersCard handleUpdateSetting={handleUpdateSetting} initialData={initialMetadataProviders} />
|
||||||
|
|
||||||
{/* Status Mappings */}
|
{/* Status Mappings */}
|
||||||
<StatusMappingsCard />
|
<StatusMappingsCard initialStatusMappings={initialStatusMappings} initialSeriesStatuses={initialSeriesStatuses} initialProviderStatuses={initialProviderStatuses} />
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
{activeTab === "downloadTools" && (<>
|
{activeTab === "downloadTools" && (<>
|
||||||
{/* Prowlarr */}
|
{/* Prowlarr */}
|
||||||
<ProwlarrCard handleUpdateSetting={handleUpdateSetting} />
|
<ProwlarrCard handleUpdateSetting={handleUpdateSetting} initialData={initialProwlarr} />
|
||||||
|
|
||||||
{/* qBittorrent */}
|
{/* qBittorrent */}
|
||||||
<QBittorrentCard handleUpdateSetting={handleUpdateSetting} />
|
<QBittorrentCard handleUpdateSetting={handleUpdateSetting} initialQbittorrent={initialQbittorrent} initialTorrentImport={initialTorrentImport} />
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
{activeTab === "notifications" && (<>
|
{activeTab === "notifications" && (<>
|
||||||
{/* Telegram Notifications */}
|
{/* Telegram Notifications */}
|
||||||
<TelegramCard handleUpdateSetting={handleUpdateSetting} />
|
<TelegramCard handleUpdateSetting={handleUpdateSetting} initialData={initialTelegram} />
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
{activeTab === "readingStatus" && (<>
|
{activeTab === "readingStatus" && (<>
|
||||||
<AnilistTab handleUpdateSetting={handleUpdateSetting} users={users} />
|
<AnilistTab handleUpdateSetting={handleUpdateSetting} users={users} initialData={initialAnilist} />
|
||||||
<KomgaSyncCard users={users} />
|
<KomgaSyncCard users={users} initialData={initialKomga} />
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
@@ -8,19 +8,21 @@ import { useTranslation } from "@/lib/i18n/context";
|
|||||||
export function AnilistTab({
|
export function AnilistTab({
|
||||||
handleUpdateSetting,
|
handleUpdateSetting,
|
||||||
users,
|
users,
|
||||||
|
initialData,
|
||||||
}: {
|
}: {
|
||||||
handleUpdateSetting: (key: string, value: unknown) => Promise<void>;
|
handleUpdateSetting: (key: string, value: unknown) => Promise<void>;
|
||||||
users: UserDto[];
|
users: UserDto[];
|
||||||
|
initialData: Record<string, unknown> | null;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [origin, setOrigin] = useState("");
|
const [origin, setOrigin] = useState("");
|
||||||
useEffect(() => { setOrigin(window.location.origin); }, []);
|
useEffect(() => { setOrigin(window.location.origin); }, []);
|
||||||
|
|
||||||
const [clientId, setClientId] = useState("");
|
const [clientId, setClientId] = useState(initialData?.client_id ? String(initialData.client_id) : "");
|
||||||
const [token, setToken] = useState("");
|
const [token, setToken] = useState(initialData?.access_token ? String(initialData.access_token) : "");
|
||||||
const [userId, setUserId] = useState("");
|
const [userId, setUserId] = useState(initialData?.user_id ? String(initialData.user_id) : "");
|
||||||
const [localUserId, setLocalUserId] = useState("");
|
const [localUserId, setLocalUserId] = useState(initialData?.local_user_id ? String(initialData.local_user_id) : "");
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
const [viewer, setViewer] = useState<AnilistStatusDto | null>(null);
|
const [viewer, setViewer] = useState<AnilistStatusDto | null>(null);
|
||||||
const [testError, setTestError] = useState<string | null>(null);
|
const [testError, setTestError] = useState<string | null>(null);
|
||||||
@@ -33,21 +35,6 @@ export function AnilistTab({
|
|||||||
const [isPreviewing, setIsPreviewing] = useState(false);
|
const [isPreviewing, setIsPreviewing] = useState(false);
|
||||||
const [previewItems, setPreviewItems] = useState<AnilistSyncPreviewItemDto[] | null>(null);
|
const [previewItems, setPreviewItems] = useState<AnilistSyncPreviewItemDto[] | null>(null);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch("/api/settings/anilist")
|
|
||||||
.then((r) => r.ok ? r.json() : null)
|
|
||||||
.then((data) => {
|
|
||||||
if (data) {
|
|
||||||
if (data.client_id) setClientId(String(data.client_id));
|
|
||||||
if (data.access_token) setToken(data.access_token);
|
|
||||||
if (data.user_id) setUserId(String(data.user_id));
|
|
||||||
if (data.local_user_id) setLocalUserId(String(data.local_user_id));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function buildAnilistSettings() {
|
function buildAnilistSettings() {
|
||||||
return {
|
return {
|
||||||
client_id: clientId || undefined,
|
client_id: clientId || undefined,
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, Form
|
|||||||
import { KomgaSyncResponse, KomgaSyncReportSummary, UserDto } from "@/lib/api";
|
import { KomgaSyncResponse, KomgaSyncReportSummary, UserDto } from "@/lib/api";
|
||||||
import { useTranslation } from "@/lib/i18n/context";
|
import { useTranslation } from "@/lib/i18n/context";
|
||||||
|
|
||||||
export function KomgaSyncCard({ users }: { users: UserDto[] }) {
|
export function KomgaSyncCard({ users, initialData }: { users: UserDto[]; initialData: Record<string, unknown> | null }) {
|
||||||
const { t, locale } = useTranslation();
|
const { t, locale } = useTranslation();
|
||||||
const [komgaUrl, setKomgaUrl] = useState("");
|
const [komgaUrl, setKomgaUrl] = useState(initialData?.url ? String(initialData.url) : "");
|
||||||
const [komgaUsername, setKomgaUsername] = useState("");
|
const [komgaUsername, setKomgaUsername] = useState(initialData?.username ? String(initialData.username) : "");
|
||||||
const [komgaPassword, setKomgaPassword] = useState("");
|
const [komgaPassword, setKomgaPassword] = useState("");
|
||||||
const [komgaUserId, setKomgaUserId] = useState(users[0]?.id ?? "");
|
const [komgaUserId, setKomgaUserId] = useState(initialData?.user_id ? String(initialData.user_id) : (users[0]?.id ?? ""));
|
||||||
const [isSyncing, setIsSyncing] = useState(false);
|
const [isSyncing, setIsSyncing] = useState(false);
|
||||||
const [syncResult, setSyncResult] = useState<KomgaSyncResponse | null>(null);
|
const [syncResult, setSyncResult] = useState<KomgaSyncResponse | null>(null);
|
||||||
const [syncError, setSyncError] = useState<string | null>(null);
|
const [syncError, setSyncError] = useState<string | null>(null);
|
||||||
@@ -39,13 +39,6 @@ export function KomgaSyncCard({ users }: { users: UserDto[] }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchReports();
|
fetchReports();
|
||||||
fetch("/api/settings/komga").then(r => r.ok ? r.json() : null).then(data => {
|
|
||||||
if (data) {
|
|
||||||
if (data.url) setKomgaUrl(data.url);
|
|
||||||
if (data.username) setKomgaUsername(data.username);
|
|
||||||
if (data.user_id) setKomgaUserId(data.user_id);
|
|
||||||
}
|
|
||||||
}).catch(() => {});
|
|
||||||
}, [fetchReports]);
|
}, [fetchReports]);
|
||||||
|
|
||||||
async function handleViewReport(id: string) {
|
async function handleViewReport(id: string) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui";
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui";
|
||||||
import { ProviderIcon } from "@/app/components/ProviderIcon";
|
import { ProviderIcon } from "@/app/components/ProviderIcon";
|
||||||
import { useTranslation } from "@/lib/i18n/context";
|
import { useTranslation } from "@/lib/i18n/context";
|
||||||
@@ -11,25 +11,22 @@ export const METADATA_LANGUAGES = [
|
|||||||
{ value: "es", label: "Español" },
|
{ value: "es", label: "Español" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export function MetadataProvidersCard({ handleUpdateSetting }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void> }) {
|
function extractInitialApiKeys(data: Record<string, unknown> | null): Record<string, string> {
|
||||||
const { t } = useTranslation();
|
const keys: Record<string, string> = {};
|
||||||
const [defaultProvider, setDefaultProvider] = useState("google_books");
|
if (data) {
|
||||||
const [metadataLanguage, setMetadataLanguage] = useState("en");
|
const comicvine = data.comicvine as Record<string, unknown> | undefined;
|
||||||
const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
|
const googleBooks = data.google_books as Record<string, unknown> | undefined;
|
||||||
|
if (comicvine?.api_key) keys.comicvine = String(comicvine.api_key);
|
||||||
|
if (googleBooks?.api_key) keys.google_books = String(googleBooks.api_key);
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
export function MetadataProvidersCard({ handleUpdateSetting, initialData }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void>; initialData: Record<string, unknown> | null }) {
|
||||||
fetch("/api/settings/metadata_providers")
|
const { t } = useTranslation();
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
const [defaultProvider, setDefaultProvider] = useState(initialData?.default_provider ? String(initialData.default_provider) : "google_books");
|
||||||
.then((data) => {
|
const [metadataLanguage, setMetadataLanguage] = useState(initialData?.metadata_language ? String(initialData.metadata_language) : "en");
|
||||||
if (data) {
|
const [apiKeys, setApiKeys] = useState<Record<string, string>>(extractInitialApiKeys(initialData));
|
||||||
if (data.default_provider) setDefaultProvider(data.default_provider);
|
|
||||||
if (data.metadata_language) setMetadataLanguage(data.metadata_language);
|
|
||||||
if (data.comicvine?.api_key) setApiKeys((prev) => ({ ...prev, comicvine: data.comicvine.api_key }));
|
|
||||||
if (data.google_books?.api_key) setApiKeys((prev) => ({ ...prev, google_books: data.google_books.api_key }));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function save(provider: string, lang: string, keys: Record<string, string>) {
|
function save(provider: string, lang: string, keys: Record<string, string>) {
|
||||||
const value: Record<string, unknown> = {
|
const value: Record<string, unknown> = {
|
||||||
|
|||||||
@@ -1,30 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, Icon } from "@/app/components/ui";
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, Icon } from "@/app/components/ui";
|
||||||
import { useTranslation } from "@/lib/i18n/context";
|
import { useTranslation } from "@/lib/i18n/context";
|
||||||
|
|
||||||
export function ProwlarrCard({ handleUpdateSetting }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void> }) {
|
export function ProwlarrCard({ handleUpdateSetting, initialData }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void>; initialData: Record<string, unknown> | null }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [prowlarrUrl, setProwlarrUrl] = useState("");
|
const [prowlarrUrl, setProwlarrUrl] = useState(initialData?.url ? String(initialData.url) : "");
|
||||||
const [prowlarrApiKey, setProwlarrApiKey] = useState("");
|
const [prowlarrApiKey, setProwlarrApiKey] = useState(initialData?.api_key ? String(initialData.api_key) : "");
|
||||||
const [prowlarrCategories, setProwlarrCategories] = useState("7030, 7020");
|
const [prowlarrCategories, setProwlarrCategories] = useState(
|
||||||
|
Array.isArray(initialData?.categories) ? (initialData.categories as number[]).join(", ") : "7030, 7020"
|
||||||
|
);
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch("/api/settings/prowlarr")
|
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
|
||||||
.then((data) => {
|
|
||||||
if (data) {
|
|
||||||
if (data.url) setProwlarrUrl(data.url);
|
|
||||||
if (data.api_key) setProwlarrApiKey(data.api_key);
|
|
||||||
if (data.categories) setProwlarrCategories(data.categories.join(", "));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function saveProwlarr(url?: string, apiKey?: string, cats?: string) {
|
function saveProwlarr(url?: string, apiKey?: string, cats?: string) {
|
||||||
const categories = (cats ?? prowlarrCategories)
|
const categories = (cats ?? prowlarrCategories)
|
||||||
.split(",")
|
.split(",")
|
||||||
|
|||||||
@@ -1,34 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui";
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui";
|
||||||
import { useTranslation } from "@/lib/i18n/context";
|
import { useTranslation } from "@/lib/i18n/context";
|
||||||
|
|
||||||
export function QBittorrentCard({ handleUpdateSetting }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void> }) {
|
export function QBittorrentCard({ handleUpdateSetting, initialQbittorrent, initialTorrentImport }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void>; initialQbittorrent: Record<string, unknown> | null; initialTorrentImport: Record<string, unknown> | null }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [qbUrl, setQbUrl] = useState("");
|
const [qbUrl, setQbUrl] = useState(initialQbittorrent?.url ? String(initialQbittorrent.url) : "");
|
||||||
const [qbUsername, setQbUsername] = useState("");
|
const [qbUsername, setQbUsername] = useState(initialQbittorrent?.username ? String(initialQbittorrent.username) : "");
|
||||||
const [qbPassword, setQbPassword] = useState("");
|
const [qbPassword, setQbPassword] = useState(initialQbittorrent?.password ? String(initialQbittorrent.password) : "");
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||||
const [importEnabled, setImportEnabled] = useState(false);
|
const [importEnabled, setImportEnabled] = useState(initialTorrentImport?.enabled === true);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch("/api/settings/qbittorrent")
|
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
|
||||||
.then((data) => {
|
|
||||||
if (data) {
|
|
||||||
if (data.url) setQbUrl(data.url);
|
|
||||||
if (data.username) setQbUsername(data.username);
|
|
||||||
if (data.password) setQbPassword(data.password);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
fetch("/api/settings/torrent_import")
|
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
|
||||||
.then((data) => { if (data?.enabled !== undefined) setImportEnabled(data.enabled); })
|
|
||||||
.catch(() => {});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function saveQbittorrent() {
|
function saveQbittorrent() {
|
||||||
handleUpdateSetting("qbittorrent", {
|
handleUpdateSetting("qbittorrent", {
|
||||||
|
|||||||
@@ -1,36 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui";
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui";
|
||||||
import { StatusMappingDto } from "@/lib/api";
|
import { StatusMappingDto } from "@/lib/api";
|
||||||
import { useTranslation } from "@/lib/i18n/context";
|
import { useTranslation } from "@/lib/i18n/context";
|
||||||
|
|
||||||
export function StatusMappingsCard() {
|
export function StatusMappingsCard({ initialStatusMappings, initialSeriesStatuses, initialProviderStatuses }: { initialStatusMappings: Record<string, unknown>[]; initialSeriesStatuses: string[]; initialProviderStatuses: string[] }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [mappings, setMappings] = useState<StatusMappingDto[]>([]);
|
const [mappings, setMappings] = useState<StatusMappingDto[]>(initialStatusMappings as unknown as StatusMappingDto[]);
|
||||||
const [targetStatuses, setTargetStatuses] = useState<string[]>([]);
|
const [targetStatuses, setTargetStatuses] = useState<string[]>(initialSeriesStatuses);
|
||||||
const [providerStatuses, setProviderStatuses] = useState<string[]>([]);
|
const [providerStatuses] = useState<string[]>(initialProviderStatuses);
|
||||||
const [newTargetName, setNewTargetName] = useState("");
|
const [newTargetName, setNewTargetName] = useState("");
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const [mRes, sRes, pRes] = await Promise.all([
|
|
||||||
fetch("/api/settings/status-mappings").then((r) => r.ok ? r.json() : []),
|
|
||||||
fetch("/api/series/statuses").then((r) => r.ok ? r.json() : []),
|
|
||||||
fetch("/api/series/provider-statuses").then((r) => r.ok ? r.json() : []),
|
|
||||||
]);
|
|
||||||
setMappings(mRes);
|
|
||||||
setTargetStatuses(sRes);
|
|
||||||
setProviderStatuses(pRes);
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => { loadData(); }, [loadData]);
|
|
||||||
|
|
||||||
// Group mappings by target status (only those with a non-null mapped_status)
|
// Group mappings by target status (only those with a non-null mapped_status)
|
||||||
const grouped = useMemo(() => {
|
const grouped = useMemo(() => {
|
||||||
@@ -108,14 +88,6 @@ export function StatusMappingsCard() {
|
|||||||
return translated !== key ? translated : status;
|
return translated !== key ? translated : status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<Card className="mb-6">
|
|
||||||
<CardContent><p className="text-muted-foreground py-4">{t("common.loading")}</p></CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="mb-6">
|
<Card className="mb-6">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, Icon } from "@/app/components/ui";
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, Icon } from "@/app/components/ui";
|
||||||
import { useTranslation } from "@/lib/i18n/context";
|
import { useTranslation } from "@/lib/i18n/context";
|
||||||
|
|
||||||
@@ -25,30 +25,18 @@ export const DEFAULT_EVENTS = {
|
|||||||
download_detection_failed: true,
|
download_detection_failed: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TelegramCard({ handleUpdateSetting }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void> }) {
|
export function TelegramCard({ handleUpdateSetting, initialData }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void>; initialData: Record<string, unknown> | null }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [botToken, setBotToken] = useState("");
|
const [botToken, setBotToken] = useState(initialData?.bot_token ? String(initialData.bot_token) : "");
|
||||||
const [chatId, setChatId] = useState("");
|
const [chatId, setChatId] = useState(initialData?.chat_id ? String(initialData.chat_id) : "");
|
||||||
const [enabled, setEnabled] = useState(false);
|
const [enabled, setEnabled] = useState(initialData?.enabled === true);
|
||||||
const [events, setEvents] = useState(DEFAULT_EVENTS);
|
const [events, setEvents] = useState(
|
||||||
|
initialData?.events ? { ...DEFAULT_EVENTS, ...(initialData.events as Record<string, boolean>) } : DEFAULT_EVENTS
|
||||||
|
);
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||||
const [showHelp, setShowHelp] = useState(false);
|
const [showHelp, setShowHelp] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch("/api/settings/telegram")
|
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
|
||||||
.then((data) => {
|
|
||||||
if (data) {
|
|
||||||
if (data.bot_token) setBotToken(data.bot_token);
|
|
||||||
if (data.chat_id) setChatId(data.chat_id);
|
|
||||||
if (data.enabled !== undefined) setEnabled(data.enabled);
|
|
||||||
if (data.events) setEvents({ ...DEFAULT_EVENTS, ...data.events });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function saveTelegram(token?: string, chat?: string, en?: boolean, ev?: typeof events) {
|
function saveTelegram(token?: string, chat?: string, en?: boolean, ev?: typeof events) {
|
||||||
handleUpdateSetting("telegram", {
|
handleUpdateSetting("telegram", {
|
||||||
bot_token: token ?? botToken,
|
bot_token: token ?? botToken,
|
||||||
|
|||||||
@@ -1,30 +1,49 @@
|
|||||||
import { getSettings, getCacheStats, getThumbnailStats, fetchUsers } from "@/lib/api";
|
import { getSettings, getCacheStats, getThumbnailStats, fetchUsers, apiFetch } from "@/lib/api";
|
||||||
import SettingsPage from "./SettingsPage";
|
import SettingsPage from "./SettingsPage";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function SettingsPageWrapper({ searchParams }: { searchParams: Promise<{ tab?: string }> }) {
|
export default async function SettingsPageWrapper({ searchParams }: { searchParams: Promise<{ tab?: string }> }) {
|
||||||
const { tab } = await searchParams;
|
const { tab } = await searchParams;
|
||||||
const settings = await getSettings().catch(() => ({
|
const [settings, cacheStats, thumbnailStats, users, prowlarr, qbittorrent, torrentImport, telegram, anilist, komga, metadataProviders, statusMappings, seriesStatuses, providerStatuses] = await Promise.all([
|
||||||
image_processing: { format: "webp", quality: 85, filter: "lanczos3", max_width: 2160 },
|
getSettings().catch(() => ({
|
||||||
cache: { enabled: true, directory: "/tmp/stripstream-image-cache", max_size_mb: 10000 },
|
image_processing: { format: "webp", quality: 85, filter: "lanczos3", max_width: 2160 },
|
||||||
limits: { concurrent_renders: 4, timeout_seconds: 12, rate_limit_per_second: 120 },
|
cache: { enabled: true, directory: "/tmp/stripstream-image-cache", max_size_mb: 10000 },
|
||||||
thumbnail: { enabled: true, width: 300, height: 400, quality: 80, format: "webp", directory: "/data/thumbnails" }
|
limits: { concurrent_renders: 4, timeout_seconds: 12, rate_limit_per_second: 120 },
|
||||||
}));
|
thumbnail: { enabled: true, width: 300, height: 400, quality: 80, format: "webp", directory: "/data/thumbnails" }
|
||||||
|
})),
|
||||||
|
getCacheStats().catch(() => ({ total_size_mb: 0, file_count: 0, directory: "/tmp/stripstream-image-cache" })),
|
||||||
|
getThumbnailStats().catch(() => ({ total_size_mb: 0, file_count: 0, directory: "/data/thumbnails" })),
|
||||||
|
fetchUsers().catch(() => []),
|
||||||
|
apiFetch<Record<string, unknown>>("/settings/prowlarr").catch(() => null),
|
||||||
|
apiFetch<Record<string, unknown>>("/settings/qbittorrent").catch(() => null),
|
||||||
|
apiFetch<Record<string, unknown>>("/settings/torrent_import").catch(() => null),
|
||||||
|
apiFetch<Record<string, unknown>>("/settings/telegram").catch(() => null),
|
||||||
|
apiFetch<Record<string, unknown>>("/settings/anilist").catch(() => null),
|
||||||
|
apiFetch<Record<string, unknown>>("/settings/komga").catch(() => null),
|
||||||
|
apiFetch<Record<string, unknown>>("/settings/metadata_providers").catch(() => null),
|
||||||
|
apiFetch<unknown[]>("/settings/status-mappings").catch(() => []),
|
||||||
|
apiFetch<unknown[]>("/series/statuses").catch(() => []),
|
||||||
|
apiFetch<unknown[]>("/series/provider-statuses").catch(() => []),
|
||||||
|
]);
|
||||||
|
|
||||||
const cacheStats = await getCacheStats().catch(() => ({
|
return (
|
||||||
total_size_mb: 0,
|
<SettingsPage
|
||||||
file_count: 0,
|
initialSettings={settings}
|
||||||
directory: "/tmp/stripstream-image-cache"
|
initialCacheStats={cacheStats}
|
||||||
}));
|
initialThumbnailStats={thumbnailStats}
|
||||||
|
users={users}
|
||||||
const thumbnailStats = await getThumbnailStats().catch(() => ({
|
initialTab={tab}
|
||||||
total_size_mb: 0,
|
initialProwlarr={prowlarr}
|
||||||
file_count: 0,
|
initialQbittorrent={qbittorrent}
|
||||||
directory: "/data/thumbnails"
|
initialTorrentImport={torrentImport}
|
||||||
}));
|
initialTelegram={telegram}
|
||||||
|
initialAnilist={anilist}
|
||||||
const users = await fetchUsers().catch(() => []);
|
initialKomga={komga}
|
||||||
|
initialMetadataProviders={metadataProviders}
|
||||||
return <SettingsPage initialSettings={settings} initialCacheStats={cacheStats} initialThumbnailStats={thumbnailStats} users={users} initialTab={tab} />;
|
initialStatusMappings={statusMappings as Record<string, unknown>[]}
|
||||||
|
initialSeriesStatuses={seriesStatuses as string[]}
|
||||||
|
initialProviderStatuses={providerStatuses as string[]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user