import { type ReactNode } from 'react'; import { useNavigate } from 'react-router-dom'; import { Heart, MessageCircle, Repeat2 } from 'lucide-react'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { Skeleton } from '@/components/ui/skeleton'; import { formatNumber } from '@/lib/formatNumber'; import { cn } from '@/lib/utils'; /** A single image attachment. */ export interface ExternalImage { thumb: string; alt: string; } /** An external link card. */ export interface ExternalExternal { title: string; thumb?: string; } /** * Shared shape for a social post from any external platform * (Bluesky, Mastodon, etc.). */ export interface ExternalPostData { /** Display name of the post author. */ displayName: string; /** Handle / username (without leading @). */ handle: string; /** Avatar URL. */ avatar?: string; /** Plaintext content of the post. */ text: string; /** ISO-8601 creation date. */ createdAt: string; /** Canonical URL of the post on the source platform. */ postUrl: string; /** Canonical URL of the author's profile on the source platform. */ profileUrl: string; /** Reply count. */ replyCount: number; /** Repost / boost count (includes quotes if applicable). */ repostCount: number; /** Like / favourite count. */ likeCount: number; /** Image attachments. */ images?: ExternalImage[]; /** External link card (when there are no images). */ external?: ExternalExternal; /** Small branding icon rendered in the bottom-right. */ brandIcon?: ReactNode; } /** Format an ISO date string into a relative or short date. */ function formatDate(iso: string): string { const date = new Date(iso); const now = Date.now(); const diffSec = Math.floor((now - date.getTime()) / 1000); if (diffSec < 60) return `${diffSec}s`; if (diffSec < 3600) return `${Math.floor(diffSec / 60)}m`; if (diffSec < 86400) return `${Math.floor(diffSec / 3600)}h`; if (diffSec < 604800) return `${Math.floor(diffSec / 86400)}d`; if (diffSec < 2592000) return `${Math.floor(diffSec / 604800)}w`; return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } interface ExternalPostCardProps { post: ExternalPostData; /** When true, hides image and external link thumbnails. */ hideImage?: boolean; className?: string; } /** * Renders an external social post as a native quote-post card. * * Clicking the card body navigates to `/i/{postUrl}`. * Clicking the avatar or display name navigates to `/i/{profileUrl}`. */ export function ExternalPostCard({ post, hideImage, className }: ExternalPostCardProps) { const navigate = useNavigate(); return (
{post.text}
)} {/* External link title */} {post.external && post.external.title && (