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:
parent
84296bfdde
commit
48a249eaa9
52
web/components/bet-panel.tsx
Normal file
52
web/components/bet-panel.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
64
web/components/contract-overview.tsx
Normal file
64
web/components/contract-overview.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
5
web/components/layout/col.tsx
Normal file
5
web/components/layout/col.tsx
Normal 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>
|
||||||
|
}
|
5
web/components/layout/row.tsx
Normal file
5
web/components/layout/row.tsx
Normal 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>
|
||||||
|
}
|
8
web/components/layout/spacer.tsx
Normal file
8
web/components/layout/spacer.tsx
Normal 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 }} />
|
||||||
|
}
|
66
web/components/yes-no-selector.tsx
Normal file
66
web/components/yes-no-selector.tsx
Normal 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(' ')
|
||||||
|
}
|
|
@ -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')
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user