import type { NostrEvent } from '@nostrify/nostrify'; import { useMemo } from 'react'; import { Mail } from 'lucide-react'; import { Link, useNavigate } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { ProfileHoverCard } from '@/components/ProfileHoverCard'; import { useAuthor } from '@/hooks/useAuthor'; import { useProfileUrl } from '@/hooks/useProfileUrl'; import { getDisplayName } from '@/lib/getDisplayName'; import { genUserName } from '@/lib/genUserName'; import { Skeleton } from '@/components/ui/skeleton'; import { timeAgo } from '@/lib/timeAgo'; import { cn } from '@/lib/utils'; interface EncryptedMessageContentProps { event: NostrEvent; /** Whether to use the compact card layout (feed) vs expanded (detail page). */ compact?: boolean; className?: string; } /** Renders a single participant avatar with name and link. */ function Participant({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; const displayName = getDisplayName(metadata, pubkey); const profileUrl = useProfileUrl(pubkey, metadata); if (author.isLoading) { return (
); } return (
e.stopPropagation()}> {displayName[0]?.toUpperCase()} e.stopPropagation()} className="text-xs font-medium text-foreground/80 hover:text-foreground truncate max-w-[80px] transition-colors" > {displayName}
); } /** * Visual display for kind 4 (NIP-04 encrypted DM) events. * * Instead of rendering the encrypted ciphertext, it shows a * "mail in transit" visualization: sender avatar -> dashed path with mail icon -> recipient avatar. */ export function EncryptedMessageContent({ event, compact: _compact, className }: EncryptedMessageContentProps) { const recipientPubkey = event.tags.find(([n]) => n === 'p')?.[1]; const senderAuthor = useAuthor(event.pubkey); const senderName = getDisplayName(senderAuthor.data?.metadata, event.pubkey); if (!recipientPubkey) { return (

Encrypted message (no recipient found)

); } return (
{/* Description */}

{senderName} sent a direct message

{/* Sender */} {/* Transit path */}
{/* Dotted line */}
{/* Mail icon in the center */}
{/* Recipient */}
); } interface EncryptedMessageCompactProps { event: { id: string; kind: number; pubkey: string; content: string; created_at: number; tags: string[][] }; className?: string; } /** * Compact inline card for kind 4 events used in quote posts, reply indicators, * and the reply composer. Matches the style of EmbeddedNoteCard. */ export function EncryptedMessageCompact({ event, className }: EncryptedMessageCompactProps) { const navigate = useNavigate(); const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; const displayName = metadata?.name || genUserName(event.pubkey); const recipientPubkey = event.tags.find(([n]) => n === 'p')?.[1]; const recipientAuthor = useAuthor(recipientPubkey ?? ''); const recipientName = recipientPubkey ? getDisplayName(recipientAuthor.data?.metadata, recipientPubkey) : undefined; const neventId = useMemo( () => nip19.neventEncode({ id: event.id, author: event.pubkey }), [event.id, event.pubkey], ); const profileUrl = useProfileUrl(event.pubkey, metadata); return (
{ e.stopPropagation(); navigate(`/${neventId}`); }} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); navigate(`/${neventId}`); } }} >
{/* Author row */}
{author.isLoading ? ( <> ) : ( <> e.stopPropagation()} > {displayName[0]?.toUpperCase()} e.stopPropagation()} > {displayName} )} ยท {timeAgo(event.created_at)}
{/* Content line */}

Sent a direct message{recipientName ? <> to {recipientName} : ''}

); }