Files
grimoire/src/components/nostr/compact/ZapCompactPreview.tsx
Alejandro b3aaabfd5c fix: extract inner zap request for zap-to-zap emoji rendering in compact view (#199)
When displaying a zap that targets another zap (kind 9735), the compact
preview was passing the inner zap receipt directly to RichText. Since a
zap receipt's content is empty (the user's message with emoji tags is in
the embedded zap request), custom emojis weren't rendering.

Now when the zapped event is a zap receipt, we extract its zap request
using getZapRequest() and use that for the preview, matching how the
full renderer handles this via recursive KindRenderer.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-22 13:34:27 +01:00

77 lines
2.7 KiB
TypeScript

import type { NostrEvent } from "@/types/nostr";
import { Zap } from "lucide-react";
import { useMemo } from "react";
import {
getZapAmount,
getZapEventPointer,
getZapAddressPointer,
getZapRequest,
getZapRecipient,
} from "applesauce-common/helpers/zap";
import { useNostrEvent } from "@/hooks/useNostrEvent";
import { UserName } from "../UserName";
import { RichText } from "../RichText";
/**
* Compact preview for Kind 9735 (Zap Receipt)
* Layout: [amount] [recipient] [zap message] [preview]
*/
export function ZapCompactPreview({ event }: { event: NostrEvent }) {
const zapAmount = useMemo(() => getZapAmount(event), [event]);
const zapRequest = useMemo(() => getZapRequest(event), [event]);
const zapRecipient = useMemo(() => getZapRecipient(event), [event]);
// Get zapped content pointers
const eventPointer = useMemo(() => getZapEventPointer(event), [event]);
const addressPointer = useMemo(() => getZapAddressPointer(event), [event]);
// Fetch the zapped event (prefer address pointer for replaceable events)
const zappedByEvent = useNostrEvent(eventPointer || undefined);
const zappedByAddress = useNostrEvent(addressPointer || undefined);
const zappedEvent = zappedByAddress || zappedByEvent;
// If zapped event is a zap receipt (kind 9735), extract its zap request
// The actual content with emoji tags is in the zap request, not the receipt
const zappedEventForPreview = useMemo(() => {
if (zappedEvent?.kind === 9735) {
const innerZapRequest = getZapRequest(zappedEvent);
return innerZapRequest || zappedEvent;
}
return zappedEvent;
}, [zappedEvent]);
// Convert from msats to sats
const amountInSats = useMemo(() => {
if (!zapAmount) return 0;
return Math.floor(zapAmount / 1000);
}, [zapAmount]);
return (
<span className="flex items-center gap-1 text-sm truncate">
<Zap className="size-3 fill-yellow-500 text-yellow-500 shrink-0" />
<span className="text-yellow-500 font-medium shrink-0">
{amountInSats.toLocaleString("en", { notation: "compact" })}
</span>
{zapRecipient && <UserName pubkey={zapRecipient} />}
{zapRequest?.content && (
<span className="truncate line-clamp-1 flex-shrink-0">
<RichText
event={zapRequest}
className="inline text-sm leading-none"
options={{ showMedia: false, showEventEmbeds: false }}
/>
</span>
)}
{zappedEventForPreview && (
<span className="text-muted-foreground truncate line-clamp-1">
<RichText
event={zappedEventForPreview}
className="inline text-sm leading-none"
options={{ showMedia: false, showEventEmbeds: false }}
/>
</span>
)}
</span>
);
}