feat(plex): Settings toggle for Plex watch-sync + one-click import
Admins get a 'Plex watch sync' section in Settings → Account (shown when the Plex module is on):
a two-way-sync toggle whose first enable imports the Plex watch history, a last-import line, and an
'Import from Plex now' button. api.plexWatch{Link,SetLink,Import}; trilingual EN/HU/DE strings.
This commit is contained in:
parent
04fb3fb16e
commit
a0475eb7bb
5 changed files with 118 additions and 2 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Bell, Monitor, Trash2, User } from "lucide-react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Bell, Monitor, RefreshCw, Trash2, User } from "lucide-react";
|
||||
import { SCHEMES, type Scheme, type ThemePrefs } from "../lib/theme";
|
||||
import { api, type Me } from "../lib/api";
|
||||
import { LS, useAccountPersistedState } from "../lib/storage";
|
||||
|
|
@ -426,6 +426,70 @@ function SignInMethods({ me }: { me: Me }) {
|
|||
);
|
||||
}
|
||||
|
||||
// Two-way Plex watch-state sync (Phase A): the owner links their account to the Plex admin account
|
||||
// and does a one-time "Plex is master" import. Admin-only (rides the server admin token) and only
|
||||
// shown when the Plex module is enabled. Two-way push/pull arrive in later phases.
|
||||
function PlexWatchSync() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const qc = useQueryClient();
|
||||
const link = useQuery({ queryKey: ["plex-watch-link"], queryFn: () => api.plexWatchLink() });
|
||||
const enabled = link.data?.sync_enabled ?? false;
|
||||
|
||||
const announce = (imp: { watched: number; in_progress: number } | null | undefined) => {
|
||||
if (imp) {
|
||||
notify({
|
||||
level: "success",
|
||||
message: t("settings.plexSync.imported", { watched: imp.watched, in_progress: imp.in_progress }),
|
||||
});
|
||||
// The browse grid's watch badges read plex_states — refresh them.
|
||||
qc.invalidateQueries({ queryKey: ["plex"] });
|
||||
}
|
||||
};
|
||||
|
||||
const toggle = useMutation({
|
||||
mutationFn: (next: boolean) => api.plexWatchSetLink(next),
|
||||
onSuccess: (res) => {
|
||||
qc.setQueryData(["plex-watch-link"], res);
|
||||
announce(res.import);
|
||||
},
|
||||
});
|
||||
const reimport = useMutation({
|
||||
mutationFn: () => api.plexWatchImport(),
|
||||
onSuccess: (res) => {
|
||||
qc.setQueryData(["plex-watch-link"], res);
|
||||
announce(res.import);
|
||||
},
|
||||
});
|
||||
|
||||
const last = link.data?.last_watch_sync_at
|
||||
? new Date(link.data.last_watch_sync_at).toLocaleString(i18n.language)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Section title={t("settings.plexSync.title")}>
|
||||
<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} onChange={(v) => toggle.mutate(v)} />
|
||||
</SettingRow>
|
||||
{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"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${reimport.isPending ? "animate-spin" : ""}`} />
|
||||
{t("settings.plexSync.importNow")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const confirm = useConfirm();
|
||||
|
|
@ -464,6 +528,8 @@ function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) {
|
|||
|
||||
{!me.is_demo && <SignInMethods me={me} />}
|
||||
|
||||
{me.role === "admin" && me.plex_enabled && <PlexWatchSync />}
|
||||
|
||||
{me.is_demo ? (
|
||||
<Section title={t("settings.account.youtubeAccess")}>
|
||||
<p className="text-xs text-muted leading-relaxed">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue