import { useEffect, useState } from "react"; import { useForm, 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 --- interface SpotifyApiSettings { client_id: string; client_secret: string; } interface WebhookSettings { url: string; events: string[]; available_events: string[]; // Provided by API, not saved } interface ServerConfig { client_id?: string; client_secret?: string; utilityConcurrency?: number; librespotConcurrency?: number; url?: string; events?: string[]; } const fetchServerConfig = async (): Promise => { const [spotifyConfig, generalConfig] = await Promise.all([ authApiClient.client.get("/credentials/spotify_api_config").catch(() => ({ data: {} })), authApiClient.getConfig(), ]); return { ...spotifyConfig.data, ...generalConfig, }; }; const saveServerConfig = async (data: Partial) => { const payload = { ...data }; const { data: response } = await authApiClient.client.post("/config", payload); return response; }; const fetchWebhookConfig = async (): Promise => { // Mock a response since backend endpoint doesn't exist // This will prevent the UI from crashing. return Promise.resolve({ url: "", events: [], available_events: ["download_start", "download_complete", "download_failed", "watch_added"], }); }; const saveWebhookConfig = async (data: Partial) => { const payload = { ...data }; const { data: response } = await authApiClient.client.post("/config", payload); return response; }; const testWebhook = (url: string) => { toast.info("Webhook testing is not available."); return Promise.resolve(url); }; // --- Components --- function SpotifyApiForm({ config, onConfigChange }: { config: ServerConfig; onConfigChange: (updates: Partial) => void }) { const { register, handleSubmit, reset } = useForm(); useEffect(() => { if (config) { reset({ client_id: config.client_id || "", client_secret: config.client_secret || "", }); } }, [config, reset]); const onSubmit = (formData: SpotifyApiSettings) => { onConfigChange(formData); }; return (
); } function UtilityConcurrencyForm({ config, onConfigChange }: { config: ServerConfig; onConfigChange: (updates: Partial) => void }) { const { register, handleSubmit, reset, formState: { isDirty } } = useForm<{ utilityConcurrency: number }>(); useEffect(() => { if (config) { reset({ utilityConcurrency: Number(config.utilityConcurrency ?? 1) }); } }, [config, reset]); const onSubmit = (values: { utilityConcurrency: number }) => { const value = Math.max(1, Number(values.utilityConcurrency || 1)); onConfigChange({ utilityConcurrency: value }); }; return (

Controls concurrency of the utility Celery worker. Minimum 1.

); } function LibrespotConcurrencyForm({ config, onConfigChange }: { config: ServerConfig; onConfigChange: (updates: Partial) => void }) { const { register, handleSubmit, reset, formState: { isDirty } } = useForm<{ librespotConcurrency: number }>(); useEffect(() => { if (config) { reset({ librespotConcurrency: Number(config.librespotConcurrency ?? 2) }); } }, [config, reset]); const onSubmit = (values: { librespotConcurrency: number }) => { const raw = Number(values.librespotConcurrency || 2); const safe = Math.max(1, Math.min(16, raw)); onConfigChange({ librespotConcurrency: safe }); }; return (

Controls worker threads used by the Librespot client. 1–16 is recommended.

); } // --- Components --- function WebhookForm() { const queryClient = useQueryClient(); const { data, isLoading } = useQuery({ queryKey: ["webhookConfig"], queryFn: fetchWebhookConfig }); const { register, handleSubmit, control, reset, watch } = useForm(); const currentUrl = watch("url"); const mutation = useMutation({ mutationFn: saveWebhookConfig, onSuccess: () => { // No toast needed since the function shows one queryClient.invalidateQueries({ queryKey: ["webhookConfig"] }); }, onError: (e) => { toast.error(`Failed to save: ${(e as any).message}`); }, }); const testMutation = useMutation({ mutationFn: testWebhook, onSuccess: () => { // No toast needed }, onError: (e) => toast.error(`Webhook test failed: ${(e as any).message}`), }); useEffect(() => { if (data) reset(data); }, [data, reset]); const onSubmit = (formData: WebhookSettings) => mutation.mutate(formData); if (isLoading) return

Loading Webhook settings...

; return (
{data?.available_events.map((event) => ( ( )} /> ))}
); } export function ServerTab() { const queryClient = useQueryClient(); const [localConfig, setLocalConfig] = useState({}); const { data: serverConfig, isLoading } = useQuery({ queryKey: ["serverConfig"], queryFn: fetchServerConfig, }); const mutation = useMutation({ mutationFn: saveServerConfig, onSuccess: () => { toast.success("Server settings saved successfully!"); queryClient.invalidateQueries({ queryKey: ["serverConfig"] }); queryClient.invalidateQueries({ queryKey: ["config"] }); }, onError: (error) => { console.error("Failed to save server settings", (error as any).message); toast.error(`Failed to save server settings: ${(error as any).message}`); }, }); useEffect(() => { if (serverConfig) { setLocalConfig(serverConfig); } }, [serverConfig]); const handleConfigChange = (updates: Partial) => { const newConfig = { ...localConfig, ...updates }; setLocalConfig(newConfig); mutation.mutate(newConfig); }; if (isLoading) { return
Loading server settings...
; } return (

Spotify API

Provide your own API credentials to avoid rate-limiting issues.


Utility Worker

Tune background utility worker concurrency for low-powered systems.


Librespot

Adjust Librespot client worker threads.


Webhooks

Get notifications for events like download completion. (Currently disabled)

); }