diff --git a/web/components/button.tsx b/web/components/button.tsx index ea9a3e88..cb39cba8 100644 --- a/web/components/button.tsx +++ b/web/components/button.tsx @@ -11,7 +11,6 @@ export type ColorType = | 'gray' | 'gradient' | 'gray-white' - | 'highlight-blue' export function Button(props: { className?: string @@ -57,9 +56,7 @@ export function Button(props: { color === 'gradient' && 'border-none bg-gradient-to-r from-indigo-500 to-blue-500 text-white hover:from-indigo-700 hover:to-blue-700', color === 'gray-white' && - 'text-greyscale-6 hover:bg-greyscale-2 border-none shadow-none', - color === 'highlight-blue' && - 'text-highlight-blue border-none shadow-none', + 'border-none bg-white text-gray-500 shadow-none hover:bg-gray-200', className )} disabled={disabled} diff --git a/web/components/contract/contract-details.tsx b/web/components/contract/contract-details.tsx index fad62c86..e28ab41a 100644 --- a/web/components/contract/contract-details.tsx +++ b/web/components/contract/contract-details.tsx @@ -1,4 +1,9 @@ -import { ClockIcon } from '@heroicons/react/outline' +import { + ClockIcon, + DatabaseIcon, + PencilIcon, + UserGroupIcon, +} from '@heroicons/react/outline' import clsx from 'clsx' import { Editor } from '@tiptap/react' import dayjs from 'dayjs' @@ -11,8 +16,9 @@ import { DateTimeTooltip } from '../datetime-tooltip' import { fromNow } from 'web/lib/util/time' import { Avatar } from '../avatar' import { useState } from 'react' +import { ContractInfoDialog } from './contract-info-dialog' import NewContractBadge from '../new-contract-badge' -import { MiniUserFollowButton } from '../follow-button' +import { UserFollowButton } from '../follow-button' import { DAY_MS } from 'common/util/time' import { useUser } from 'web/hooks/use-user' import { exhibitExts } from 'common/util/parse' @@ -28,8 +34,6 @@ import { UserLink } from 'web/components/user-link' import { FeaturedContractBadge } from 'web/components/contract/featured-contract-badge' import { Tooltip } from 'web/components/tooltip' import { useWindowSize } from 'web/hooks/use-window-size' -import { ExtraContractActionsRow } from './extra-contract-actions-row' -import { PlusCircleIcon } from '@heroicons/react/solid' export type ShowTime = 'resolve-date' | 'close-date' @@ -100,174 +104,90 @@ export function AvatarDetails(props: { ) } -export function useIsMobile() { - const { width } = useWindowSize() - return (width ?? 0) < 600 -} - export function ContractDetails(props: { contract: Contract disabled?: boolean }) { const { contract, disabled } = props - const { creatorName, creatorUsername, creatorId, creatorAvatarUrl } = contract - const { resolvedDate } = contractMetrics(contract) + const { + closeTime, + creatorName, + creatorUsername, + creatorId, + creatorAvatarUrl, + resolutionTime, + } = contract + const { volumeLabel, resolvedDate } = contractMetrics(contract) const user = useUser() const isCreator = user?.id === creatorId - const isMobile = useIsMobile() - - return ( - - - - {!disabled && ( -
- -
- )} - - - {disabled ? ( - creatorName - ) : ( - - )} - - - - {!isMobile && ( - - )} - - -
- -
-
- {/* GROUPS */} - {isMobile && ( -
- -
- )} - - ) -} - -export function CloseOrResolveTime(props: { - contract: Contract - resolvedDate: any - isCreator: boolean -}) { - const { contract, resolvedDate, isCreator } = props - const { resolutionTime, closeTime } = contract - console.log(closeTime, resolvedDate) - if (!!closeTime || !!resolvedDate) { - return ( - - {resolvedDate && resolutionTime ? ( - <> - - -
resolved 
- {resolvedDate} -
-
- - ) : null} - - {!resolvedDate && closeTime && ( - - {dayjs().isBefore(closeTime) &&
closes 
} - {!dayjs().isBefore(closeTime) &&
closed 
} - -
- )} -
- ) - } else return <> -} - -export function MarketGroups(props: { - contract: Contract - isMobile: boolean | undefined - disabled: boolean | undefined -}) { const [open, setOpen] = useState(false) - const user = useUser() - const { contract, isMobile, disabled } = props + const { width } = useWindowSize() + const isMobile = (width ?? 0) < 600 const groupToDisplay = getGroupLinkToDisplay(contract) const groupInfo = groupToDisplay ? ( -
- {groupToDisplay.name} -
+ + {groupToDisplay.name}
) : ( - !groupToDisplay && setOpen(true)} > -
- No Group -
-
+ + + No Group + + ) + return ( - <> - + + + {disabled ? ( - { groupInfo } + creatorName + ) : ( + + )} + {!disabled && } + + + {disabled ? ( + groupInfo + ) : !groupToDisplay && !user ? ( +
) : ( {groupInfo} - {user && ( - + + )} )} @@ -281,7 +201,45 @@ export function MarketGroups(props: { - + + {(!!closeTime || !!resolvedDate) && ( + + {resolvedDate && resolutionTime ? ( + <> + + + {resolvedDate} + + + ) : null} + + {!resolvedDate && closeTime && user && ( + <> + + + + )} + + )} + {user && ( + <> + + +
{volumeLabel}
+
+ {!disabled && ( + + )} + + )} + ) } @@ -322,12 +280,12 @@ export function ExtraMobileContractDetails(props: { !resolvedDate && closeTime && ( - Closes  + Ends ) )} diff --git a/web/components/contract/contract-info-dialog.tsx b/web/components/contract/contract-info-dialog.tsx index 07958378..ae586725 100644 --- a/web/components/contract/contract-info-dialog.tsx +++ b/web/components/contract/contract-info-dialog.tsx @@ -18,7 +18,6 @@ import { deleteField } from 'firebase/firestore' import ShortToggle from '../widgets/short-toggle' import { DuplicateContractButton } from '../copy-contract-button' import { Row } from '../layout/row' -import { Button } from '../button' 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' @@ -68,21 +67,19 @@ export function ContractInfoDialog(props: { return ( <> - + - + <Title className="!mt-0 !mb-0" text="Market info" /> <table className="table-compact table-zebra table w-full text-gray-500"> <tbody> diff --git a/web/components/contract/contract-overview.tsx b/web/components/contract/contract-overview.tsx index bfb4829f..1bfe84de 100644 --- a/web/components/contract/contract-overview.tsx +++ b/web/components/contract/contract-overview.tsx @@ -25,11 +25,11 @@ import { NumericContract, PseudoNumericContract, } from 'common/contract' -import { ContractDetails } from './contract-details' +import { ContractDetails, ExtraMobileContractDetails } from './contract-details' import { NumericGraph } from './numeric-graph' const OverviewQuestion = (props: { text: string }) => ( - <Linkify className="text-lg text-indigo-700 sm:text-2xl" text={props.text} /> + <Linkify className="text-2xl text-indigo-700 md:text-3xl" text={props.text} /> ) const BetWidget = (props: { contract: CPMMContract }) => { @@ -73,7 +73,7 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => { const { contract, bets } = props return ( <Col className="gap-1 md:gap-2"> - <Col className="gap-1 px-2"> + <Col className="gap-3 px-2 sm:gap-4"> <ContractDetails contract={contract} /> <Row className="justify-between gap-4"> <OverviewQuestion text={contract.question} /> @@ -85,6 +85,7 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => { </Row> <Row className="items-center justify-between gap-4 xl:hidden"> <BinaryResolutionOrChance contract={contract} /> + <ExtraMobileContractDetails contract={contract} /> {tradingAllowed(contract) && ( <BetWidget contract={contract as CPMMBinaryContract} /> )} @@ -112,6 +113,10 @@ const ChoiceOverview = (props: { </Col> <Col className={'mb-1 gap-y-2'}> <AnswersGraph contract={contract} bets={[...bets].reverse()} /> + <ExtraMobileContractDetails + contract={contract} + forceShowVolume={true} + /> </Col> </Col> ) @@ -135,6 +140,7 @@ const PseudoNumericOverview = (props: { </Row> <Row className="items-center justify-between gap-4 xl:hidden"> <PseudoNumericResolutionOrExpectation contract={contract} /> + <ExtraMobileContractDetails contract={contract} /> {tradingAllowed(contract) && <BetWidget contract={contract} />} </Row> </Col> diff --git a/web/components/contract/extra-contract-actions-row.tsx b/web/components/contract/extra-contract-actions-row.tsx index af5db9c3..5d5ee4d8 100644 --- a/web/components/contract/extra-contract-actions-row.tsx +++ b/web/components/contract/extra-contract-actions-row.tsx @@ -11,29 +11,38 @@ import { FollowMarketButton } from 'web/components/follow-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' +import { withTracking } from 'web/lib/service/analytics' +import { CreateChallengeModal } from 'web/components/challenges/create-challenge-modal' +import { CHALLENGES_ENABLED } from 'common/challenge' +import ChallengeIcon from 'web/lib/icons/challenge-icon' export function ExtraContractActionsRow(props: { contract: Contract }) { const { contract } = props + const { outcomeType, resolution } = contract const user = useUser() const [isShareOpen, setShareOpen] = useState(false) + const [openCreateChallengeModal, setOpenCreateChallengeModal] = + useState(false) + const showChallenge = + user && outcomeType === 'BINARY' && !resolution && CHALLENGES_ENABLED return ( - <Row> - <FollowMarketButton contract={contract} user={user} /> - {user?.id !== contract.creatorId && ( - <LikeMarketButton contract={contract} user={user} /> - )} + <Row className={'mt-0.5 justify-around sm:mt-2 lg:justify-start'}> <Button - size="sm" + size="lg" color="gray-white" className={'flex'} onClick={() => { setShareOpen(true) }} > - <Row> - <ShareIcon className={clsx('h-5 w-5')} aria-hidden="true" /> - </Row> + <Col className={'items-center sm:flex-row'}> + <ShareIcon + className={clsx('h-[24px] w-5 sm:mr-2')} + aria-hidden="true" + /> + <span>Share</span> + </Col> <ShareModal isOpen={isShareOpen} setOpen={setShareOpen} @@ -41,7 +50,35 @@ export function ExtraContractActionsRow(props: { contract: Contract }) { user={user} /> </Button> - <Col className={'justify-center'}> + + {showChallenge && ( + <Button + size="lg" + color="gray-white" + className="max-w-xs self-center" + onClick={withTracking( + () => setOpenCreateChallengeModal(true), + 'click challenge button' + )} + > + <Col className="items-center sm:flex-row"> + <ChallengeIcon className="mx-auto h-[24px] w-5 text-gray-500 sm:mr-2" /> + <span>Challenge</span> + </Col> + <CreateChallengeModal + isOpen={openCreateChallengeModal} + setOpen={setOpenCreateChallengeModal} + user={user} + contract={contract} + /> + </Button> + )} + + <FollowMarketButton contract={contract} user={user} /> + {user?.id !== contract.creatorId && ( + <LikeMarketButton contract={contract} user={user} /> + )} + <Col className={'justify-center md:hidden'}> <ContractInfoDialog contract={contract} /> </Col> </Row> diff --git a/web/components/contract/like-market-button.tsx b/web/components/contract/like-market-button.tsx index 01dce32f..e35e3e7e 100644 --- a/web/components/contract/like-market-button.tsx +++ b/web/components/contract/like-market-button.tsx @@ -38,16 +38,15 @@ export function LikeMarketButton(props: { return ( <Button - size={'sm'} + size={'lg'} className={'max-w-xs self-center'} color={'gray-white'} onClick={onLike} > - <Col className={'relative items-center sm:flex-row'}> + <Col className={'items-center sm:flex-row'}> <HeartIcon className={clsx( - 'h-5 w-5 sm:h-6 sm:w-6', - totalTipped > 0 ? 'mr-2' : '', + 'h-[24px] w-5 sm:mr-2', user && (userLikedContractIds?.includes(contract.id) || (!likes && contract.likedByUserIds?.includes(user.id))) @@ -55,18 +54,7 @@ export function LikeMarketButton(props: { : '' )} /> - {totalTipped > 0 && ( - <div - className={clsx( - 'bg-greyscale-6 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', - totalTipped > 99 - ? 'text-[0.4rem] sm:text-[0.5rem]' - : 'sm:text-2xs text-[0.5rem]' - )} - > - {totalTipped} - </div> - )} + Tip {totalTipped > 0 ? `(${formatMoney(totalTipped)})` : ''} </Col> </Button> ) diff --git a/web/components/follow-button.tsx b/web/components/follow-button.tsx index 6344757d..09495169 100644 --- a/web/components/follow-button.tsx +++ b/web/components/follow-button.tsx @@ -1,6 +1,4 @@ -import { CheckCircleIcon, PlusCircleIcon } from '@heroicons/react/solid' import clsx from 'clsx' -import { useEffect, useRef, useState } from 'react' import { useFollows } from 'web/hooks/use-follows' import { useUser } from 'web/hooks/use-user' import { follow, unfollow } from 'web/lib/firebase/users' @@ -56,73 +54,18 @@ export function FollowButton(props: { export function UserFollowButton(props: { userId: string; small?: boolean }) { const { userId, small } = props - const user = useUser() - const following = useFollows(user?.id) + const currentUser = useUser() + const following = useFollows(currentUser?.id) const isFollowing = following?.includes(userId) - if (!user || user.id === userId) return null + if (!currentUser || currentUser.id === userId) return null return ( <FollowButton isFollowing={isFollowing} - onFollow={() => follow(user.id, userId)} - onUnfollow={() => unfollow(user.id, userId)} + onFollow={() => follow(currentUser.id, userId)} + onUnfollow={() => unfollow(currentUser.id, userId)} small={small} /> ) } - -export function MiniUserFollowButton(props: { userId: string }) { - const { userId } = props - const user = useUser() - const following = useFollows(user?.id) - const isFollowing = following?.includes(userId) - const isFirstRender = useRef(true) - const [justFollowed, setJustFollowed] = useState(false) - - useEffect(() => { - if (isFirstRender.current) { - if (isFollowing != undefined) { - isFirstRender.current = false - } - return - } - if (isFollowing) { - setJustFollowed(true) - setTimeout(() => { - setJustFollowed(false) - }, 1000) - } - }, [isFollowing]) - - if (justFollowed) { - return ( - <CheckCircleIcon - className={clsx( - 'text-highlight-blue ml-3 mt-2 h-5 w-5 rounded-full bg-white sm:mr-2' - )} - aria-hidden="true" - /> - ) - } - if ( - !user || - user.id === userId || - isFollowing || - !user || - isFollowing === undefined - ) - return null - return ( - <> - <button onClick={withTracking(() => follow(user.id, userId), 'follow')}> - <PlusCircleIcon - className={clsx( - 'text-highlight-blue hover:text-hover-blue mt-2 ml-3 h-5 w-5 rounded-full bg-white sm:mr-2' - )} - aria-hidden="true" - /> - </button> - </> - ) -} diff --git a/web/components/follow-market-button.tsx b/web/components/follow-market-button.tsx index 0e65165b..1dd261cb 100644 --- a/web/components/follow-market-button.tsx +++ b/web/components/follow-market-button.tsx @@ -25,7 +25,7 @@ export const FollowMarketButton = (props: { return ( <Button - size={'sm'} + size={'lg'} color={'gray-white'} onClick={async () => { if (!user) return firebaseLogin() @@ -56,19 +56,13 @@ export const FollowMarketButton = (props: { > {followers?.includes(user?.id ?? 'nope') ? ( <Col className={'items-center gap-x-2 sm:flex-row'}> - <EyeOffIcon - className={clsx('h-5 w-5 sm:h-6 sm:w-6')} - aria-hidden="true" - /> - {/* Unwatch */} + <EyeOffIcon className={clsx('h-6 w-6')} aria-hidden="true" /> + Unwatch </Col> ) : ( <Col className={'items-center gap-x-2 sm:flex-row'}> - <EyeIcon - className={clsx('h-5 w-5 sm:h-6 sm:w-6')} - aria-hidden="true" - /> - {/* Watch */} + <EyeIcon className={clsx('h-6 w-6')} aria-hidden="true" /> + Watch </Col> )} <WatchMarketModal diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index a0b2ed50..2c011c90 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -37,6 +37,7 @@ import { User } from 'common/user' import { ContractComment } from 'common/comment' import { getOpenGraphProps } from 'common/contract-details' import { ContractDescription } from 'web/components/contract/contract-description' +import { ExtraContractActionsRow } from 'web/components/contract/extra-contract-actions-row' import { ContractLeaderboard, ContractTopTrades, @@ -256,6 +257,7 @@ export function ContractPageContent( )} <ContractOverview contract={contract} bets={nonChallengeBets} /> + <ExtraContractActionsRow contract={contract} /> <ContractDescription className="mb-6 px-2" contract={contract} /> {outcomeType === 'NUMERIC' && ( diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 7bea3ec2..eb411216 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -26,8 +26,6 @@ module.exports = { 'greyscale-5': '#9191A7', 'greyscale-6': '#66667C', 'greyscale-7': '#111140', - 'highlight-blue': '#5BCEFF', - 'hover-blue': '#90DEFF', }, typography: { quoteless: {