From a0f62ba1729ebf226f4779fe96f32921b4515f50 Mon Sep 17 00:00:00 2001 From: Ian Philips Date: Fri, 19 Aug 2022 11:43:57 -0600 Subject: [PATCH] Markets emails (#764) * Send out email template for 3 trending markets * Rich text to plaintext descriptions, other ui changes * Lint * Filter for closed markets * Change sign * First order must be closeTime * Send 6 emails, check flag twice * Exclude contracts with trump and president in the name * interesting markets email * sendInterestingMarketsEmail * Change subject line back Co-authored-by: mantikoros --- common/contract-details.ts | 151 ++++++ common/user.ts | 1 + firestore.rules | 2 +- .../email-templates/interesting-markets.html | 476 ++++++++++++++++++ functions/src/emails.ts | 59 +++ functions/src/index.ts | 2 + functions/src/unsubscribe.ts | 4 + functions/src/utils.ts | 6 + functions/src/weekly-markets-emails.ts | 82 +++ web/components/SEO.tsx | 56 +-- .../contract/contract-card-preview.tsx | 44 -- web/components/contract/contract-details.tsx | 26 +- web/components/contract/quick-bet.tsx | 3 +- web/components/feed/feed-items.tsx | 2 +- web/lib/firebase/contracts.ts | 29 +- web/pages/[username]/[contractSlug].tsx | 2 +- .../[contractSlug]/[challengeSlug].tsx | 2 +- 17 files changed, 791 insertions(+), 156 deletions(-) create mode 100644 common/contract-details.ts create mode 100644 functions/src/email-templates/interesting-markets.html create mode 100644 functions/src/weekly-markets-emails.ts delete mode 100644 web/components/contract/contract-card-preview.tsx diff --git a/common/contract-details.ts b/common/contract-details.ts new file mode 100644 index 00000000..02af6359 --- /dev/null +++ b/common/contract-details.ts @@ -0,0 +1,151 @@ +import { Challenge } from './challenge' +import { BinaryContract, Contract } from './contract' +import { getFormattedMappedValue } from './pseudo-numeric' +import { getProbability } from './calculate' +import { richTextToString } from './util/parse' +import { getCpmmProbability } from './calculate-cpmm' +import { getDpmProbability } from './calculate-dpm' +import { formatMoney, formatPercent } from './util/format' + +export function contractMetrics(contract: Contract) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const dayjs = require('dayjs') + const { createdTime, resolutionTime, isResolved } = contract + + const createdDate = dayjs(createdTime).format('MMM D') + + const resolvedDate = isResolved + ? dayjs(resolutionTime).format('MMM D') + : undefined + + const volumeLabel = `${formatMoney(contract.volume)} bet` + + return { volumeLabel, createdDate, resolvedDate } +} + +// String version of the above, to send to the OpenGraph image generator +export function contractTextDetails(contract: Contract) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const dayjs = require('dayjs') + const { closeTime, tags } = contract + const { createdDate, resolvedDate, volumeLabel } = contractMetrics(contract) + + const hashtags = tags.map((tag) => `#${tag}`) + + return ( + `${resolvedDate ? `${createdDate} - ${resolvedDate}` : createdDate}` + + (closeTime + ? ` • ${closeTime > Date.now() ? 'Closes' : 'Closed'} ${dayjs( + closeTime + ).format('MMM D, h:mma')}` + : '') + + ` • ${volumeLabel}` + + (hashtags.length > 0 ? ` • ${hashtags.join(' ')}` : '') + ) +} + +export function getBinaryProb(contract: BinaryContract) { + const { pool, resolutionProbability, mechanism } = contract + + return ( + resolutionProbability ?? + (mechanism === 'cpmm-1' + ? getCpmmProbability(pool, contract.p) + : getDpmProbability(contract.totalShares)) + ) +} + +export const getOpenGraphProps = (contract: Contract) => { + const { + resolution, + question, + creatorName, + creatorUsername, + outcomeType, + creatorAvatarUrl, + description: desc, + } = contract + const probPercent = + outcomeType === 'BINARY' + ? formatPercent(getBinaryProb(contract)) + : undefined + + const numericValue = + outcomeType === 'PSEUDO_NUMERIC' + ? getFormattedMappedValue(contract)(getProbability(contract)) + : undefined + + const stringDesc = typeof desc === 'string' ? desc : richTextToString(desc) + + const description = resolution + ? `Resolved ${resolution}. ${stringDesc}` + : probPercent + ? `${probPercent} chance. ${stringDesc}` + : stringDesc + + return { + question, + probability: probPercent, + metadata: contractTextDetails(contract), + creatorName, + creatorUsername, + creatorAvatarUrl, + description, + numericValue, + } +} + +export type OgCardProps = { + question: string + probability?: string + metadata: string + creatorName: string + creatorUsername: string + creatorAvatarUrl?: string + numericValue?: string +} + +export function buildCardUrl(props: OgCardProps, challenge?: Challenge) { + const { + creatorAmount, + acceptances, + acceptorAmount, + creatorOutcome, + acceptorOutcome, + } = challenge || {} + const { userName, userAvatarUrl } = acceptances?.[0] ?? {} + + const probabilityParam = + props.probability === undefined + ? '' + : `&probability=${encodeURIComponent(props.probability ?? '')}` + + const numericValueParam = + props.numericValue === undefined + ? '' + : `&numericValue=${encodeURIComponent(props.numericValue ?? '')}` + + const creatorAvatarUrlParam = + props.creatorAvatarUrl === undefined + ? '' + : `&creatorAvatarUrl=${encodeURIComponent(props.creatorAvatarUrl ?? '')}` + + const challengeUrlParams = challenge + ? `&creatorAmount=${creatorAmount}&creatorOutcome=${creatorOutcome}` + + `&challengerAmount=${acceptorAmount}&challengerOutcome=${acceptorOutcome}` + + `&acceptedName=${userName ?? ''}&acceptedAvatarUrl=${userAvatarUrl ?? ''}` + : '' + + // URL encode each of the props, then add them as query params + return ( + `https://manifold-og-image.vercel.app/m.png` + + `?question=${encodeURIComponent(props.question)}` + + probabilityParam + + numericValueParam + + `&metadata=${encodeURIComponent(props.metadata)}` + + `&creatorName=${encodeURIComponent(props.creatorName)}` + + creatorAvatarUrlParam + + `&creatorUsername=${encodeURIComponent(props.creatorUsername)}` + + challengeUrlParams + ) +} diff --git a/common/user.ts b/common/user.ts index 8ad4c91b..2910c54e 100644 --- a/common/user.ts +++ b/common/user.ts @@ -59,6 +59,7 @@ export type PrivateUser = { unsubscribedFromCommentEmails?: boolean unsubscribedFromAnswerEmails?: boolean unsubscribedFromGenericEmails?: boolean + unsubscribedFromWeeklyTrendingEmails?: boolean manaBonusEmailSent?: boolean initialDeviceToken?: string initialIpAddress?: string diff --git a/firestore.rules b/firestore.rules index 81ab4eed..c0d17dac 100644 --- a/firestore.rules +++ b/firestore.rules @@ -63,7 +63,7 @@ service cloud.firestore { allow read: if userId == request.auth.uid || isAdmin(); allow update: if (userId == request.auth.uid || isAdmin()) && request.resource.data.diff(resource.data).affectedKeys() - .hasOnly(['apiKey', 'unsubscribedFromResolutionEmails', 'unsubscribedFromCommentEmails', 'unsubscribedFromAnswerEmails', 'notificationPreferences' ]); + .hasOnly(['apiKey', 'unsubscribedFromResolutionEmails', 'unsubscribedFromCommentEmails', 'unsubscribedFromAnswerEmails', 'notificationPreferences', 'unsubscribedFromWeeklyTrendingEmails' ]); } match /private-users/{userId}/views/{viewId} { diff --git a/functions/src/email-templates/interesting-markets.html b/functions/src/email-templates/interesting-markets.html new file mode 100644 index 00000000..fc067643 --- /dev/null +++ b/functions/src/email-templates/interesting-markets.html @@ -0,0 +1,476 @@ + + + + + Interesting markets on Manifold + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + + + +
+ + + + + + + +
+ + + + banner logo + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ Hi {{name}},

+
+
+
+

+ Here is a selection of markets on Manifold you might find + interesting!

+
+
+
+ + {{question1Title}} + +
+ + + + + +
+ + View market + +
+ +
+
+ + {{question2Title}} + +
+ + + + + +
+ + View market + +
+ +
+
+ + {{question3Title}} + +
+ + + + + +
+ + View market + +
+ +
+
+ + {{question4Title}} + +
+ + + + + +
+ + View market + +
+ +
+
+ + {{question5Title}} + +
+ + + + + +
+ + View market + +
+ +
+
+ + {{question6Title}} + +
+ + + + + +
+ + View market + +
+ +
+
+ +
+
+ + +
+ + + +
+ + +
+ + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + +
+
+

+ This e-mail has been sent to + {{name}}, + click here to unsubscribe. +

+
+
+
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/functions/src/emails.ts b/functions/src/emails.ts index acab22d8..97ffce10 100644 --- a/functions/src/emails.ts +++ b/functions/src/emails.ts @@ -20,6 +20,7 @@ import { sendTemplateEmail, sendTextEmail } from './send-email' import { getPrivateUser, getUser } from './utils' import { getFunctionUrl } from '../../common/api' import { richTextToString } from '../../common/util/parse' +import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details' const UNSUBSCRIBE_ENDPOINT = getFunctionUrl('unsubscribe') @@ -460,3 +461,61 @@ export const sendNewAnswerEmail = async ( { from } ) } + +export const sendInterestingMarketsEmail = async ( + user: User, + privateUser: PrivateUser, + contractsToSend: Contract[], + deliveryTime?: string +) => { + if ( + !privateUser || + !privateUser.email || + privateUser?.unsubscribedFromWeeklyTrendingEmails + ) + return + + const emailType = 'weekly-trending' + const unsubscribeUrl = `${UNSUBSCRIBE_ENDPOINT}?id=${privateUser.id}&type=${emailType}` + + const { name } = user + const firstName = name.split(' ')[0] + + await sendTemplateEmail( + privateUser.email, + `${contractsToSend[0].question} & 5 more interesting markets on Manifold`, + 'interesting-markets', + { + name: firstName, + unsubscribeLink: unsubscribeUrl, + + question1Title: contractsToSend[0].question, + question1Link: contractUrl(contractsToSend[0]), + question1ImgSrc: imageSourceUrl(contractsToSend[0]), + question2Title: contractsToSend[1].question, + question2Link: contractUrl(contractsToSend[1]), + question2ImgSrc: imageSourceUrl(contractsToSend[1]), + question3Title: contractsToSend[2].question, + question3Link: contractUrl(contractsToSend[2]), + question3ImgSrc: imageSourceUrl(contractsToSend[2]), + question4Title: contractsToSend[3].question, + question4Link: contractUrl(contractsToSend[3]), + question4ImgSrc: imageSourceUrl(contractsToSend[3]), + question5Title: contractsToSend[4].question, + question5Link: contractUrl(contractsToSend[4]), + question5ImgSrc: imageSourceUrl(contractsToSend[4]), + question6Title: contractsToSend[5].question, + question6Link: contractUrl(contractsToSend[5]), + question6ImgSrc: imageSourceUrl(contractsToSend[5]), + }, + deliveryTime ? { 'o:deliverytime': deliveryTime } : undefined + ) +} + +function contractUrl(contract: Contract) { + return `https://manifold.markets/${contract.creatorUsername}/${contract.slug}` +} + +function imageSourceUrl(contract: Contract) { + return buildCardUrl(getOpenGraphProps(contract)) +} diff --git a/functions/src/index.ts b/functions/src/index.ts index c9f62484..ec1947f1 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -25,8 +25,10 @@ export * from './on-create-comment-on-group' export * from './on-create-txn' export * from './on-delete-group' export * from './score-contracts' +export * from './weekly-markets-emails' export * from './reset-betting-streaks' + // v2 export * from './health' export * from './transact' diff --git a/functions/src/unsubscribe.ts b/functions/src/unsubscribe.ts index fda20e16..4db91539 100644 --- a/functions/src/unsubscribe.ts +++ b/functions/src/unsubscribe.ts @@ -21,6 +21,7 @@ export const unsubscribe: EndpointDefinition = { 'market-comment', 'market-answer', 'generic', + 'weekly-trending', ].includes(type) ) { res.status(400).send('Invalid type parameter.') @@ -49,6 +50,9 @@ export const unsubscribe: EndpointDefinition = { ...(type === 'generic' && { unsubscribedFromGenericEmails: true, }), + ...(type === 'weekly-trending' && { + unsubscribedFromWeeklyTrendingEmails: true, + }), } await firestore.collection('private-users').doc(id).update(update) diff --git a/functions/src/utils.ts b/functions/src/utils.ts index 721f33d0..2d620728 100644 --- a/functions/src/utils.ts +++ b/functions/src/utils.ts @@ -88,6 +88,12 @@ export const getPrivateUser = (userId: string) => { return getDoc('private-users', userId) } +export const getAllPrivateUsers = async () => { + const firestore = admin.firestore() + const users = await firestore.collection('private-users').get() + return users.docs.map((doc) => doc.data() as PrivateUser) +} + export const getUserByUsername = async (username: string) => { const firestore = admin.firestore() const snap = await firestore diff --git a/functions/src/weekly-markets-emails.ts b/functions/src/weekly-markets-emails.ts new file mode 100644 index 00000000..c5805d0b --- /dev/null +++ b/functions/src/weekly-markets-emails.ts @@ -0,0 +1,82 @@ +import * as functions from 'firebase-functions' +import * as admin from 'firebase-admin' + +import { Contract } from '../../common/contract' +import { getPrivateUser, getUser, getValues, isProd, log } from './utils' +import { filterDefined } from '../../common/util/array' +import { sendInterestingMarketsEmail } from './emails' +import { createRNG, shuffle } from '../../common/util/random' +import { DAY_MS } from '../../common/util/time' + +export const weeklyMarketsEmails = functions + .runWith({ secrets: ['MAILGUN_KEY'] }) + .pubsub.schedule('every 1 minutes') + .onRun(async () => { + await sendTrendingMarketsEmailsToAllUsers() + }) + +const firestore = admin.firestore() + +export async function getTrendingContracts() { + return await getValues( + firestore + .collection('contracts') + .where('isResolved', '==', false) + .where('closeTime', '>', Date.now() + DAY_MS) + .where('visibility', '==', 'public') + .orderBy('closeTime', 'asc') + .orderBy('popularityScore', 'desc') + .limit(15) + ) +} + +async function sendTrendingMarketsEmailsToAllUsers() { + const numEmailsToSend = 6 + // const privateUsers = await getAllPrivateUsers() + // uses dev ian's private user for testing + const privateUser = await getPrivateUser( + isProd() ? 'AJwLWoo3xue32XIiAVrL5SyR1WB2' : '6hHpzvRG0pMq8PNJs7RZj2qlZGn2' + ) + const privateUsers = filterDefined([privateUser]) + // get all users that haven't unsubscribed from weekly emails + const privateUsersToSendEmailsTo = privateUsers.filter((user) => { + return !user.unsubscribedFromWeeklyTrendingEmails + }) + const trendingContracts = (await getTrendingContracts()).filter( + (contract) => + !( + contract.question.toLowerCase().includes('trump') && + contract.question.toLowerCase().includes('president') + ) + ) + for (const privateUser of privateUsersToSendEmailsTo) { + if (!privateUser.email) { + log(`No email for ${privateUser.username}`) + continue + } + const contractsAvailableToSend = trendingContracts.filter((contract) => { + return !contract.uniqueBettorIds?.includes(privateUser.id) + }) + if (contractsAvailableToSend.length < numEmailsToSend) { + log('not enough new, unbet-on contracts to send to user', privateUser.id) + continue + } + // choose random subset of contracts to send to user + const contractsToSend = chooseRandomSubset( + contractsAvailableToSend, + numEmailsToSend + ) + + const user = await getUser(privateUser.id) + if (!user) continue + + await sendInterestingMarketsEmail(user, privateUser, contractsToSend) + } +} + +function chooseRandomSubset(contracts: Contract[], count: number) { + const fiveMinutes = 5 * 60 * 1000 + const seed = Math.round(Date.now() / fiveMinutes).toString() + shuffle(contracts, createRNG(seed)) + return contracts.slice(0, count) +} diff --git a/web/components/SEO.tsx b/web/components/SEO.tsx index 08dee31e..2c9327ec 100644 --- a/web/components/SEO.tsx +++ b/web/components/SEO.tsx @@ -1,61 +1,7 @@ import { ReactNode } from 'react' import Head from 'next/head' import { Challenge } from 'common/challenge' - -export type OgCardProps = { - question: string - probability?: string - metadata: string - creatorName: string - creatorUsername: string - creatorAvatarUrl?: string - numericValue?: string -} - -function buildCardUrl(props: OgCardProps, challenge?: Challenge) { - const { - creatorAmount, - acceptances, - acceptorAmount, - creatorOutcome, - acceptorOutcome, - } = challenge || {} - const { userName, userAvatarUrl } = acceptances?.[0] ?? {} - - const probabilityParam = - props.probability === undefined - ? '' - : `&probability=${encodeURIComponent(props.probability ?? '')}` - - const numericValueParam = - props.numericValue === undefined - ? '' - : `&numericValue=${encodeURIComponent(props.numericValue ?? '')}` - - const creatorAvatarUrlParam = - props.creatorAvatarUrl === undefined - ? '' - : `&creatorAvatarUrl=${encodeURIComponent(props.creatorAvatarUrl ?? '')}` - - const challengeUrlParams = challenge - ? `&creatorAmount=${creatorAmount}&creatorOutcome=${creatorOutcome}` + - `&challengerAmount=${acceptorAmount}&challengerOutcome=${acceptorOutcome}` + - `&acceptedName=${userName ?? ''}&acceptedAvatarUrl=${userAvatarUrl ?? ''}` - : '' - - // URL encode each of the props, then add them as query params - return ( - `https://manifold-og-image.vercel.app/m.png` + - `?question=${encodeURIComponent(props.question)}` + - probabilityParam + - numericValueParam + - `&metadata=${encodeURIComponent(props.metadata)}` + - `&creatorName=${encodeURIComponent(props.creatorName)}` + - creatorAvatarUrlParam + - `&creatorUsername=${encodeURIComponent(props.creatorUsername)}` + - challengeUrlParams - ) -} +import { buildCardUrl, OgCardProps } from 'common/contract-details' export function SEO(props: { title: string diff --git a/web/components/contract/contract-card-preview.tsx b/web/components/contract/contract-card-preview.tsx deleted file mode 100644 index 354fe308..00000000 --- a/web/components/contract/contract-card-preview.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Contract } from 'common/contract' -import { getBinaryProbPercent } from 'web/lib/firebase/contracts' -import { richTextToString } from 'common/util/parse' -import { contractTextDetails } from 'web/components/contract/contract-details' -import { getFormattedMappedValue } from 'common/pseudo-numeric' -import { getProbability } from 'common/calculate' - -export const getOpenGraphProps = (contract: Contract) => { - const { - resolution, - question, - creatorName, - creatorUsername, - outcomeType, - creatorAvatarUrl, - description: desc, - } = contract - const probPercent = - outcomeType === 'BINARY' ? getBinaryProbPercent(contract) : undefined - - const numericValue = - outcomeType === 'PSEUDO_NUMERIC' - ? getFormattedMappedValue(contract)(getProbability(contract)) - : undefined - - const stringDesc = typeof desc === 'string' ? desc : richTextToString(desc) - - const description = resolution - ? `Resolved ${resolution}. ${stringDesc}` - : probPercent - ? `${probPercent} chance. ${stringDesc}` - : stringDesc - - return { - question, - probability: probPercent, - metadata: contractTextDetails(contract), - creatorName, - creatorUsername, - creatorAvatarUrl, - description, - numericValue, - } -} diff --git a/web/components/contract/contract-details.tsx b/web/components/contract/contract-details.tsx index 5a62313f..833b37eb 100644 --- a/web/components/contract/contract-details.tsx +++ b/web/components/contract/contract-details.tsx @@ -9,11 +9,7 @@ import { import { Row } from '../layout/row' import { formatMoney } from 'common/util/format' import { UserLink } from '../user-page' -import { - Contract, - contractMetrics, - updateContract, -} from 'web/lib/firebase/contracts' +import { Contract, updateContract } from 'web/lib/firebase/contracts' import dayjs from 'dayjs' import { DateTimeTooltip } from '../datetime-tooltip' import { fromNow } from 'web/lib/util/time' @@ -35,6 +31,7 @@ import { SiteLink } from 'web/components/site-link' import { groupPath } from 'web/lib/firebase/groups' import { insertContent } from '../editor/utils' import clsx from 'clsx' +import { contractMetrics } from 'common/contract-details' export type ShowTime = 'resolve-date' | 'close-date' @@ -245,25 +242,6 @@ export function ContractDetails(props: { ) } -// String version of the above, to send to the OpenGraph image generator -export function contractTextDetails(contract: Contract) { - const { closeTime, tags } = contract - const { createdDate, resolvedDate, volumeLabel } = contractMetrics(contract) - - const hashtags = tags.map((tag) => `#${tag}`) - - return ( - `${resolvedDate ? `${createdDate} - ${resolvedDate}` : createdDate}` + - (closeTime - ? ` • ${closeTime > Date.now() ? 'Closes' : 'Closed'} ${dayjs( - closeTime - ).format('MMM D, h:mma')}` - : '') + - ` • ${volumeLabel}` + - (hashtags.length > 0 ? ` • ${hashtags.join(' ')}` : '') - ) -} - function EditableCloseDate(props: { closeTime: number contract: Contract diff --git a/web/components/contract/quick-bet.tsx b/web/components/contract/quick-bet.tsx index 92cee018..7ef371f0 100644 --- a/web/components/contract/quick-bet.tsx +++ b/web/components/contract/quick-bet.tsx @@ -23,7 +23,7 @@ import { useState } from 'react' import toast from 'react-hot-toast' import { useUserContractBets } from 'web/hooks/use-user-bets' import { placeBet } from 'web/lib/firebase/api' -import { getBinaryProb, getBinaryProbPercent } from 'web/lib/firebase/contracts' +import { getBinaryProbPercent } from 'web/lib/firebase/contracts' import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon' import TriangleFillIcon from 'web/lib/icons/triangle-fill-icon' import { Col } from '../layout/col' @@ -34,6 +34,7 @@ import { calculateCpmmSale, getCpmmProbability } from 'common/calculate-cpmm' import { track } from 'web/lib/service/analytics' import { formatNumericProbability } from 'common/pseudo-numeric' import { useUnfilledBets } from 'web/hooks/use-bets' +import { getBinaryProb } from 'common/contract-details' const BET_SIZE = 10 diff --git a/web/components/feed/feed-items.tsx b/web/components/feed/feed-items.tsx index dcd5743b..62673428 100644 --- a/web/components/feed/feed-items.tsx +++ b/web/components/feed/feed-items.tsx @@ -11,7 +11,6 @@ import clsx from 'clsx' import { OutcomeLabel } from '../outcome-label' import { Contract, - contractMetrics, contractPath, tradingAllowed, } from 'web/lib/firebase/contracts' @@ -38,6 +37,7 @@ import { FeedLiquidity } from './feed-liquidity' import { SignUpPrompt } from '../sign-up-prompt' import { User } from 'common/user' import { PlayMoneyDisclaimer } from '../play-money-disclaimer' +import { contractMetrics } from 'common/contract-details' export function FeedItems(props: { contract: Contract diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index 243a453a..1f83372e 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -1,4 +1,3 @@ -import dayjs from 'dayjs' import { collection, deleteDoc, @@ -17,14 +16,13 @@ import { sortBy, sum } from 'lodash' import { coll, getValues, listenForValue, listenForValues } from './utils' import { BinaryContract, Contract } from 'common/contract' -import { getDpmProbability } from 'common/calculate-dpm' import { createRNG, shuffle } from 'common/util/random' -import { getCpmmProbability } from 'common/calculate-cpmm' import { formatMoney, formatPercent } from 'common/util/format' import { DAY_MS } from 'common/util/time' import { Bet } from 'common/bet' import { Comment } from 'common/comment' import { ENV_CONFIG } from 'common/envs/constants' +import { getBinaryProb } from 'common/contract-details' export const contracts = coll('contracts') @@ -49,20 +47,6 @@ export function contractUrl(contract: Contract) { return `https://${ENV_CONFIG.domain}${contractPath(contract)}` } -export function contractMetrics(contract: Contract) { - const { createdTime, resolutionTime, isResolved } = contract - - const createdDate = dayjs(createdTime).format('MMM D') - - const resolvedDate = isResolved - ? dayjs(resolutionTime).format('MMM D') - : undefined - - const volumeLabel = `${formatMoney(contract.volume)} bet` - - return { volumeLabel, createdDate, resolvedDate } -} - export function contractPool(contract: Contract) { return contract.mechanism === 'cpmm-1' ? formatMoney(contract.totalLiquidity) @@ -71,17 +55,6 @@ export function contractPool(contract: Contract) { : 'Empty pool' } -export function getBinaryProb(contract: BinaryContract) { - const { pool, resolutionProbability, mechanism } = contract - - return ( - resolutionProbability ?? - (mechanism === 'cpmm-1' - ? getCpmmProbability(pool, contract.p) - : getDpmProbability(contract.totalShares)) - ) -} - export function getBinaryProbPercent(contract: BinaryContract) { return formatPercent(getBinaryProb(contract)) } diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index 41ad5957..c86f9c55 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -36,13 +36,13 @@ import { AlertBox } from 'web/components/alert-box' import { useTracking } from 'web/hooks/use-tracking' import { CommentTipMap, useTipTxns } from 'web/hooks/use-tip-txns' import { useSaveReferral } from 'web/hooks/use-save-referral' -import { getOpenGraphProps } from 'web/components/contract/contract-card-preview' import { User } from 'common/user' import { ContractComment } from 'common/comment' import { listUsers } from 'web/lib/firebase/users' import { FeedComment } from 'web/components/feed/feed-comments' import { Title } from 'web/components/title' import { FeedBet } from 'web/components/feed/feed-bets' +import { getOpenGraphProps } from 'common/contract-details' export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz(props: { diff --git a/web/pages/challenges/[username]/[contractSlug]/[challengeSlug].tsx b/web/pages/challenges/[username]/[contractSlug]/[challengeSlug].tsx index 55e78616..f15c5809 100644 --- a/web/pages/challenges/[username]/[contractSlug]/[challengeSlug].tsx +++ b/web/pages/challenges/[username]/[contractSlug]/[challengeSlug].tsx @@ -28,11 +28,11 @@ import { LoadingIndicator } from 'web/components/loading-indicator' import { useWindowSize } from 'web/hooks/use-window-size' import { Bet, listAllBets } from 'web/lib/firebase/bets' import { SEO } from 'web/components/SEO' -import { getOpenGraphProps } from 'web/components/contract/contract-card-preview' import Custom404 from 'web/pages/404' import { useSaveReferral } from 'web/hooks/use-save-referral' import { BinaryContract } from 'common/contract' import { Title } from 'web/components/title' +import { getOpenGraphProps } from 'common/contract-details' export const getStaticProps = fromPropz(getStaticPropz)