feat(notifications): link the subscribe activity to the channel

The "Subscribed on YouTube" inbox entry now names the channel and offers
two convenience links that survive a reload (driven by the typed payload,
not the live callback): "Channel manager" (jumps to the manager focused on
that channel) and "Open on YouTube". Wires the panel to the app's
focus-channel navigation; trilingual strings.
This commit is contained in:
npeter83 2026-06-19 02:16:50 +02:00
parent fb6f0c5dcb
commit 75ecf19147
5 changed files with 35 additions and 2 deletions

View file

@ -1,7 +1,7 @@
import { useEffect, useSyncExternalStore } from "react";
import { useTranslation } from "react-i18next";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Bell, Check, CheckCheck, Eye, RotateCcw, Search, Trash2, X } from "lucide-react";
import { Bell, Check, CheckCheck, ExternalLink, Eye, RotateCcw, Search, Trash2, Tv, X } from "lucide-react";
import { api, type AppNotification, type FeedFilters } from "../lib/api";
import { useLiveQuery } from "../lib/useLiveQuery";
import {
@ -14,6 +14,7 @@ import {
resolveVideo,
subscribe,
type Notification as ClientNotif,
type ChannelSubscribedMeta,
type VideoHiddenMeta,
type VideoWatchedMeta,
} from "../lib/notifications";
@ -46,10 +47,12 @@ export default function NotificationsPanel({
filters,
setFilters,
setPage,
onFocusChannel,
}: {
filters: FeedFilters;
setFilters: (f: FeedFilters) => void;
setPage: (p: Page) => void;
onFocusChannel: (name: string) => void;
}) {
const { t } = useTranslation();
const qc = useQueryClient();
@ -183,6 +186,7 @@ export default function NotificationsPanel({
t={t}
onFind={locateHidden}
onRevert={revertState}
onFocusChannel={onFocusChannel}
onRemove={() => removeClient(n.id)}
/>
))}
@ -287,18 +291,22 @@ function ClientActivityRow({
t,
onFind,
onRevert,
onFocusChannel,
onRemove,
}: {
n: ClientNotif;
t: (k: string, o?: any) => string;
onFind: (meta: VideoHiddenMeta) => void;
onRevert: (meta: VideoHiddenMeta | VideoWatchedMeta) => void;
onFocusChannel: (name: string) => void;
onRemove: () => void;
}) {
const { icon: Icon, color } = LEVEL_STYLE[n.level];
const needsAction = n.requiresInteraction && !n.dismissed;
const hidden = n.meta?.kind === "video-hidden" ? n.meta : null;
const watched = n.meta?.kind === "video-watched" ? n.meta : null;
const subscribed: ChannelSubscribedMeta | null =
n.meta?.kind === "channel-subscribed" ? n.meta : null;
return (
<div
className={`glass rounded-xl p-3.5 flex items-start gap-3 ${needsAction ? "ring-1 ring-accent/30" : ""}`}
@ -342,6 +350,25 @@ function ClientActivityRow({
{t("notifications.unwatch")}
</button>
</div>
) : subscribed ? (
<div className="flex items-center gap-3 mt-1">
<button
onClick={() => onFocusChannel(subscribed.channelName)}
className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
>
<Tv className="w-3.5 h-3.5" />
{t("notifications.openInManager")}
</button>
<a
href={`https://www.youtube.com/channel/${subscribed.channelId}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
>
<ExternalLink className="w-3.5 h-3.5" />
{t("notifications.openOnYouTube")}
</a>
</div>
) : (
n.action &&