Files

80 lines
2.7 KiB
TypeScript

"use client";
/**
* A post shared into a chat, rendered as a card with its author.
*
* The contents are resolved by the BFF on every read, per viewer — never from
* a snapshot frozen at send time. So a post that has since been deleted or
* hidden arrives as `entity_unavailable` and renders as a tombstone rather
* than quietly showing content the reader is no longer allowed to see.
*/
import Link from "next/link";
import { ImageOff } from "lucide-react";
import { UserAvatar } from "@/components/ui/user-avatar";
import type { Attachment } from "@/lib/chat";
export function SharedPostCard({ attachment }: { attachment: Attachment }) {
const post = attachment.entity;
if (!post || attachment.entity_unavailable) {
return (
<div className="flex w-full items-center gap-2 rounded-lg border border-border bg-muted/30 px-3 py-2.5 sm:max-w-64">
<ImageOff className="size-4 shrink-0 text-muted-foreground" aria-hidden />
<span className="text-sm text-muted-foreground">
This post is no longer available
</span>
</div>
);
}
return (
<Link
href={`/u/${encodeURIComponent(post.author_handle)}/posts#post-${post.id}`}
// Capped on wide screens. The bubble allows 78% of the column, which on
// a phone is a sensible ~265px but on desktop is over 500px — and with
// a square image that makes a shared post taller than the viewport.
// Mobile keeps the bubble's own width; only sm+ needs the ceiling.
className="block w-full overflow-hidden rounded-lg border border-border bg-card transition-colors hover:bg-muted/40 sm:max-w-64"
>
<span className="flex items-center gap-2 px-3 py-2">
<UserAvatar
src={post.author_avatar_url ?? null}
name={post.author_name}
className="size-6 shrink-0"
/>
<span className="min-w-0 flex-1">
<span className="block truncate text-xs font-medium text-foreground">
{post.author_name}
</span>
<span className="block truncate text-[11px] text-muted-foreground">
@{post.author_handle}
</span>
</span>
</span>
{post.image_url && (
// eslint-disable-next-line @next/next/no-img-element
<img
src={post.image_url}
alt=""
loading="lazy"
className="aspect-square w-full object-cover"
/>
)}
{post.caption && (
<span className="block px-3 py-2 text-sm leading-relaxed text-foreground line-clamp-3">
{post.caption}
</span>
)}
{post.source_title && (
<span className="block truncate px-3 pb-2 text-xs text-muted-foreground">
{post.source_title}
</span>
)}
</Link>
);
}