stacker.news/components/upvote.js

236 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { LightningConsumer } from './lightning'
import UpBolt from '../svgs/bolt.svg'
import styles from './upvote.module.css'
import { gql, useMutation } from '@apollo/client'
import { signIn } from 'next-auth/client'
import { useFundError } from './fund-error'
import ActionTooltip from './action-tooltip'
import { useItemAct } from './item-act'
import { useMe } from './me'
import Rainbow from '../lib/rainbow'
import { useRef, useState } from 'react'
import LongPressable from 'react-longpressable'
import { Overlay, Popover } from 'react-bootstrap'
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 (
<Overlay
show={show}
target={target}
placement='right'
>
<Popover id='popover-basic'>
<Popover.Title className='d-flex justify-content-between alert-dismissible' as='h3'>Tipping
<button type='button' className='close' onClick={handleClose}><span aria-hidden='true'>×</span><span className='sr-only'>Close alert</span></button>
</Popover.Title>
<Popover.Content>
<div className='mb-2'>Press the bolt again to tip {me?.tipDefault || 1} more sat{me?.tipDefault > 1 ? 's' : ''}.</div>
<div>Repeatedly press the bolt to tip more sats.</div>
</Popover.Content>
</Popover>
</Overlay>
)
}
const TipPopover = ({ target, show, handleClose }) => (
<Overlay
show={show}
target={target}
placement='right'
>
<Popover id='popover-basic'>
<Popover.Title className='d-flex justify-content-between alert-dismissible' as='h3'>Press and hold
<button type='button' class='close' onClick={handleClose}><span aria-hidden='true'>×</span><span class='sr-only'>Close alert</span></button>
</Popover.Title>
<Popover.Content>
<div className='mb-2'>Press and hold bolt to tip a custom amount.</div>
<div>As you tip more, the bolt color follows the rainbow.</div>
</Popover.Content>
</Popover>
</Overlay>
)
export default function UpVote ({ item, className }) {
const { setError } = useFundError()
const { setItem } = useItemAct()
const [voteShow, _setVoteShow] = useState(false)
const [tipShow, _setTipShow] = useState(false)
const ref = useRef()
const me = useMe()
const [setWalkthrough] = useMutation(
gql`
mutation setWalkthrough($upvotePopover: Boolean, $tipPopover: Boolean) {
setWalkthrough(upvotePopover: $upvotePopover, tipPopover: $tipPopover)
}`
)
const fwd2me = me && me?.id === item?.fwdUser?.id
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) {
vote,
sats
}
}`, {
update (cache, { data: { act: { vote, sats } } }) {
cache.modify({
id: `Item:${item.id}`,
fields: {
sats (existingSats = 0) {
return existingSats + sats
},
meSats (existingSats = 0) {
if (existingSats === 0) {
setVoteShow(true)
} else {
setTipShow(true)
}
return existingSats + sats
},
upvotes (existingUpvotes = 0) {
return existingUpvotes + vote
}
}
})
}
}
)
const overlayText = () => {
if (me?.tipDefault) {
return `${me.tipDefault} sat${me.tipDefault > 1 ? 's' : ''}`
}
return '1 sat'
}
const color = getColor(item?.meSats)
return (
<LightningConsumer>
{({ strike }) =>
<div ref={ref} className='upvoteParent'>
<LongPressable
onLongPress={
async (e) => {
if (!item) return
// we can't tip ourselves
if (item?.mine || fwd2me) {
return
}
setTipShow(false)
setItem({ itemId: item.id, act, strike })
}
}
onShortPress={
me
? async (e) => {
if (!item) return
// we can't tip ourselves
if (item?.mine || fwd2me) {
return
}
if (item?.meSats) {
setVoteShow(false)
}
strike()
try {
await act({
variables: { id: item.id, sats: me.tipDefault || 1 },
optimisticResponse: {
act: {
id: `Item:${item.id}`,
sats: me.tipDefault || 1,
vote: 0
}
}
})
} catch (error) {
if (error.toString().includes('insufficient funds')) {
setError(true)
return
}
throw new Error({ message: error.toString() })
}
}
: signIn
}
>
<ActionTooltip notForm disable={item?.mine || fwd2me} overlayText={overlayText()}>
<div
className={`${item?.mine || fwd2me ? styles.noSelfTips : ''}
${styles.upvoteWrapper}`}
>
<UpBolt
width={24}
height={24}
className={
`${styles.upvote}
${className || ''}
${item?.mine || fwd2me ? styles.noSelfTips : ''}
${item?.meSats ? styles.voted : ''}`
}
style={item?.meSats
? {
fill: color,
filter: `drop-shadow(0 0 6px ${color}90)`
}
: undefined}
/>
</div>
</ActionTooltip>
</LongPressable>
<TipPopover target={ref.current} show={tipShow} handleClose={() => setTipShow(false)} />
<UpvotePopover target={ref.current} show={voteShow} handleClose={() => setVoteShow(false)} />
</div>}
</LightningConsumer>
)
}