Remove undo. Show full tip amount. Linear scale. (#573)

This commit is contained in:
Sinclair Chen 2022-06-27 11:18:15 -05:00 committed by GitHub
parent c1765ca0cb
commit 54356b8d2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -7,8 +7,8 @@ import clsx from 'clsx'
import { Comment } from 'common/comment' import { Comment } from 'common/comment'
import { User } from 'common/user' import { User } from 'common/user'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { debounce, sumBy } from 'lodash' import { debounce, sum } from 'lodash'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { CommentTips } from 'web/hooks/use-tip-txns' import { CommentTips } from 'web/hooks/use-tip-txns'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { transact } from 'web/lib/firebase/fn-call' import { transact } from 'web/lib/firebase/fn-call'
@ -16,33 +16,24 @@ import { track } from 'web/lib/service/analytics'
import { Row } from './layout/row' import { Row } from './layout/row'
import { Tooltip } from './tooltip' import { Tooltip } from './tooltip'
// xth triangle number * 5 = 5 + 10 + 15 + ... + (x * 5)
const quad = (x: number) => (5 / 2) * x * (x + 1)
// inverse (see https://math.stackexchange.com/questions/2041988/how-to-get-inverse-of-formula-for-sum-of-integers-from-1-to-nsee )
const invQuad = (y: number) => Math.sqrt((2 / 5) * y + 1 / 4) - 1 / 2
export function Tipper(prop: { comment: Comment; tips: CommentTips }) { export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
const { comment, tips } = prop const { comment, tips } = prop
const me = useUser() const me = useUser()
const myId = me?.id ?? '' const myId = me?.id ?? ''
const savedTip = tips[myId] as number | undefined const savedTip = tips[myId] ?? 0
// optimistically increase the tip count, but debounce the update const [localTip, setLocalTip] = useState(savedTip)
const [localTip, setLocalTip] = useState(savedTip ?? 0) // listen for user being set
const initialized = useRef(false) const initialized = useRef(false)
useEffect(() => { useEffect(() => {
if (savedTip && !initialized.current) { if (tips[myId] && !initialized.current) {
setLocalTip(savedTip) setLocalTip(tips[myId])
initialized.current = true initialized.current = true
} }
}, [savedTip]) }, [tips, myId])
const score = useMemo(() => { const total = sum(Object.values(tips)) - savedTip + localTip
const tipVals = Object.values({ ...tips, [myId]: localTip })
return sumBy(tipVals, invQuad)
}, [localTip, tips, myId])
// declare debounced function only on first render // declare debounced function only on first render
const [saveTip] = useState(() => const [saveTip] = useState(() =>
@ -80,7 +71,7 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
const changeTip = (tip: number) => { const changeTip = (tip: number) => {
setLocalTip(tip) setLocalTip(tip)
me && saveTip(me, tip - (savedTip ?? 0)) me && saveTip(me, tip - savedTip)
} }
return ( return (
@ -88,13 +79,13 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
<DownTip <DownTip
value={localTip} value={localTip}
onChange={changeTip} onChange={changeTip}
disabled={!me || localTip <= 0} disabled={!me || localTip <= savedTip}
/> />
<span className="font-bold">{Math.floor(score)} </span> <span className="font-bold">{Math.floor(total)}</span>
<UpTip <UpTip
value={localTip} value={localTip}
onChange={changeTip} onChange={changeTip}
disabled={!me || me.id === comment.userId} disabled={!me || me.id === comment.userId || me.balance < localTip + 5}
/> />
{localTip === 0 ? ( {localTip === 0 ? (
'' ''
@ -118,16 +109,15 @@ function DownTip(prop: {
disabled?: boolean disabled?: boolean
}) { }) {
const { onChange, value, disabled } = prop const { onChange, value, disabled } = prop
const marginal = 5 * invQuad(value)
return ( return (
<Tooltip <Tooltip
className="tooltip-bottom" className="tooltip-bottom"
text={!disabled && `Refund ${formatMoney(marginal)}`} text={!disabled && `-${formatMoney(5)}`}
> >
<button <button
className="flex h-max items-center hover:text-red-600 disabled:text-gray-300" className="flex h-max items-center hover:text-red-600 disabled:text-gray-300"
disabled={disabled} disabled={disabled}
onClick={() => onChange(value - marginal)} onClick={() => onChange(value - 5)}
> >
<ChevronLeftIcon className="h-6 w-6" /> <ChevronLeftIcon className="h-6 w-6" />
</button> </button>
@ -141,19 +131,18 @@ function UpTip(prop: {
disabled?: boolean disabled?: boolean
}) { }) {
const { onChange, value, disabled } = prop const { onChange, value, disabled } = prop
const marginal = 5 * invQuad(value) + 5
return ( return (
<Tooltip <Tooltip
className="tooltip-bottom" className="tooltip-bottom"
text={!disabled && `Tip ${formatMoney(marginal)}`} text={!disabled && `Tip ${formatMoney(5)}`}
> >
<button <button
className="hover:text-primary flex h-max items-center disabled:text-gray-300" className="hover:text-primary flex h-max items-center disabled:text-gray-300"
disabled={disabled} disabled={disabled}
onClick={() => onChange(value + marginal)} onClick={() => onChange(value + 5)}
> >
{value >= quad(2) ? ( {value >= 10 ? (
<ChevronDoubleRightIcon className="text-primary mx-1 h-6 w-6" /> <ChevronDoubleRightIcon className="text-primary mx-1 h-6 w-6" />
) : value > 0 ? ( ) : value > 0 ? (
<ChevronRightIcon className="text-primary h-6 w-6" /> <ChevronRightIcon className="text-primary h-6 w-6" />