feat: hide post
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user