diff --git a/app/(app)/u/me/hidden/page.tsx b/app/(app)/u/me/hidden/page.tsx index 51b85e77..94afed37 100644 --- a/app/(app)/u/me/hidden/page.tsx +++ b/app/(app)/u/me/hidden/page.tsx @@ -11,12 +11,14 @@ */ import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; import { useAuth } from "@/components/auth/auth-provider"; import { PostScrollCard } from "@/components/social/post/post-scroll-card"; import { posts, SocialError, type Post } from "@/lib/social"; export default function MyHiddenPostsPage() { const { user } = useAuth(); + const router = useRouter(); const [feed, setFeed] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -25,7 +27,13 @@ export default function MyHiddenPostsPage() { if (!user) return; posts .hidden() - .then((rows) => setFeed(rows ?? [])) + .then((rows) => { + const list = rows ?? []; + setFeed(list); + // Nothing hidden → the "Hidden" tab isn't in the strip, so this page + // is a tab-less dead-end. Send the user to their Posts tab instead. + if (list.length === 0) router.replace("/u/me"); + }) .catch((err) => setError( err instanceof SocialError @@ -34,15 +42,21 @@ export default function MyHiddenPostsPage() { ), ) .finally(() => setLoading(false)); - }, [user]); + }, [user, router]); function onPostUpdated(updated: Post) { setFeed((rows) => rows.map((p) => (p.id === updated.id ? updated : p))); } - // Unhiding a post means it's no longer hidden → remove it from this list. + // Unhiding a post means it's no longer hidden → drop it from this list. + // When that empties the list, the "Hidden" tab disappears (the layout's + // count hits 0), so move the user to their Posts tab rather than leaving + // them on an empty page with no active tab. function onHiddenChange(id: string, hidden: boolean) { - if (!hidden) setFeed((rows) => rows.filter((p) => p.id !== id)); + if (hidden) return; + const remaining = feed.filter((p) => p.id !== id); + setFeed(remaining); + if (remaining.length === 0) router.replace("/u/me"); } if (!user) return null; diff --git a/app/(app)/u/me/layout.tsx b/app/(app)/u/me/layout.tsx index 9e99286b..d438dfa1 100644 --- a/app/(app)/u/me/layout.tsx +++ b/app/(app)/u/me/layout.tsx @@ -13,7 +13,7 @@ * lives outside this layout so it isn't framed by the header + tabs). */ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { AtSign, @@ -27,6 +27,7 @@ import { useAuthGate } from "@/components/auth/auth-gate"; import { profile } from "@/lib/social"; import { ProfileHeader } from "@/components/profile/profile-header"; import { ProfileTabs, type ProfileTab } from "@/components/profile/profile-tabs"; +import { HiddenCountProvider } from "@/components/profile/hidden-count-context"; export default function MeLayout({ children }: { children: React.ReactNode }) { const { user, loading } = useAuth(); @@ -78,6 +79,15 @@ export default function MeLayout({ children }: { children: React.ReactNode }) { }; }, [handle]); + // Let a hide/unhide action anywhere in the subtree (the PostCardMenu) nudge + // the hidden count so the "Hidden" tab toggles instantly. Clamped at 0. + const adjustHiddenCount = useCallback((delta: number) => { + setCounts((c) => + c ? { ...c, hidden: Math.max(0, c.hidden + delta) } : c, + ); + }, []); + const hiddenCtx = useMemo(() => ({ adjustHiddenCount }), [adjustHiddenCount]); + if (loading || !user) { return (
@@ -136,7 +146,9 @@ export default function MeLayout({ children }: { children: React.ReactNode }) { followingCount={counts?.following} /> -
{children}
+ +
{children}
+
); } diff --git a/components/profile/hidden-count-context.tsx b/components/profile/hidden-count-context.tsx new file mode 100644 index 00000000..1f54afdd --- /dev/null +++ b/components/profile/hidden-count-context.tsx @@ -0,0 +1,29 @@ +"use client"; + +/** + * HiddenCountContext — lets a hide/unhide action deep in the tree (the + * PostCardMenu) nudge the owner's hidden-post count held by the /u/me layout, + * so the "Hidden" tab appears/disappears instantly without a refetch. + * + * The /u/me layout provides it around its subtree. Anywhere else (home feed, + * other users' profiles) there's no provider, so `adjustHiddenCount` defaults + * to a no-op — hiding from those surfaces simply doesn't touch a tab that + * isn't there. + */ + +import { createContext, useContext } from "react"; + +type HiddenCountValue = { + /** Adjust the owner's hidden-post count by `delta` (the layout clamps at 0). */ + adjustHiddenCount: (delta: number) => void; +}; + +const HiddenCountContext = createContext({ + adjustHiddenCount: () => {}, +}); + +export const HiddenCountProvider = HiddenCountContext.Provider; + +export function useHiddenCount(): HiddenCountValue { + return useContext(HiddenCountContext); +} diff --git a/components/social/post/post-card-menu.tsx b/components/social/post/post-card-menu.tsx index c2e619ae..c75c47cc 100644 --- a/components/social/post/post-card-menu.tsx +++ b/components/social/post/post-card-menu.tsx @@ -38,6 +38,7 @@ import { } from "@/lib/social"; import { useAuth } from "@/components/auth/auth-provider"; import { useAuthGate } from "@/components/auth/auth-gate"; +import { useHiddenCount } from "@/components/profile/hidden-count-context"; import { useMediaQuery } from "@/components/selection-toolbar/shared"; import { ConfirmModal } from "@/components/ui/confirm-modal"; import { ReportDialog, type ReportOption } from "@/components/social/report-dialog"; @@ -78,6 +79,8 @@ export function PostCardMenu({ }) { const { user, loading } = useAuth(); const { requireAuth } = useAuthGate(); + // No-op outside the /u/me layout; there, it toggles the "Hidden" tab live. + const { adjustHiddenCount } = useHiddenCount(); const isMobile = useMediaQuery("(max-width: 767px)"); const [open, setOpen] = useState(false); const [blockOpen, setBlockOpen] = useState(false); @@ -219,6 +222,8 @@ export function PostCardMenu({ setOpen(false); const next = !hidden; setHidden(next); // optimistic + // Optimistically toggle the owner's "Hidden" tab via the layout context. + adjustHiddenCount(next ? 1 : -1); try { if (next) await posts.hide(post.id); else await posts.unhide(post.id); @@ -228,6 +233,7 @@ export function PostCardMenu({ onHiddenChange?.(post.id, next); } catch { setHidden(!next); // revert + adjustHiddenCount(next ? -1 : 1); // revert the tab count too toast.error("Could not update post visibility"); } }