added cancelling tipping and lil coin (#1047)
* added cancelling tipping and lil coin * forced timeout to fix weird toast bug
This commit is contained in:
parent
3f8988bf27
commit
4359ad0530
|
@ -6,3 +6,4 @@ export type Like = {
|
|||
tipTxnId?: string // only holds most recent tip txn id
|
||||
}
|
||||
export const LIKE_TIP_AMOUNT = 10
|
||||
export const TIP_UNDO_DURATION = 2000
|
||||
|
|
|
@ -108,7 +108,7 @@ export function IconButton(props: {
|
|||
className={clsx(
|
||||
'inline-flex items-center justify-center transition-colors disabled:cursor-not-allowed',
|
||||
sizeClasses[size],
|
||||
'disabled:text-greyscale-2 text-greyscale-6 hover:text-indigo-600',
|
||||
'disabled:text-greyscale-2 text-greyscale-5 hover:text-greyscale-6',
|
||||
className
|
||||
)}
|
||||
disabled={disabled || loading}
|
||||
|
|
|
@ -3,13 +3,13 @@ import { Contract } from 'common/contract'
|
|||
import { User } from 'common/user'
|
||||
import { useUserLikes } from 'web/hooks/use-likes'
|
||||
import toast from 'react-hot-toast'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import { likeContract } from 'web/lib/firebase/likes'
|
||||
import { LIKE_TIP_AMOUNT } from 'common/like'
|
||||
import { LIKE_TIP_AMOUNT, TIP_UNDO_DURATION } from 'common/like'
|
||||
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||
import { useMarketTipTxns } from 'web/hooks/use-tip-txns'
|
||||
import { sum } from 'lodash'
|
||||
import { TipButton } from './tip-button'
|
||||
import { TipToast } from '../tipper'
|
||||
|
||||
export function LikeMarketButton(props: {
|
||||
contract: Contract
|
||||
|
@ -35,8 +35,20 @@ export function LikeMarketButton(props: {
|
|||
if (!user) return firebaseLogin()
|
||||
|
||||
setIsLiking(true)
|
||||
const timeoutId = setTimeout(() => {
|
||||
likeContract(user, contract).catch(() => setIsLiking(false))
|
||||
toast(`You tipped ${contract.creatorName} ${formatMoney(LIKE_TIP_AMOUNT)}!`)
|
||||
}, 3000)
|
||||
toast.custom(
|
||||
() => (
|
||||
<TipToast
|
||||
userName={contract.creatorUsername}
|
||||
onUndoClick={() => {
|
||||
clearTimeout(timeoutId)
|
||||
}}
|
||||
/>
|
||||
),
|
||||
{ duration: TIP_UNDO_DURATION }
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Col } from 'web/components/layout/col'
|
|||
import { Tooltip } from '../tooltip'
|
||||
import TipJar from 'web/public/custom-components/tipJar'
|
||||
import { useState } from 'react'
|
||||
import Coin from 'web/public/custom-components/coin'
|
||||
|
||||
export function TipButton(props: {
|
||||
tipAmount: number
|
||||
|
@ -23,7 +24,7 @@ export function TipButton(props: {
|
|||
<Tooltip
|
||||
text={
|
||||
disabled
|
||||
? `Tips (${formatMoney(totalTipped)})`
|
||||
? `Total tips ${formatMoney(totalTipped)}`
|
||||
: `Tip ${formatMoney(tipAmount)}`
|
||||
}
|
||||
placement="bottom"
|
||||
|
@ -35,16 +36,44 @@ export function TipButton(props: {
|
|||
disabled={disabled}
|
||||
className={clsx(
|
||||
'px-2 py-1 text-xs', //2xs button
|
||||
'text-greyscale-6 transition-transform hover:text-indigo-600 disabled:cursor-not-allowed',
|
||||
!disabled ? 'hover:rotate-12' : ''
|
||||
'text-greyscale-5 transition-transform disabled:cursor-not-allowed',
|
||||
!disabled ? 'hover:text-greyscale-6' : ''
|
||||
)}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOver={() => {
|
||||
if (!disabled) {
|
||||
setHover(true)
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
>
|
||||
<Col className={clsx('relative', disabled ? 'opacity-30' : '')}>
|
||||
<Col className={clsx('relative')}>
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute transition-all',
|
||||
hover ? 'left-[6px] -top-[9px]' : 'left-[8px] -top-[10px]'
|
||||
)}
|
||||
>
|
||||
<Coin
|
||||
size={10}
|
||||
color={
|
||||
hover && !userTipped
|
||||
? '#66667C'
|
||||
: userTipped
|
||||
? '#4f46e5'
|
||||
: '#9191a7'
|
||||
}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</div>
|
||||
<TipJar
|
||||
size={18}
|
||||
color={userTipped || (hover && !disabled) ? '#4f46e5' : '#66667C'}
|
||||
color={
|
||||
hover && !disabled && !userTipped
|
||||
? '#66667C'
|
||||
: userTipped
|
||||
? '#4f46e5'
|
||||
: '#9191a7'
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className={clsx(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
import { Comment } from 'common/comment'
|
||||
import { User } from 'common/user'
|
||||
|
@ -9,8 +8,10 @@ import { transact } from 'web/lib/firebase/api'
|
|||
import { track } from 'web/lib/service/analytics'
|
||||
import { TipButton } from './contract/tip-button'
|
||||
import { Row } from './layout/row'
|
||||
import { LIKE_TIP_AMOUNT } from 'common/like'
|
||||
import { LIKE_TIP_AMOUNT, TIP_UNDO_DURATION } from 'common/like'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import { Button } from './button'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function Tipper(prop: {
|
||||
comment: Comment
|
||||
|
@ -19,24 +20,13 @@ export function Tipper(prop: {
|
|||
}) {
|
||||
const { comment, myTip, totalTip } = prop
|
||||
|
||||
// This is a temporary tipping amount before it actually gets confirmed. This is so tha we dont accidentally tip more than you have
|
||||
const [tempTip, setTempTip] = useState(0)
|
||||
|
||||
const me = useUser()
|
||||
|
||||
const [localTip, setLocalTip] = useState(myTip)
|
||||
|
||||
// listen for user being set
|
||||
const initialized = useRef(false)
|
||||
useEffect(() => {
|
||||
if (myTip && !initialized.current) {
|
||||
setLocalTip(myTip)
|
||||
initialized.current = true
|
||||
}
|
||||
}, [myTip])
|
||||
|
||||
const total = totalTip - myTip + localTip
|
||||
|
||||
// declare debounced function only on first render
|
||||
const [saveTip] = useState(() =>
|
||||
debounce(async (user: User, comment: Comment, change: number) => {
|
||||
const [saveTip] = useState(
|
||||
() => async (user: User, comment: Comment, change: number) => {
|
||||
if (change === 0) {
|
||||
return
|
||||
}
|
||||
|
@ -67,30 +57,88 @@ export function Tipper(prop: {
|
|||
fromId: user.id,
|
||||
toId: comment.userId,
|
||||
})
|
||||
}, 1500)
|
||||
}
|
||||
)
|
||||
// instant save on unrender
|
||||
useEffect(() => () => void saveTip.flush(), [saveTip])
|
||||
|
||||
const addTip = (delta: number) => {
|
||||
setLocalTip(localTip + delta)
|
||||
me && saveTip(me, comment, localTip - myTip + delta)
|
||||
toast(`You tipped ${comment.userName} ${formatMoney(LIKE_TIP_AMOUNT)}!`)
|
||||
setTempTip(tempTip + delta)
|
||||
const timeoutId = setTimeout(() => {
|
||||
me &&
|
||||
saveTip(me, comment, delta)
|
||||
.then(() => setTempTip(tempTip - delta))
|
||||
.catch((e) => console.error(e))
|
||||
}, TIP_UNDO_DURATION + 1000)
|
||||
toast.custom(
|
||||
() => (
|
||||
<TipToast
|
||||
userName={comment.userName}
|
||||
onUndoClick={() => {
|
||||
clearTimeout(timeoutId)
|
||||
setTempTip(tempTip - delta)
|
||||
}}
|
||||
/>
|
||||
),
|
||||
{ duration: TIP_UNDO_DURATION }
|
||||
)
|
||||
}
|
||||
|
||||
const canUp =
|
||||
me && comment.userId !== me.id && me.balance >= localTip + LIKE_TIP_AMOUNT
|
||||
me && comment.userId !== me.id && me.balance - tempTip >= LIKE_TIP_AMOUNT
|
||||
|
||||
return (
|
||||
<Row className="items-center gap-0.5">
|
||||
<TipButton
|
||||
tipAmount={LIKE_TIP_AMOUNT}
|
||||
totalTipped={total}
|
||||
totalTipped={totalTip}
|
||||
onClick={() => addTip(+LIKE_TIP_AMOUNT)}
|
||||
userTipped={localTip > 0}
|
||||
userTipped={tempTip > 0 || myTip > 0}
|
||||
disabled={!canUp}
|
||||
isCompact
|
||||
/>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export function TipToast(props: { userName: string; onUndoClick: () => void }) {
|
||||
const { userName, onUndoClick } = props
|
||||
const [cancelled, setCancelled] = useState(false)
|
||||
|
||||
// There is a strange bug with toast where sometimes if you interact with one popup, the others will not dissappear at the right time, overriding it for now with this
|
||||
const [timedOut, setTimedOut] = useState(false)
|
||||
setTimeout(() => {
|
||||
setTimedOut(true)
|
||||
}, TIP_UNDO_DURATION)
|
||||
if (timedOut) {
|
||||
return <></>
|
||||
}
|
||||
return (
|
||||
<div className="relative overflow-hidden rounded-lg bg-white drop-shadow-md">
|
||||
<div
|
||||
className={clsx(
|
||||
'animate-progress-loading absolute bottom-0 z-10 h-1 w-full bg-indigo-600',
|
||||
cancelled ? 'hidden' : ''
|
||||
)}
|
||||
/>
|
||||
<Row className="text-greyscale-6 items-center gap-4 px-4 py-2 text-sm">
|
||||
<div className={clsx(cancelled ? 'hidden' : 'inline')}>
|
||||
Tipping {userName} {formatMoney(LIKE_TIP_AMOUNT)}...
|
||||
</div>
|
||||
<div className={clsx('py-1', cancelled ? 'inline' : 'hidden')}>
|
||||
Cancelled tipping
|
||||
</div>
|
||||
<Button
|
||||
className={clsx(cancelled ? 'hidden' : 'inline')}
|
||||
size="xs"
|
||||
color="gray-outline"
|
||||
onClick={() => {
|
||||
onUndoClick()
|
||||
setCancelled(true)
|
||||
}}
|
||||
disabled={cancelled}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
31
web/public/custom-components/coin.tsx
Normal file
31
web/public/custom-components/coin.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { rootCertificates } from 'tls'
|
||||
|
||||
export default function Coin({
|
||||
size = 18,
|
||||
color = '#66667C',
|
||||
strokeWidth = 1.5,
|
||||
fill = 'none',
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 18 18"
|
||||
width={size}
|
||||
height={size}
|
||||
fill={fill}
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
opacity={50}
|
||||
transform="rotate(-30)"
|
||||
>
|
||||
<path
|
||||
className="cls-2"
|
||||
d="M15,9c0,.35-.07,.68-.2,1-.66,1.73-3.01,3-5.8,3s-5.14-1.27-5.8-3c-.13-.32-.2-.65-.2-1,0-2.21,2.69-4,6-4s6,1.79,6,4Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
d="M15,9v2c0,2.21-2.69,4-6,4s-6-1.79-6-4v-2c0,.35,.07,.68,.2,1,.66,1.73,3.01,3,5.8,3s5.14-1.27,5.8-3c.13-.32,.2-.65,.2-1Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -16,6 +16,15 @@ module.exports = {
|
|||
}
|
||||
),
|
||||
extend: {
|
||||
keyframes: {
|
||||
progress: {
|
||||
'0%': { width: '0%' },
|
||||
'100%': { width: '100%' },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'progress-loading': 'progress 2s linear',
|
||||
},
|
||||
colors: {
|
||||
primary: '#11b981',
|
||||
'primary-focus': '#069668',
|
||||
|
|
Loading…
Reference in New Issue
Block a user