diff --git a/web/components/challenges/create-challenge-button.tsx b/web/components/challenges/create-challenge-button.tsx new file mode 100644 index 00000000..6eab9bc5 --- /dev/null +++ b/web/components/challenges/create-challenge-button.tsx @@ -0,0 +1,255 @@ +import clsx from 'clsx' +import dayjs from 'dayjs' +import React, { useEffect, useState } from 'react' +import { LinkIcon, SwitchVerticalIcon } from '@heroicons/react/outline' + +import { Col } from '../layout/col' +import { Row } from '../layout/row' +import { Title } from '../title' +import { User } from 'common/user' +import { Modal } from 'web/components/layout/modal' +import { Button } from '../button' +import { createChallenge, getChallengeUrl } from 'web/lib/firebase/challenges' +import { BinaryContract } from 'common/contract' +import { SiteLink } from 'web/components/site-link' +import { formatMoney } from 'common/util/format' +import { NoLabel, YesLabel } from '../outcome-label' +import { QRCode } from '../qr-code' +import { copyToClipboard } from 'web/lib/util/copy' +import toast from 'react-hot-toast' + +type challengeInfo = { + amount: number + expiresTime: number | null + message: string + outcome: 'YES' | 'NO' | number + acceptorAmount: number +} +export function CreateChallengeButton(props: { + user: User | null | undefined + contract: BinaryContract +}) { + const { user, contract } = props + const [open, setOpen] = useState(false) + const [challengeSlug, setChallengeSlug] = useState('') + + return ( + <> + setOpen(newOpen)} size={'sm'}> + + {/*// add a sign up to challenge button?*/} + {user && ( + { + const challenge = await createChallenge({ + creator: user, + creatorAmount: newChallenge.amount, + expiresTime: newChallenge.expiresTime, + message: newChallenge.message, + acceptorAmount: newChallenge.acceptorAmount, + outcome: newChallenge.outcome, + contract: contract, + }) + challenge && setChallengeSlug(getChallengeUrl(challenge)) + }} + challengeSlug={challengeSlug} + /> + )} + + + + setOpen(true)} + className="btn btn-outline mb-4 max-w-xs whitespace-nowrap normal-case" + > + Challenge a friend + + > + ) +} + +function CreateChallengeForm(props: { + user: User + contract: BinaryContract + onCreate: (m: challengeInfo) => Promise + challengeSlug: string +}) { + const { user, onCreate, contract, challengeSlug } = props + const [isCreating, setIsCreating] = useState(false) + const [finishedCreating, setFinishedCreating] = useState(false) + const [error, setError] = useState('') + const [editingAcceptorAmount, setEditingAcceptorAmount] = useState(false) + const defaultExpire = 'week' + + const defaultMessage = `${user.name} is challenging you to a bet! Do you think ${contract.question}` + + const [challengeInfo, setChallengeInfo] = useState({ + expiresTime: dayjs().add(2, defaultExpire).valueOf(), + outcome: 'YES', + amount: 100, + acceptorAmount: 100, + message: defaultMessage, + }) + useEffect(() => { + setError('') + }, [challengeInfo]) + + return ( + <> + {!finishedCreating && ( + { + e.preventDefault() + if (user.balance < challengeInfo.amount) { + setError('You do not have enough mana to create this challenge') + return + } + setIsCreating(true) + onCreate(challengeInfo).finally(() => setIsCreating(false)) + setFinishedCreating(true) + }} + > + + + You'll bet: + + + + + M$ + + + setChallengeInfo((m: challengeInfo) => { + return { + ...m, + amount: parseInt(e.target.value), + acceptorAmount: editingAcceptorAmount + ? m.acceptorAmount + : parseInt(e.target.value), + } + }) + } + /> + + + on + {challengeInfo.outcome === 'YES' ? : } + + + + setChallengeInfo((m: challengeInfo) => { + return { + ...m, + outcome: m.outcome === 'YES' ? 'NO' : 'YES', + } + }) + } + > + + + + If they bet: + + + {editingAcceptorAmount ? ( + + + + M$ + + + setChallengeInfo((m: challengeInfo) => { + return { + ...m, + acceptorAmount: parseInt(e.target.value), + } + }) + } + /> + + + ) : ( + + {formatMoney(challengeInfo.acceptorAmount)} + + )} + + on + {challengeInfo.outcome === 'YES' ? : } + + + + {!editingAcceptorAmount && ( + setEditingAcceptorAmount(!editingAcceptorAmount)} + > + Edit + + )} + + Continue + + + {error} + + )} + {finishedCreating && ( + <> + + + Share the challenge using the link. + { + copyToClipboard(challengeSlug) + toast('Link copied to clipboard!') + }} + className={'btn btn-outline mb-4 whitespace-nowrap normal-case'} + > + + Copy link + + + + + See your other + + challenges + + + > + )} + > + ) +} diff --git a/web/components/contract/contract-overview.tsx b/web/components/contract/contract-overview.tsx index b95bb02b..79d05ec2 100644 --- a/web/components/contract/contract-overview.tsx +++ b/web/components/contract/contract-overview.tsx @@ -1,7 +1,11 @@ +<<<<<<< HEAD import React from 'react' import clsx from 'clsx' import { tradingAllowed } from 'web/lib/firebase/contracts' +======= +import { contractUrl, tradingAllowed } from 'web/lib/firebase/contracts' +>>>>>>> 798253f8 (Challenge Bets (#679)) import { Col } from '../layout/col' import { Spacer } from '../layout/spacer' import { ContractProbGraph } from './contract-prob-graph' @@ -21,7 +25,16 @@ import { Contract, CPMMBinaryContract } from 'common/contract' import { ContractDescription } from './contract-description' import { ContractDetails } from './contract-details' import { NumericGraph } from './numeric-graph' +<<<<<<< HEAD import { ShareRow } from './share-row' +======= +import { CreateChallengeButton } from 'web/components/challenges/create-challenge-button' +import React from 'react' +import { copyToClipboard } from 'web/lib/util/copy' +import toast from 'react-hot-toast' +import { LinkIcon } from '@heroicons/react/outline' +import { CHALLENGES_ENABLED } from 'common/challenge' +>>>>>>> 798253f8 (Challenge Bets (#679)) export const ContractOverview = (props: { contract: Contract @@ -36,6 +49,7 @@ export const ContractOverview = (props: { const isBinary = outcomeType === 'BINARY' const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC' + const showChallenge = user && isBinary && !resolution && CHALLENGES_ENABLED return ( @@ -118,12 +132,51 @@ export const ContractOverview = (props: { )} {outcomeType === 'NUMERIC' && } +<<<<<<< HEAD +======= + {/* {(contract.description || isCreator) && } */} +>>>>>>> 798253f8 (Challenge Bets (#679)) + {/**/} + {/* {showChallenge && (*/} + {/* */} + {/* ⚔️ Challenge a friend ⚔️*/} + {/* */} + {/* */} + {/* )}*/} + {/* {isCreator && (*/} + {/* */} + {/* Share your market*/} + {/* */} + {/* */} + {/* )}*/} + {/**/} + + {showChallenge && ( + + + + )} + {isCreator && ( + + { + copyToClipboard(contractUrl(contract)) + toast('Link copied to clipboard!') + }} + className={'btn btn-outline mb-4 whitespace-nowrap normal-case'} + > + + Share market + + + )} + ) } diff --git a/web/pages/challenges/index.tsx b/web/pages/challenges/index.tsx index 9553bb95..343611a7 100644 --- a/web/pages/challenges/index.tsx +++ b/web/pages/challenges/index.tsx @@ -27,6 +27,11 @@ import { Button } from 'web/components/button' import { ClipboardCopyIcon, QrcodeIcon } from '@heroicons/react/outline' import { copyToClipboard } from 'web/lib/util/copy' import toast from 'react-hot-toast' +<<<<<<< HEAD +======= +import { Modal } from 'web/components/layout/modal' +import { QRCode } from 'web/components/qr-code' +>>>>>>> 798253f8 (Challenge Bets (#679)) dayjs.extend(customParseFormat) const columnClass = 'sm:px-5 px-2 py-3.5 max-w-[100px] truncate' @@ -107,11 +112,16 @@ function YourChallengesTable(props: { links: Challenge[] }) { function YourLinkSummaryRow(props: { challenge: Challenge }) { const { challenge } = props const { acceptances } = challenge +<<<<<<< HEAD +======= + const [open, setOpen] = React.useState(false) +>>>>>>> 798253f8 (Challenge Bets (#679)) const className = clsx( 'whitespace-nowrap text-sm hover:cursor-pointer text-gray-500 hover:bg-sky-50 bg-white' ) return ( +<<<<<<< HEAD @@ -180,6 +190,87 @@ function YourLinkSummaryRow(props: { challenge: Challenge }) { +======= + <> + + + + Have your friend scan this to accept the challenge! + + + + + + + + {formatMoney(challenge.creatorAmount)} + + + + + { + copyToClipboard(getChallengeUrl(challenge)) + toast('Link copied to clipboard!') + }} + > + + + { + setOpen(true) + }} + > + + + + {`...${challenge.contractSlug}/${challenge.slug}`} + + + + + + + {acceptances.length > 0 ? ( + <> + + + > + ) : ( + + No one - + {challenge.expiresTime && + ` (expires ${fromNow(challenge.expiresTime)})`} + + )} + + + + > +>>>>>>> 798253f8 (Challenge Bets (#679)) ) }