feat(discovery): total-videos column + subscribe confirmation

Add a "Videos" column (channel total uploads, already in the discovery
response) to the Discover-from-playlists table, and guard the Subscribe button
with a confirm dialog warning it changes the real YouTube account and spends
quota (mirrors the unsubscribe guard). Strings added in EN/HU/DE.
This commit is contained in:
npeter83 2026-06-19 11:18:28 +02:00
parent 479574b726
commit 94fc5c9806
4 changed files with 33 additions and 1 deletions

View file

@ -7,6 +7,7 @@ import { notify } from "../lib/notifications";
import Tooltip from "./Tooltip"; import Tooltip from "./Tooltip";
import ChannelLink from "./ChannelLink"; import ChannelLink from "./ChannelLink";
import DataTable, { type Column } from "./DataTable"; import DataTable, { type Column } from "./DataTable";
import { useConfirm } from "./ConfirmProvider";
// The Channel manager's "Discovery" tab: channels that turn up in the user's playlists but // The Channel manager's "Discovery" tab: channels that turn up in the user's playlists but
// that they don't subscribe to. Subscribing here only creates the subscription + enriches // that they don't subscribe to. Subscribing here only creates the subscription + enriches
@ -20,6 +21,7 @@ export default function ChannelDiscovery({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const qc = useQueryClient(); const qc = useQueryClient();
const confirm = useConfirm();
const query = useQuery({ const query = useQuery({
queryKey: ["discovered-channels"], queryKey: ["discovered-channels"],
@ -60,6 +62,17 @@ export default function ChannelDiscovery({
}, },
}); });
// Subscribing changes the user's real YouTube account and spends a little quota — confirm
// first (mirrors the unsubscribe guard in the Subscriptions tab).
const onSubscribe = async (c: DiscoveredChannel) => {
const ok = await confirm({
title: t("channels.discovery.subscribe"),
message: t("channels.discovery.confirmSubscribe", { name: c.title ?? c.id }),
confirmLabel: t("channels.discovery.subscribe"),
});
if (ok) subscribe.mutate(c);
};
const rows = query.data ?? []; const rows = query.data ?? [];
const columns: Column<DiscoveredChannel>[] = [ const columns: Column<DiscoveredChannel>[] = [
@ -87,6 +100,19 @@ export default function ChannelDiscovery({
</span> </span>
), ),
}, },
{
key: "videos",
header: t("channels.discovery.cols.totalVideos"),
align: "right",
nowrap: true,
sortable: true,
sortValue: (c) => c.video_count ?? -1,
render: (c) => (
<span className="text-muted tabular-nums">
{c.video_count != null ? formatViews(c.video_count) : "—"}
</span>
),
},
{ {
key: "inPlaylists", key: "inPlaylists",
header: t("channels.discovery.cols.inPlaylists"), header: t("channels.discovery.cols.inPlaylists"),
@ -123,7 +149,7 @@ export default function ChannelDiscovery({
} }
> >
<button <button
onClick={() => subscribe.mutate(c)} onClick={() => onSubscribe(c)}
disabled={!canWrite || subscribe.isPending} disabled={!canWrite || subscribe.isPending}
className="glass-card glass-hover inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs disabled:opacity-50 transition" className="glass-card glass-hover inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs disabled:opacity-50 transition"
> >

View file

@ -19,7 +19,9 @@
"subscribedTitle": "Auf YouTube abonniert", "subscribedTitle": "Auf YouTube abonniert",
"subscribedBody": "Du folgst jetzt {{name}} — neue Uploads erscheinen in deinem Feed.", "subscribedBody": "Du folgst jetzt {{name}} — neue Uploads erscheinen in deinem Feed.",
"subscribeFailed": "Abonnieren fehlgeschlagen", "subscribeFailed": "Abonnieren fehlgeschlagen",
"confirmSubscribe": "„{{name}}“ auf YouTube abonnieren? Das ändert dein echtes YouTube-Konto und verbraucht etwas API-Kontingent.",
"cols": { "cols": {
"totalVideos": "Videos",
"inPlaylists": "In Playlists", "inPlaylists": "In Playlists",
"inPlaylistsHint": "{{videos}} Video(s) von diesem Kanal in {{playlists}} deiner Playlist(s)." "inPlaylistsHint": "{{videos}} Video(s) von diesem Kanal in {{playlists}} deiner Playlist(s)."
} }

View file

@ -19,7 +19,9 @@
"subscribedTitle": "Subscribed on YouTube", "subscribedTitle": "Subscribed on YouTube",
"subscribedBody": "You're now following {{name}} — its new uploads will start arriving in your feed.", "subscribedBody": "You're now following {{name}} — its new uploads will start arriving in your feed.",
"subscribeFailed": "Subscribe failed", "subscribeFailed": "Subscribe failed",
"confirmSubscribe": "Subscribe to \"{{name}}\" on YouTube? This changes your real YouTube account and spends a little API quota.",
"cols": { "cols": {
"totalVideos": "Videos",
"inPlaylists": "In playlists", "inPlaylists": "In playlists",
"inPlaylistsHint": "{{videos}} video(s) from this channel across {{playlists}} of your playlist(s)." "inPlaylistsHint": "{{videos}} video(s) from this channel across {{playlists}} of your playlist(s)."
} }

View file

@ -19,7 +19,9 @@
"subscribedTitle": "Feliratkozva a YouTube-on", "subscribedTitle": "Feliratkozva a YouTube-on",
"subscribedBody": "Mostantól követed: {{name}} — az új feltöltései megjelennek a hírfolyamodban.", "subscribedBody": "Mostantól követed: {{name}} — az új feltöltései megjelennek a hírfolyamodban.",
"subscribeFailed": "A feliratkozás sikertelen", "subscribeFailed": "A feliratkozás sikertelen",
"confirmSubscribe": "Feliratkozol erre a YouTube-on: „{{name}}”? Ez módosítja a valódi YouTube-fiókodat és kevés API-kvótát használ.",
"cols": { "cols": {
"totalVideos": "Videók",
"inPlaylists": "Listákban", "inPlaylists": "Listákban",
"inPlaylistsHint": "{{videos}} videó ettől a csatornától, {{playlists}} lejátszási listádban." "inPlaylistsHint": "{{videos}} videó ettől a csatornától, {{playlists}} lejátszási listádban."
} }