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 const last = link.data?.last_watch_sync_at
? new Date(link.data.last_watch_sync_at).toLocaleString(i18n.language) ? new Date(link.data.last_watch_sync_at).toLocaleString(i18n.language)
: null; : null;
@ -470,25 +474,31 @@ function PlexWatchSync() {
<p className="text-xs text-muted leading-relaxed mb-2">{t("settings.plexSync.intro")}</p> <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")}> <SettingRow label={t("settings.plexSync.toggle")} hint={t("settings.plexSync.toggleHint")}>
<Switch <Switch
checked={enabled} checked={shown}
disabled={toggle.isPending || reimport.isPending} disabled={busy}
onChange={(v) => !toggle.isPending && toggle.mutate(v)} onChange={(v) => !busy && toggle.mutate(v)}
/> />
</SettingRow> </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"> <div className="mt-2 flex items-center justify-between gap-3">
<p className="text-xs text-muted"> <p className="text-xs text-muted">
{last ? t("settings.plexSync.lastSync", { when: last }) : t("settings.plexSync.never")} {last ? t("settings.plexSync.lastSync", { when: last }) : t("settings.plexSync.never")}
</p> </p>
<button <button
onClick={() => reimport.mutate()} 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 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 disabled:opacity-50 transition shrink-0"
> >
<RefreshCw className={`w-4 h-4 ${reimport.isPending ? "animate-spin" : ""}`} /> <RefreshCw className="w-4 h-4" />
{t("settings.plexSync.importNow")} {t("settings.plexSync.importNow")}
</button> </button>
</div> </div>
)
)} )}
</Section> </Section>
); );

View file

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

View file

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

View file

@ -13,7 +13,8 @@
"importNow": "Importálás a Plexből most", "importNow": "Importálás a Plexből most",
"imported": "Importálva a Plexből: {{watched}} megnézve, {{in_progress}} folyamatban.", "imported": "Importálva a Plexből: {{watched}} megnézve, {{in_progress}} folyamatban.",
"lastSync": "Utolsó import: {{when}}", "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": { "save": {
"unsaved": "Nem mentett változások", "unsaved": "Nem mentett változások",