feat: hide post
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* /u/me/hidden — the owner's hidden posts.
|
||||
*
|
||||
* Posts hidden from timelines + public collections live here (and in any
|
||||
* private collections). Each card's overflow menu offers "Unhide from
|
||||
* timeline"; on unhide we drop the post from this list since it's no longer
|
||||
* hidden. The tab itself is only shown (by the /u/me layout) when the user
|
||||
* has at least one hidden post.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
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 [feed, setFeed] = useState<Post[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
posts
|
||||
.hidden()
|
||||
.then((rows) => setFeed(rows ?? []))
|
||||
.catch((err) =>
|
||||
setError(
|
||||
err instanceof SocialError
|
||||
? err.message
|
||||
: "Could not load hidden posts.",
|
||||
),
|
||||
)
|
||||
.finally(() => setLoading(false));
|
||||
}, [user]);
|
||||
|
||||
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.
|
||||
function onHiddenChange(id: string, hidden: boolean) {
|
||||
if (!hidden) setFeed((rows) => rows.filter((p) => p.id !== id));
|
||||
}
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<div className="max-w-xl w-full mx-auto space-y-6">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Hidden posts don't appear on your timeline, in the feed, or in
|
||||
public collections — but stay in your private collections. Use a card's
|
||||
menu to unhide.
|
||||
</p>
|
||||
|
||||
{error ? (
|
||||
<p className="text-sm text-destructive">{error}</p>
|
||||
) : loading ? (
|
||||
<p className="text-sm text-muted-foreground">Loading…</p>
|
||||
) : feed.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No hidden posts.</p>
|
||||
) : (
|
||||
feed.map((p) => (
|
||||
<PostScrollCard
|
||||
key={p.id}
|
||||
post={p}
|
||||
onUpdated={onPostUpdated}
|
||||
onHiddenChange={onHiddenChange}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { useRouter, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
AtSign,
|
||||
Bookmark,
|
||||
EyeOff,
|
||||
FolderClosed,
|
||||
LayoutGrid,
|
||||
} from "lucide-react";
|
||||
@@ -51,6 +52,7 @@ export default function MeLayout({ children }: { children: React.ReactNode }) {
|
||||
posts: number;
|
||||
followers: number;
|
||||
following: number;
|
||||
hidden: number;
|
||||
} | null>(null);
|
||||
const handle = user?.handle;
|
||||
useEffect(() => {
|
||||
@@ -64,6 +66,7 @@ export default function MeLayout({ children }: { children: React.ReactNode }) {
|
||||
posts: p.post_count,
|
||||
followers: p.follower_count,
|
||||
following: p.following_count,
|
||||
hidden: p.hidden_count ?? 0,
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -109,6 +112,15 @@ export default function MeLayout({ children }: { children: React.ReactNode }) {
|
||||
},
|
||||
];
|
||||
|
||||
// Surface the "Hidden" tab only when the user actually has hidden posts.
|
||||
if ((counts?.hidden ?? 0) > 0) {
|
||||
tabs.push({
|
||||
href: "/u/me/hidden",
|
||||
label: "Hidden",
|
||||
icon: <EyeOff className="size-5" />,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex-1 max-w-3xl w-full mx-auto px-4 sm:px-6">
|
||||
<ProfileHeader
|
||||
|
||||
Reference in New Issue
Block a user