|
|
|
@ -2,108 +2,86 @@ import { useFetchEvent } from '@/hooks' |
|
|
|
import { getZapInfoFromEvent } from '@/lib/event-metadata' |
|
|
|
import { getZapInfoFromEvent } from '@/lib/event-metadata' |
|
|
|
import { shouldHideInteractions } from '@/lib/event-filtering' |
|
|
|
import { shouldHideInteractions } from '@/lib/event-filtering' |
|
|
|
import { formatAmount } from '@/lib/lightning' |
|
|
|
import { formatAmount } from '@/lib/lightning' |
|
|
|
|
|
|
|
import { openNoteFromFetchOrCache } from '@/lib/navigation-related-events' |
|
|
|
|
|
|
|
import { relayHintsFromEventTags } from '@/lib/relay-list-builder' |
|
|
|
import { getSuperchatPaytoType } from '@/lib/superchat' |
|
|
|
import { getSuperchatPaytoType } from '@/lib/superchat' |
|
|
|
import { toNote, toProfile } from '@/lib/link' |
|
|
|
import { toProfile } from '@/lib/link' |
|
|
|
import { cn } from '@/lib/utils' |
|
|
|
import { cn } from '@/lib/utils' |
|
|
|
import { Event } from 'nostr-tools' |
|
|
|
import { Event } from 'nostr-tools' |
|
|
|
import { useMemo, type MouseEvent } from 'react' |
|
|
|
import { useMemo, type MouseEvent } from 'react' |
|
|
|
import { useTranslation } from 'react-i18next' |
|
|
|
import { useTranslation } from 'react-i18next' |
|
|
|
import { useSmartNoteNavigationOptional, useSecondaryPageOptional } from '@/PageManager' |
|
|
|
import { useSmartNoteNavigationOptional, useSecondaryPageOptional } from '@/PageManager' |
|
|
|
import Username from '../Username' |
|
|
|
import Username from '../Username' |
|
|
|
import UserAvatar from '../UserAvatar' |
|
|
|
|
|
|
|
import SuperchatPaymentMethodLabel from './SuperchatPaymentMethodLabel' |
|
|
|
import SuperchatPaymentMethodLabel from './SuperchatPaymentMethodLabel' |
|
|
|
|
|
|
|
import SuperchatCommentMarkdown from './SuperchatCommentMarkdown' |
|
|
|
import TurnIntoSuperchatButton from '../TurnIntoSuperchatButton' |
|
|
|
import TurnIntoSuperchatButton from '../TurnIntoSuperchatButton' |
|
|
|
|
|
|
|
|
|
|
|
export default function Zap({ |
|
|
|
export default function Zap({ |
|
|
|
event, |
|
|
|
event, |
|
|
|
className, |
|
|
|
className |
|
|
|
/** When the parent row already shows the zapper (e.g. reply list), hide the duplicate sender line. */ |
|
|
|
|
|
|
|
omitSenderHeading, |
|
|
|
|
|
|
|
/** Dense thread row (e.g. kind 1111–sized), not the full note card. */ |
|
|
|
|
|
|
|
variant = 'default' |
|
|
|
|
|
|
|
}: { |
|
|
|
}: { |
|
|
|
event: Event |
|
|
|
event: Event |
|
|
|
className?: string |
|
|
|
className?: string |
|
|
|
omitSenderHeading?: boolean |
|
|
|
|
|
|
|
variant?: 'default' | 'compact' |
|
|
|
|
|
|
|
}) { |
|
|
|
}) { |
|
|
|
// In quiet mode, we need to check the target event (if this is a zap receipt for an event)
|
|
|
|
const { t } = useTranslation() |
|
|
|
// For profile zaps, we can't check quiet mode since we don't have an event
|
|
|
|
|
|
|
|
const zapInfo = useMemo(() => getZapInfoFromEvent(event), [event]) |
|
|
|
const zapInfo = useMemo(() => getZapInfoFromEvent(event), [event]) |
|
|
|
const { event: targetEvent } = useFetchEvent(zapInfo?.eventId) |
|
|
|
const zapRelayHints = useMemo(() => relayHintsFromEventTags(event), [event]) |
|
|
|
|
|
|
|
const zapFetchOpts = useMemo( |
|
|
|
|
|
|
|
() => (zapRelayHints.length ? { relayHints: zapRelayHints } : undefined), |
|
|
|
|
|
|
|
[zapRelayHints] |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
const { event: targetEvent } = useFetchEvent(zapInfo?.eventId, undefined, zapFetchOpts) |
|
|
|
|
|
|
|
|
|
|
|
// Check if the target event (if any) is in quiet mode
|
|
|
|
const isEventZap = Boolean(targetEvent || zapInfo?.eventId) |
|
|
|
const inQuietMode = targetEvent ? shouldHideInteractions(targetEvent) : false |
|
|
|
const isProfileZap = Boolean(!isEventZap && zapInfo?.recipientPubkey) |
|
|
|
|
|
|
|
|
|
|
|
// Hide zap receipts in quiet mode as they contain emojis and text
|
|
|
|
const actualRecipientPubkey = useMemo(() => { |
|
|
|
if (inQuietMode) { |
|
|
|
if (isEventZap && targetEvent) { |
|
|
|
return null |
|
|
|
return targetEvent.pubkey |
|
|
|
} |
|
|
|
} |
|
|
|
const { t } = useTranslation() |
|
|
|
if (isProfileZap) { |
|
|
|
|
|
|
|
return zapInfo?.recipientPubkey |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return undefined |
|
|
|
|
|
|
|
}, [isEventZap, isProfileZap, targetEvent, zapInfo?.recipientPubkey]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const paytoType = useMemo(() => getSuperchatPaytoType(event), [event]) |
|
|
|
const { navigateToNote } = useSmartNoteNavigationOptional() |
|
|
|
const { navigateToNote } = useSmartNoteNavigationOptional() |
|
|
|
const secondaryPage = useSecondaryPageOptional() |
|
|
|
const secondaryPage = useSecondaryPageOptional() |
|
|
|
const push = secondaryPage?.push ?? ((url: string) => { window.location.href = url }) |
|
|
|
const push = secondaryPage?.push ?? ((url: string) => { window.location.href = url }) |
|
|
|
|
|
|
|
|
|
|
|
if (!zapInfo || !zapInfo.senderPubkey || (variant === 'default' && !zapInfo.amount)) { |
|
|
|
const inQuietMode = targetEvent ? shouldHideInteractions(targetEvent) : false |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (inQuietMode) { |
|
|
|
|
|
|
|
return null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!zapInfo || !zapInfo.senderPubkey) { |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div |
|
|
|
<div className={cn('py-0.5 text-sm text-muted-foreground', className)}> |
|
|
|
className={cn( |
|
|
|
|
|
|
|
'text-sm text-muted-foreground', |
|
|
|
|
|
|
|
variant === 'compact' |
|
|
|
|
|
|
|
? 'py-0.5' |
|
|
|
|
|
|
|
: 'rounded-lg border border-border bg-muted/20 p-4', |
|
|
|
|
|
|
|
className |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
[{t('Invalid zap receipt')}] |
|
|
|
[{t('Invalid zap receipt')}] |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Determine if this is an event zap or profile zap
|
|
|
|
|
|
|
|
const isEventZap = targetEvent || zapInfo?.eventId |
|
|
|
|
|
|
|
const isProfileZap = !isEventZap && zapInfo?.recipientPubkey |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// For event zaps, we need to determine the recipient from the zapped event
|
|
|
|
|
|
|
|
const actualRecipientPubkey = useMemo(() => { |
|
|
|
|
|
|
|
if (isEventZap && targetEvent) { |
|
|
|
|
|
|
|
// Event zap - recipient is the author of the zapped event
|
|
|
|
|
|
|
|
return targetEvent.pubkey |
|
|
|
|
|
|
|
} else if (isProfileZap) { |
|
|
|
|
|
|
|
// Profile zap - recipient is directly specified
|
|
|
|
|
|
|
|
return zapInfo?.recipientPubkey |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return undefined |
|
|
|
|
|
|
|
}, [isEventZap, isProfileZap, targetEvent, zapInfo?.recipientPubkey]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { senderPubkey, recipientPubkey, amount, comment } = zapInfo |
|
|
|
const { senderPubkey, recipientPubkey, amount, comment } = zapInfo |
|
|
|
const paytoType = useMemo(() => getSuperchatPaytoType(event), [event]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const openZapTarget = (e: MouseEvent<HTMLButtonElement>) => { |
|
|
|
const openZapTarget = (e: MouseEvent<HTMLButtonElement>) => { |
|
|
|
e.stopPropagation() |
|
|
|
e.stopPropagation() |
|
|
|
if (isEventZap) { |
|
|
|
if (isEventZap && zapInfo?.eventId) { |
|
|
|
if (targetEvent) { |
|
|
|
openNoteFromFetchOrCache(navigateToNote, zapInfo.eventId, targetEvent) |
|
|
|
navigateToNote(toNote(targetEvent), targetEvent) |
|
|
|
|
|
|
|
} else if (zapInfo.eventId) { |
|
|
|
|
|
|
|
navigateToNote(toNote(zapInfo.eventId)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else if (isProfileZap && actualRecipientPubkey) { |
|
|
|
} else if (isProfileZap && actualRecipientPubkey) { |
|
|
|
push(toProfile(actualRecipientPubkey)) |
|
|
|
push(toProfile(actualRecipientPubkey)) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (variant === 'compact') { |
|
|
|
|
|
|
|
const hasMetaLine = |
|
|
|
const hasMetaLine = |
|
|
|
(recipientPubkey && recipientPubkey !== senderPubkey) || isEventZap || isProfileZap |
|
|
|
(recipientPubkey && recipientPubkey !== senderPubkey) || isEventZap || isProfileZap |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div className={cn('text-sm text-muted-foreground', className)}> |
|
|
|
<div className={cn('text-sm text-muted-foreground', className)}> |
|
|
|
<div className="flex flex-wrap items-center gap-x-2 gap-y-1"> |
|
|
|
|
|
|
|
<SuperchatPaymentMethodLabel paytoType={paytoType} /> |
|
|
|
|
|
|
|
<span className="text-base font-semibold text-yellow-400/90">{t('Superchat')}</span> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
{hasMetaLine ? ( |
|
|
|
{hasMetaLine ? ( |
|
|
|
<div className="mt-1 flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-sm"> |
|
|
|
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-sm"> |
|
|
|
{recipientPubkey && recipientPubkey !== senderPubkey && ( |
|
|
|
{recipientPubkey && recipientPubkey !== senderPubkey && ( |
|
|
|
<span> |
|
|
|
<span> |
|
|
|
<span>{t('zapped')}</span>{' '} |
|
|
|
<span>{t('zapped')}</span>{' '} |
|
|
|
@ -128,79 +106,24 @@ export default function Zap({ |
|
|
|
)} |
|
|
|
)} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
) : null} |
|
|
|
) : null} |
|
|
|
{comment ? ( |
|
|
|
|
|
|
|
<p className="mt-2 text-base font-medium leading-snug text-foreground whitespace-pre-wrap break-words"> |
|
|
|
|
|
|
|
{comment} |
|
|
|
|
|
|
|
</p> |
|
|
|
|
|
|
|
) : null} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<div |
|
|
|
<div |
|
|
|
className={cn( |
|
|
|
className={cn( |
|
|
|
'relative rounded-lg border border-border bg-card p-4 text-card-foreground shadow-sm', |
|
|
|
'flex flex-wrap items-center gap-x-2 gap-y-1', |
|
|
|
className |
|
|
|
hasMetaLine && 'mt-1' |
|
|
|
)} |
|
|
|
)} |
|
|
|
> |
|
|
|
> |
|
|
|
<button |
|
|
|
<SuperchatPaymentMethodLabel paytoType={paytoType} /> |
|
|
|
type="button" |
|
|
|
|
|
|
|
onClick={openZapTarget} |
|
|
|
|
|
|
|
className="absolute bottom-3 right-3 flex items-center gap-2 rounded-md border border-border bg-secondary/80 px-2.5 py-1.5 text-xs font-medium text-secondary-foreground shadow-sm transition-colors hover:bg-secondary" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{isEventZap ? ( |
|
|
|
|
|
|
|
<span className="font-mono text-muted-foreground"> |
|
|
|
|
|
|
|
{(targetEvent?.id || zapInfo.eventId)?.substring(0, 12)}… |
|
|
|
|
|
|
|
</span> |
|
|
|
|
|
|
|
) : isProfileZap && actualRecipientPubkey ? ( |
|
|
|
|
|
|
|
<> |
|
|
|
|
|
|
|
<UserAvatar userId={actualRecipientPubkey} size="xSmall" /> |
|
|
|
|
|
|
|
<span>{t('Zapped profile')}</span> |
|
|
|
|
|
|
|
</> |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
t('Zap') |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
</button> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-start gap-3 pb-10 pr-2 sm:pr-36"> |
|
|
|
|
|
|
|
<div className="mt-1 shrink-0"> |
|
|
|
|
|
|
|
<SuperchatPaymentMethodLabel paytoType={paytoType} className="text-base" /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
<div className="min-w-0 flex-1"> |
|
|
|
|
|
|
|
{!omitSenderHeading && ( |
|
|
|
|
|
|
|
<div className="mb-3 flex flex-wrap items-center gap-2"> |
|
|
|
|
|
|
|
<UserAvatar userId={senderPubkey} size="small" /> |
|
|
|
|
|
|
|
<Username userId={senderPubkey} className="font-semibold text-foreground" /> |
|
|
|
|
|
|
|
<span className="text-base font-semibold text-yellow-400/90">{t('Superchat')}</span> |
|
|
|
<span className="text-base font-semibold text-yellow-400/90">{t('Superchat')}</span> |
|
|
|
{recipientPubkey && recipientPubkey !== senderPubkey && ( |
|
|
|
{amount != null ? ( |
|
|
|
<span className="w-full basis-full flex flex-wrap items-center gap-2 text-sm text-muted-foreground"> |
|
|
|
<span className="text-lg font-bold tabular-nums tracking-tight text-foreground"> |
|
|
|
<span>{t('zapped')}</span> |
|
|
|
{formatAmount(amount)} {t('sats')} |
|
|
|
<UserAvatar userId={recipientPubkey} size="small" /> |
|
|
|
|
|
|
|
<Username userId={recipientPubkey} className="font-semibold text-foreground" /> |
|
|
|
|
|
|
|
</span> |
|
|
|
</span> |
|
|
|
)} |
|
|
|
) : null} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{comment ? ( |
|
|
|
{comment ? ( |
|
|
|
<div className="mb-3 rounded-r-md border-l-[3px] border-primary bg-muted/40 py-2.5 pl-3 pr-2 dark:bg-muted/25"> |
|
|
|
<SuperchatCommentMarkdown event={event} comment={comment} className="mt-2" /> |
|
|
|
<p className="text-xl font-semibold leading-snug tracking-tight text-foreground whitespace-pre-wrap break-words"> |
|
|
|
|
|
|
|
{comment} |
|
|
|
|
|
|
|
</p> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) : null} |
|
|
|
) : null} |
|
|
|
|
|
|
|
<TurnIntoSuperchatButton event={event} prominent className="mt-3" /> |
|
|
|
<div className="flex flex-wrap items-baseline gap-x-2 gap-y-0.5"> |
|
|
|
|
|
|
|
<span className="text-2xl font-bold tabular-nums tracking-tight text-foreground sm:text-3xl"> |
|
|
|
|
|
|
|
{formatAmount(amount)} |
|
|
|
|
|
|
|
</span> |
|
|
|
|
|
|
|
<span className="text-base font-medium text-muted-foreground">{t('sats')}</span> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
<TurnIntoSuperchatButton event={event} prominent className="mt-4" /> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|