Files
siftlode/frontend/src/modules/channels/useWatchedChannelActions.ts
T
peter 4a0d4b0964 fix(notifications): a notice value is no plain string, so a translated name cannot freeze in one language
A name goes in as raw(), a translated word as its own msg(): tsc now lists every value, and the
ones that froze are said when shown — Watch later and an untitled channel in the pin and channel
notices, a Plex season's name in its removal toast (a new seasonTitleText, which seasonTitle says).
2026-10-30 00:03:48 +01:00

71 lines
3.0 KiB
TypeScript

import { useTranslation } from "react-i18next";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { qk } from "../../lib/queryKeys";
import { invalidateSubscription } from "../../lib/subscriptionCache";
import { api, type WatchedChannel } from "../../lib/api";
import { msg, notify, raw } from "../../lib/notifications";
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
import { useConfirm } from "../../components/ui/confirmContext";
/** Follow and take-off-the-list for a watched channel: the tab's rows and its videos dialog share
* them, so the confirm, the toasts and the cache refresh are written once. */
export function useWatchedChannelActions(onOpenWizard: () => void) {
const { t } = useTranslation();
const qc = useQueryClient();
const confirm = useConfirm();
const nameOf = (c: WatchedChannel) => c.title ?? c.id;
const subscribe = useMutation({
mutationFn: (c: WatchedChannel) => api.subscribeChannel(c.id),
onSuccess: (_data, c) => {
invalidateSubscription(qc, c.id);
notify({
level: "success",
title: msg("channels.discovery.subscribedTitle"),
message: msg("channels.discovery.subscribedBody", { name: raw(nameOf(c)) }),
meta: { kind: "channel-subscribed", channelId: c.id, channelName: nameOf(c) },
});
},
onError: (err: unknown) =>
notifyYouTubeActionError(err, msg("channels.discovery.subscribeFailed"), onOpenWizard),
});
const restore = useMutation({
mutationFn: (c: WatchedChannel) => api.restoreWatchedChannel(c.id),
meta: { failTitle: "errors.writeFailed.restoreWatchedChannel" },
onSuccess: () => qc.invalidateQueries({ queryKey: qk.watchedChannels() }),
});
// Taken off the list at once, with the toast's Undo as the way back (the same bargain as hiding a
// channel): the next channel steps up into its place.
const dismiss = useMutation({
mutationFn: (c: WatchedChannel) => api.dismissWatchedChannel(c.id),
meta: { failTitle: "errors.writeFailed.dismissWatchedChannel" },
onSuccess: (_data, c) => {
void qc.invalidateQueries({ queryKey: qk.watchedChannels() });
notify({
level: "success",
message: msg("channels.watched.dismissed", { name: raw(nameOf(c)) }),
action: { label: t("common.undo"), onClick: () => restore.mutate(c) },
});
},
});
// Subscribing changes the user's real YouTube account and spends a little quota — confirmed, as
// on the Discovery tab. Resolves whether it went ahead.
const onSubscribe = async (c: WatchedChannel): Promise<boolean> => {
const ok = await confirm({
title: t("channels.discovery.subscribe"),
message: t("channels.discovery.confirmSubscribe", { name: raw(nameOf(c)) }),
confirmLabel: t("channels.discovery.subscribe"),
});
if (ok) subscribe.mutate(c);
return ok;
};
return {
onSubscribe,
subscribing: subscribe.isPending,
dismiss: (c: WatchedChannel) => dismiss.mutate(c),
};
}
export type WatchedChannelActions = ReturnType<typeof useWatchedChannelActions>;