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 ( + <> +
+