Single source of truth for predict

This commit is contained in:
Ian Philips 2022-09-15 09:12:56 -06:00
parent 4c10c8499b
commit e9f136a653
23 changed files with 88 additions and 33 deletions

View File

@ -15,6 +15,9 @@ export type EnvConfig = {
// Branding
moneyMoniker: string // e.g. 'M$'
bettor?: string // e.g. 'bettor' or 'predictor'
presentBet?: string // e.g. 'bet' or 'predict'
pastBet?: string // e.g. 'bet' or 'prediction'
faviconPath?: string // Should be a file in /public
navbarLogoPath?: string
newQuestionPlaceholders: string[]
@ -79,6 +82,9 @@ export const PROD_CONFIG: EnvConfig = {
visibility: 'PUBLIC',
moneyMoniker: 'M$',
bettor: 'predictor',
pastBet: 'prediction',
presentBet: 'predict',
navbarLogoPath: '',
faviconPath: '/favicon.ico',
newQuestionPlaceholders: [

View File

@ -7,6 +7,7 @@ type AnyTxnType =
| Referral
| UniqueBettorBonus
| BettingStreakBonus
| CancelUniqueBettorBonus
type SourceType = 'USER' | 'CONTRACT' | 'CHARITY' | 'BANK'
export type Txn<T extends AnyTxnType = AnyTxnType> = {
@ -29,6 +30,7 @@ export type Txn<T extends AnyTxnType = AnyTxnType> = {
| 'REFERRAL'
| 'UNIQUE_BETTOR_BONUS'
| 'BETTING_STREAK_BONUS'
| 'CANCEL_UNIQUE_BETTOR_BONUS'
// Any extra data
data?: { [key: string]: any }
@ -89,9 +91,19 @@ type BettingStreakBonus = {
}
}
type CancelUniqueBettorBonus = {
fromType: 'USER'
toType: 'BANK'
category: 'CANCEL_UNIQUE_BETTOR_BONUS'
data: {
contractId: string
}
}
export type DonationTxn = Txn & Donation
export type TipTxn = Txn & Tip
export type ManalinkTxn = Txn & Manalink
export type ReferralTxn = Txn & Referral
export type BettingStreakBonusTxn = Txn & BettingStreakBonus
export type UniqueBettorBonusTxn = Txn & UniqueBettorBonus
export type CancelUniqueBettorBonusTxn = Txn & CancelUniqueBettorBonus

View File

@ -1,4 +1,5 @@
import { notification_preferences } from './user-notification-preferences'
import { ENV_CONFIG } from 'common/envs/constants'
export type User = {
id: string
@ -83,3 +84,10 @@ export type PortfolioMetrics = {
export const MANIFOLD_USERNAME = 'ManifoldMarkets'
export const MANIFOLD_AVATAR_URL = 'https://manifold.markets/logo-bg-white.png'
export const BETTOR = ENV_CONFIG.bettor ?? 'bettor' // aka predictor
export const BETTORS = ENV_CONFIG.bettor + 's' ?? 'bettors'
export const PRESENT_BET = ENV_CONFIG.presentBet ?? 'bet' // aka predict
export const PRESENT_BETS = ENV_CONFIG.presentBet + 's' ?? 'bets'
export const PAST_BET = ENV_CONFIG.pastBet ?? 'bet' // aka prediction
export const PAST_BETS = ENV_CONFIG.pastBet + 's' ?? 'bets' // aka predictions

View File

@ -16,6 +16,10 @@ export function formatMoneyWithDecimals(amount: number) {
return ENV_CONFIG.moneyMoniker + amount.toFixed(2)
}
export function capitalFirst(s: string) {
return s.charAt(0).toUpperCase() + s.slice(1)
}
export function formatWithCommas(amount: number) {
return formatter.format(Math.floor(amount)).replace('$', '')
}

View File

@ -10,6 +10,7 @@ import { useSaveBinaryShares } from './use-save-binary-shares'
import { Col } from './layout/col'
import { Button } from 'web/components/button'
import { BetSignUpPrompt } from './sign-up-prompt'
import { PRESENT_BET } from 'common/user'
/** Button that opens BetPanel in a new modal */
export default function BetButton(props: {
@ -36,12 +37,12 @@ export default function BetButton(props: {
<Button
size="lg"
className={clsx(
'my-auto inline-flex min-w-[75px] whitespace-nowrap',
'my-auto inline-flex min-w-[75px] whitespace-nowrap capitalize',
btnClassName
)}
onClick={() => setOpen(true)}
>
Predict
{PRESENT_BET}
</Button>
) : (
<BetSignUpPrompt />

View File

@ -3,7 +3,7 @@ import algoliasearch from 'algoliasearch/lite'
import { SearchOptions } from '@algolia/client-search'
import { useRouter } from 'next/router'
import { Contract } from 'common/contract'
import { User } from 'common/user'
import { PAST_BETS, User } from 'common/user'
import {
ContractHighlightOptions,
ContractsGrid,
@ -41,7 +41,7 @@ const searchIndexName = ENV === 'DEV' ? 'dev-contracts' : 'contractsIndex'
export const SORTS = [
{ label: 'Newest', value: 'newest' },
{ label: 'Trending', value: 'score' },
{ label: 'Most traded', value: 'most-traded' },
{ label: `Most ${PAST_BETS}`, value: 'most-traded' },
{ label: '24h volume', value: '24-hour-vol' },
{ label: '24h change', value: 'prob-change-day' },
{ label: 'Last updated', value: 'last-updated' },
@ -450,7 +450,7 @@ function ContractSearchControls(props: {
selected={state.pillFilter === 'your-bets'}
onSelect={selectPill('your-bets')}
>
Your trades
Your {PAST_BETS}
</PillButton>
)}

View File

@ -18,6 +18,7 @@ import { deleteField } from 'firebase/firestore'
import ShortToggle from '../widgets/short-toggle'
import { DuplicateContractButton } from '../copy-contract-button'
import { Row } from '../layout/row'
import { BETTORS } from 'common/user'
export const contractDetailsButtonClassName =
'group flex items-center rounded-md px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-100 text-gray-400 hover:text-gray-500'
@ -135,7 +136,7 @@ export function ContractInfoDialog(props: {
</tr> */}
<tr>
<td>Traders</td>
<td>{BETTORS}</td>
<td>{bettorsCount}</td>
</tr>

View File

@ -12,6 +12,7 @@ import { FeedComment } from '../feed/feed-comments'
import { Spacer } from '../layout/spacer'
import { Leaderboard } from '../leaderboard'
import { Title } from '../title'
import { BETTORS } from 'common/user'
export function ContractLeaderboard(props: {
contract: Contract
@ -48,7 +49,7 @@ export function ContractLeaderboard(props: {
return users && users.length > 0 ? (
<Leaderboard
title="🏅 Top traders"
title={`🏅 Top ${BETTORS}`}
users={users || []}
columns={[
{

View File

@ -1,7 +1,7 @@
import { Bet } from 'common/bet'
import { Contract, CPMMBinaryContract } from 'common/contract'
import { ContractComment } from 'common/comment'
import { User } from 'common/user'
import { PAST_BETS, User } from 'common/user'
import {
ContractCommentsActivity,
ContractBetsActivity,
@ -114,13 +114,13 @@ export function ContractTabs(props: {
badge: `${comments.length}`,
},
{
title: 'Trades',
title: PAST_BETS,
content: betActivity,
badge: `${visibleBets.length}`,
},
...(!user || !userBets?.length
? []
: [{ title: 'Your trades', content: yourTrades }]),
: [{ title: `Your ${PAST_BETS}`, content: yourTrades }]),
]}
/>
{!user ? (

View File

@ -14,6 +14,7 @@ import { SiteLink } from 'web/components/site-link'
import { getChallenge, getChallengeUrl } from 'web/lib/firebase/challenges'
import { Challenge } from 'common/challenge'
import { UserLink } from 'web/components/user-link'
import { BETTOR } from 'common/user'
export function FeedBet(props: { contract: Contract; bet: Bet }) {
const { contract, bet } = props
@ -94,7 +95,7 @@ export function BetStatusText(props: {
{!hideUser ? (
<UserLink name={bet.userName} username={bet.userUsername} />
) : (
<span>{self?.id === bet.userId ? 'You' : 'A trader'}</span>
<span>{self?.id === bet.userId ? 'You' : `A ${BETTOR}`}</span>
)}{' '}
{bought} {money}
{outOfTotalAmount}

View File

@ -1,6 +1,6 @@
import { Bet } from 'common/bet'
import { ContractComment } from 'common/comment'
import { User } from 'common/user'
import { PRESENT_BET, User } from 'common/user'
import { Contract } from 'common/contract'
import React, { useEffect, useState } from 'react'
import { minBy, maxBy, partition, sumBy, Dictionary } from 'lodash'
@ -255,7 +255,7 @@ function CommentStatus(props: {
const { contract, outcome, prob } = props
return (
<>
{' betting '}
{` ${PRESENT_BET}ing `}
<OutcomeLabel outcome={outcome} contract={contract} truncate="short" />
{prob && ' at ' + Math.round(prob * 100) + '%'}
</>

View File

@ -1,6 +1,6 @@
import clsx from 'clsx'
import dayjs from 'dayjs'
import { User } from 'common/user'
import { BETTOR, User } from 'common/user'
import { useUser, useUserById } from 'web/hooks/use-user'
import { Row } from 'web/components/layout/row'
import { Avatar, EmptyAvatar } from 'web/components/avatar'
@ -74,7 +74,7 @@ export function LiquidityStatusText(props: {
{bettor ? (
<UserLink name={bettor.name} username={bettor.username} />
) : (
<span>{isSelf ? 'You' : 'A trader'}</span>
<span>{isSelf ? 'You' : `A ${BETTOR}`}</span>
)}{' '}
{bought} a subsidy of {money}
<RelativeTimestamp time={createdTime} />

View File

@ -13,6 +13,7 @@ import { NoLabel, YesLabel } from './outcome-label'
import { Col } from './layout/col'
import { track } from 'web/lib/service/analytics'
import { InfoTooltip } from './info-tooltip'
import { BETTORS, PRESENT_BET } from 'common/user'
export function LiquidityPanel(props: { contract: CPMMContract }) {
const { contract } = props
@ -104,7 +105,9 @@ function AddLiquidityPanel(props: { contract: CPMMContract }) {
<>
<div className="mb-4 text-gray-500">
Contribute your M$ to make this market more accurate.{' '}
<InfoTooltip text="More liquidity stabilizes the market, encouraging traders to bet. You can withdraw your subsidy at any time." />
<InfoTooltip
text={`More liquidity stabilizes the market, encouraging ${BETTORS} to ${PRESENT_BET}. You can withdraw your subsidy at any time.`}
/>
</div>
<Row>

View File

@ -17,6 +17,7 @@ import { useRouter } from 'next/router'
import NotificationsIcon from 'web/components/notifications-icon'
import { useIsIframe } from 'web/hooks/use-is-iframe'
import { trackCallback } from 'web/lib/service/analytics'
import { PAST_BETS } from 'common/user'
function getNavigation() {
return [
@ -64,7 +65,7 @@ export function BottomNavBar() {
item={{
name: formatMoney(user.balance),
trackingEventName: 'profile',
href: `/${user.username}?tab=trades`,
href: `/${user.username}?tab=${PAST_BETS}`,
icon: () => (
<Avatar
className="mx-auto my-1"

View File

@ -4,11 +4,12 @@ import { User } from 'web/lib/firebase/users'
import { formatMoney } from 'common/util/format'
import { Avatar } from '../avatar'
import { trackCallback } from 'web/lib/service/analytics'
import { PAST_BETS } from 'common/user'
export function ProfileSummary(props: { user: User }) {
const { user } = props
return (
<Link href={`/${user.username}?tab=trades`}>
<Link href={`/${user.username}?tab=${PAST_BETS}`}>
<a
onClick={trackCallback('sidebar: profile')}
className="group mb-3 flex flex-row items-center gap-4 truncate rounded-md py-3 text-gray-500 hover:bg-gray-100 hover:text-gray-700"

View File

@ -10,6 +10,7 @@ import { NumericContract, PseudoNumericContract } from 'common/contract'
import { APIError, resolveMarket } from 'web/lib/firebase/api'
import { BucketInput } from './bucket-input'
import { getPseudoProbability } from 'common/pseudo-numeric'
import { BETTOR, BETTORS, PAST_BETS } from 'common/user'
export function NumericResolutionPanel(props: {
isAdmin: boolean
@ -111,9 +112,12 @@ export function NumericResolutionPanel(props: {
<div>
{outcome === 'CANCEL' ? (
<>All trades will be returned with no fees.</>
<>
All {PAST_BETS} will be returned. Unique {BETTOR} bonuses will be
withdrawn from your account
</>
) : (
<>Resolving this market will immediately pay out traders.</>
<>Resolving this market will immediately pay out {BETTORS}.</>
)}
</div>

View File

@ -1,5 +1,6 @@
import { Modal } from 'web/components/layout/modal'
import { Col } from 'web/components/layout/col'
import { PAST_BETS } from 'common/user'
export function LoansModal(props: {
isOpen: boolean
@ -11,7 +12,7 @@ export function LoansModal(props: {
<Modal open={isOpen} setOpen={setOpen}>
<Col className="items-center gap-4 rounded-md bg-white px-8 py-6">
<span className={'text-8xl'}>🏦</span>
<span className="text-xl">Daily loans on your trades</span>
<span className="text-xl">Daily loans on your {PAST_BETS}</span>
<Col className={'gap-2'}>
<span className={'text-indigo-700'}> What are daily loans?</span>
<span className={'ml-2'}>

View File

@ -10,6 +10,7 @@ import { APIError, resolveMarket } from 'web/lib/firebase/api'
import { ProbabilitySelector } from './probability-selector'
import { getProbability } from 'common/calculate'
import { BinaryContract, resolution } from 'common/contract'
import { BETTOR, BETTORS, PAST_BETS } from 'common/user'
export function ResolutionPanel(props: {
isAdmin: boolean
@ -90,23 +91,28 @@ export function ResolutionPanel(props: {
<div>
{outcome === 'YES' ? (
<>
Winnings will be paid out to traders who bought YES.
Winnings will be paid out to {BETTORS} who bought YES.
{/* <br />
<br />
You will earn {earnedFees}. */}
</>
) : outcome === 'NO' ? (
<>
Winnings will be paid out to traders who bought NO.
Winnings will be paid out to {BETTORS} who bought NO.
{/* <br />
<br />
You will earn {earnedFees}. */}
</>
) : outcome === 'CANCEL' ? (
<>All trades will be returned with no fees.</>
<>
All {PAST_BETS} will be returned. Unique {BETTOR} bonuses will be
withdrawn from your account
</>
) : outcome === 'MKT' ? (
<Col className="gap-6">
<div>Traders will be paid out at the probability you specify:</div>
<div>
{PAST_BETS} will be paid out at the probability you specify:
</div>
<ProbabilitySelector
probabilityInt={Math.round(prob)}
setProbabilityInt={setProb}
@ -114,7 +120,7 @@ export function ResolutionPanel(props: {
{/* You will earn {earnedFees}. */}
</Col>
) : (
<>Resolving this market will immediately pay out traders.</>
<>Resolving this market will immediately pay out {BETTORS}.</>
)}
</div>

View File

@ -25,7 +25,7 @@ import { UserFollowButton } from './follow-button'
import { GroupsButton } from 'web/components/groups/groups-button'
import { PortfolioValueSection } from './portfolio/portfolio-value-section'
import { ReferralsButton } from 'web/components/referrals-button'
import { formatMoney } from 'common/util/format'
import { capitalFirst, formatMoney } from 'common/util/format'
import { ShareIconButton } from 'web/components/share-icon-button'
import { ENV_CONFIG } from 'common/envs/constants'
import {
@ -35,6 +35,7 @@ import {
import { REFERRAL_AMOUNT } from 'common/economy'
import { LoansModal } from './profile/loans-modal'
import { UserLikesButton } from 'web/components/profile/user-likes-button'
import { PAST_BETS } from 'common/user'
export function UserPage(props: { user: User }) {
const { user } = props
@ -269,7 +270,7 @@ export function UserPage(props: { user: User }) {
),
},
{
title: 'Trades',
title: capitalFirst(PAST_BETS),
content: (
<>
<BetsList user={user} />

View File

@ -8,6 +8,7 @@ import {
usePersistentState,
urlParamStore,
} from 'web/hooks/use-persistent-state'
import { PAST_BETS } from 'common/user'
const MAX_CONTRACTS_RENDERED = 100
@ -101,7 +102,7 @@ export default function ContractSearchFirestore(props: {
>
<option value="score">Trending</option>
<option value="newest">Newest</option>
<option value="most-traded">Most traded</option>
<option value="most-traded">Most ${PAST_BETS}</option>
<option value="24-hour-vol">24h volume</option>
<option value="close-date">Closing soon</option>
</select>

View File

@ -50,6 +50,7 @@ import { usePost } from 'web/hooks/use-post'
import { useAdmin } from 'web/hooks/use-admin'
import { track } from '@amplitude/analytics-browser'
import { SelectMarketsModal } from 'web/components/contract-select-modal'
import { BETTORS } from 'common/user'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
@ -155,7 +156,7 @@ export default function GroupPage(props: {
<div className="mt-4 flex flex-col gap-8 px-4 md:flex-row">
<GroupLeaderboard
topUsers={topTraders}
title="🏅 Top traders"
title={`🏅 Top ${BETTORS}`}
header="Profit"
maxToShow={maxLeaderboardSize}
/>

View File

@ -14,6 +14,7 @@ import { Title } from 'web/components/title'
import { Tabs } from 'web/components/layout/tabs'
import { useTracking } from 'web/hooks/use-tracking'
import { SEO } from 'web/components/SEO'
import { BETTORS } from 'common/user'
export async function getStaticProps() {
const props = await fetchProps()
@ -79,7 +80,7 @@ export default function Leaderboards(_props: {
<>
<Col className="mx-4 items-center gap-10 lg:flex-row">
<Leaderboard
title="🏅 Top traders"
title={`🏅 Top ${BETTORS}`}
users={topTraders}
columns={[
{
@ -126,7 +127,7 @@ export default function Leaderboards(_props: {
<Page>
<SEO
title="Leaderboards"
description="Manifold's leaderboards show the top traders and market creators."
description={`Manifold's leaderboards show the top ${BETTORS} and market creators.`}
url="/leaderboards"
/>
<Title text={'Leaderboards'} className={'hidden md:block'} />

View File

@ -13,6 +13,7 @@ import { SiteLink } from 'web/components/site-link'
import { Linkify } from 'web/components/linkify'
import { getStats } from 'web/lib/firebase/stats'
import { Stats } from 'common/stats'
import { PAST_BETS } from 'common/user'
export default function Analytics() {
const [stats, setStats] = useState<Stats | undefined>(undefined)
@ -156,7 +157,7 @@ export function CustomAnalytics(props: {
defaultIndex={0}
tabs={[
{
title: 'Trades',
title: PAST_BETS,
content: (
<DailyCountChart
dailyCounts={dailyBetCounts}