From 09b463919835d77ee44ffd7add46e617161d0ade Mon Sep 17 00:00:00 2001 From: Austin Chen Date: Thu, 9 Dec 2021 20:54:40 -0800 Subject: [PATCH] Allow users to create new contracts --- web/lib/firebase/contracts.ts | 75 +++++++++++-- web/pages/contract/index.tsx | 203 ++++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+), 12 deletions(-) create mode 100644 web/pages/contract/index.tsx diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index 42166817..fddb4b3d 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -1,20 +1,71 @@ -import { collection, onSnapshot, doc } from '@firebase/firestore' -import { db } from './init' +import { app } from './init' +import { + getFirestore, + doc, + setDoc, + deleteDoc, + where, + collection, + query, + getDocs, +} from 'firebase/firestore' export type Contract = { - id: string + id: string // Chosen by creator; must be unique + creatorId: string + question: string + description: string // More info about what the contract is about + outcomeType: 'BINARY' // | 'MULTI' | 'interval' | 'date' + // outcomes: ['YES', 'NO'] + seedAmounts: { YES: number; NO: number } // seedBets: [number, number] + + createdTime: number // Milliseconds since epoch + lastUpdatedTime: number // If the question or description was changed + closeTime?: number // When no more trading is allowed + + // isResolved: boolean + resolutionTime?: 10293849 // When the contract creator resolved the market; 0 if unresolved + resolution?: 'YES' | 'NO' | 'CANCEL' // Chosen by creator; must be one of outcomes } -const contractCollection = collection(db, 'contracts') +export type Bet = { + id: string + userId: string + contractId: string -export function listenForContract( - contractId: string, - setContract: (contract: Contract) => void -) { - const contractRef = doc(contractCollection, contractId) + size: number // Amount of USD bid + outcome: 'YES' | 'NO' // Chosen outcome + createdTime: number - return onSnapshot(contractRef, (contractSnap) => { - setContract(contractSnap.data() as Contract) - }) + dpmWeight: number // Dynamic Parimutuel weight +} + +const db = getFirestore(app) + +// Push contract to Firestore +export async function setContract(contract: Contract) { + const docRef = doc(db, 'contracts', contract.id) + await setDoc(docRef, contract) +} + +export async function deleteContract(contractId: string) { + const docRef = doc(db, 'contracts', contractId) + await deleteDoc(docRef) +} + +export async function listContracts(creatorId: string): Promise { + const contractsRef = collection(db, 'contracts') + const q = query(contractsRef, where('creatorId', '==', creatorId)) + const snapshot = await getDocs(q) + const contracts: Contract[] = [] + snapshot.forEach((doc) => contracts.push(doc.data() as Contract)) + return contracts +} + +// Push bet to Firestore +// TODO: Should bets be subcollections under its contract? +export async function setBet(bet: Bet) { + const docRef = doc(db, 'bets', bet.id) + await setDoc(docRef, bet) } diff --git a/web/pages/contract/index.tsx b/web/pages/contract/index.tsx new file mode 100644 index 00000000..9be4bc55 --- /dev/null +++ b/web/pages/contract/index.tsx @@ -0,0 +1,203 @@ +import { FieldValue, serverTimestamp } from '@firebase/firestore' +import { useEffect, useState } from 'react' +import { Header } from '../../components/header' +import { useUser } from '../../hooks/use-user' +import { + Contract, + deleteContract, + listContracts, + setContract as pushContract, +} from '../../lib/firebase/contracts' + +function ContractCard(props: { contract: Contract }) { + const { contract } = props + return ( +
  • + +
    +
    +

    + {contract.question} +

    +
    +

    + {contract.outcomeType} +

    +
    +
    +
    +
    +

    + {/*

    +

    + {/*

    +
    +
    + {/*
    +
    +
    +
    +
  • + ) +} + +// Allow user to create a new contract +export default function NewContract() { + const creator = useUser() + const [contract, setContract] = useState({ + // creatorId: creator?.id || '', + // TODO: Set create time to Firestore timestamp + createdTime: Date.now(), + lastUpdatedTime: Date.now(), + } as Contract) + + const [contracts, setContracts] = useState([]) + useEffect(() => { + if (creator?.id) { + setContract({ ...contract, creatorId: creator.id }) + listContracts(creator?.id).then(setContracts) + } + }, [creator?.id]) + + async function saveContract() { + await pushContract(contract) + // Update local contract list + setContracts([...contracts, { ...contract }]) + } + + function saveField(field: keyof Contract) { + return (changeEvent: React.ChangeEvent) => + setContract({ ...contract, [field]: changeEvent.target.value }) + } + + const descriptionPlaceholder = `e.g. This market will resolve to “Yes” if, by June 2, 2021, 11:59:59 PM ET, Paxlovid (also known under PF-07321332)...` + + return ( +
    +
    +
    +
    +
    +

    + Create a new contract +

    +
    + + {/* Create a Tailwind form that takes in all the fields needed for a new contract */} + {/* When the form is submitted, create a new contract in the database */} +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    + + {/* TODO: Show a preview of the created market here? */} + +
    + +
    +
    +
    + + {/* Show a separate card for each contract */} +
    +
      + {contracts.map((contract) => ( + + ))} +
    +
    +
    +
    + ) +}