import { LONG_PRESS_THRESHOLD } from '@/constants' import { useStuffStatsById } from '@/hooks/useStuffStatsById' import { useStuff } from '@/hooks/useStuff' import { getLightningAddressFromProfile } from '@/lib/lightning' import { cn } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' import { useZap } from '@/providers/ZapProvider' import client from '@/services/client.service' import lightning from '@/services/lightning.service' import stuffStatsService from '@/services/stuff-stats.service' import { Loader, Zap } from 'lucide-react' import { Event } from 'nostr-tools' import { MouseEvent, TouchEvent, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import ZapDialog from '../ZapDialog' export default function ZapButton({ stuff }: { stuff: Event | string }) { const { t } = useTranslation() const { checkLogin, pubkey } = useNostr() const { event, stuffKey } = useStuff(stuff) const noteStats = useStuffStatsById(stuffKey) const { defaultZapSats, defaultZapComment, quickZap } = useZap() const [touchStart, setTouchStart] = useState<{ x: number; y: number } | null>(null) const [openZapDialog, setOpenZapDialog] = useState(false) const [zapping, setZapping] = useState(false) const { zapAmount, hasZapped } = useMemo(() => { return { zapAmount: noteStats?.zaps?.reduce((acc, zap) => acc + zap.amount, 0), hasZapped: pubkey ? noteStats?.zaps?.some((zap) => zap.pubkey === pubkey) : false } }, [noteStats, pubkey]) const [disable, setDisable] = useState(true) const timerRef = useRef | null>(null) const isLongPressRef = useRef(false) useEffect(() => { if (!event) { setDisable(true) return } client.fetchProfile(event.pubkey).then((profile) => { if (!profile) return const lightningAddress = getLightningAddressFromProfile(profile) if (lightningAddress) setDisable(false) }) }, [event]) const handleZap = async () => { try { if (!pubkey) { throw new Error('You need to be logged in to zap') } if (zapping || !event) return setZapping(true) const zapResult = await lightning.zap(pubkey, event, defaultZapSats, defaultZapComment) // user canceled if (!zapResult) { return } stuffStatsService.addZap( pubkey, event.id, zapResult.invoice, defaultZapSats, defaultZapComment ) } catch (error) { toast.error(`${t('Zap failed')}: ${(error as Error).message}`) } finally { setZapping(false) } } const handleClickStart = (e: MouseEvent | TouchEvent) => { e.stopPropagation() e.preventDefault() if (disable) return isLongPressRef.current = false if ('touches' in e) { const touch = e.touches[0] setTouchStart({ x: touch.clientX, y: touch.clientY }) } if (quickZap) { timerRef.current = setTimeout(() => { isLongPressRef.current = true checkLogin(() => { setOpenZapDialog(true) setZapping(true) }) }, LONG_PRESS_THRESHOLD) } } const handleClickEnd = (e: MouseEvent | TouchEvent) => { e.stopPropagation() e.preventDefault() if (timerRef.current) { clearTimeout(timerRef.current) } if (disable) return if ('touches' in e) { setTouchStart(null) if (!touchStart) return const touch = e.changedTouches[0] const diffX = Math.abs(touch.clientX - touchStart.x) const diffY = Math.abs(touch.clientY - touchStart.y) if (diffX > 10 || diffY > 10) return } if (!quickZap) { checkLogin(() => { setOpenZapDialog(true) setZapping(true) }) } else if (!isLongPressRef.current) { checkLogin(() => handleZap()) } isLongPressRef.current = false } const handleMouseLeave = () => { if (timerRef.current) { clearTimeout(timerRef.current) } } return ( <> {event && ( { setOpenZapDialog(open) setZapping(open) }} pubkey={event.pubkey} event={event} /> )} ) } function formatAmount(amount: number) { if (amount < 1000) return amount if (amount < 1000000) return `${Math.round(amount / 100) / 10}k` return `${Math.round(amount / 100000) / 10}M` }