Inga/tip button (#1043)

* added tip jar
* made market actions/comments and manalink buttons IconButtons
This commit is contained in:
ingawei 2022-10-13 01:53:26 -05:00 committed by GitHub
parent 4e5b78f4ee
commit 5ba4a9dce7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 323 additions and 298 deletions

View File

@ -82,3 +82,39 @@ export function Button(props: {
</button> </button>
) )
} }
export function IconButton(props: {
className?: string
onClick?: MouseEventHandler<any> | undefined
children?: ReactNode
size?: SizeType
type?: 'button' | 'reset' | 'submit'
disabled?: boolean
loading?: boolean
}) {
const {
children,
className,
onClick,
size = 'md',
type = 'button',
disabled = false,
loading,
} = props
return (
<button
type={type}
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',
className
)}
disabled={disabled || loading}
onClick={onClick}
>
{children}
</button>
)
}

View File

@ -19,11 +19,9 @@ import ShortToggle from '../widgets/short-toggle'
import { DuplicateContractButton } from '../duplicate-contract-button' import { DuplicateContractButton } from '../duplicate-contract-button'
import { Row } from '../layout/row' import { Row } from '../layout/row'
import { BETTORS, User } from 'common/user' import { BETTORS, User } from 'common/user'
import { Button } from '../button' import { IconButton } from '../button'
import { AddLiquidityButton } from './add-liquidity-button' import { AddLiquidityButton } from './add-liquidity-button'
import { Tooltip } from '../tooltip'
export const contractDetailsButtonClassName =
'group flex items-center rounded-md px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-100 text-gray-400 hover:text-gray-500'
export function ContractInfoDialog(props: { export function ContractInfoDialog(props: {
contract: Contract contract: Contract
@ -84,17 +82,17 @@ export function ContractInfoDialog(props: {
return ( return (
<> <>
<Button <Tooltip text="Market details" placement="bottom" noTap noFade>
size="sm" <IconButton
color="gray-white" size="2xs"
className={clsx(contractDetailsButtonClassName, className)} className={clsx(className)}
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
> >
<DotsHorizontalIcon <DotsHorizontalIcon
className={clsx('h-5 w-5 flex-shrink-0')} className={clsx('h-5 w-5 flex-shrink-0')}
aria-hidden="true" aria-hidden="true"
/> />
</Button> </IconButton>
<Modal open={open} setOpen={setOpen}> <Modal open={open} setOpen={setOpen}>
<Col className="gap-4 rounded bg-white p-6"> <Col className="gap-4 rounded bg-white p-6">
@ -186,7 +184,8 @@ export function ContractInfoDialog(props: {
<td> <td>
{mechanism === 'cpmm-1' && outcomeType === 'BINARY' {mechanism === 'cpmm-1' && outcomeType === 'BINARY'
? `${Math.round(pool.YES)} YES, ${Math.round(pool.NO)} NO` ? `${Math.round(pool.YES)} YES, ${Math.round(pool.NO)} NO`
: mechanism === 'cpmm-1' && outcomeType === 'PSEUDO_NUMERIC' : mechanism === 'cpmm-1' &&
outcomeType === 'PSEUDO_NUMERIC'
? `${Math.round(pool.YES)} HIGHER, ${Math.round( ? `${Math.round(pool.YES)} HIGHER, ${Math.round(
pool.NO pool.NO
)} LOWER` )} LOWER`
@ -249,6 +248,7 @@ export function ContractInfoDialog(props: {
</Row> </Row>
</Col> </Col>
</Modal> </Modal>
</Tooltip>
</> </>
) )
} }

View File

@ -2,7 +2,7 @@ import { ShareIcon } from '@heroicons/react/outline'
import { Row } from '../layout/row' import { Row } from '../layout/row'
import { Contract } from 'web/lib/firebase/contracts' import { Contract } from 'web/lib/firebase/contracts'
import React, { useState } from 'react' import React, { useState } from 'react'
import { Button } from 'web/components/button' import { IconButton } from 'web/components/button'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { ShareModal } from './share-modal' import { ShareModal } from './share-modal'
import { FollowMarketButton } from 'web/components/follow-market-button' import { FollowMarketButton } from 'web/components/follow-market-button'
@ -16,15 +16,14 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
const [isShareOpen, setShareOpen] = useState(false) const [isShareOpen, setShareOpen] = useState(false)
return ( return (
<Row> <Row className="gap-1">
<FollowMarketButton contract={contract} user={user} /> <FollowMarketButton contract={contract} user={user} />
<LikeMarketButton contract={contract} user={user} /> <LikeMarketButton contract={contract} user={user} />
<Tooltip text="Share" placement="bottom" noTap noFade> <Tooltip text="Share" placement="bottom" noTap noFade>
<Button <IconButton
size="sm" size="2xs"
color="gray-white"
className={'flex'} className={'flex'}
onClick={() => setShareOpen(true)} onClick={() => setShareOpen(true)}
> >
@ -35,7 +34,7 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
contract={contract} contract={contract}
user={user} user={user}
/> />
</Button> </IconButton>
</Tooltip> </Tooltip>
<ContractInfoDialog contract={contract} user={user} /> <ContractInfoDialog contract={contract} user={user} />

View File

@ -1,10 +1,9 @@
import clsx from 'clsx' import clsx from 'clsx'
import { HeartIcon } from '@heroicons/react/outline'
import { Button } from 'web/components/button'
import { formatMoney, shortFormatNumber } from 'common/util/format' import { formatMoney, shortFormatNumber } from 'common/util/format'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
import { Tooltip } from '../tooltip' import { Tooltip } from '../tooltip'
import TipJar from 'web/public/custom-components/tipJar'
import { useState } from 'react'
export function TipButton(props: { export function TipButton(props: {
tipAmount: number tipAmount: number
@ -14,11 +13,12 @@ export function TipButton(props: {
isCompact?: boolean isCompact?: boolean
disabled?: boolean disabled?: boolean
}) { }) {
const { tipAmount, totalTipped, userTipped, isCompact, onClick, disabled } = const { tipAmount, totalTipped, userTipped, onClick, disabled } = props
props
const tipDisplay = shortFormatNumber(Math.ceil(totalTipped / 10)) const tipDisplay = shortFormatNumber(Math.ceil(totalTipped / 10))
const [hover, setHover] = useState(false)
return ( return (
<Tooltip <Tooltip
text={ text={
@ -30,39 +30,40 @@ export function TipButton(props: {
noTap noTap
noFade noFade
> >
<Button <button
size={'sm'}
className={clsx(
'max-w-xs self-center',
isCompact && 'px-0 py-0',
disabled && 'hover:bg-inherit'
)}
color={'gray-white'}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
>
<Col className={'relative items-center sm:flex-row'}>
<HeartIcon
className={clsx( className={clsx(
'h-5 w-5', 'px-2 py-1 text-xs', //2xs button
totalTipped > 0 ? 'mr-2' : '', 'text-greyscale-6 transition-transform hover:text-indigo-600 disabled:cursor-not-allowed',
userTipped ? 'fill-teal-500 text-teal-500' : '' !disabled ? 'hover:rotate-12' : ''
)} )}
onMouseOver={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<Col className={clsx('relative', disabled ? 'opacity-30' : '')}>
<TipJar
size={18}
color={userTipped || (hover && !disabled) ? '#4f46e5' : '#66667C'}
fill={userTipped ? '#4f46e5' : 'none'}
/> />
{totalTipped > 0 && (
<div <div
className={clsx( className={clsx(
'bg-greyscale-5 absolute ml-3.5 mt-2 h-4 w-4 rounded-full align-middle text-white sm:mt-3 sm:h-5 sm:w-5 sm:px-1', ' absolute top-[2px] text-[0.5rem]',
tipDisplay.length > 2 userTipped ? 'text-white' : '',
? 'text-[0.4rem] sm:text-[0.5rem]' tipDisplay.length === 1
: 'sm:text-2xs text-[0.5rem]' ? 'left-[7px]'
: tipDisplay.length === 2
? 'left-[4.5px]'
: tipDisplay.length > 2
? 'left-[4px] top-[2.5px] text-[0.35rem]'
: ''
)} )}
> >
{tipDisplay} {totalTipped > 0 ? tipDisplay : ''}
</div> </div>
)}
</Col> </Col>
</Button> </button>
</Tooltip> </Tooltip>
) )
} }

View File

@ -24,7 +24,7 @@ import { UserLink } from 'web/components/user-link'
import { CommentInput } from '../comment-input' import { CommentInput } from '../comment-input'
import { AwardBountyButton } from 'web/components/award-bounty-button' import { AwardBountyButton } from 'web/components/award-bounty-button'
import { ReplyIcon } from '@heroicons/react/solid' import { ReplyIcon } from '@heroicons/react/solid'
import { Button } from '../button' import { IconButton } from '../button'
import { ReplyToggle } from '../comments/reply-toggle' import { ReplyToggle } from '../comments/reply-toggle'
export type ReplyTo = { id: string; username: string } export type ReplyTo = { id: string; username: string }
@ -154,33 +154,43 @@ export function ParentFeedComment(props: {
numComments={numComments} numComments={numComments}
onClick={onSeeReplyClick} onClick={onSeeReplyClick}
/> />
<Row className="grow justify-end gap-2"> <CommentActions
onReplyClick={onReplyClick}
comment={comment}
showTip={showTip}
myTip={myTip}
totalTip={totalTip}
contract={contract}
/>
</Row>
</Col>
</Row>
)
}
export function CommentActions(props: {
onReplyClick?: (comment: ContractComment) => void
comment: ContractComment
showTip?: boolean
myTip?: number
totalTip?: number
contract: Contract
}) {
const { onReplyClick, comment, showTip, myTip, totalTip, contract } = props
return (
<Row className="grow justify-end">
{onReplyClick && ( {onReplyClick && (
<Button <IconButton size={'xs'} onClick={() => onReplyClick(comment)}>
size={'sm'}
className={clsx(
'hover:bg-greyscale-2 mt-0 mb-1 max-w-xs px-0 py-0'
)}
color={'gray-white'}
onClick={() => onReplyClick(comment)}
>
<ReplyIcon className="h-5 w-5" /> <ReplyIcon className="h-5 w-5" />
</Button> </IconButton>
)} )}
{showTip && ( {showTip && (
<Tipper <Tipper comment={comment} myTip={myTip ?? 0} totalTip={totalTip ?? 0} />
comment={comment}
myTip={myTip ?? 0}
totalTip={totalTip ?? 0}
/>
)} )}
{(contract.openCommentBounties ?? 0) > 0 && ( {(contract.openCommentBounties ?? 0) > 0 && (
<AwardBountyButton comment={comment} contract={contract} /> <AwardBountyButton comment={comment} contract={contract} />
)} )}
</Row> </Row>
</Row>
</Col>
</Row>
) )
} }
@ -233,30 +243,14 @@ export const FeedComment = memo(function FeedComment(props: {
content={content || text} content={content || text}
smallImage smallImage
/> />
<Row className="grow justify-end gap-2"> <CommentActions
{onReplyClick && ( onReplyClick={onReplyClick}
<Button
size={'sm'}
className={clsx(
'hover:bg-greyscale-2 mt-0 mb-1 max-w-xs px-0 py-0'
)}
color={'gray-white'}
onClick={() => onReplyClick(comment)}
>
<ReplyIcon className="h-5 w-5" />
</Button>
)}
{showTip && (
<Tipper
comment={comment} comment={comment}
myTip={myTip ?? 0} showTip={showTip}
totalTip={totalTip ?? 0} myTip={myTip}
totalTip={totalTip}
contract={contract}
/> />
)}
{(contract.openCommentBounties ?? 0) > 0 && (
<AwardBountyButton comment={comment} contract={contract} />
)}
</Row>
</Col> </Col>
</Row> </Row>
) )

View File

@ -1,4 +1,4 @@
import { Button } from 'web/components/button' import { IconButton } from 'web/components/button'
import { import {
Contract, Contract,
followContract, followContract,
@ -33,9 +33,8 @@ export const FollowMarketButton = (props: {
noTap noTap
noFade noFade
> >
<Button <IconButton
size={'sm'} size="2xs"
color={'gray-white'}
onClick={async () => { onClick={async () => {
if (!user) return firebaseLogin() if (!user) return firebaseLogin()
if (followers?.includes(user.id)) { if (followers?.includes(user.id)) {
@ -65,18 +64,12 @@ export const FollowMarketButton = (props: {
> >
{watching ? ( {watching ? (
<Col className={'items-center gap-x-2 sm:flex-row'}> <Col className={'items-center gap-x-2 sm:flex-row'}>
<EyeOffIcon <EyeOffIcon className={clsx('h-5 w-5')} aria-hidden="true" />
className={clsx('h-5 w-5 sm:h-6 sm:w-6')}
aria-hidden="true"
/>
{/* Unwatch */} {/* Unwatch */}
</Col> </Col>
) : ( ) : (
<Col className={'items-center gap-x-2 sm:flex-row'}> <Col className={'items-center gap-x-2 sm:flex-row'}>
<EyeIcon <EyeIcon className={clsx('h-5 w-5')} aria-hidden="true" />
className={clsx('h-5 w-5 sm:h-6 sm:w-6')}
aria-hidden="true"
/>
{/* Watch */} {/* Watch */}
</Col> </Col>
)} )}
@ -87,7 +80,7 @@ export const FollowMarketButton = (props: {
followers?.includes(user?.id ?? 'nope') ? 'watched' : 'unwatched' followers?.includes(user?.id ?? 'nope') ? 'watched' : 'unwatched'
} a question!`} } a question!`}
/> />
</Button> </IconButton>
</Tooltip> </Tooltip>
) )
} }

View File

@ -9,9 +9,9 @@ import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Claim, Manalink } from 'common/manalink' import { Claim, Manalink } from 'common/manalink'
import { ShareIconButton } from './share-icon-button' import { ShareIconButton } from './share-icon-button'
import { contractDetailsButtonClassName } from './contract/contract-info-dialog'
import { useUserById } from 'web/hooks/use-user' import { useUserById } from 'web/hooks/use-user'
import getManalinkUrl from 'web/get-manalink-url' import getManalinkUrl from 'web/get-manalink-url'
import { IconButton } from './button'
export type ManalinkInfo = { export type ManalinkInfo = {
expiresTime: number | null expiresTime: number | null
@ -123,7 +123,7 @@ export function ManalinkCardFromView(props: {
src="/logo-white.svg" src="/logo-white.svg"
/> />
</Col> </Col>
<Row className="relative w-full gap-1 rounded-b-lg bg-white px-4 py-2 text-lg"> <Row className="relative w-full rounded-b-lg bg-white px-4 py-2 align-middle text-lg">
<div <div
className={clsx( className={clsx(
'my-auto mb-1 w-full', 'my-auto mb-1 w-full',
@ -133,32 +133,23 @@ export function ManalinkCardFromView(props: {
{formatMoney(amount)} {formatMoney(amount)}
</div> </div>
<button <IconButton size="2xs" onClick={() => (window.location.href = qrUrl)}>
onClick={() => (window.location.href = qrUrl)}
className={clsx(contractDetailsButtonClassName)}
>
<QrcodeIcon className="h-6 w-6" /> <QrcodeIcon className="h-6 w-6" />
</button> </IconButton>
<ShareIconButton <ShareIconButton
toastClassName={'-left-48 min-w-[250%]'} toastClassName={'-left-48 min-w-[250%]'}
buttonClassName={'transition-colors'}
onCopyButtonClassName={
'bg-gray-200 text-gray-600 transition-none hover:bg-gray-200 hover:text-gray-600'
}
copyPayload={getManalinkUrl(link.slug)} copyPayload={getManalinkUrl(link.slug)}
/> />
<button <IconButton
size="xs"
onClick={() => setShowDetails(!showDetails)} onClick={() => setShowDetails(!showDetails)}
className={clsx( className={clsx(
contractDetailsButtonClassName, showDetails ? ' text-indigo-600 hover:text-indigo-700' : ''
showDetails
? 'bg-gray-200 text-gray-600 hover:bg-gray-200 hover:text-gray-600'
: ''
)} )}
> >
<DotsHorizontalIcon className="h-[24px] w-5" /> <DotsHorizontalIcon className="h-5 w-5" />
</button> </IconButton>
</Row> </Row>
</Col> </Col>
<div className="mt-2 mb-4 text-xs text-gray-500 md:text-sm"> <div className="mt-2 mb-4 text-xs text-gray-500 md:text-sm">

View File

@ -5,34 +5,22 @@ import clsx from 'clsx'
import { copyToClipboard } from 'web/lib/util/copy' import { copyToClipboard } from 'web/lib/util/copy'
import { ToastClipboard } from 'web/components/toast-clipboard' import { ToastClipboard } from 'web/components/toast-clipboard'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { contractDetailsButtonClassName } from 'web/components/contract/contract-info-dialog' import { IconButton } from './button'
export function ShareIconButton(props: { export function ShareIconButton(props: {
buttonClassName?: string
onCopyButtonClassName?: string
toastClassName?: string toastClassName?: string
children?: React.ReactNode children?: React.ReactNode
iconClassName?: string iconClassName?: string
copyPayload: string copyPayload: string
}) { }) {
const { const { toastClassName, children, iconClassName, copyPayload } = props
buttonClassName,
onCopyButtonClassName,
toastClassName,
children,
iconClassName,
copyPayload,
} = props
const [showToast, setShowToast] = useState(false) const [showToast, setShowToast] = useState(false)
return ( return (
<div className="relative z-10 flex-shrink-0"> <div className="relative z-10 flex-shrink-0">
<button <IconButton
className={clsx( size="2xs"
contractDetailsButtonClassName, className={clsx('mt-1', showToast ? 'text-indigo-600' : '')}
buttonClassName,
showToast ? onCopyButtonClassName : ''
)}
onClick={() => { onClick={() => {
copyToClipboard(copyPayload) copyToClipboard(copyPayload)
track('copy share link') track('copy share link')
@ -41,11 +29,11 @@ export function ShareIconButton(props: {
}} }}
> >
<LinkIcon <LinkIcon
className={clsx(iconClassName ? iconClassName : 'h-[24px] w-5')} className={clsx(iconClassName ? iconClassName : 'h-5 w-5')}
aria-hidden="true" aria-hidden="true"
/> />
{children} {children}
</button> </IconButton>
{showToast && <ToastClipboard className={toastClassName} />} {showToast && <ToastClipboard className={toastClassName} />}
</div> </div>

View File

@ -0,0 +1,23 @@
export default function TipJar({
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}
>
<path d="M15.5,8.1v5.8c0,1.43-1.16,2.6-2.6,2.6H5.1c-1.44,0-2.6-1.16-2.6-2.6v-5.8c0-1.04,.89-3.25,1.5-4.1h0v-2c0-.55,.45-1,1-1H13c.55,0,1,.45,1,1v2h0c.61,.85,1.5,3.06,1.5,4.1Z" />
<line x1="4" y1="4" x2="9" y2="4" />
<line x1="11.26" y1="4" x2="14" y2="4" />
</svg>
)
}