feat(channels): clearer error when a YouTube action lacks the scope
api: HttpError now carries the server's detail. Channels sync/backfill/unsubscribe now detect a 403 (no YouTube grant) and show a 'connect your YouTube account' message with a Connect action that opens the onboarding wizard, instead of a vague 'failed' toast. Translated HU/EN/DE.
This commit is contained in:
parent
9c61bd898d
commit
196eff6232
6 changed files with 43 additions and 10 deletions
|
|
@ -13,7 +13,7 @@ import {
|
|||
UserMinus,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { api, type ManagedChannel, type Tag } from "../lib/api";
|
||||
import { api, HttpError, type ManagedChannel, type Tag } from "../lib/api";
|
||||
import { formatEta } from "../lib/format";
|
||||
import { notify } from "../lib/notifications";
|
||||
import Tooltip from "./Tooltip";
|
||||
|
|
@ -33,14 +33,30 @@ export default function Channels({
|
|||
onViewChannel,
|
||||
statusFilter,
|
||||
setStatusFilter,
|
||||
onOpenWizard,
|
||||
}: {
|
||||
canWrite: boolean;
|
||||
onViewChannel: (id: string, name: string) => void;
|
||||
statusFilter: ChannelStatusFilter;
|
||||
setStatusFilter: (f: ChannelStatusFilter) => void;
|
||||
onOpenWizard: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const qc = useQueryClient();
|
||||
|
||||
// A YouTube-gated action (sync, backfill, unsubscribe) that 403s means the user hasn't
|
||||
// granted the needed scope — surface that with a "Connect" action instead of a vague fail.
|
||||
const notifyActionError = (err: unknown, fallbackKey: string) => {
|
||||
if (err instanceof HttpError && err.status === 403) {
|
||||
notify({
|
||||
level: "error",
|
||||
message: t("channels.notify.needYouTube"),
|
||||
action: { label: t("channels.notify.connect"), onClick: onOpenWizard },
|
||||
});
|
||||
} else {
|
||||
notify({ level: "error", message: t(fallbackKey) });
|
||||
}
|
||||
};
|
||||
const channelsQuery = useQuery({ queryKey: ["channels"], queryFn: api.channels });
|
||||
const tagsQuery = useQuery({ queryKey: ["tags"], queryFn: api.tags });
|
||||
const statusQuery = useQuery({ queryKey: ["my-status"], queryFn: api.myStatus });
|
||||
|
|
@ -96,7 +112,7 @@ export default function Channels({
|
|||
invalidate();
|
||||
notify({ level: "success", message: t("channels.notify.synced", { count: r.subscriptions ?? 0 }) });
|
||||
},
|
||||
onError: () => notify({ level: "error", message: t("channels.notify.syncFailed") }),
|
||||
onError: (e) => notifyActionError(e, "channels.notify.syncFailed"),
|
||||
});
|
||||
const createTag = useMutation({
|
||||
mutationFn: (name: string) => api.createTag({ name, category: "other" }),
|
||||
|
|
@ -116,7 +132,7 @@ export default function Channels({
|
|||
qc.invalidateQueries({ queryKey: ["my-status"] });
|
||||
notify({ level: "success", message: t("channels.notify.unsubscribed") });
|
||||
},
|
||||
onError: () => notify({ level: "error", message: t("channels.notify.unsubscribeFailed") }),
|
||||
onError: (e) => notifyActionError(e, "channels.notify.unsubscribeFailed"),
|
||||
});
|
||||
const deepAll = useMutation({
|
||||
mutationFn: () => api.deepAll(true),
|
||||
|
|
@ -128,7 +144,7 @@ export default function Channels({
|
|||
message: t("channels.notify.fullHistoryRequested", { count: r.updated ?? 0 }),
|
||||
});
|
||||
},
|
||||
onError: () => notify({ level: "error", message: t("channels.notify.fullHistoryFailed") }),
|
||||
onError: (e) => notifyActionError(e, "channels.notify.fullHistoryFailed"),
|
||||
});
|
||||
|
||||
const channels = (channelsQuery.data ?? [])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue