feat: hide post

This commit is contained in:
gosangam
2026-06-14 19:52:16 +05:30
parent 7585e6cb31
commit 79f0bd9223
5 changed files with 154 additions and 2 deletions
+39 -1
View File
@@ -21,7 +21,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { Ban, Copy, Flag, MoreVertical, Trash2 } from "lucide-react";
import { Ban, Copy, Eye, EyeOff, Flag, MoreVertical, Trash2 } from "lucide-react";
import { toast } from "sonner";
import {
Drawer,
@@ -64,6 +64,7 @@ type MenuItem = {
export function PostCardMenu({
post,
onDelete,
onHiddenChange,
}: {
post: Post;
/** Owner-only: when provided AND the viewer is the author, the menu
@@ -71,6 +72,9 @@ export function PostCardMenu({
* expected to confirm + invoke the delete itself (matches the
* existing /u/me Delete-with-modal pattern). */
onDelete?: (id: string) => void;
/** Owner-only: called after a successful hide/unhide so the parent can
* update its local copy of the post (e.g. show a "Hidden" badge). */
onHiddenChange?: (id: string, hidden: boolean) => void;
}) {
const { user, loading } = useAuth();
const { requireAuth } = useAuthGate();
@@ -78,6 +82,9 @@ export function PostCardMenu({
const [open, setOpen] = useState(false);
const [blockOpen, setBlockOpen] = useState(false);
const [reportOpen, setReportOpen] = useState(false);
// Optimistic local mirror of post.is_hidden so the menu label flips
// instantly on toggle without waiting for a parent refetch.
const [hidden, setHidden] = useState(!!post.is_hidden);
const triggerRef = useRef<HTMLButtonElement>(null);
const popoverRef = useRef<HTMLDivElement>(null);
const [popPos, setPopPos] = useState<{ top: number; left: number } | null>(
@@ -208,6 +215,23 @@ export function PostCardMenu({
});
}
async function toggleHidden() {
setOpen(false);
const next = !hidden;
setHidden(next); // optimistic
try {
if (next) await posts.hide(post.id);
else await posts.unhide(post.id);
toast(next ? "Hidden from your timeline" : "Restored to your timeline", {
duration: 2500,
});
onHiddenChange?.(post.id, next);
} catch {
setHidden(!next); // revert
toast.error("Could not update post visibility");
}
}
// Build the items list AFTER all hooks have run so a conditional
// early-return doesn't break the rules-of-hooks contract on
// re-renders where viewer state changes.
@@ -220,6 +244,20 @@ export function PostCardMenu({
onClick: copyCaption,
});
if (isOwner) {
// Hide / unhide from timelines + public collections (independent of the
// delete-with-modal surface, so it's always available to the owner).
items.push({
key: "hide",
label: hidden ? "Unhide from timeline" : "Hide from timeline",
icon: hidden ? (
<Eye className="size-4" />
) : (
<EyeOff className="size-4" />
),
onClick: () => {
void toggleHidden();
},
});
if (onDelete) {
items.push({
key: "delete",