Contract page (#5)

* Add Firestore package and config

* Upload basic Firebase Auth code

* Basic ability to sign in and view profile

* Move html head content to Next's _document

* Apply dark theme to all DaisyUI components

* Add contract page

* Smaller width bet input

* Add some buttons

* Add Row, Col, and Spacer components

* Implement skeleton ContractPage

* Apply dark theme to all DaisyUI components

* Fix hooks lints (#3)

* Add background to bet panel

* Changes based on review comments

Co-authored-by: Austin Chen <akrolsmir@gmail.com>
This commit is contained in:
James Grugett 2021-12-10 08:56:17 -06:00 committed by GitHub
parent 84296bfdde
commit 48a249eaa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 221 additions and 8 deletions

View File

@ -0,0 +1,52 @@
import React, { useState } from 'react'
import { Contract } from '../lib/firebase/contracts'
import { Col } from './layout/col'
import { Spacer } from './layout/spacer'
import { YesNoSelector } from './yes-no-selector'
export function BetPanel(props: { contract: Contract; className?: string }) {
const { contract, className } = props
const [betChoice, setBetChoice] = useState<'YES' | 'NO'>('YES')
const [shares, setShares] = useState(0)
return (
<Col className={'bg-gray-600 p-6 rounded ' + className}>
<div className="p-2 font-medium">Pick outcome</div>
<YesNoSelector
className="p-2"
selected={betChoice}
onSelect={setBetChoice}
yesLabel="Yes 57"
noLabel="No 43"
/>
<Spacer h={4} />
<div className="p-2 font-medium">Shares</div>
<div className="p-2">
<input
className="input input-bordered input-md"
style={{ maxWidth: 80 }}
type="text"
value={shares}
onChange={(e) => setShares(parseInt(e.target.value) || 0)}
onFocus={(e) => e.target.select()}
/>
</div>
<Spacer h={4} />
<div className="p-2 font-medium">Price</div>
<div className="px-2">
{shares * (betChoice === 'YES' ? 57 : 43)} points
</div>
<Spacer h={6} />
{shares !== 0 && (
<button className="btn btn-primary">Place bet</button>
)}
</Col>
)
}

View File

@ -0,0 +1,64 @@
import React from 'react'
import { Line } from 'react-chartjs-2'
import {
CategoryScale,
Chart,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from 'chart.js'
import { Contract } from '../lib/firebase/contracts'
import { Col } from './layout/col'
import { Row } from './layout/row'
import { Spacer } from './layout/spacer'
// Auto import doesn't work for some reason...
// So we manually register ChartJS components instead:
Chart.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
)
const chartData = {
labels: Array.from({ length: 0 }, (_, i) => i + 1),
datasets: [
{
label: 'Implied probability',
data: [],
borderColor: 'rgb(75, 192, 192)',
},
],
}
export const ContractOverview = (props: { contract: Contract }) => {
const { contract } = props
return (
<Col className="max-w-3xl w-full">
<div className="text-3xl font-medium p-2">{contract.question}</div>
<Row>
<div className="p-2">By {contract.creatorName}</div>
<div className="py-2"></div>
<div className="p-2">Dec 9</div>
<div className="py-2"></div>
<div className="p-2">200,000 volume</div>
</Row>
<Spacer h={4} />
<Line data={chartData} height={150} />
<Spacer h={12} />
<div className="text-gray-200">{contract.description}</div>
</Col>
)
}

View File

@ -0,0 +1,5 @@
export function Col(props: { children?: any; className?: string }) {
const { children, className } = props
return <div className={`${className} flex flex-col`}>{children}</div>
}

View File

@ -0,0 +1,5 @@
export function Row(props: { children?: any; className?: string }) {
const { children, className } = props
return <div className={`${className} flex flex-row`}>{children}</div>
}

View File

@ -0,0 +1,8 @@
export function Spacer(props: { w?: number; h?: number }) {
const { w, h } = props
const width = w === undefined ? undefined : w * 0.25 + 'rem'
const height = h === undefined ? undefined : h * 0.25 + 'rem'
return <div style={{ width, height, flexShrink: 0 }} />
}

View File

@ -0,0 +1,66 @@
import React from 'react'
import { Row } from './layout/row'
export function YesNoSelector(props: {
selected: 'YES' | 'NO'
onSelect: (selected: 'YES' | 'NO') => void
yesLabel?: string
noLabel?: string
className?: string
}) {
const { selected, onSelect, yesLabel, noLabel, className } = props
return (
<Row className={className}>
<Button
color={selected === 'YES' ? 'green' : 'deemphasized'}
hideFocusRing
onClick={() => onSelect('YES')}
>
{yesLabel ?? 'Yes'}
</Button>
<Button
color={selected === 'NO' ? 'red' : 'deemphasized'}
hideFocusRing
onClick={() => onSelect('NO')}
className="ml-3"
>
{noLabel ?? 'No'}
</Button>
</Row>
)
}
function Button(props: {
className?: string
onClick?: () => void
color: 'green' | 'red' | 'deemphasized'
hideFocusRing?: boolean
children?: any
}) {
const { className, onClick, children, color, hideFocusRing } = props
return (
<button
type="button"
className={classNames(
'inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white',
!hideFocusRing && 'focus:outline-none focus:ring-2 focus:ring-offset-2',
color === 'green' &&
'bg-green-500 hover:bg-green-600 focus:ring-green-500',
color === 'red' && 'bg-red-500 hover:bg-red-600 focus:ring-red-500',
color === 'deemphasized' &&
'bg-transparent hover:bg-gray-500 focus:ring-gray-400',
className
)}
onClick={onClick}
>
{children}
</button>
)
}
function classNames(...classes: any[]) {
return classes.filter(Boolean).join(' ')
}

View File

@ -3,7 +3,10 @@ import { db } from './init'
export type Contract = { export type Contract = {
id: string id: string
creatorId: string
creatorName: string
question: string question: string
description: string
} }
const contractCollection = collection(db, 'contracts') const contractCollection = collection(db, 'contracts')

View File

@ -1,27 +1,36 @@
import React from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useContract } from '../../hooks/use-contract' import { useContract } from '../../hooks/use-contract'
import { useUser } from '../../hooks/use-user' import { Header } from '../../components/header'
import { Row } from '../../components/layout/row'
import { ContractOverview } from '../../components/contract-overview'
import { BetPanel } from '../../components/bet-panel'
export default function ContractPage() { export default function ContractPage() {
const user = useUser()
const router = useRouter() const router = useRouter()
const { contractId } = router.query as { contractId: string } const { contractId } = router.query as { contractId: string }
const contract = useContract(contractId) const contract = useContract(contractId)
if (contract === 'loading') { if (contract === 'loading') {
return <div>Loading...</div> return <div />
} }
if (contract === null) { if (!contract) {
return <div>Contract not found...</div> return <div>Contract not found...</div>
} }
return ( return (
<div> <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div>{contract.id}</div> <Header />
<div>{contract.question}</div>
<div className="w-full flex flex-col p-4 mt-4">
<Row className="justify-between">
<ContractOverview contract={contract} />
<BetPanel className="self-start" contract={contract} />
</Row>
</div>
</div> </div>
) )
} }

View File

@ -199,6 +199,7 @@ function NewBidTable(props: {
type="number" type="number"
placeholder="0" placeholder="0"
className="input input-bordered" className="input input-bordered"
style={{ maxWidth: 100 }}
value={newBid} value={newBid}
onChange={(e) => setNewBid(parseInt(e.target.value) || 0)} onChange={(e) => setNewBid(parseInt(e.target.value) || 0)}
onKeyUp={(e) => { onKeyUp={(e) => {