new market view (#819)

* Show old details on lg, don't unfill heart

* Hide tip market if creator

* Small ui tweaks

* Remove contract. calls

* Update high-medium-low

* Remove unused bets prop

* Show uniques

* Remove unused bets prop
This commit is contained in:
Ian Philips 2022-08-30 17:13:25 -06:00 committed by GitHub
parent 3e1e84ee5e
commit aad5f6528b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 245 additions and 176 deletions

View File

@ -3,6 +3,6 @@ export type Like = {
userId: string userId: string
type: 'contract' type: 'contract'
createdTime: number createdTime: number
tipTxnId?: string tipTxnId?: string // only holds most recent tip txn id
} }
export const LIKE_TIP_AMOUNT = 5 export const LIKE_TIP_AMOUNT = 5

View File

@ -31,8 +31,7 @@ export * from './weekly-markets-emails'
export * from './reset-betting-streaks' export * from './reset-betting-streaks'
export * from './reset-weekly-emails-flag' export * from './reset-weekly-emails-flag'
export * from './on-update-contract-follow' export * from './on-update-contract-follow'
export * from './on-create-like' export * from './on-update-like'
export * from './on-delete-like'
// v2 // v2
export * from './health' export * from './health'

View File

@ -1,32 +0,0 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { Like } from '../../common/like'
import { getContract, log } from './utils'
import { uniq } from 'lodash'
const firestore = admin.firestore()
export const onDeleteLike = functions.firestore
.document('users/{userId}/likes/{likeId}')
.onDelete(async (change) => {
const like = change.data() as Like
if (like.type === 'contract') {
await removeContractLike(like)
}
})
const removeContractLike = async (like: Like) => {
const contract = await getContract(like.id)
if (!contract) {
log('Could not find contract')
return
}
const likedByUserIds = uniq(contract.likedByUserIds ?? [])
const newLikedByUserIds = likedByUserIds.filter(
(userId) => userId !== like.userId
)
await firestore.collection('contracts').doc(like.id).update({
likedByUserIds: newLikedByUserIds,
likedByUserCount: newLikedByUserIds.length,
})
}

View File

@ -19,14 +19,36 @@ export const onCreateLike = functions.firestore
} }
}) })
export const onUpdateLike = functions.firestore
.document('users/{userId}/likes/{likeId}')
.onUpdate(async (change, context) => {
const like = change.after.data() as Like
const prevLike = change.before.data() as Like
const { eventId } = context
if (like.type === 'contract' && like.tipTxnId !== prevLike.tipTxnId) {
await handleCreateLikeNotification(like, eventId)
await updateContractLikes(like)
}
})
export const onDeleteLike = functions.firestore
.document('users/{userId}/likes/{likeId}')
.onDelete(async (change) => {
const like = change.data() as Like
if (like.type === 'contract') {
await removeContractLike(like)
}
})
const updateContractLikes = async (like: Like) => { const updateContractLikes = async (like: Like) => {
const contract = await getContract(like.id) const contract = await getContract(like.id)
if (!contract) { if (!contract) {
log('Could not find contract') log('Could not find contract')
return return
} }
const likedByUserIds = uniq(contract.likedByUserIds ?? []) const likedByUserIds = uniq(
likedByUserIds.push(like.userId) (contract.likedByUserIds ?? []).concat(like.userId)
)
await firestore await firestore
.collection('contracts') .collection('contracts')
.doc(like.id) .doc(like.id)
@ -69,3 +91,19 @@ const handleCreateLikeNotification = async (like: Like, eventId: string) => {
tipTxnData tipTxnData
) )
} }
const removeContractLike = async (like: Like) => {
const contract = await getContract(like.id)
if (!contract) {
log('Could not find contract')
return
}
const likedByUserIds = uniq(contract.likedByUserIds ?? [])
const newLikedByUserIds = likedByUserIds.filter(
(userId) => userId !== like.userId
)
await firestore.collection('contracts').doc(like.id).update({
likedByUserIds: newLikedByUserIds,
likedByUserCount: newLikedByUserIds.length,
})
}

View File

@ -18,7 +18,6 @@ import { fromNow } from 'web/lib/util/time'
import { Avatar } from '../avatar' import { Avatar } from '../avatar'
import { useState } from 'react' import { useState } from 'react'
import { ContractInfoDialog } from './contract-info-dialog' import { ContractInfoDialog } from './contract-info-dialog'
import { Bet } from 'common/bet'
import NewContractBadge from '../new-contract-badge' import NewContractBadge from '../new-contract-badge'
import { UserFollowButton } from '../follow-button' import { UserFollowButton } from '../follow-button'
import { DAY_MS } from 'common/util/time' import { DAY_MS } from 'common/util/time'
@ -35,6 +34,8 @@ import { contractMetrics } from 'common/contract-details'
import { User } from 'common/user' import { User } from 'common/user'
import { UserLink } from 'web/components/user-link' import { UserLink } from 'web/components/user-link'
import { FeaturedContractBadge } from 'web/components/contract/featured-contract-badge' import { FeaturedContractBadge } from 'web/components/contract/featured-contract-badge'
import { Tooltip } from 'web/components/tooltip'
import { useWindowSize } from 'web/hooks/use-window-size'
export type ShowTime = 'resolve-date' | 'close-date' export type ShowTime = 'resolve-date' | 'close-date'
@ -78,7 +79,7 @@ export function MiscDetails(props: {
) : (contract?.featuredOnHomeRank ?? 0) > 0 ? ( ) : (contract?.featuredOnHomeRank ?? 0) > 0 ? (
<FeaturedContractBadge /> <FeaturedContractBadge />
) : volume > 0 || !isNew ? ( ) : volume > 0 || !isNew ? (
<Row className={'shrink-0'}>{formatMoney(contract.volume)} bet</Row> <Row className={'shrink-0'}>{formatMoney(volume)} bet</Row>
) : ( ) : (
<NewContractBadge /> <NewContractBadge />
)} )}
@ -101,7 +102,7 @@ export function AvatarDetails(props: {
short?: boolean short?: boolean
}) { }) {
const { contract, short, className } = props const { contract, short, className } = props
const { creatorName, creatorUsername } = contract const { creatorName, creatorUsername, creatorAvatarUrl } = contract
return ( return (
<Row <Row
@ -109,7 +110,7 @@ export function AvatarDetails(props: {
> >
<Avatar <Avatar
username={creatorUsername} username={creatorUsername}
avatarUrl={contract.creatorAvatarUrl} avatarUrl={creatorAvatarUrl}
size={6} size={6}
/> />
<UserLink name={creatorName} username={creatorUsername} short={short} /> <UserLink name={creatorName} username={creatorUsername} short={short} />
@ -138,20 +139,28 @@ export function AbbrContractDetails(props: {
export function ContractDetails(props: { export function ContractDetails(props: {
contract: Contract contract: Contract
bets: Bet[]
user: User | null | undefined user: User | null | undefined
isCreator?: boolean isCreator?: boolean
disabled?: boolean disabled?: boolean
}) { }) {
const { contract, bets, isCreator, disabled } = props const { contract, isCreator, disabled } = props
const { closeTime, creatorName, creatorUsername, creatorId, groupLinks } = const {
contract closeTime,
creatorName,
creatorUsername,
creatorId,
groupLinks,
creatorAvatarUrl,
resolutionTime,
} = contract
const { volumeLabel, resolvedDate } = contractMetrics(contract) const { volumeLabel, resolvedDate } = contractMetrics(contract)
const groupToDisplay = const groupToDisplay =
groupLinks?.sort((a, b) => a.createdTime - b.createdTime)[0] ?? null groupLinks?.sort((a, b) => a.createdTime - b.createdTime)[0] ?? null
const user = useUser() const user = useUser()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { width } = useWindowSize()
const isMobile = (width ?? 0) < 600
const groupInfo = ( const groupInfo = (
<Row> <Row>
@ -167,7 +176,7 @@ export function ContractDetails(props: {
<Row className="items-center gap-2"> <Row className="items-center gap-2">
<Avatar <Avatar
username={creatorUsername} username={creatorUsername}
avatarUrl={contract.creatorAvatarUrl} avatarUrl={creatorAvatarUrl}
noLink={disabled} noLink={disabled}
size={6} size={6}
/> />
@ -178,6 +187,7 @@ export function ContractDetails(props: {
className="whitespace-nowrap" className="whitespace-nowrap"
name={creatorName} name={creatorName}
username={creatorUsername} username={creatorUsername}
short={isMobile}
/> />
)} )}
{!disabled && <UserFollowButton userId={creatorId} small />} {!disabled && <UserFollowButton userId={creatorId} small />}
@ -228,14 +238,11 @@ export function ContractDetails(props: {
</Modal> </Modal>
{(!!closeTime || !!resolvedDate) && ( {(!!closeTime || !!resolvedDate) && (
<Row className="items-center gap-1"> <Row className="hidden items-center gap-1 md:inline-flex">
{resolvedDate && contract.resolutionTime ? ( {resolvedDate && resolutionTime ? (
<> <>
<ClockIcon className="h-5 w-5" /> <ClockIcon className="h-5 w-5" />
<DateTimeTooltip <DateTimeTooltip text="Market resolved:" time={resolutionTime}>
text="Market resolved:"
time={contract.resolutionTime}
>
{resolvedDate} {resolvedDate}
</DateTimeTooltip> </DateTimeTooltip>
</> </>
@ -255,17 +262,84 @@ export function ContractDetails(props: {
)} )}
{user && ( {user && (
<> <>
<Row className="items-center gap-1"> <Row className="hidden items-center gap-1 md:inline-flex">
<DatabaseIcon className="h-5 w-5" /> <DatabaseIcon className="h-5 w-5" />
<div className="whitespace-nowrap">{volumeLabel}</div> <div className="whitespace-nowrap">{volumeLabel}</div>
</Row> </Row>
{!disabled && <ContractInfoDialog contract={contract} bets={bets} />} {!disabled && (
<ContractInfoDialog
contract={contract}
className={'hidden md:inline-flex'}
/>
)}
</> </>
)} )}
</Row> </Row>
) )
} }
export function ExtraMobileContractDetails(props: {
contract: Contract
user: User | null | undefined
forceShowVolume?: boolean
}) {
const { contract, user, forceShowVolume } = props
const { volume, resolutionTime, closeTime, creatorId, uniqueBettorCount } =
contract
const uniqueBettors = uniqueBettorCount ?? 0
const { resolvedDate } = contractMetrics(contract)
const volumeTranslation =
volume > 800 || uniqueBettors > 20
? 'High'
: volume > 300 || uniqueBettors > 10
? 'Medium'
: 'Low'
return (
<Row
className={clsx(
'items-center justify-around md:hidden',
user ? 'w-full' : ''
)}
>
{resolvedDate && resolutionTime ? (
<Col className={'items-center text-sm'}>
<Row className={'text-gray-500'}>
<DateTimeTooltip text="Market resolved:" time={resolutionTime}>
{resolvedDate}
</DateTimeTooltip>
</Row>
<Row className={'text-gray-400'}>Ended</Row>
</Col>
) : (
!resolvedDate &&
closeTime && (
<Col className={'items-center text-sm text-gray-500'}>
<EditableCloseDate
closeTime={closeTime}
contract={contract}
isCreator={creatorId === user?.id}
/>
<Row className={'text-gray-400'}>Ends</Row>
</Col>
)
)}
{(user || forceShowVolume) && (
<Col className={'items-center text-sm text-gray-500'}>
<Tooltip
text={`${formatMoney(
volume
)} bet - ${uniqueBettors} unique bettors`}
>
{volumeTranslation}
</Tooltip>
<Row className={'text-gray-400'}>Activity</Row>
</Col>
)}
</Row>
)
}
function EditableCloseDate(props: { function EditableCloseDate(props: {
closeTime: number closeTime: number
contract: Contract contract: Contract
@ -318,10 +392,10 @@ function EditableCloseDate(props: {
return ( return (
<> <>
{isEditingCloseTime ? ( {isEditingCloseTime ? (
<Row className="mr-1 items-start"> <Row className="z-10 mr-2 w-full shrink-0 items-start items-center gap-1">
<input <input
type="date" type="date"
className="input input-bordered" className="input input-bordered shrink-0"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onChange={(e) => setCloseDate(e.target.value)} onChange={(e) => setCloseDate(e.target.value)}
min={Date.now()} min={Date.now()}
@ -329,39 +403,32 @@ function EditableCloseDate(props: {
/> />
<input <input
type="time" type="time"
className="input input-bordered ml-2" className="input input-bordered shrink-0"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onChange={(e) => setCloseHoursMinutes(e.target.value)} onChange={(e) => setCloseHoursMinutes(e.target.value)}
min="00:00" min="00:00"
value={closeHoursMinutes} value={closeHoursMinutes}
/> />
<Button size={'xs'} color={'blue'} onClick={onSave}>
Done
</Button>
</Row> </Row>
) : ( ) : (
<DateTimeTooltip <DateTimeTooltip
text={closeTime > Date.now() ? 'Trading ends:' : 'Trading ended:'} text={closeTime > Date.now() ? 'Trading ends:' : 'Trading ended:'}
time={closeTime} time={closeTime}
> >
{isSameYear <span
? dayJsCloseTime.format('MMM D') className={isCreator ? 'cursor-pointer' : ''}
: dayJsCloseTime.format('MMM D, YYYY')} onClick={() => isCreator && setIsEditingCloseTime(true)}
{isSameDay && <> ({fromNow(closeTime)})</>} >
{isSameYear
? dayJsCloseTime.format('MMM D')
: dayJsCloseTime.format('MMM D, YYYY')}
{isSameDay && <> ({fromNow(closeTime)})</>}
</span>
</DateTimeTooltip> </DateTimeTooltip>
)} )}
{isCreator &&
(isEditingCloseTime ? (
<button className="btn btn-xs" onClick={onSave}>
Done
</button>
) : (
<Button
size={'xs'}
color={'gray-white'}
onClick={() => setIsEditingCloseTime(true)}
>
<PencilIcon className="!container mr-0.5 mb-0.5 inline h-4 w-4" />
</Button>
))}
</> </>
) )
} }

View File

@ -1,9 +1,7 @@
import { DotsHorizontalIcon } from '@heroicons/react/outline' import { DotsHorizontalIcon } from '@heroicons/react/outline'
import clsx from 'clsx' import clsx from 'clsx'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { uniqBy } from 'lodash'
import { useState } from 'react' import { useState } from 'react'
import { Bet } from 'common/bet'
import { Contract } from 'common/contract' import { Contract } from 'common/contract'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
@ -22,8 +20,11 @@ import ShortToggle from '../widgets/short-toggle'
export const contractDetailsButtonClassName = 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' '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: { contract: Contract; bets: Bet[] }) { export function ContractInfoDialog(props: {
const { contract, bets } = props contract: Contract
className?: string
}) {
const { contract, className } = props
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [featured, setFeatured] = useState( const [featured, setFeatured] = useState(
@ -37,11 +38,7 @@ export function ContractInfoDialog(props: { contract: Contract; bets: Bet[] }) {
const { createdTime, closeTime, resolutionTime, mechanism, outcomeType, id } = const { createdTime, closeTime, resolutionTime, mechanism, outcomeType, id } =
contract contract
const tradersCount = uniqBy( const bettorsCount = contract.uniqueBettorCount ?? 'Unknown'
bets.filter((bet) => !bet.isAnte),
'userId'
).length
const typeDisplay = const typeDisplay =
outcomeType === 'BINARY' outcomeType === 'BINARY'
? 'YES / NO' ? 'YES / NO'
@ -69,7 +66,7 @@ export function ContractInfoDialog(props: { contract: Contract; bets: Bet[] }) {
return ( return (
<> <>
<button <button
className={contractDetailsButtonClassName} className={clsx(contractDetailsButtonClassName, className)}
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
> >
<DotsHorizontalIcon <DotsHorizontalIcon
@ -136,8 +133,8 @@ export function ContractInfoDialog(props: { contract: Contract; bets: Bet[] }) {
</tr> */} </tr> */}
<tr> <tr>
<td>Traders</td> <td>Bettors</td>
<td>{tradersCount}</td> <td>{bettorsCount}</td>
</tr> </tr>
<tr> <tr>

View File

@ -18,10 +18,9 @@ import BetButton from '../bet-button'
import { AnswersGraph } from '../answers/answers-graph' import { AnswersGraph } from '../answers/answers-graph'
import { Contract, CPMMBinaryContract } from 'common/contract' import { Contract, CPMMBinaryContract } from 'common/contract'
import { ContractDescription } from './contract-description' import { ContractDescription } from './contract-description'
import { ContractDetails } from './contract-details' import { ContractDetails, ExtraMobileContractDetails } from './contract-details'
import { NumericGraph } from './numeric-graph' import { NumericGraph } from './numeric-graph'
import { ShareRow } from './share-row' import { ExtraContractActionsRow } from 'web/components/contract/extra-contract-actions-row'
import { LikeMarketButton } from 'web/components/contract/like-market-button'
export const ContractOverview = (props: { export const ContractOverview = (props: {
contract: Contract contract: Contract
@ -40,17 +39,15 @@ export const ContractOverview = (props: {
return ( return (
<Col className={clsx('mb-6', className)}> <Col className={clsx('mb-6', className)}>
<Col className="gap-3 px-2 sm:gap-4"> <Col className="gap-3 px-2 sm:gap-4">
<ContractDetails
contract={contract}
user={user}
isCreator={isCreator}
/>
<Row className="justify-between gap-4"> <Row className="justify-between gap-4">
<div className="text-2xl text-indigo-700 md:text-3xl"> <div className="text-2xl text-indigo-700 md:text-3xl">
<Linkify text={question} /> <Linkify text={question} />
</div> </div>
{(outcomeType === 'FREE_RESPONSE' ||
outcomeType === 'MULTIPLE_CHOICE') &&
!resolution && (
<div className={'sm:hidden'}>
<LikeMarketButton contract={contract} user={user} />
</div>
)}
<Row className={'hidden gap-3 xl:flex'}> <Row className={'hidden gap-3 xl:flex'}>
{isBinary && ( {isBinary && (
<BinaryResolutionOrChance <BinaryResolutionOrChance
@ -79,11 +76,9 @@ export const ContractOverview = (props: {
{isBinary ? ( {isBinary ? (
<Row className="items-center justify-between gap-4 xl:hidden"> <Row className="items-center justify-between gap-4 xl:hidden">
<BinaryResolutionOrChance contract={contract} /> <BinaryResolutionOrChance contract={contract} />
<ExtraMobileContractDetails contract={contract} user={user} />
{tradingAllowed(contract) && ( {tradingAllowed(contract) && (
<Row> <Row>
<div className={'sm:hidden'}>
<LikeMarketButton contract={contract} user={user} />
</div>
<Col> <Col>
<BetButton contract={contract as CPMMBinaryContract} /> <BetButton contract={contract as CPMMBinaryContract} />
{!user && ( {!user && (
@ -98,11 +93,9 @@ export const ContractOverview = (props: {
) : isPseudoNumeric ? ( ) : isPseudoNumeric ? (
<Row className="items-center justify-between gap-4 xl:hidden"> <Row className="items-center justify-between gap-4 xl:hidden">
<PseudoNumericResolutionOrExpectation contract={contract} /> <PseudoNumericResolutionOrExpectation contract={contract} />
<ExtraMobileContractDetails contract={contract} user={user} />
{tradingAllowed(contract) && ( {tradingAllowed(contract) && (
<Row> <Row>
<div className={'sm:hidden'}>
<LikeMarketButton contract={contract} user={user} />
</div>
<Col> <Col>
<BetButton contract={contract} /> <BetButton contract={contract} />
{!user && ( {!user && (
@ -130,13 +123,6 @@ export const ContractOverview = (props: {
<NumericResolutionOrExpectation contract={contract} /> <NumericResolutionOrExpectation contract={contract} />
</Row> </Row>
)} )}
<ContractDetails
contract={contract}
bets={bets}
isCreator={isCreator}
user={user}
/>
</Col> </Col>
<div className={'my-1 md:my-2'}></div> <div className={'my-1 md:my-2'}></div>
{(isBinary || isPseudoNumeric) && ( {(isBinary || isPseudoNumeric) && (
@ -144,10 +130,17 @@ export const ContractOverview = (props: {
)}{' '} )}{' '}
{(outcomeType === 'FREE_RESPONSE' || {(outcomeType === 'FREE_RESPONSE' ||
outcomeType === 'MULTIPLE_CHOICE') && ( outcomeType === 'MULTIPLE_CHOICE') && (
<AnswersGraph contract={contract} bets={bets} /> <Col className={'mb-1 gap-y-2'}>
<AnswersGraph contract={contract} bets={bets} />
<ExtraMobileContractDetails
contract={contract}
user={user}
forceShowVolume={true}
/>
</Col>
)} )}
{outcomeType === 'NUMERIC' && <NumericGraph contract={contract} />} {outcomeType === 'NUMERIC' && <NumericGraph contract={contract} />}
<ShareRow user={user} contract={contract} /> <ExtraContractActionsRow user={user} contract={contract} />
<ContractDescription <ContractDescription
className="px-2" className="px-2"
contract={contract} contract={contract}

View File

@ -3,31 +3,25 @@ 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 { useState } from 'react' import React, { useState } from 'react'
import { Button } from 'web/components/button' import { Button } from 'web/components/button'
import { CreateChallengeModal } from '../challenges/create-challenge-modal'
import { User } from 'common/user' import { User } from 'common/user'
import { CHALLENGES_ENABLED } from 'common/challenge'
import { ShareModal } from './share-modal' import { ShareModal } from './share-modal'
import { withTracking } from 'web/lib/service/analytics'
import { FollowMarketButton } from 'web/components/follow-market-button' import { FollowMarketButton } from 'web/components/follow-market-button'
import { LikeMarketButton } from 'web/components/contract/like-market-button' import { LikeMarketButton } from 'web/components/contract/like-market-button'
import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog'
import { Col } from 'web/components/layout/col'
export function ShareRow(props: { export function ExtraContractActionsRow(props: {
contract: Contract contract: Contract
user: User | undefined | null user: User | undefined | null
}) { }) {
const { user, contract } = props const { user, contract } = props
const { outcomeType, resolution } = contract
const showChallenge =
user && outcomeType === 'BINARY' && !resolution && CHALLENGES_ENABLED
const [isOpen, setIsOpen] = useState(false)
const [isShareOpen, setShareOpen] = useState(false) const [isShareOpen, setShareOpen] = useState(false)
return ( return (
<Row className="mt-0.5 sm:mt-2"> <Row className={'mt-0.5 justify-around sm:mt-2 lg:justify-start'}>
<Button <Button
size="lg" size="lg"
color="gray-white" color="gray-white"
@ -36,8 +30,14 @@ export function ShareRow(props: {
setShareOpen(true) setShareOpen(true)
}} }}
> >
<ShareIcon className={clsx('mr-2 h-[24px] w-5')} aria-hidden="true" /> <Col className={'items-center sm:flex-row'}>
Share <ShareIcon
className={clsx('h-[24px] w-5 sm:mr-2')}
aria-hidden="true"
/>
<span>Share</span>
</Col>
<ShareModal <ShareModal
isOpen={isShareOpen} isOpen={isShareOpen}
setOpen={setShareOpen} setOpen={setShareOpen}
@ -46,28 +46,13 @@ export function ShareRow(props: {
/> />
</Button> </Button>
{showChallenge && (
<Button
size="lg"
color="gray-white"
onClick={withTracking(
() => setIsOpen(true),
'click challenge button'
)}
>
Challenge
<CreateChallengeModal
isOpen={isOpen}
setOpen={setIsOpen}
user={user}
contract={contract}
/>
</Button>
)}
<FollowMarketButton contract={contract} user={user} /> <FollowMarketButton contract={contract} user={user} />
<div className={'hidden sm:block'}> {user?.id !== contract.creatorId && (
<LikeMarketButton contract={contract} user={user} /> <LikeMarketButton contract={contract} user={user} />
</div> )}
<Col className={'justify-center md:hidden'}>
<ContractInfoDialog contract={contract} />
</Col>
</Row> </Row>
) )
} }

View File

@ -6,10 +6,11 @@ import { User } from 'common/user'
import { useUserLikes } from 'web/hooks/use-likes' import { useUserLikes } from 'web/hooks/use-likes'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { likeContract, unLikeContract } from 'web/lib/firebase/likes' import { likeContract } from 'web/lib/firebase/likes'
import { LIKE_TIP_AMOUNT } from 'common/like' import { LIKE_TIP_AMOUNT } from 'common/like'
import clsx from 'clsx' import clsx from 'clsx'
import { Row } from 'web/components/layout/row' import { Col } from 'web/components/layout/col'
import { firebaseLogin } from 'web/lib/firebase/users'
export function LikeMarketButton(props: { export function LikeMarketButton(props: {
contract: Contract contract: Contract
@ -18,16 +19,12 @@ export function LikeMarketButton(props: {
const { contract, user } = props const { contract, user } = props
const likes = useUserLikes(user?.id) const likes = useUserLikes(user?.id)
const likedContractIds = likes const userLikedContractIds = likes
?.filter((l) => l.type === 'contract') ?.filter((l) => l.type === 'contract')
.map((l) => l.id) .map((l) => l.id)
if (!user) return <div />
const onLike = async () => { const onLike = async () => {
if (likedContractIds?.includes(contract.id)) { if (!user) return firebaseLogin()
await unLikeContract(user.id, contract.id)
return
}
await likeContract(user, contract) await likeContract(user, contract)
toast(`You tipped ${contract.creatorName} ${formatMoney(LIKE_TIP_AMOUNT)}!`) toast(`You tipped ${contract.creatorName} ${formatMoney(LIKE_TIP_AMOUNT)}!`)
} }
@ -39,18 +36,19 @@ export function LikeMarketButton(props: {
color={'gray-white'} color={'gray-white'}
onClick={onLike} onClick={onLike}
> >
<Row className={'gap-0 sm:gap-2'}> <Col className={'sm:flex-row sm:gap-x-2'}>
<HeartIcon <HeartIcon
className={clsx( className={clsx(
'h-6 w-6', 'h-6 w-6',
likedContractIds?.includes(contract.id) || user &&
(!likes && contract.likedByUserIds?.includes(user.id)) (userLikedContractIds?.includes(contract.id) ||
(!likes && contract.likedByUserIds?.includes(user.id)))
? 'fill-red-500 text-red-500' ? 'fill-red-500 text-red-500'
: '' : ''
)} )}
/> />
<span className={'hidden sm:block'}>Tip</span> Tip
</Row> </Col>
</Button> </Button>
) )
} }

View File

@ -12,12 +12,15 @@ import { TweetButton } from '../tweet-button'
import { DuplicateContractButton } from '../copy-contract-button' import { DuplicateContractButton } from '../copy-contract-button'
import { Button } from '../button' import { Button } from '../button'
import { copyToClipboard } from 'web/lib/util/copy' import { copyToClipboard } from 'web/lib/util/copy'
import { track } from 'web/lib/service/analytics' import { track, withTracking } from 'web/lib/service/analytics'
import { ENV_CONFIG } from 'common/envs/constants' import { ENV_CONFIG } from 'common/envs/constants'
import { User } from 'common/user' import { User } from 'common/user'
import { SiteLink } from '../site-link' import { SiteLink } from '../site-link'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { REFERRAL_AMOUNT } from 'common/economy' import { REFERRAL_AMOUNT } from 'common/economy'
import { CreateChallengeModal } from 'web/components/challenges/create-challenge-modal'
import { useState } from 'react'
import { CHALLENGES_ENABLED } from 'common/challenge'
export function ShareModal(props: { export function ShareModal(props: {
contract: Contract contract: Contract
@ -26,8 +29,13 @@ export function ShareModal(props: {
setOpen: (open: boolean) => void setOpen: (open: boolean) => void
}) { }) {
const { contract, user, isOpen, setOpen } = props const { contract, user, isOpen, setOpen } = props
const { outcomeType, resolution } = contract
const [openCreateChallengeModal, setOpenCreateChallengeModal] =
useState(false)
const linkIcon = <LinkIcon className="mr-2 h-6 w-6" aria-hidden="true" /> const linkIcon = <LinkIcon className="mr-2 h-6 w-6" aria-hidden="true" />
const showChallenge =
user && outcomeType === 'BINARY' && !resolution && CHALLENGES_ENABLED
const shareUrl = `https://${ENV_CONFIG.domain}${contractPath(contract)}${ const shareUrl = `https://${ENV_CONFIG.domain}${contractPath(contract)}${
user?.username && contract.creatorUsername !== user?.username user?.username && contract.creatorUsername !== user?.username
@ -46,7 +54,6 @@ export function ShareModal(props: {
</SiteLink>{' '} </SiteLink>{' '}
if a new user signs up using the link! if a new user signs up using the link!
</p> </p>
<Button <Button
size="2xl" size="2xl"
color="gradient" color="gradient"
@ -61,8 +68,31 @@ export function ShareModal(props: {
> >
{linkIcon} Copy link {linkIcon} Copy link
</Button> </Button>
{showChallenge && (
<Row className="z-0 justify-start gap-4 self-center"> <Button
size="lg"
color="gray-white"
className={'mb-2 flex max-w-xs self-center'}
onClick={withTracking(
() => setOpenCreateChallengeModal(true),
'click challenge button'
)}
>
<span> Challenge a friend</span>
<CreateChallengeModal
isOpen={openCreateChallengeModal}
setOpen={(open) => {
if (!open) {
setOpenCreateChallengeModal(false)
setOpen(false)
} else setOpenCreateChallengeModal(open)
}}
user={user}
contract={contract}
/>
</Button>
)}
<Row className="z-0 flex-wrap justify-center gap-4 self-center">
<TweetButton <TweetButton
className="self-start" className="self-start"
tweetText={getTweetText(contract, shareUrl)} tweetText={getTweetText(contract, shareUrl)}

View File

@ -13,7 +13,7 @@ import { firebaseLogin, updateUser } from 'web/lib/firebase/users'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { FollowMarketModal } from 'web/components/contract/follow-market-modal' import { FollowMarketModal } from 'web/components/contract/follow-market-modal'
import { useState } from 'react' import { useState } from 'react'
import { Row } from 'web/components/layout/row' import { Col } from 'web/components/layout/col'
export const FollowMarketButton = (props: { export const FollowMarketButton = (props: {
contract: Contract contract: Contract
@ -55,15 +55,15 @@ export const FollowMarketButton = (props: {
}} }}
> >
{followers?.includes(user?.id ?? 'nope') ? ( {followers?.includes(user?.id ?? 'nope') ? (
<Row className={'gap-2'}> <Col className={'items-center gap-x-2 sm:flex-row'}>
<EyeOffIcon className={clsx('h-6 w-6')} aria-hidden="true" /> <EyeOffIcon className={clsx('h-6 w-6')} aria-hidden="true" />
Unwatch Unwatch
</Row> </Col>
) : ( ) : (
<Row className={'gap-2'}> <Col className={'items-center gap-x-2 sm:flex-row'}>
<EyeIcon className={clsx('h-6 w-6')} aria-hidden="true" /> <EyeIcon className={clsx('h-6 w-6')} aria-hidden="true" />
Watch Watch
</Row> </Col>
)} )}
<FollowMarketModal <FollowMarketModal
open={open} open={open}

View File

@ -11,7 +11,7 @@ function shortenName(name: string) {
const firstName = name.split(' ')[0] const firstName = name.split(' ')[0]
const maxLength = 10 const maxLength = 10
const shortName = const shortName =
firstName.length >= 3 firstName.length >= 4
? firstName.length < maxLength ? firstName.length < maxLength
? firstName ? firstName
: firstName.substring(0, maxLength - 3) + '...' : firstName.substring(0, maxLength - 3) + '...'

View File

@ -105,13 +105,7 @@ export function ContractEmbed(props: { contract: Contract; bets: Bet[] }) {
<Spacer h={3} /> <Spacer h={3} />
<Row className="items-center justify-between gap-4 px-2"> <Row className="items-center justify-between gap-4 px-2">
<ContractDetails <ContractDetails contract={contract} user={null} disabled />
contract={contract}
bets={bets}
isCreator={false}
user={null}
disabled
/>
{(isBinary || isPseudoNumeric) && {(isBinary || isPseudoNumeric) &&
tradingAllowed(contract) && tradingAllowed(contract) &&