diff --git a/common/like.ts b/common/like.ts index 7ec14726..303a3841 100644 --- a/common/like.ts +++ b/common/like.ts @@ -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 diff --git a/web/components/button.tsx b/web/components/button.tsx index 8e00a077..e2e1e20d 100644 --- a/web/components/button.tsx +++ b/web/components/button.tsx @@ -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} diff --git a/web/components/contract/like-market-button.tsx b/web/components/contract/like-market-button.tsx index d31b630e..1650f2f4 100644 --- a/web/components/contract/like-market-button.tsx +++ b/web/components/contract/like-market-button.tsx @@ -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) - likeContract(user, contract).catch(() => setIsLiking(false)) - toast(`You tipped ${contract.creatorName} ${formatMoney(LIKE_TIP_AMOUNT)}!`) + const timeoutId = setTimeout(() => { + likeContract(user, contract).catch(() => setIsLiking(false)) + }, 3000) + toast.custom( + () => ( + { + clearTimeout(timeoutId) + }} + /> + ), + { duration: TIP_UNDO_DURATION } + ) } return ( diff --git a/web/components/contract/tip-button.tsx b/web/components/contract/tip-button.tsx index 3a928385..06c15df2 100644 --- a/web/components/contract/tip-button.tsx +++ b/web/components/contract/tip-button.tsx @@ -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: { setHover(true)} + onMouseOver={() => { + if (!disabled) { + setHover(true) + } + }} onMouseLeave={() => setHover(false)} > - + +
+ +
{ - 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( + () => ( + { + 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 ( addTip(+LIKE_TIP_AMOUNT)} - userTipped={localTip > 0} + userTipped={tempTip > 0 || myTip > 0} disabled={!canUp} isCompact /> ) } + +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 ( +
+
+ +
+ Tipping {userName} {formatMoney(LIKE_TIP_AMOUNT)}... +
+
+ Cancelled tipping +
+ +
+
+ ) +} diff --git a/web/public/custom-components/coin.tsx b/web/public/custom-components/coin.tsx new file mode 100644 index 00000000..ee87cd89 --- /dev/null +++ b/web/public/custom-components/coin.tsx @@ -0,0 +1,31 @@ +import { rootCertificates } from 'tls' + +export default function Coin({ + size = 18, + color = '#66667C', + strokeWidth = 1.5, + fill = 'none', +}) { + return ( + + + + + ) +} diff --git a/web/tailwind.config.js b/web/tailwind.config.js index a21a595c..1b5ee1d3 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -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',