stacker.news/components/poll.js

108 lines
3.4 KiB
JavaScript
Raw Normal View History

2022-07-30 13:25:46 +00:00
import { gql, useMutation } from '@apollo/client'
2023-07-24 18:35:05 +00:00
import Button from 'react-bootstrap/Button'
import { fixedDecimal, numWithUnits } from '../lib/format'
2022-07-30 13:25:46 +00:00
import { timeLeft } from '../lib/time'
import { useMe } from './me'
import styles from './poll.module.css'
import Check from '../svgs/checkbox-circle-fill.svg'
import { signIn } from 'next-auth/react'
2022-07-30 13:25:46 +00:00
import ActionTooltip from './action-tooltip'
import { POLL_COST } from '../lib/constants'
2023-10-06 20:01:51 +00:00
import { payOrLoginError, useInvoiceModal } from './invoice'
2022-07-30 13:25:46 +00:00
export default function Poll ({ item }) {
const me = useMe()
const [pollVote] = useMutation(
gql`
mutation pollVote($id: ID!, $hash: String, $hmac: String) {
pollVote(id: $id, hash: $hash, hmac: $hmac)
2022-07-30 13:25:46 +00:00
}`, {
update (cache, { data: { pollVote } }) {
cache.modify({
id: `Item:${item.id}`,
fields: {
poll (existingPoll) {
const poll = { ...existingPoll }
poll.meVoted = true
poll.count += 1
return poll
}
}
})
cache.modify({
id: `PollOption:${pollVote}`,
fields: {
count (existingCount) {
return existingCount + 1
},
meVoted () {
return true
}
}
})
}
}
)
const PollButton = ({ v }) => {
2023-10-06 20:01:51 +00:00
const showInvoiceModal = useInvoiceModal(async ({ hash, hmac }, { variables }) => {
await pollVote({ variables: { ...variables, hash, hmac } })
}, [pollVote])
const variables = { id: v.id }
2022-07-30 13:25:46 +00:00
return (
<ActionTooltip placement='left' notForm>
<Button
variant='outline-info' className={styles.pollButton}
onClick={me
? async () => {
2023-07-25 14:14:45 +00:00
try {
await pollVote({
2023-10-06 20:01:51 +00:00
variables,
2023-07-25 14:14:45 +00:00
optimisticResponse: {
pollVote: v.id
2022-07-30 13:25:46 +00:00
}
2023-07-25 14:14:45 +00:00
})
} catch (error) {
2023-10-06 20:01:51 +00:00
if (payOrLoginError(error)) {
showInvoiceModal({ amount: item.pollCost || POLL_COST }, { variables })
return
}
throw new Error({ message: error.toString() })
2022-07-30 13:25:46 +00:00
}
2023-07-25 14:14:45 +00:00
}
2022-07-30 13:25:46 +00:00
: signIn}
>
{v.option}
</Button>
</ActionTooltip>
)
}
const expiresIn = timeLeft(new Date(+new Date(item.createdAt) + 864e5))
const mine = item.user.id === me?.id
return (
<div className={styles.pollBox}>
{item.poll.options.map(v =>
expiresIn && !item.poll.meVoted && !mine
? <PollButton key={v.id} v={v} />
: <PollResult
key={v.id} v={v}
progress={item.poll.count ? fixedDecimal(v.count * 100 / item.poll.count, 1) : 0}
/>)}
<div className='text-muted mt-1'>{numWithUnits(item.poll.count, { unitSingular: 'vote', unitPlural: 'votes' })} \ {expiresIn ? `${expiresIn} left` : 'poll ended'}</div>
2022-07-30 13:25:46 +00:00
</div>
)
}
function PollResult ({ v, progress }) {
return (
<div className={styles.pollResult}>
<span className={styles.pollOption}>{v.option}{v.meVoted && <Check className='fill-grey ms-1 align-self-center flex-shrink-0' width={16} height={16} />}</span>
2023-07-24 18:35:05 +00:00
<span className='ms-auto me-2 align-self-center'>{progress}%</span>
2022-07-30 13:25:46 +00:00
<div className={styles.pollProgress} style={{ width: `${progress}%` }} />
</div>
)
}