You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

197 lines
7.1 KiB

import { useFetchEvent } from '@/hooks'
import { usePaymentAttestationStatus } from '@/hooks/usePaymentAttestationStatus'
import { getZapInfoFromEvent } from '@/lib/event-metadata'
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 {
superchatChromePaymentChipClass,
superchatChromePaymentIconClass,
superchatChromeRowClass,
superchatTitleClass
} from '@/lib/superchat-ui'
import { toProfile } from '@/lib/link'
import { cn } from '@/lib/utils'
import { Event } from 'nostr-tools'
import { Zap as ZapIcon } from 'lucide-react'
import { useMemo, type MouseEvent } from 'react'
import { useTranslation } from 'react-i18next'
import { useSmartNoteNavigationOptional, useSecondaryPageOptional } from '@/PageManager'
import Username from '../Username'
import SuperchatPaymentMethodLabel from './SuperchatPaymentMethodLabel'
import SuperchatMessageArea from './SuperchatMessageArea'
import TurnIntoSuperchatButton from '../TurnIntoSuperchatButton'
import UserAvatar from '../UserAvatar'
import type { SuperchatLayoutVariant } from './Superchat'
export default function Zap({
event,
className,
variant = 'thread'
}: {
event: Event
className?: string
/** @deprecated Attestation button is shown automatically for payment recipients. */
showAttestationAction?: boolean
variant?: SuperchatLayoutVariant
}) {
const { t } = useTranslation()
const zapInfo = useMemo(() => getZapInfoFromEvent(event), [event])
const zapRelayHints = useMemo(() => relayHintsFromEventTags(event), [event])
const zapFetchOpts = useMemo(
() => (zapRelayHints.length ? { relayHints: zapRelayHints } : undefined),
[zapRelayHints]
)
const { event: targetEvent } = useFetchEvent(zapInfo?.eventId, undefined, zapFetchOpts)
const isEventZap = Boolean(targetEvent || zapInfo?.eventId)
const isProfileZap = Boolean(!isEventZap && zapInfo?.recipientPubkey)
const actualRecipientPubkey = useMemo(() => {
if (isEventZap && targetEvent) {
return targetEvent.pubkey
}
if (isProfileZap) {
return zapInfo?.recipientPubkey
}
return undefined
}, [isEventZap, isProfileZap, targetEvent, zapInfo?.recipientPubkey])
const paytoType = useMemo(() => getSuperchatPaytoType(event), [event])
const { navigateToNote } = useSmartNoteNavigationOptional()
const secondaryPage = useSecondaryPageOptional()
const push = secondaryPage?.push ?? ((url: string) => { window.location.href = url })
if (!zapInfo || !zapInfo.senderPubkey) {
return (
<div className={cn('py-0.5 text-sm text-muted-foreground', className)}>
[{t('Invalid zap receipt')}]
</div>
)
}
const { senderPubkey, recipientPubkey, amount, comment } = zapInfo
const attestationRecipientPubkey = actualRecipientPubkey ?? recipientPubkey ?? null
const { attested } = usePaymentAttestationStatus(event, attestationRecipientPubkey)
const openZapTarget = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
if (isEventZap && zapInfo?.eventId) {
openNoteFromFetchOrCache(navigateToNote, zapInfo.eventId, targetEvent)
} else if (isProfileZap && actualRecipientPubkey) {
push(toProfile(actualRecipientPubkey))
}
}
const isNotification = variant === 'notification'
const isProfileWall = variant === 'profileWall'
const showAmount = isNotification && amount != null && amount > 0
const showAsSuperchat = isProfileWall || attested
const hasMetaLine =
isProfileWall ||
(isNotification &&
((recipientPubkey && recipientPubkey !== senderPubkey) || isEventZap || isProfileZap))
return (
<div className={cn('min-w-0', className)}>
<div className="text-sm text-muted-foreground">
{hasMetaLine ? (
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-sm">
{isProfileWall ? (
<div className="flex min-w-0 items-center gap-2">
<UserAvatar userId={senderPubkey} size="small" className="shrink-0" />
<Username
userId={senderPubkey}
showAt
className="min-w-0 font-medium text-foreground/85 hover:text-foreground"
/>
<SuperchatPaymentMethodLabel
paytoType={paytoType}
className="shrink-0"
imgClassName="size-5"
/>
</div>
) : (
<>
{recipientPubkey && recipientPubkey !== senderPubkey && (
<span>
<span>{t('zapped')}</span>{' '}
<Username
userId={recipientPubkey}
className="inline font-medium text-foreground/85 hover:text-foreground"
/>
</span>
)}
{(isNotification && (isEventZap || isProfileZap)) && (
<button
type="button"
onClick={openZapTarget}
className="text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
>
{isEventZap
? t('Zapped note')
: isProfileZap && actualRecipientPubkey
? t('Zapped profile')
: t('Zap')}
</button>
)}
</>
)}
</div>
) : null}
{!isProfileWall ? (
<div
className={cn(
'flex flex-wrap items-center gap-x-2 gap-y-1',
hasMetaLine && 'mt-1'
)}
>
{showAsSuperchat ? (
<>
<SuperchatPaymentMethodLabel
paytoType={paytoType}
className={superchatChromePaymentChipClass}
imgClassName={superchatChromePaymentIconClass}
/>
<span className={cn(superchatChromeRowClass, superchatTitleClass)}>{t('Superchat')}</span>
{showAmount ? (
<span className="text-sm font-bold tabular-nums tracking-tight text-foreground">
{formatAmount(amount)} {t('sats')}
</span>
) : null}
</>
) : (
<>
<ZapIcon className="size-4 shrink-0 text-primary" aria-hidden />
<span className="text-sm font-semibold text-foreground">{t('Zap')}</span>
{showAmount ? (
<span className="text-sm font-bold tabular-nums tracking-tight text-foreground">
{formatAmount(amount)} {t('sats')}
</span>
) : null}
</>
)}
</div>
) : null}
</div>
<SuperchatMessageArea
event={event}
comment={comment}
showEmptyFallback={showAsSuperchat}
/>
{isNotification ? (
<div className="text-sm text-muted-foreground">
<TurnIntoSuperchatButton
event={event}
prominent
attestationRecipientPubkey={attestationRecipientPubkey}
className="mt-3"
/>
</div>
) : null}
</div>
)
}