"use client"; /** * PostCard — renders one post in the public feed and the /u/me feed. * * • Author header — avatar + display name + @handle on the left, * three-dot overflow menu on the right. Menu contents vary by * viewer (see PostCardMenu): author sees Delete; signed-in * non-author sees Block + Report; anonymous sees Report. * • Caption in serif (locked, can't be edited) * • Image grid (max 5; collapses to one column on the cheap) * • "Source" link back to the page the quote came from * • Optional onDelete callback — passed through to PostCardMenu so * Delete actually does something on owner views (parent confirms + * calls the API; same pattern as before the menu refactor). * * The source link always lands the user on the original passage with `?hl=` * highlighting. If the stored source_url already carries `?hl=` we use it * verbatim; otherwise we synthesise one from the post's caption so older * posts (or imported data) still scroll to + highlight the text on click. */ import Image from "next/image"; import Link from "next/link"; import { ExternalLink, Repeat2 } from "lucide-react"; import type { Post } from "@/lib/social"; import { displaySourceLabel } from "@/lib/source-reference"; import { appendHighlight, hasHighlight } from "@/lib/text-highlight"; import { PostActionsFooter } from "./post-actions-footer"; import { PostCardMenu } from "./post-card-menu"; export function PostCard({ post, onDelete, }: { post: Post; onDelete?: (id: string) => void; }) { const sourceHref = hasHighlight(post.source_url) || !post.caption ? post.source_url : appendHighlight(post.source_url, post.caption); const authorName = post.author_name || "Unknown"; const authorHandle = post.author_handle ?? null; return (
{/* Reshare attribution — only on rows the feed owner reshared. The author block below still shows the original author; this strip just signals "this is on this feed because it was reshared," matching the Twitter/X retweet convention. */} {post.reshared_at ? (
Reshared
) : null} {/* Author header — identity on the left, overflow menu on the right. The menu trigger only renders when there are items to show (delete for owners, block/report for everyone else), so a view with nothing to expose simply hides the dot. */}
{authorHandle ? ( {authorName} ) : (

{authorName}

)} {authorHandle ? (

@{authorHandle}

) : null}

“{post.caption}”

{post.images && post.images.length > 0 ? (
{post.images.map((img) => (
))}
) : null}
{displaySourceLabel(post.source_url, post.source_title)} {new Date(post.created_at).toLocaleDateString()}
{/* Action footer — like / comment / reshare on the left, add to collection on the right. */}
); } function AuthorAvatar({ src, name, }: { src: string | null; name: string; }) { if (src) { return ( {name} ); } const initial = name.charAt(0).toUpperCase() || "?"; return (
{initial}
); }