import { useEffect, useState } from "react"; import { useForm, type SubmitHandler, Controller } from "react-hook-form"; import { authApiClient } from "../../lib/api-client"; import { toast } from "sonner"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; // --- Type Definitions --- const ALBUM_GROUPS = ["album", "single", "compilation", "appears_on"] as const; type AlbumGroup = (typeof ALBUM_GROUPS)[number]; interface WatchSettings { enabled: boolean; watchPollIntervalSeconds: number; watchedArtistAlbumGroup: AlbumGroup[]; maxItemsPerRun: number; } interface DownloadSettings { realTime: boolean; fallback: boolean; maxConcurrentDownloads: number; convertTo: string; bitrate: string; maxRetries: number; retryDelaySeconds: number; retryDelayIncrease: number; deezerQuality: string; spotifyQuality: string; } interface Credential { name: string; } // --- API Functions --- const fetchWatchConfig = async (): Promise => { const { data } = await authApiClient.client.get("/config/watch"); return data; }; const fetchDownloadConfig = async (): Promise => { const { data } = await authApiClient.client.get("/config"); return data; }; const fetchCredentials = async (service: "spotify" | "deezer"): Promise => { const { data } = await authApiClient.client.get(`/credentials/${service}`); return data.map((name) => ({ name })); }; const saveWatchConfig = async (data: Partial) => { const { data: response } = await authApiClient.client.post("/config/watch", data); return response; }; // --- Component --- export function WatchTab() { const queryClient = useQueryClient(); const [validationError, setValidationError] = useState(""); const { data: config, isLoading } = useQuery({ queryKey: ["watchConfig"], queryFn: fetchWatchConfig, }); // Fetch download config to validate requirements const { data: downloadConfig } = useQuery({ queryKey: ["config"], queryFn: fetchDownloadConfig, staleTime: 30000, // 30 seconds }); // Fetch credentials for fallback validation const { data: spotifyCredentials } = useQuery({ queryKey: ["credentials", "spotify"], queryFn: () => fetchCredentials("spotify"), staleTime: 30000, }); const { data: deezerCredentials } = useQuery({ queryKey: ["credentials", "deezer"], queryFn: () => fetchCredentials("deezer"), staleTime: 30000, }); const mutation = useMutation({ mutationFn: saveWatchConfig, onSuccess: () => { toast.success("Watch settings saved successfully!"); queryClient.invalidateQueries({ queryKey: ["watchConfig"] }); queryClient.invalidateQueries({ queryKey: ["config"] }); // Invalidate main config to refresh watch.enabled in SettingsProvider }, onError: (error: any) => { const message = error?.response?.data?.error || error?.message || "Unknown error"; toast.error(`Failed to save settings: ${message}`); console.error("Failed to save watch settings:", message); }, }); const { register, handleSubmit, control, reset, watch } = useForm(); useEffect(() => { if (config) { reset(config); } }, [config, reset]); const watchEnabled = watch("enabled"); const maxItemsPerRunValue = watch("maxItemsPerRun"); // Validation effect for watch + download method requirement useEffect(() => { let error = ""; // Check if watch can be enabled (need download methods) if (watchEnabled && downloadConfig && !downloadConfig.realTime && !downloadConfig.fallback) { error = "To enable watch, either Real-time downloading or Download Fallback must be enabled in Download Settings."; } // Check fallback account requirements if watch is enabled and fallback is being used if (watchEnabled && downloadConfig?.fallback && (!spotifyCredentials?.length || !deezerCredentials?.length)) { const missingServices: string[] = []; if (!spotifyCredentials?.length) missingServices.push("Spotify"); if (!deezerCredentials?.length) missingServices.push("Deezer"); error = `Watch with Fallback requires accounts for both services. Missing: ${missingServices.join(", ")}. Configure accounts in the Accounts tab.`; } // Validate maxItemsPerRun range (1..50) const mir = Number(maxItemsPerRunValue); if (!error && (Number.isNaN(mir) || mir < 1 || mir > 50)) { error = "Max items per run must be between 1 and 50."; } setValidationError(error); }, [watchEnabled, downloadConfig?.realTime, downloadConfig?.fallback, spotifyCredentials?.length, deezerCredentials?.length, maxItemsPerRunValue]); const onSubmit: SubmitHandler = (data) => { // Check validation before submitting if (data.enabled && downloadConfig && !downloadConfig.realTime && !downloadConfig.fallback) { setValidationError("To enable watch, either Real-time downloading or Download Fallback must be enabled in Download Settings."); toast.error("Validation failed: Watch requires at least one download method to be enabled in Download Settings."); return; } // Check fallback account requirements if enabling watch with fallback if (data.enabled && downloadConfig?.fallback && (!spotifyCredentials?.length || !deezerCredentials?.length)) { const missingServices: string[] = []; if (!spotifyCredentials?.length) missingServices.push("Spotify"); if (!deezerCredentials?.length) missingServices.push("Deezer"); const error = `Watch with Fallback requires accounts for both services. Missing: ${missingServices.join(", ")}. Configure accounts in the Accounts tab.`; setValidationError(error); toast.error("Validation failed: " + error); return; } // Validate maxItemsPerRun in handler too, to be safe const mir = Number(data.maxItemsPerRun); if (Number.isNaN(mir) || mir < 1 || mir > 50) { setValidationError("Max items per run must be between 1 and 50."); toast.error("Validation failed: Max items per run must be between 1 and 50."); return; } mutation.mutate({ ...data, watchPollIntervalSeconds: Number(data.watchPollIntervalSeconds), maxItemsPerRun: Number(data.maxItemsPerRun), }); }; if (isLoading) { return
Loading watch settings...
; } return (

Watchlist Behavior

{/* Download requirements info */} {downloadConfig && (!downloadConfig.realTime && !downloadConfig.fallback) && (

Download methods required

To use watch functionality, enable either Real-time downloading or Download Fallback in the Downloads tab.

)} {/* Fallback account requirements info */} {downloadConfig?.fallback && (!spotifyCredentials?.length || !deezerCredentials?.length) && (

Fallback accounts required

Download Fallback is enabled but requires accounts for both Spotify and Deezer. Configure accounts in the Accounts tab.

)} {/* Validation error display */} {validationError && (

{validationError}

)}

How often to check for new items in watchlist.

Batch size per watch cycle (1–50).

Artist Album Groups

Select which album groups to monitor for watched artists.

{ALBUM_GROUPS.map((group) => ( ( )} /> ))}
); }