manifold/web/lib/firebase/challenges.ts

151 lines
4.0 KiB
TypeScript
Raw Normal View History

Challenge Bets (#679) * 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>
2022-08-04 21:27:02 +00:00
import {
collectionGroup,
doc,
getDoc,
orderBy,
query,
setDoc,
where,
} from 'firebase/firestore'
import { Challenge } from 'common/challenge'
import { customAlphabet } from 'nanoid'
import { coll, listenForValue, listenForValues } from './utils'
import { useEffect, useState } from 'react'
import { User } from 'common/user'
import { db } from './init'
import { Contract } from 'common/contract'
import { ENV_CONFIG } from 'common/envs/constants'
export const challenges = (contractId: string) =>
coll<Challenge>(`contracts/${contractId}/challenges`)
export function getChallengeUrl(challenge: Challenge) {
return `https://${ENV_CONFIG.domain}/challenges/${challenge.creatorUsername}/${challenge.contractSlug}/${challenge.slug}`
}
export async function createChallenge(data: {
creator: User
outcome: 'YES' | 'NO' | number
contract: Contract
creatorAmount: number
acceptorAmount: number
expiresTime: number | null
message: string
}) {
const {
creator,
creatorAmount,
expiresTime,
message,
contract,
outcome,
acceptorAmount,
} = data
// At 100 IDs per hour, using this alphabet and 8 chars, there's a 1% chance of collision in 2 years
// See https://zelark.github.io/nano-id-cc/
const nanoid = customAlphabet(
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
8
)
const slug = nanoid()
if (creatorAmount <= 0 || isNaN(creatorAmount) || !isFinite(creatorAmount))
return null
const challenge: Challenge = {
slug,
creatorId: creator.id,
creatorUsername: creator.username,
creatorName: creator.name,
creatorAvatarUrl: creator.avatarUrl,
creatorAmount,
creatorOutcome: outcome.toString(),
creatorOutcomeProb: creatorAmount / (creatorAmount + acceptorAmount),
acceptorOutcome: outcome === 'YES' ? 'NO' : 'YES',
acceptorAmount,
contractSlug: contract.slug,
contractId: contract.id,
contractQuestion: contract.question,
contractCreatorUsername: contract.creatorUsername,
createdTime: Date.now(),
expiresTime,
maxUses: 1,
acceptedByUserIds: [],
acceptances: [],
isResolved: false,
message,
}
await setDoc(doc(challenges(contract.id), slug), challenge)
return challenge
}
// TODO: This required an index, make sure to also set up in prod
function listUserChallenges(fromId?: string) {
return query(
collectionGroup(db, 'challenges'),
where('creatorId', '==', fromId),
orderBy('createdTime', 'desc')
)
}
function listChallenges() {
return query(collectionGroup(db, 'challenges'))
}
export const useAcceptedChallenges = () => {
const [links, setLinks] = useState<Challenge[]>([])
useEffect(() => {
listenForValues(listChallenges(), (challenges: Challenge[]) => {
setLinks(
challenges
.sort((a: Challenge, b: Challenge) => b.createdTime - a.createdTime)
.filter((challenge) => challenge.acceptedByUserIds.length > 0)
)
})
}, [])
return links
}
export function listenForChallenge(
slug: string,
contractId: string,
setLinks: (challenge: Challenge | null) => void
) {
return listenForValue<Challenge>(doc(challenges(contractId), slug), setLinks)
}
export function useChallenge(slug: string, contractId: string | undefined) {
const [challenge, setChallenge] = useState<Challenge | null>()
useEffect(() => {
if (slug && contractId) {
listenForChallenge(slug, contractId, setChallenge)
}
}, [contractId, slug])
return challenge
}
export function listenForUserChallenges(
fromId: string | undefined,
setLinks: (links: Challenge[]) => void
) {
return listenForValues<Challenge>(listUserChallenges(fromId), setLinks)
}
export const useUserChallenges = (fromId: string) => {
const [links, setLinks] = useState<Challenge[]>([])
useEffect(() => {
return listenForUserChallenges(fromId, setLinks)
}, [fromId])
return links
}
export const getChallenge = async (slug: string, contractId: string) => {
const challenge = await getDoc(doc(challenges(contractId), slug))
return challenge.data() as Challenge
}