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>
151 lines
4.0 KiB
TypeScript
151 lines
4.0 KiB
TypeScript
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
|
|
}
|