2024-03-20 00:37:31 +00:00
import UpBolt from '@/svgs/bolt.svg'
2021-04-22 22:14:32 +00:00
import styles from './upvote.module.css'
2021-04-26 21:55:15 +00:00
import { gql , useMutation } from '@apollo/client'
2021-07-08 18:42:57 +00:00
import ActionTooltip from './action-tooltip'
2024-05-28 17:18:54 +00:00
import ItemAct , { ZapUndoController , useZap } from './item-act'
2021-09-12 16:55:38 +00:00
import { useMe } from './me'
2024-03-20 00:37:31 +00:00
import getColor from '@/lib/rainbow'
2023-10-06 20:01:51 +00:00
import { useCallback , useMemo , useRef , useState } from 'react'
2024-04-12 23:37:04 +00:00
import LongPressable from './long-pressable'
2023-07-24 18:35:05 +00:00
import Overlay from 'react-bootstrap/Overlay'
import Popover from 'react-bootstrap/Popover'
2023-01-10 23:13:37 +00:00
import { useShowModal } from './modal'
2024-03-20 00:37:31 +00:00
import { numWithUnits } from '@/lib/format'
2023-12-20 01:55:19 +00:00
import { Dropdown } from 'react-bootstrap'
2024-09-19 18:13:14 +00:00
import classNames from 'classnames'
2021-12-09 20:40:40 +00:00
2022-04-12 21:09:12 +00:00
const UpvotePopover = ( { target , show , handleClose } ) => {
2024-09-12 18:05:11 +00:00
const { me } = useMe ( )
2022-04-12 21:09:12 +00:00
return (
< Overlay
show = { show }
target = { target }
placement = 'right'
>
< Popover id = 'popover-basic' >
2023-08-26 00:03:02 +00:00
< Popover . Header className = 'd-flex justify-content-between alert-dismissible' as = 'h4' > Zapping
< button type = 'button' className = 'btn-close' onClick = { handleClose } > < span className = 'visually-hidden-focusable' > Close alert < / s p a n > < / b u t t o n >
< / P o p o v e r . H e a d e r >
2023-07-24 18:35:05 +00:00
< Popover . Body >
2024-07-27 03:37:03 +00:00
< div className = 'mb-2' > Press the bolt again to zap { me ? . privates ? . tipRandom ? 'a random amount of' : ` ${ me ? . privates ? . tipDefault || 1 } more ` } sat { me ? . privates ? . tipDefault > 1 ? 's' : '' } . < / d i v >
2023-06-19 18:21:55 +00:00
< div > Repeatedly press the bolt to zap more sats . < / d i v >
2023-07-24 18:35:05 +00:00
< / P o p o v e r . B o d y >
2022-04-12 21:09:12 +00:00
< / P o p o v e r >
< / O v e r l a y >
)
}
2021-12-09 20:40:40 +00:00
const TipPopover = ( { target , show , handleClose } ) => (
< Overlay
show = { show }
target = { target }
placement = 'right'
>
< Popover id = 'popover-basic' >
2023-08-26 00:03:02 +00:00
< Popover . Header className = 'd-flex justify-content-between alert-dismissible' as = 'h4' > Press and hold
< button type = 'button' className = 'btn-close' onClick = { handleClose } > < span className = 'visually-hidden-focusable' > Close alert < / s p a n > < / b u t t o n >
< / P o p o v e r . H e a d e r >
2023-07-24 18:35:05 +00:00
< Popover . Body >
2023-06-19 18:21:55 +00:00
< div className = 'mb-2' > Press and hold bolt to zap a custom amount . < / d i v >
< div > As you zap more , the bolt color follows the rainbow . < / d i v >
2023-07-24 18:35:05 +00:00
< / P o p o v e r . B o d y >
2021-12-09 20:40:40 +00:00
< / P o p o v e r >
< / O v e r l a y >
)
2021-04-26 21:55:15 +00:00
2023-12-20 01:55:19 +00:00
export function DropdownItemUpVote ( { item } ) {
const showModal = useShowModal ( )
return (
< Dropdown . Item
onClick = { async ( ) => {
showModal ( onClose =>
2024-07-01 17:02:29 +00:00
< ItemAct onClose = { onClose } item = { item } / > )
2023-12-20 01:55:19 +00:00
} }
>
< span className = 'text-success' > zap < / s p a n >
< / D r o p d o w n . I t e m >
)
}
2024-08-03 19:07:17 +00:00
export const defaultTipIncludingRandom = ( { tipDefault , tipRandom , tipRandomMin , tipRandomMax } = { } ) => {
return tipRandom
? Math . floor ( ( Math . random ( ) * ( tipRandomMax - tipRandomMin + 1 ) ) + tipRandomMin )
2024-07-30 23:09:46 +00:00
: ( tipDefault || 100 )
2024-08-03 19:07:17 +00:00
}
2024-07-27 03:37:03 +00:00
export const nextTip = ( meSats , { tipDefault , turboTipping , tipRandom , tipRandomMin , tipRandomMax } ) => {
2024-02-27 00:09:09 +00:00
if ( turboTipping ) {
2024-08-03 19:07:17 +00:00
if ( tipRandom ) {
let pow = 0
// find the first power of 10 that is greater than meSats
while ( ! ( meSats <= tipRandomMax * 10 * * pow ) ) {
pow ++
}
// if meSats is in that power of 10's range already, move into the next range
if ( meSats >= tipRandomMin * 10 * * pow ) {
pow ++
}
// make sure the our range minimum doesn't overlap with the previous range maximum
tipRandomMin = tipRandomMax * 10 * * ( pow - 1 ) >= tipRandomMin * 10 * * pow ? tipRandomMax * 10 * * ( pow - 1 ) + 1 : tipRandomMin * 10 * * pow
tipRandomMax = tipRandomMax * 10 * * pow
return Math . floor ( ( Math . random ( ) * ( tipRandomMax - tipRandomMin + 1 ) ) + tipRandomMin ) - meSats
}
let sats = defaultTipIncludingRandom ( { tipDefault , tipRandom , tipRandomMin , tipRandomMax } )
2024-02-27 00:09:09 +00:00
while ( meSats >= sats ) {
sats *= 10
}
// deduct current sats since turbo tipping is about total zap not making the next zap 10x
2024-08-03 19:07:17 +00:00
return sats - meSats
2024-02-27 00:09:09 +00:00
}
2024-08-03 19:07:17 +00:00
return defaultTipIncludingRandom ( { tipDefault , tipRandom , tipRandomMin , tipRandomMax } )
2024-02-27 00:09:09 +00:00
}
2023-12-27 02:27:52 +00:00
export default function UpVote ( { item , className } ) {
2023-12-20 01:55:19 +00:00
const showModal = useShowModal ( )
const [ voteShow , _setVoteShow ] = useState ( false )
const [ tipShow , _setTipShow ] = useState ( false )
const ref = useRef ( )
2024-09-12 18:05:11 +00:00
const { me } = useMe ( )
2024-02-20 23:46:27 +00:00
const [ hover , setHover ] = useState ( false )
2023-12-20 01:55:19 +00:00
const [ setWalkthrough ] = useMutation (
gql `
mutation setWalkthrough ( $upvotePopover : Boolean , $tipPopover : Boolean ) {
setWalkthrough ( upvotePopover : $upvotePopover , tipPopover : $tipPopover )
} `
)
2024-07-01 17:02:29 +00:00
const [ controller , setController ] = useState ( null )
2024-08-02 22:41:47 +00:00
const [ pending , setPending ] = useState ( 0 )
2024-05-28 17:18:54 +00:00
2023-12-20 01:55:19 +00:00
const setVoteShow = useCallback ( ( yes ) => {
if ( ! me ) return
// if they haven't seen the walkthrough and they have sats
if ( yes && ! me . privates ? . upvotePopover && me . privates ? . sats ) {
_setVoteShow ( true )
}
if ( voteShow && ! yes ) {
_setVoteShow ( false )
setWalkthrough ( { variables : { upvotePopover : true } } )
}
} , [ me , voteShow , setWalkthrough ] )
const setTipShow = useCallback ( ( yes ) => {
if ( ! me ) return
// if we want to show it, yet we still haven't shown
if ( yes && ! me . privates ? . tipPopover && me . privates ? . sats ) {
_setTipShow ( true )
}
// if it's currently showing and we want to hide it
if ( tipShow && ! yes ) {
_setTipShow ( false )
setWalkthrough ( { variables : { tipPopover : true } } )
}
} , [ me , tipShow , setWalkthrough ] )
2023-12-27 02:27:52 +00:00
const zap = useZap ( )
2023-07-09 16:15:46 +00:00
2023-08-28 14:40:29 +00:00
const disabled = useMemo ( ( ) => item ? . mine || item ? . meForward || item ? . deletedAt ,
[ item ? . mine , item ? . meForward , item ? . deletedAt ] )
2023-07-09 16:15:46 +00:00
2024-02-20 23:46:27 +00:00
const [ meSats , overlayText , color , nextColor ] = useMemo ( ( ) => {
2024-09-12 18:05:11 +00:00
const meSats = ( me ? item ? . meSats : item ? . meAnonSats ) || 0
2022-12-09 19:25:38 +00:00
2023-07-11 18:33:13 +00:00
// what should our next tip be?
2024-08-02 22:41:47 +00:00
const sats = pending || nextTip ( meSats , { ... me ? . privates } )
2024-07-27 03:37:03 +00:00
let overlayTextContent
if ( me ) {
overlayTextContent = me . privates ? . tipRandom ? 'random' : numWithUnits ( sats , { abbreviate : false } )
} else {
overlayTextContent = 'zap it'
}
2021-09-12 16:55:38 +00:00
2024-02-20 23:46:27 +00:00
return [
2024-07-27 03:37:03 +00:00
meSats , overlayTextContent ,
2024-02-27 00:09:09 +00:00
getColor ( meSats ) , getColor ( meSats + sats ) ]
2024-08-02 22:41:47 +00:00
} , [
2024-09-12 18:05:11 +00:00
me , item ? . meSats , item ? . meAnonSats , me ? . privates ? . tipDefault , me ? . privates ? . turboDefault ,
2024-08-02 22:41:47 +00:00
me ? . privates ? . tipRandom , me ? . privates ? . tipRandomMin , me ? . privates ? . tipRandomMax , pending ] )
2023-01-12 23:53:09 +00:00
2024-04-12 23:37:04 +00:00
const handleModalClosed = ( ) => {
setHover ( false )
}
const handleLongPress = ( e ) => {
if ( ! item ) return
// we can't tip ourselves
if ( disabled ) {
return
}
setTipShow ( false )
2024-05-28 17:18:54 +00:00
if ( pending ) {
controller . abort ( )
2024-06-06 13:22:05 +00:00
setController ( null )
2024-05-28 17:18:54 +00:00
return
}
2024-08-02 22:41:47 +00:00
const c = new ZapUndoController ( { onStart : ( sats ) => setPending ( sats ) , onDone : ( ) => setPending ( 0 ) } )
2024-05-28 17:18:54 +00:00
setController ( c )
2024-04-12 23:37:04 +00:00
showModal ( onClose =>
2024-07-01 17:02:29 +00:00
< ItemAct onClose = { onClose } item = { item } abortSignal = { c . signal } / > , { onClose : handleModalClosed } )
2024-04-12 23:37:04 +00:00
}
2024-05-28 17:18:54 +00:00
const handleShortPress = async ( ) => {
2024-04-12 23:37:04 +00:00
if ( me ) {
if ( ! item ) return
// we can't tip ourselves
if ( disabled ) {
return
}
if ( meSats ) {
setVoteShow ( false )
} else {
setTipShow ( true )
}
2024-05-28 17:18:54 +00:00
if ( pending ) {
controller . abort ( )
2024-06-06 13:22:05 +00:00
setController ( null )
2024-05-28 17:18:54 +00:00
return
}
2024-08-02 22:41:47 +00:00
const c = new ZapUndoController ( { onStart : ( sats ) => setPending ( sats ) , onDone : ( ) => setPending ( 0 ) } )
2024-05-28 17:18:54 +00:00
setController ( c )
2024-07-01 17:02:29 +00:00
await zap ( { item , me , abortSignal : c . signal } )
2024-04-12 23:37:04 +00:00
} else {
2024-07-01 17:02:29 +00:00
showModal ( onClose => < ItemAct onClose = { onClose } item = { item } / > , { onClose : handleModalClosed } )
2024-04-12 23:37:04 +00:00
}
}
2024-05-07 19:20:22 +00:00
2024-09-20 15:41:46 +00:00
const style = useMemo ( ( ) => {
const fillColor = pending || hover ? nextColor : color
return meSats || hover || pending
? {
fill : fillColor ,
filter : ` drop-shadow(0 0 6px ${ fillColor } 90) `
}
: undefined
} , [ hover , pending , nextColor , color , meSats ] )
2024-05-07 19:20:22 +00:00
2021-04-22 22:14:32 +00:00
return (
2023-12-27 02:27:52 +00:00
< div ref = { ref } className = 'upvoteParent' >
< LongPressable
2024-04-12 23:37:04 +00:00
onLongPress = { handleLongPress }
onShortPress = { handleShortPress }
2023-12-27 02:27:52 +00:00
>
< ActionTooltip notForm disable = { disabled } overlayText = { overlayText } >
< div
2024-09-19 18:13:14 +00:00
className = { classNames ( disabled && styles . noSelfTips , styles . upvoteWrapper ) }
2021-12-09 20:40:40 +00:00
>
2023-12-27 02:27:52 +00:00
< UpBolt
2024-03-02 00:32:40 +00:00
onPointerEnter = { ( ) => setHover ( true ) }
2024-02-20 23:46:27 +00:00
onMouseLeave = { ( ) => setHover ( false ) }
2024-03-02 00:32:40 +00:00
onTouchEnd = { ( ) => setHover ( false ) }
2023-12-27 02:27:52 +00:00
width = { 26 }
height = { 26 }
2024-09-19 18:13:14 +00:00
className = { classNames ( styles . upvote ,
className ,
disabled && styles . noSelfTips ,
meSats && styles . voted ,
pending && styles . pending ) }
style = { style }
2023-12-27 02:27:52 +00:00
/ >
< / d i v >
< / A c t i o n T o o l t i p >
< / L o n g P r e s s a b l e >
< TipPopover target = { ref . current } show = { tipShow } handleClose = { ( ) => setTipShow ( false ) } / >
< UpvotePopover target = { ref . current } show = { voteShow } handleClose = { ( ) => setVoteShow ( false ) } / >
< / d i v >
2021-04-22 22:14:32 +00:00
)
}