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}> : ''}
);
}