import UpBolt from '../svgs/bolt.svg' import styles from './upvote.module.css' import { gql, useMutation } from '@apollo/client' import FundError from './fund-error' import ActionTooltip from './action-tooltip' import ItemAct from './item-act' import { useMe } from './me' import Rainbow from '../lib/rainbow' import { useCallback, useEffect, useRef, useState } from 'react' import LongPressable from 'react-longpressable' import { Overlay, Popover } from 'react-bootstrap' import { useShowModal } from './modal' import { useRouter } from 'next/router' import { LightningConsumer } from './lightning' const getColor = (meSats) => { if (!meSats || meSats <= 10) { return 'var(--secondary)' } const idx = Math.min( Math.floor((Math.log(meSats) / Math.log(10000)) * (Rainbow.length - 1)), Rainbow.length - 1) return Rainbow[idx] } const UpvotePopover = ({ target, show, handleClose }) => { const me = useMe() return ( Zapping
Press the bolt again to zap {me?.tipDefault || 1} more sat{me?.tipDefault > 1 ? 's' : ''}.
Repeatedly press the bolt to zap more sats.
) } const TipPopover = ({ target, show, handleClose }) => ( Press and hold
Press and hold bolt to zap a custom amount.
As you zap more, the bolt color follows the rainbow.
) export default function UpVote ({ item, className, pendingSats, setPendingSats }) { const showModal = useShowModal() const router = useRouter() const [voteShow, _setVoteShow] = useState(false) const [tipShow, _setTipShow] = useState(false) const ref = useRef() const timerRef = useRef(null) const me = useMe() const [setWalkthrough] = useMutation( gql` mutation setWalkthrough($upvotePopover: Boolean, $tipPopover: Boolean) { setWalkthrough(upvotePopover: $upvotePopover, tipPopover: $tipPopover) }` ) const fwd2me = me && Number(me?.id) === item?.fwdUserId const setVoteShow = (yes) => { if (!me) return // if they haven't seen the walkthrough and they have sats if (yes && !me.upvotePopover && me.sats) { _setVoteShow(true) } if (voteShow && !yes) { _setVoteShow(false) setWalkthrough({ variables: { upvotePopover: true } }) } } const setTipShow = (yes) => { if (!me) return // if we want to show it, yet we still haven't shown if (yes && !me.tipPopover && me.sats) { _setTipShow(true) } // if it's currently showing and we want to hide it if (tipShow && !yes) { _setTipShow(false) setWalkthrough({ variables: { tipPopover: true } }) } } const [act] = useMutation( gql` mutation act($id: ID!, $sats: Int!) { act(id: $id, sats: $sats) { sats } }`, { update (cache, { data: { act: { sats } } }) { setPendingSats(0) cache.modify({ id: `Item:${item.id}`, fields: { sats (existingSats = 0) { return existingSats + sats }, meSats (existingSats = 0) { if (sats <= me.sats) { if (existingSats === 0) { setVoteShow(true) } else { setTipShow(true) } } return existingSats + sats } } }) // update all ancestors item.path.split('.').forEach(id => { if (Number(id) === Number(item.id)) return cache.modify({ id: `Item:${id}`, fields: { commentSats (existingCommentSats = 0) { return existingCommentSats + sats } } }) }) } } ) // prevent updating pendingSats after unmount useEffect(() => { return () => { timerRef.current = null } }, []) // if we want to use optimistic response, we need to buffer the votes // because if someone votes in quick succession, responses come back out of order // so we wait a bit to see if there are more votes coming in // this effectively performs our own debounced optimistic response const bufferVotes = useCallback((sats) => { if (timerRef.current) { clearTimeout(timerRef.current) } timerRef.current = setTimeout(async (pendingSats, sats) => { try { await act({ variables: { id: item.id, sats: pendingSats + sats } }) } catch (error) { if (error.toString().includes('insufficient funds')) { showModal(onClose => { return }) return } throw new Error({ message: error.toString() }) } }, 1000, pendingSats, sats) timerRef.current && setPendingSats(pendingSats + sats) }, [item, pendingSats, act, setPendingSats, showModal]) const meSats = (item?.meSats || 0) + pendingSats // what should our next tip be? let sats = me?.tipDefault || 1 if (me?.turboTipping && me) { let raiseTip = sats while (meSats >= raiseTip) { raiseTip *= 10 } sats = raiseTip - meSats } const overlayText = () => { return `${sats} sat${sats > 1 ? 's' : ''}` } const disabled = item?.mine || fwd2me || item?.deletedAt const color = getColor(meSats) return ( {({ strike }) =>
{ if (!item) return // we can't tip ourselves if (disabled) { return } setTipShow(false) showModal(onClose => ) } } onShortPress={ me ? async (e) => { if (!item) return // we can't tip ourselves if (disabled) { return } if (meSats) { setVoteShow(false) } strike() bufferVotes(sats) } : async () => await router.push({ pathname: '/signup', query: { callbackUrl: window.location.origin + router.asPath } }) } >
setTipShow(false)} /> setVoteShow(false)} />
}
) }