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",
|
||||
|
||||
@@ -47,11 +47,15 @@ export function PostScrollCard({
|
||||
post,
|
||||
onDelete,
|
||||
onUpdated,
|
||||
onHiddenChange,
|
||||
enableFollow = false,
|
||||
}: {
|
||||
post: Post;
|
||||
onDelete?: (id: string) => void;
|
||||
onUpdated?: (updated: Post) => void;
|
||||
/** Owner-only: forwarded to the overflow menu so a parent can react to a
|
||||
* hide/unhide (e.g. drop the post from the Hidden tab on unhide). */
|
||||
onHiddenChange?: (id: string, hidden: boolean) => void;
|
||||
/** Home-feed only: show a Follow / Follow back button in the header for
|
||||
* authors the viewer doesn't already follow. Off everywhere else. */
|
||||
enableFollow?: boolean;
|
||||
@@ -147,7 +151,11 @@ export function PostScrollCard({
|
||||
followsYou={!!post.author_follows_viewer}
|
||||
/>
|
||||
) : null}
|
||||
<PostCardMenu post={post} onDelete={onDelete} />
|
||||
<PostCardMenu
|
||||
post={post}
|
||||
onDelete={onDelete}
|
||||
onHiddenChange={onHiddenChange}
|
||||
/>
|
||||
</header>
|
||||
|
||||
{/* Media — image carousel or gradient-with-quote */}
|
||||
|
||||
Reference in New Issue
Block a user