From 050ce79d510efc31e162782aa97148dfe61264b8 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Sun, 28 Jun 2026 23:04:20 -0700 Subject: [PATCH] playlist export: gate Spotify/Deezer buttons on connection (#945, increment 8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The export modal now checks connection on open via /api/discover/your-albums/sources (cheap token/ARL check, no live verify) and greys out + relabels any service that is not connected ("Not connected — set up X in Settings → Connections first"). Clicking a gated button nudges to Settings instead of starting an export that would just fail with "not connected". The fetch runs after the modal renders, so a slow/failed check never blocks the modal (buttons stay usable). Pairs with the existing scope-403 handling: a Spotify token without playlist-modify still shows as connected (it IS), and the writer returns the clear "Reconnect Spotify to grant playlist write access" message — so "not connected at all" and "connected but needs reconnect for write" are both covered. Static file, no rebuild. --- webui/static/stats-automations.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/webui/static/stats-automations.js b/webui/static/stats-automations.js index 61eff33e..500606e3 100644 --- a/webui/static/stats-automations.js +++ b/webui/static/stats-automations.js @@ -686,6 +686,13 @@ function exportMirroredPlaylist(playlistId, name) { overlay.querySelectorAll('.pl-export-choice').forEach(btn => { btn.addEventListener('click', () => { const mode = btn.dataset.mode; + // Gated service button (not connected) → nudge to Settings instead of a doomed export. + if (btn.dataset.disconnected) { + const dest = mode[0].toUpperCase() + mode.slice(1); + overlay.remove(); + _setExportStatus(playlistId, `Connect ${dest} in Settings → Connections to export here`, 9000); + return; + } const bfEl = overlay.querySelector('#pl-export-backfill'); const backfill = !!(bfEl && bfEl.checked); overlay.remove(); @@ -693,6 +700,21 @@ function exportMirroredPlaylist(playlistId, name) { }); }); document.body.appendChild(overlay); + + // Gate Spotify/Deezer on connection (cheap token/ARL check — no live verify). Disconnected + // services grey out + nudge to Settings rather than letting the export fail with "not connected". + fetch('/api/discover/your-albums/sources').then(r => r.json()).then(data => { + const connected = (data && data.connected) || []; + overlay.querySelectorAll('.pl-export-choice[data-mode]').forEach(btn => { + const m = btn.dataset.mode; + if ((m === 'spotify' || m === 'deezer') && !connected.includes(m)) { + btn.dataset.disconnected = '1'; + btn.style.opacity = '0.5'; + const hint = btn.querySelector('div:last-child'); + if (hint) hint.innerHTML = `Not connected — set up ${m[0].toUpperCase() + m.slice(1)} in Settings → Connections first.`; + } + }); + }).catch(() => {}); } async function _startPlaylistExport(playlistId, mode, name, backfill) {