fix(plex): optimistic toggle + 'importing' indicator for watch-sync

The watch-sync switch only flipped once the (multi-second) import request returned, so a click read as
'nothing happened' and invited repeat clicks. Reflect the toggle's target optimistically while the
mutation is in flight and show an 'Importing your Plex watch history…' spinner line, so it's obvious
the switch responded and is working.
This commit is contained in:
npeter83 2026-07-09 15:34:28 +02:00
parent cbe203391e
commit 02837b9a4b
4 changed files with 32 additions and 19 deletions

View file

@ -461,6 +461,10 @@ function PlexWatchSync() {
},
});
const busy = toggle.isPending || reimport.isPending;
// Reflect the toggle's target immediately (optimistic) so the switch responds on click rather than
// only flipping once the multi-second import returns — otherwise it reads as "nothing happened".
const shown = toggle.isPending ? Boolean(toggle.variables) : enabled;
const last = link.data?.last_watch_sync_at
? new Date(link.data.last_watch_sync_at).toLocaleString(i18n.language)
: null;
@ -470,25 +474,31 @@ function PlexWatchSync() {
<p className="text-xs text-muted leading-relaxed mb-2">{t("settings.plexSync.intro")}</p>
<SettingRow label={t("settings.plexSync.toggle")} hint={t("settings.plexSync.toggleHint")}>
<Switch
checked={enabled}
disabled={toggle.isPending || reimport.isPending}
onChange={(v) => !toggle.isPending && toggle.mutate(v)}
checked={shown}
disabled={busy}
onChange={(v) => !busy && toggle.mutate(v)}
/>
</SettingRow>
{enabled && (
{busy ? (
<div className="mt-2 flex items-center gap-2 text-xs text-muted">
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
{t("settings.plexSync.importing")}
</div>
) : (
enabled && (
<div className="mt-2 flex items-center justify-between gap-3">
<p className="text-xs text-muted">
{last ? t("settings.plexSync.lastSync", { when: last }) : t("settings.plexSync.never")}
</p>
<button
onClick={() => reimport.mutate()}
disabled={reimport.isPending}
className="inline-flex items-center gap-1.5 px-3 py-2 rounded-xl text-sm border border-border hover:bg-card/60 disabled:opacity-50 transition shrink-0"
className="inline-flex items-center gap-1.5 px-3 py-2 rounded-xl text-sm border border-border hover:bg-card/60 transition shrink-0"
>
<RefreshCw className={`w-4 h-4 ${reimport.isPending ? "animate-spin" : ""}`} />
<RefreshCw className="w-4 h-4" />
{t("settings.plexSync.importNow")}
</button>
</div>
)
)}
</Section>
);

View file

@ -13,7 +13,8 @@
"importNow": "Jetzt aus Plex importieren",
"imported": "Aus Plex importiert: {{watched}} gesehen, {{in_progress}} laufend.",
"lastSync": "Letzter Import: {{when}}",
"never": "Noch nicht importiert"
"never": "Noch nicht importiert",
"importing": "Plex-Wiedergabeverlauf wird importiert…"
},
"save": {
"unsaved": "Nicht gespeicherte Änderungen",

View file

@ -13,7 +13,8 @@
"importNow": "Import from Plex now",
"imported": "Imported from Plex: {{watched}} watched, {{in_progress}} in progress.",
"lastSync": "Last import: {{when}}",
"never": "Not imported yet"
"never": "Not imported yet",
"importing": "Importing your Plex watch history…"
},
"save": {
"unsaved": "Unsaved changes",

View file

@ -13,7 +13,8 @@
"importNow": "Importálás a Plexből most",
"imported": "Importálva a Plexből: {{watched}} megnézve, {{in_progress}} folyamatban.",
"lastSync": "Utolsó import: {{when}}",
"never": "Még nincs importálva"
"never": "Még nincs importálva",
"importing": "Plex-előzmény importálása…"
},
"save": {
"unsaved": "Nem mentett változások",