From 7737b35bf8b034fd582f6a75033c0de1673b10b2 Mon Sep 17 00:00:00 2001 From: Frank Stellmacher <171614930+Psychotoxical@users.noreply.github.com> Date: Wed, 13 May 2026 02:11:06 +0200 Subject: [PATCH] =?UTF-8?q?refactor(settings):=20G.57=20=E2=80=94=20extrac?= =?UTF-8?q?t=20Integrations=20+=20Library=20tabs=20(#622)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the Integrations tab (Last.fm connect/disconnect, scrobbling toggles, ListenBrainz, Discord RPC) into IntegrationsTab. The Last.fm token+session poll flow is now owned by IntegrationsTab as a useCallback closing over useAuthStore directly. Move the Library tab (Random Mix blacklist, Lucky Mix menu toggle, hard-coded audiobook genre badges, ratings sliders and mix min-rating thresholds) into LibraryTab; AUDIOBOOK_GENRES_DISPLAY and the per-row star sliders move with it. Settings.tsx 2741 → 2266 LOC (−475). Unused imports removed: lastfm api helpers, LastfmIcon, Shuffle, Star, StarRating, MIX_MIN_RATING_FILTER_MAX_STARS. Pure code move — no behaviour change. --- src/components/settings/IntegrationsTab.tsx | 280 +++++++++++ src/components/settings/LibraryTab.tsx | 225 +++++++++ src/pages/Settings.tsx | 489 +------------------- 3 files changed, 512 insertions(+), 482 deletions(-) create mode 100644 src/components/settings/IntegrationsTab.tsx create mode 100644 src/components/settings/LibraryTab.tsx diff --git a/src/components/settings/IntegrationsTab.tsx b/src/components/settings/IntegrationsTab.tsx new file mode 100644 index 00000000..837970fb --- /dev/null +++ b/src/components/settings/IntegrationsTab.tsx @@ -0,0 +1,280 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { AlertTriangle, Info, Sparkles, Wifi } from 'lucide-react'; +import { open as openUrl } from '@tauri-apps/plugin-shell'; +import { lastfmAuthUrl, lastfmGetSession, lastfmGetToken, lastfmGetUserInfo, type LastfmUserInfo } from '../../api/lastfm'; +import { useAuthStore } from '../../store/authStore'; +import LastfmIcon from '../LastfmIcon'; +import SettingsSubSection from '../SettingsSubSection'; + +export function IntegrationsTab() { + const { t } = useTranslation(); + const auth = useAuthStore(); + const [lfmState, setLfmState] = useState<'idle' | 'waiting' | 'error'>('idle'); + // Polled token is kept here in case future flows need to display or cancel it explicitly. + const [, setLfmPendingToken] = useState(null); + const [lfmError, setLfmError] = useState(null); + const [lfmUserInfo, setLfmUserInfo] = useState(null); + + useEffect(() => { + if (!auth.lastfmSessionKey || !auth.lastfmUsername) { setLfmUserInfo(null); return; } + lastfmGetUserInfo(auth.lastfmUsername, auth.lastfmSessionKey).then(setLfmUserInfo).catch(() => {}); + }, [auth.lastfmSessionKey, auth.lastfmUsername]); + + const startLastfmConnect = useCallback(async () => { + setLfmError(null); + let token: string; + try { + token = await lastfmGetToken(); + setLfmPendingToken(token); + setLfmState('waiting'); + await openUrl(lastfmAuthUrl(token)); + } catch (e: any) { + setLfmError(e.message ?? 'Unknown error'); + setLfmState('error'); + return; + } + + // Poll every 2 s until the user authorises or we time out (2 min) + const deadline = Date.now() + 120_000; + const poll = async () => { + if (Date.now() > deadline) { + setLfmState('error'); + setLfmError('Timed out — please try again.'); + setLfmPendingToken(null); + return; + } + try { + const { key, name } = await lastfmGetSession(token); + auth.connectLastfm(key, name); + setLfmState('idle'); + setLfmPendingToken(null); + } catch (e: any) { + // Error 14 = not yet authorised, keep polling + if (e.message?.includes('14')) { + setTimeout(poll, 2000); + } else { + setLfmState('error'); + setLfmError(e.message ?? 'Unknown error'); + setLfmPendingToken(null); + } + } + }; + setTimeout(poll, 2000); + }, [auth]); + + return ( + <> +
+