798253f887
* Challenge bets * Store avatar url * Fix before and after probs * Check balance before creation * Calculate winning shares * pretty * Change winning value * Set shares to equal each other * Fix share challenge link * pretty * remove lib refs * Probability of bet is set to market * Remove peer pill * Cleanup * Button on contract page * don't show challenge if not binary or if resolved * challenge button (WIP) * fix accept challenge: don't change pool/probability * Opengraph preview [WIP] * elim lib * Edit og card props * Change challenge text * New card gen attempt * Get challenge on server * challenge button styling * Use env domain * Remove other window ref * Use challenge creator as avatar * Remove user name * Remove s from property, replace prob with outcome * challenge form * share text * Add in challenge parts to template and url * Challenge url params optional * Add challenge params to parse request * Parse please * Don't remove prob * Challenge card styling * Challenge card styling * Challenge card styling * Challenge card styling * Challenge card styling * Challenge card styling * Challenge card styling * Challenge card styling * Add to readme about how to dev og-image * Add emojis * button: gradient background, 2xl size * beautify accept bet screen * update question button * Add separate challenge template * Accepted challenge sharing card, fix accept bet call * accept challenge button * challenge winner page * create challenge screen * Your outcome/cost=> acceptorOutcome/cost * New create challenge panel * Fix main merge * Add challenge slug to bet and filter by it * Center title * Add helper text * Add FAQ section * Lint * Columnize the user areas in preview link too * Absolutely position * Spacing * Orientation * Restyle challenges list, cache contract name * Make copying easy on mobile * Link spacing * Fix spacing * qr codes! * put your challenges first * eslint * Changes to contract buttons and create challenge modal * Change titles around for current bet * Add back in contract title after winning * Cleanup * Add challenge enabled flag * Spacing of switch button * Put sharing qr code in modal Co-authored-by: mantikoros <sgrugett@gmail.com>
301 lines
8.9 KiB
TypeScript
301 lines
8.9 KiB
TypeScript
import clsx from 'clsx'
|
|
import React from 'react'
|
|
import { formatMoney } from 'common/util/format'
|
|
import { Col } from 'web/components/layout/col'
|
|
import { Row } from 'web/components/layout/row'
|
|
import { Page } from 'web/components/page'
|
|
import { SEO } from 'web/components/SEO'
|
|
import { Title } from 'web/components/title'
|
|
import { useUser } from 'web/hooks/use-user'
|
|
import { fromNow } from 'web/lib/util/time'
|
|
|
|
import dayjs from 'dayjs'
|
|
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
|
import {
|
|
getChallengeUrl,
|
|
useAcceptedChallenges,
|
|
useUserChallenges,
|
|
} from 'web/lib/firebase/challenges'
|
|
import { Challenge } from 'common/challenge'
|
|
import { Tabs } from 'web/components/layout/tabs'
|
|
import { SiteLink } from 'web/components/site-link'
|
|
import { UserLink } from 'web/components/user-page'
|
|
import { Avatar } from 'web/components/avatar'
|
|
import Router from 'next/router'
|
|
import { contractPathWithoutContract } from 'web/lib/firebase/contracts'
|
|
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'
|
|
import { Modal } from 'web/components/layout/modal'
|
|
import { QRCode } from 'web/components/qr-code'
|
|
|
|
dayjs.extend(customParseFormat)
|
|
const columnClass = 'sm:px-5 px-2 py-3.5 max-w-[100px] truncate'
|
|
const amountClass = columnClass + ' max-w-[75px] font-bold'
|
|
|
|
export default function ChallengesListPage() {
|
|
const user = useUser()
|
|
const userChallenges = useUserChallenges(user?.id ?? '')
|
|
const challenges = useAcceptedChallenges()
|
|
|
|
const userTab = user
|
|
? [
|
|
{
|
|
content: <YourChallengesTable links={userChallenges} />,
|
|
title: 'Your Challenges',
|
|
},
|
|
]
|
|
: []
|
|
|
|
const publicTab = [
|
|
{
|
|
content: <PublicChallengesTable links={challenges} />,
|
|
title: 'Public Challenges',
|
|
},
|
|
]
|
|
|
|
return (
|
|
<Page>
|
|
<SEO
|
|
title="Challenges"
|
|
description="Challenge your friends to a bet!"
|
|
url="/send"
|
|
/>
|
|
|
|
<Col className="w-full px-8">
|
|
<Row className="items-center justify-between">
|
|
<Title text="Challenges" />
|
|
</Row>
|
|
<p>Find or create a question to challenge someone to a bet.</p>
|
|
|
|
<Tabs tabs={[...userTab, ...publicTab]} />
|
|
</Col>
|
|
</Page>
|
|
)
|
|
}
|
|
|
|
function YourChallengesTable(props: { links: Challenge[] }) {
|
|
const { links } = props
|
|
return links.length == 0 ? (
|
|
<p>There aren't currently any challenges.</p>
|
|
) : (
|
|
<div className="overflow-scroll">
|
|
<table className="w-full divide-y divide-gray-300 rounded-lg border border-gray-200">
|
|
<thead className="bg-gray-50 text-left text-sm font-semibold text-gray-900">
|
|
<tr>
|
|
<th className={amountClass}>Amount</th>
|
|
<th
|
|
className={clsx(
|
|
columnClass,
|
|
'text-center sm:pl-10 sm:text-start'
|
|
)}
|
|
>
|
|
Link
|
|
</th>
|
|
<th className={columnClass}>Accepted By</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className={'divide-y divide-gray-200 bg-white'}>
|
|
{links.map((link) => (
|
|
<YourLinkSummaryRow challenge={link} />
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function YourLinkSummaryRow(props: { challenge: Challenge }) {
|
|
const { challenge } = props
|
|
const { acceptances } = challenge
|
|
const [open, setOpen] = React.useState(false)
|
|
const className = clsx(
|
|
'whitespace-nowrap text-sm hover:cursor-pointer text-gray-500 hover:bg-sky-50 bg-white'
|
|
)
|
|
return (
|
|
<>
|
|
<Modal open={open} setOpen={setOpen} size={'sm'}>
|
|
<Col
|
|
className={
|
|
'items-center justify-center gap-4 rounded-md bg-white p-8 py-8 '
|
|
}
|
|
>
|
|
<span className={'mb-4 text-center text-xl text-indigo-700'}>
|
|
Have your friend scan this to accept the challenge!
|
|
</span>
|
|
<QRCode url={getChallengeUrl(challenge)} />
|
|
</Col>
|
|
</Modal>
|
|
<tr id={challenge.slug} key={challenge.slug} className={className}>
|
|
<td className={amountClass}>
|
|
<SiteLink href={getChallengeUrl(challenge)}>
|
|
{formatMoney(challenge.creatorAmount)}
|
|
</SiteLink>
|
|
</td>
|
|
<td
|
|
className={clsx(
|
|
columnClass,
|
|
'text-center sm:max-w-[200px] sm:text-start'
|
|
)}
|
|
>
|
|
<Row className="items-center gap-2">
|
|
<Button
|
|
color="gray-white"
|
|
size="xs"
|
|
onClick={() => {
|
|
copyToClipboard(getChallengeUrl(challenge))
|
|
toast('Link copied to clipboard!')
|
|
}}
|
|
>
|
|
<ClipboardCopyIcon className={'h-5 w-5 sm:h-4 sm:w-4'} />
|
|
</Button>
|
|
<Button
|
|
color="gray-white"
|
|
size="xs"
|
|
onClick={() => {
|
|
setOpen(true)
|
|
}}
|
|
>
|
|
<QrcodeIcon className="h-5 w-5 sm:h-4 sm:w-4" />
|
|
</Button>
|
|
<SiteLink
|
|
href={getChallengeUrl(challenge)}
|
|
className={'mx-1 mb-1 hidden sm:inline-block'}
|
|
>
|
|
{`...${challenge.contractSlug}/${challenge.slug}`}
|
|
</SiteLink>
|
|
</Row>
|
|
</td>
|
|
|
|
<td className={columnClass}>
|
|
<Row className={'items-center justify-start gap-1'}>
|
|
{acceptances.length > 0 ? (
|
|
<>
|
|
<Avatar
|
|
username={acceptances[0].userUsername}
|
|
avatarUrl={acceptances[0].userAvatarUrl}
|
|
size={'sm'}
|
|
/>
|
|
<UserLink
|
|
name={acceptances[0].userName}
|
|
username={acceptances[0].userUsername}
|
|
/>
|
|
</>
|
|
) : (
|
|
<span>
|
|
No one -
|
|
{challenge.expiresTime &&
|
|
` (expires ${fromNow(challenge.expiresTime)})`}
|
|
</span>
|
|
)}
|
|
</Row>
|
|
</td>
|
|
</tr>
|
|
</>
|
|
)
|
|
}
|
|
|
|
function PublicChallengesTable(props: { links: Challenge[] }) {
|
|
const { links } = props
|
|
return links.length == 0 ? (
|
|
<p>There aren't currently any challenges.</p>
|
|
) : (
|
|
<div className="overflow-scroll">
|
|
<table className="w-full divide-y divide-gray-300 rounded-lg border border-gray-200">
|
|
<thead className="bg-gray-50 text-left text-sm font-semibold text-gray-900">
|
|
<tr>
|
|
<th className={amountClass}>Amount</th>
|
|
<th className={columnClass}>Creator</th>
|
|
<th className={columnClass}>Acceptor</th>
|
|
<th className={columnClass}>Market</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className={'divide-y divide-gray-200 bg-white'}>
|
|
{links.map((link) => (
|
|
<PublicLinkSummaryRow challenge={link} />
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function PublicLinkSummaryRow(props: { challenge: Challenge }) {
|
|
const { challenge } = props
|
|
const {
|
|
acceptances,
|
|
creatorUsername,
|
|
creatorName,
|
|
creatorAvatarUrl,
|
|
contractCreatorUsername,
|
|
contractQuestion,
|
|
contractSlug,
|
|
} = challenge
|
|
|
|
const className = clsx(
|
|
'whitespace-nowrap text-sm hover:cursor-pointer text-gray-500 hover:bg-sky-50 bg-white'
|
|
)
|
|
return (
|
|
<tr
|
|
id={challenge.slug + '-public'}
|
|
key={challenge.slug + '-public'}
|
|
className={className}
|
|
onClick={() => Router.push(getChallengeUrl(challenge))}
|
|
>
|
|
<td className={amountClass}>
|
|
<SiteLink href={getChallengeUrl(challenge)}>
|
|
{formatMoney(challenge.creatorAmount)}
|
|
</SiteLink>
|
|
</td>
|
|
|
|
<td className={clsx(columnClass)}>
|
|
<Row className={'items-center justify-start gap-1'}>
|
|
<Avatar
|
|
username={creatorUsername}
|
|
avatarUrl={creatorAvatarUrl}
|
|
size={'sm'}
|
|
noLink={true}
|
|
/>
|
|
<UserLink name={creatorName} username={creatorUsername} />
|
|
</Row>
|
|
</td>
|
|
|
|
<td className={clsx(columnClass)}>
|
|
<Row className={'items-center justify-start gap-1'}>
|
|
{acceptances.length > 0 ? (
|
|
<>
|
|
<Avatar
|
|
username={acceptances[0].userUsername}
|
|
avatarUrl={acceptances[0].userAvatarUrl}
|
|
size={'sm'}
|
|
noLink={true}
|
|
/>
|
|
<UserLink
|
|
name={acceptances[0].userName}
|
|
username={acceptances[0].userUsername}
|
|
/>
|
|
</>
|
|
) : (
|
|
<span>
|
|
No one -
|
|
{challenge.expiresTime &&
|
|
` (expires ${fromNow(challenge.expiresTime)})`}
|
|
</span>
|
|
)}
|
|
</Row>
|
|
</td>
|
|
<td className={clsx(columnClass, 'font-bold')}>
|
|
<SiteLink
|
|
href={contractPathWithoutContract(
|
|
contractCreatorUsername,
|
|
contractSlug
|
|
)}
|
|
>
|
|
{contractQuestion}
|
|
</SiteLink>
|
|
</td>
|
|
</tr>
|
|
)
|
|
}
|