"use client"; import { useEffect, useState } from "react"; type Loaded = { qobuzEmail: string; qobuzUserId: string; slskdUrl: string; qobuzPasswordSet: boolean; qobuzTokenSet: boolean; slskdApiKeySet: boolean; }; export function SettingsForm() { const [qobuzEmail, setQobuzEmail] = useState(""); const [qobuzPassword, setQobuzPassword] = useState(""); const [qobuzUserId, setQobuzUserId] = useState(""); const [qobuzToken, setQobuzToken] = useState(""); const [slskdUrl, setSlskdUrl] = useState(""); const [slskdApiKey, setSlskdApiKey] = useState(""); const [pwSet, setPwSet] = useState(false); const [tokenSet, setTokenSet] = useState(false); const [keySet, setKeySet] = useState(false); const [saved, setSaved] = useState(false); const [scanResult, setScanResult] = useState(null); const [scanRequested, setScanRequested] = useState(false); const [discoverCfg, setDiscoverCfg] = useState>({}); const [discoverSaved, setDiscoverSaved] = useState(false); useEffect(() => { fetch("/api/scan") .then((r) => r.json()) .then((s: { result: string | null; requested: boolean }) => { setScanResult(s.result); setScanRequested(s.requested); }); }, []); async function requestScan() { await fetch("/api/scan", { method: "POST" }); setScanRequested(true); } useEffect(() => { if (!scanRequested) return; const id = setInterval(async () => { const s = await (await fetch("/api/scan")).json(); setScanResult(s.result); if (!s.requested) setScanRequested(false); // worker finished the scan }, 2000); return () => clearInterval(id); }, [scanRequested]); useEffect(() => { fetch("/api/config") .then((r) => r.json()) .then((c: Loaded) => { setQobuzEmail(c.qobuzEmail); setQobuzUserId(c.qobuzUserId); setSlskdUrl(c.slskdUrl); setPwSet(c.qobuzPasswordSet); setTokenSet(c.qobuzTokenSet); setKeySet(c.slskdApiKeySet); }); }, []); useEffect(() => { fetch("/api/discover/config") .then((r) => r.json()) .then((c: Record) => setDiscoverCfg(c)); }, []); async function submit(e: React.FormEvent) { e.preventDefault(); await fetch("/api/config", { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify({ qobuzEmail, qobuzPassword, qobuzUserId, qobuzToken, slskdUrl, slskdApiKey }), }); setQobuzPassword(""); setQobuzToken(""); setSlskdApiKey(""); setSaved(true); setPwSet(pwSet || qobuzPassword !== ""); setTokenSet(tokenSet || qobuzToken !== ""); setKeySet(keySet || slskdApiKey !== ""); } function setCfg(key: string, value: string) { setDiscoverCfg((c) => ({ ...c, [key]: value })); } async function saveDiscovery(e: React.FormEvent) { e.preventDefault(); await fetch("/api/discover/config", { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(discoverCfg), }); setDiscoverSaved(true); setTimeout(() => setDiscoverSaved(false), 1500); } return ( <>
Qobuz — email/password
Qobuz — auth token (more reliable; overrides email/password)
Soulseek (slskd)
{saved ? Saved. : null}

Library

Scan /music and record what's already on disk as owned + monitored.

{scanResult ?

Last scan: {scanResult}

: null}

Discovery

{[ ["discover.intervalHours", "Interval (hours)"], ["discover.maxSeeds", "Max seeds per sweep"], ["discover.similarPerSeed", "Similar per seed"], ["discover.albumsPerArtist", "Albums per artist"], ["discover.minScore", "Min score"], ].map(([key, label]) => (

))} {discoverSaved && Saved.}
); }