feat(notifications): unify the two rail indicators into one inbox
Fold the client-side transient bell into the inbox page so there's a single notification indicator. The inbox now has two groups — "System" (durable, server-backed) and "Activity" (client-side events with their Unhide/Unwatch/ Find-in-feed actions) — and the nav badge sums both unread counts. The separate rail bell (NotificationCenter) is removed. Activity items get a per-item clear (X) alongside the global Clear all. Unhiding or unwatching a video from anywhere — a card, the toast's Undo, or the inbox — now quietly resolves the original "Hidden/Watched X" entry (no duplicate "Unhidden X" toast, no stale entry with a dead action), via a new resolveVideo store helper.
This commit is contained in:
parent
dd83718304
commit
d3c6ce659c
8 changed files with 246 additions and 41 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
|
@ -17,13 +17,13 @@ import {
|
|||
Tv,
|
||||
UserPlus,
|
||||
} from "lucide-react";
|
||||
import { api, type FeedFilters, type Me } from "../lib/api";
|
||||
import { api, type Me } from "../lib/api";
|
||||
import { useLiveQuery } from "../lib/useLiveQuery";
|
||||
import { getUnreadCount, subscribe } from "../lib/notifications";
|
||||
import type { Page } from "../lib/urlState";
|
||||
import { type LangCode } from "../i18n";
|
||||
import AvatarImg from "./Avatar";
|
||||
import LanguageSwitcher from "./LanguageSwitcher";
|
||||
import NotificationCenter from "./NotificationCenter";
|
||||
|
||||
// Primary app navigation: a collapsible left rail with icon+label entries (Design C). The
|
||||
// modules used to hide under the avatar dropdown; they now live here. Collapsed, it becomes
|
||||
|
|
@ -35,8 +35,6 @@ export default function NavSidebar({
|
|||
onOpenAbout,
|
||||
onChangeLanguage,
|
||||
language,
|
||||
filters,
|
||||
setFilters,
|
||||
}: {
|
||||
me: Me;
|
||||
page: Page;
|
||||
|
|
@ -44,8 +42,6 @@ export default function NavSidebar({
|
|||
onOpenAbout: () => void;
|
||||
onChangeLanguage: (code: LangCode) => void;
|
||||
language: LangCode;
|
||||
filters: FeedFilters;
|
||||
setFilters: (f: FeedFilters) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [collapsed, setCollapsed] = useState<boolean>(
|
||||
|
|
@ -125,7 +121,10 @@ export default function NavSidebar({
|
|||
api.notificationUnreadCount,
|
||||
{ intervalMs: 30000 }
|
||||
);
|
||||
const unread = unreadQuery.data?.count ?? 0;
|
||||
// One indicator for both layers: durable server notifications + the client-side transient
|
||||
// events (the former separate bell is now folded into the inbox page).
|
||||
const clientUnread = useSyncExternalStore(subscribe, getUnreadCount, getUnreadCount);
|
||||
const unread = (unreadQuery.data?.count ?? 0) + clientUnread;
|
||||
|
||||
type NavItem = { page: Page; icon: typeof Home; label: string; badge?: number };
|
||||
// User-facing content modules vs. admin/system modules, separated by a divider in the rail.
|
||||
|
|
@ -243,7 +242,6 @@ export default function NavSidebar({
|
|||
>
|
||||
<Info className="w-5 h-5" />
|
||||
</button>
|
||||
<NotificationCenter variant="rail" filters={filters} setFilters={setFilters} />
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setPage("settings")}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue