From e92674117786985ae3af9d5d22c2e909eeb21bf1 Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Wed, 27 Apr 2022 13:40:02 -0400
Subject: [PATCH 01/23] sitemap: remove non-public pages; sort by 24hr volume;
make market page priority depend on volume
---
web/pages/server-sitemap.xml.tsx | 22 ++++++++++++++--------
web/public/robots.txt | 2 +-
web/public/sitemap-0.xml | 19 +++----------------
3 files changed, 18 insertions(+), 25 deletions(-)
diff --git a/web/pages/server-sitemap.xml.tsx b/web/pages/server-sitemap.xml.tsx
index 8625860c..e8f77e58 100644
--- a/web/pages/server-sitemap.xml.tsx
+++ b/web/pages/server-sitemap.xml.tsx
@@ -1,20 +1,26 @@
+import _ from 'lodash'
import { GetServerSideProps } from 'next'
-import { getServerSideSitemap } from 'next-sitemap'
+import { getServerSideSitemap, ISitemapField } from 'next-sitemap'
+
import { DOMAIN } from '../../common/envs/constants'
+import { LiteMarket } from './api/v0/_types'
export const getServerSideProps: GetServerSideProps = async (ctx) => {
- // Fetching data from https://docs.manifold.markets/api
+ // Fetching data from https://manifold.markets/api
const response = await fetch(`https://${DOMAIN}/api/v0/markets`)
- const liteMarkets = await response.json()
- const fields = liteMarkets.map((liteMarket: any) => ({
+ const liteMarkets = (await response.json()) as LiteMarket[]
+ const sortedMarkets = _.sortBy(liteMarkets, (m) => -m.volume24Hours)
+
+ const fields = sortedMarkets.map((market) => ({
// See https://www.sitemaps.org/protocol.html
- loc: liteMarket.url,
+ loc: market.url,
changefreq: 'hourly',
- priority: 0.2, // Individual markets aren't that important
+ priority: market.volume24Hours + market.volume7Days > 100 ? 0.7 : 0.1,
// TODO: Add `lastmod` aka last modified time
- }))
- return getServerSideSitemap(ctx, fields)
+ })) as ISitemapField[]
+
+ return await getServerSideSitemap(ctx, fields)
}
// Default export to prevent next.js errors
diff --git a/web/public/robots.txt b/web/public/robots.txt
index 014904fd..adb6e505 100644
--- a/web/public/robots.txt
+++ b/web/public/robots.txt
@@ -6,5 +6,5 @@ Allow: /
Host: https://manifold.markets
# Sitemaps
-Sitemap: https://manifold.markets/sitemap.xml
Sitemap: https://manifold.markets/server-sitemap.xml
+Sitemap: https://manifold.markets/sitemap.xml
diff --git a/web/public/sitemap-0.xml b/web/public/sitemap-0.xml
index 80d0d7e3..6378f587 100644
--- a/web/public/sitemap-0.xml
+++ b/web/public/sitemap-0.xml
@@ -1,19 +1,6 @@
-https://manifold.marketshourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/abouthourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/accounthourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/add-fundshourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/analyticshourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/createhourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/embed/analyticshourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/foldshourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/homehourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/landing-pagehourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/leaderboardshourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/make-predictionshourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/marketshourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/profilehourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/simulatorhourly0.72022-03-24T16:51:19.526Z
-https://manifold.markets/portfoliohourly0.72022-03-24T16:51:19.526Z
+https://manifold.marketshourly0.7
+https://manifold.markets/marketshourly0.2
+https://manifold.markets/leaderboardshourly0.2
\ No newline at end of file
From 5a5069a4198ab03d17fcc2198c2ec856dfd8003d Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Wed, 27 Apr 2022 13:47:51 -0400
Subject: [PATCH 02/23] sitemap: market changefreq; homepage priority = 1
---
web/pages/server-sitemap.xml.tsx | 2 +-
web/public/sitemap-0.xml | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/web/pages/server-sitemap.xml.tsx b/web/pages/server-sitemap.xml.tsx
index e8f77e58..9a3cdfe8 100644
--- a/web/pages/server-sitemap.xml.tsx
+++ b/web/pages/server-sitemap.xml.tsx
@@ -15,7 +15,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
const fields = sortedMarkets.map((market) => ({
// See https://www.sitemaps.org/protocol.html
loc: market.url,
- changefreq: 'hourly',
+ changefreq: market.volume24Hours > 10 ? 'hourly' : 'daily',
priority: market.volume24Hours + market.volume7Days > 100 ? 0.7 : 0.1,
// TODO: Add `lastmod` aka last modified time
})) as ISitemapField[]
diff --git a/web/public/sitemap-0.xml b/web/public/sitemap-0.xml
index 6378f587..3b4618fb 100644
--- a/web/public/sitemap-0.xml
+++ b/web/public/sitemap-0.xml
@@ -1,6 +1,6 @@
-https://manifold.marketshourly0.7
+https://manifold.marketshourly1.0https://manifold.markets/marketshourly0.2
-https://manifold.markets/leaderboardshourly0.2
+https://manifold.markets/leaderboardsdaily0.2
\ No newline at end of file
From d33913a11139965ebb535111aae14d1b22f917fb Mon Sep 17 00:00:00 2001
From: Marshall Polaris
Date: Wed, 27 Apr 2022 13:15:14 -0700
Subject: [PATCH 03/23] A couple additions to help newbies out (#104)
---
README.md | 10 +++++++---
web/README.md | 17 ++++++++++-------
2 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 7416d3bd..56dceae0 100644
--- a/README.md
+++ b/README.md
@@ -25,8 +25,9 @@ Operations with complicated contracts (e.g. buying shares) are provided in a sep
- `functions/`: Firebase cloud functions, for secure work (e.g. balances, Stripe payments, emails). Also contains in
`functions/src/scripts/` some Typescript scripts that do ad hoc CLI interaction with Firebase.
-- `common/`: Typescript library code shared between `web/` & `functions/`. Also contains in `common/envs` configuration for
- the different environments (i.e. prod, dev, Manifold for Teams instances.)
+- `common/`: Typescript library code shared between `web/` & `functions/`. If you want to look at how the market math
+ works, most of that's in here (it gets called from the `placeBet` and `sellBet` endpoints in `functions/`.) Also
+ contains in `common/envs` configuration for the different environments (i.e. prod, dev, Manifold for Teams instances.)
- `og-image/`: The OpenGraph image generator; creates the preview images shown on Twitter/social media.
@@ -34,7 +35,9 @@ Also: Our docs are currently in [a separate repo](https://github.com/manifoldmar
## Contributing
-Since we are just now open-sourcing things, we will see how things go. Feel free to open issues, submit PRs, and chat about the process on Discord. We would prefer [small PRs][small-prs] that we can effectively evaluate and review -- maybe check in with us first if you are thinking to work on a big change.
+Since we are just now open-sourcing things, we will see how things go. Feel free to open issues, submit PRs, and chat about the process on [Discord][discord]. We would prefer [small PRs][small-prs] that we can effectively evaluate and review -- maybe check in with us first if you are thinking to work on a big change.
+
+If you need additional access to any infrastructure in order to work on something (e.g. Vercel, Firebase) let us know about that on Discord as well.
[vercel]: https://vercel.com/
[jamstack]: https://jamstack.org/
@@ -45,3 +48,4 @@ Since we are just now open-sourcing things, we will see how things go. Feel free
[cloud-firestore]: https://firebase.google.com/docs/firestore
[cloud-functions]: https://firebase.google.com/docs/functions
[small-prs]: https://google.github.io/eng-practices/review/developer/small-cls.html
+[discord]: https://discord.gg/eHQBNBqXuh
diff --git a/web/README.md b/web/README.md
index 8b3e65db..2bfd5056 100644
--- a/web/README.md
+++ b/web/README.md
@@ -2,13 +2,15 @@
## Getting started
-To run the development server, install [Yarn][yarn], and then in this directory:
+To run the development server, install [Yarn 1.x][yarn], and then in this directory:
1. `yarn` to install all dependencies
2. `yarn dev:dev` starts a development web server, pointing at the development database
3. Your site will be available on http://localhost:3000
-Check package.json for other command-line tasks. (e.g. `yarn dev` will point the development server at the prod database. `yarn emulate` will run against a local emulated database, if you are serving it via `yarn serve` from the [`functions/` package][functions-readme].)
+Check package.json for other command-line tasks. (e.g. `yarn dev` will point the development server at the prod
+database. `yarn emulate` will run against a local emulated database, if you are serving it via `yarn serve` from the
+[`functions/` package][functions-readme].)
## Tech stack
@@ -24,16 +26,17 @@ branch (to production) and PR branches (to ephemeral staging servers that can be
Parts of the file structure that directly map to HTTP endpoints are organized specially per Next.js's prescriptions:
-### /public
+### public/
These are static files that will be [served by Next verbatim][next-static-files].
-### /pages
+### pages/
These are components that [Next's router][next-pages] is aware of and interprets as page roots per their filename,
-e.g. the React component in pages/portfolio.tsx is rendered on the user portfolio page at /portfolio.
+e.g. the React component in pages/portfolio.tsx is rendered on the user portfolio page at /portfolio. You should
+look in here or in `components/` to find any specific piece of UI you are interested in working on.
-### /pages/api
+### pages/api/
Modules under this route are specially interpreted by Next/Vercel as [functions that will be hosted by
Vercel][vercel-functions]. This is where the public Manifold HTTP API lives.
@@ -52,7 +55,7 @@ integration][prettier-integrations] to format it in your editor.
[nextjs]: https://nextjs.org
[vercel]: https://vercel.com
[tailwind]: https://tailwindcss.com
-[yarn]: https://yarnpkg.com
+[yarn]: https://classic.yarnpkg.com/lang/en/docs/install/
[prettier]: https://prettier.io
[prettier-integrations]: https://prettier.io/docs/en/editors.html
[next-static-files]: https://nextjs.org/docs/basic-features/static-file-serving
From 8e514de28f99156cf1dbe1e51d739df0f465a642 Mon Sep 17 00:00:00 2001
From: James Grugett
Date: Wed, 27 Apr 2022 16:25:54 -0400
Subject: [PATCH 04/23] Add volume factor to feed
---
web/hooks/use-algo-feed.ts | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/web/hooks/use-algo-feed.ts b/web/hooks/use-algo-feed.ts
index 543f484b..e8d6396b 100644
--- a/web/hooks/use-algo-feed.ts
+++ b/web/hooks/use-algo-feed.ts
@@ -151,6 +151,10 @@ function getContractsActivityScores(
const activityCountScore =
0.5 + 0.5 * logInterpolation(0, 200, activtyCount)
+ const { volume7Days, volume } = contract
+ const combinedVolume = Math.log(volume7Days + 1) + Math.log(volume + 1)
+ const volumeScore = 0.5 + 0.5 * logInterpolation(4, 25, combinedVolume)
+
const lastBetTime =
contractMostRecentBet[contract.id]?.createdTime ?? contract.createdTime
const timeSinceLastBet = Date.now() - lastBetTime
@@ -169,7 +173,11 @@ function getContractsActivityScores(
const probScore = 0.5 + frac * 0.5
const score =
- newCommentScore * activityCountScore * timeAgoScore * probScore
+ newCommentScore *
+ activityCountScore *
+ volumeScore *
+ timeAgoScore *
+ probScore
// Map score to [0.5, 1] since no recent activty is not a deal breaker.
const mappedScore = 0.5 + score / 2
From d9ee03a96ff1b3a7a1cab733154575c8cc422611 Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Wed, 27 Apr 2022 22:18:52 -0400
Subject: [PATCH 05/23] sendNewCommentEmail: handle sold shares for cfmm
---
functions/src/emails.ts | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/functions/src/emails.ts b/functions/src/emails.ts
index 290aaecb..20cd002b 100644
--- a/functions/src/emails.ts
+++ b/functions/src/emails.ts
@@ -48,8 +48,8 @@ export const sendMarketResolutionEmail = async (
creatorName: creator.name,
question: contract.question,
outcome,
- investment: `${Math.round(investment)}`,
- payout: `${Math.round(payout)}`,
+ investment: `${Math.floor(investment)}`,
+ payout: `${Math.floor(payout)}`,
url: `https://${DOMAIN}/${creator.username}/${contract.slug}`,
}
@@ -189,7 +189,9 @@ export const sendNewCommentEmail = async (
let betDescription = ''
if (bet) {
const { amount, sale } = bet
- betDescription = `${sale ? 'sold' : 'bought'} M$ ${Math.round(amount)}`
+ betDescription = `${sale || amount < 0 ? 'sold' : 'bought'} ${formatMoney(
+ amount
+ )}`
}
const subject = `Comment on ${question}`
From 45aa6646fa2cb863071486757bf0960f27ac6718 Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Wed, 27 Apr 2022 23:34:50 -0400
Subject: [PATCH 06/23] sendNewCommentEmail: forgot abs value
---
functions/src/emails.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/functions/src/emails.ts b/functions/src/emails.ts
index 20cd002b..c3b70734 100644
--- a/functions/src/emails.ts
+++ b/functions/src/emails.ts
@@ -190,7 +190,7 @@ export const sendNewCommentEmail = async (
if (bet) {
const { amount, sale } = bet
betDescription = `${sale || amount < 0 ? 'sold' : 'bought'} ${formatMoney(
- amount
+ Math.abs(amount)
)}`
}
From da153ceea92a3d20914fff26f77f5c9c8204bed9 Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Thu, 28 Apr 2022 10:47:18 -0400
Subject: [PATCH 07/23] bound initial probability to [0.1, 0.9]
---
web/components/probability-selector.tsx | 22 +++++++++++++++++-----
web/pages/create.tsx | 7 ++++++-
2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/web/components/probability-selector.tsx b/web/components/probability-selector.tsx
index 2fc03787..74dab3c6 100644
--- a/web/components/probability-selector.tsx
+++ b/web/components/probability-selector.tsx
@@ -4,8 +4,11 @@ export function ProbabilitySelector(props: {
probabilityInt: number
setProbabilityInt: (p: number) => void
isSubmitting?: boolean
+ minProb?: number
+ maxProb?: number
}) {
- const { probabilityInt, setProbabilityInt, isSubmitting } = props
+ const { probabilityInt, setProbabilityInt, isSubmitting, minProb, maxProb } =
+ props
return (
@@ -15,19 +18,28 @@ export function ProbabilitySelector(props: {
value={probabilityInt}
className="input input-bordered input-md text-lg"
disabled={isSubmitting}
- min={1}
- max={99}
+ min={minProb ?? 1}
+ max={maxProb ?? 99}
onChange={(e) =>
setProbabilityInt(parseInt(e.target.value.substring(0, 2)))
}
+ onBlur={() =>
+ setProbabilityInt(
+ maxProb && probabilityInt > maxProb
+ ? maxProb
+ : minProb && probabilityInt < minProb
+ ? minProb
+ : probabilityInt
+ )
+ }
/>
%
setProbabilityInt(parseInt(e.target.value))}
/>
diff --git a/web/pages/create.tsx b/web/pages/create.tsx
index b668fffb..4a4a07b5 100644
--- a/web/pages/create.tsx
+++ b/web/pages/create.tsx
@@ -106,11 +106,14 @@ export function NewContract(props: { question: string; tag?: string }) {
setIsSubmitting(true)
+ const boundedProb =
+ initialProb > 90 ? 90 : initialProb < 10 ? 10 : initialProb
+
const result: any = await createContract({
question,
outcomeType,
description,
- initialProb,
+ initialProb: boundedProb,
ante,
closeTime,
tags,
@@ -172,6 +175,8 @@ export function NewContract(props: { question: string; tag?: string }) {
)}
From 625308c19d6f59ea7ce4b92b01357d5256a60ef6 Mon Sep 17 00:00:00 2001
From: Ian Philips
Date: Thu, 28 Apr 2022 12:29:02 -0600
Subject: [PATCH 08/23] Readme: must be in dev for emulators to work
---
functions/README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/functions/README.md b/functions/README.md
index 5f24a217..7601b7c8 100644
--- a/functions/README.md
+++ b/functions/README.md
@@ -34,9 +34,10 @@ Adapted from https://firebase.google.com/docs/functions/get-started
## Developing locally
+0. `$ firebase use dev` if you haven't already
1. `$ yarn serve` to spin up the emulators
- The Emulator UI is at http://localhost:4000; the functions are hosted on :5001.
- Note: You have to kill and restart emulators when you change code; no hot reload =(
+ 1. The Emulator UI is at http://localhost:4000; the functions are hosted on :5001.
+ Note: You have to kill and restart emulators when you change code; no hot reload =(
2. `$ yarn dev:emulate` in `/web` to connect to emulators with the frontend
1. Note: emulated database is cleared after every shutdown
From 2e17f9f91786fac6dc408f4fdd8c45a48a56ff63 Mon Sep 17 00:00:00 2001
From: Austin Chen
Date: Thu, 28 Apr 2022 18:45:26 -0400
Subject: [PATCH 09/23] Add a "Show more..." button when there are more
contracts
---
web/components/contract/contracts-list.tsx | 40 ++++++++++++++--------
1 file changed, 26 insertions(+), 14 deletions(-)
diff --git a/web/components/contract/contracts-list.tsx b/web/components/contract/contracts-list.tsx
index 1850cf0b..d6bef2e7 100644
--- a/web/components/contract/contracts-list.tsx
+++ b/web/components/contract/contracts-list.tsx
@@ -25,15 +25,16 @@ export function ContractsGrid(props: {
showCloseTime?: boolean
}) {
const { showCloseTime } = props
+ const PAGE_SIZE = 100
+ const [page, setPage] = useState(1)
const [resolvedContracts, activeContracts] = _.partition(
props.contracts,
(c) => c.isResolved
)
- const contracts = [...activeContracts, ...resolvedContracts].slice(
- 0,
- MAX_CONTRACTS_DISPLAYED
- )
+ const allContracts = [...activeContracts, ...resolvedContracts]
+ const showMore = allContracts.length > PAGE_SIZE * page
+ const contracts = allContracts.slice(0, PAGE_SIZE * page)
if (contracts.length === 0) {
return (
@@ -47,16 +48,27 @@ export function ContractsGrid(props: {
}
return (
-
- {contracts.map((contract) => (
-
- ))}
-
+ <>
+
+ {contracts.map((contract) => (
+
+ ))}
+
+ {/* Show a link that increases the page num when clicked */}
+ {showMore && (
+
+ )}
+ >
)
}
From 4ec59be46f0dbbaf4cac1b757181217b34b197f9 Mon Sep 17 00:00:00 2001
From: Boa
Date: Thu, 28 Apr 2022 17:01:50 -0600
Subject: [PATCH 10/23] Free daily market (#107)
* Allow users a free daily market
* Show confetti on recent created market
* remove unused import
* remove comment
* Did create market -> hook, capitalize buttons
* Check for confetti with interval
* Just check once
* Capitalize create market button on feed
---
functions/src/create-contract.ts | 12 +++++++--
web/components/feed-create.tsx | 2 +-
web/components/nav/sidebar.tsx | 20 ++++++++++++---
web/hooks/use-has-created-contract-today.ts | 27 +++++++++++++++++++++
web/package.json | 1 +
web/pages/[username]/[contractSlug].tsx | 23 ++++++++++++++++++
web/pages/create.tsx | 18 ++++++++++----
yarn.lock | 12 +++++++++
8 files changed, 104 insertions(+), 11 deletions(-)
create mode 100644 web/hooks/use-has-created-contract-today.ts
diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts
index 16a416b1..e45e7497 100644
--- a/functions/src/create-contract.ts
+++ b/functions/src/create-contract.ts
@@ -1,7 +1,6 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import * as _ from 'lodash'
-
import { chargeUser, getUser } from './utils'
import {
Binary,
@@ -109,7 +108,16 @@ export const createContract = functions
tags ?? []
)
- if (ante) await chargeUser(creator.id, ante)
+ // uses utc time on server:
+ const today = new Date().setHours(0, 0, 0, 0)
+ const userContractsCreatedTodaySnapshot = await firestore
+ .collection(`contracts`)
+ .where('creatorId', '==', userId)
+ .where('createdTime', '>=', today)
+ .get()
+ const isFree = userContractsCreatedTodaySnapshot.size === 0
+
+ if (!isFree && ante) await chargeUser(creator.id, ante)
await contractRef.create(contract)
diff --git a/web/components/feed-create.tsx b/web/components/feed-create.tsx
index d184e035..0a0c6902 100644
--- a/web/components/feed-create.tsx
+++ b/web/components/feed-create.tsx
@@ -141,7 +141,7 @@ export default function FeedCreate(props: {
{/* Show a fake "Create Market" button, which gets replaced with the NewContract one*/}
{!isExpanded && (
+ )}
+ {!deservesDailyFreeMarket && ante > balance && (
Insufficient balance
Date: Thu, 28 Apr 2022 19:31:43 -0600
Subject: [PATCH 11/23] use Date instead of dayjs
---
web/hooks/use-has-created-contract-today.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/web/hooks/use-has-created-contract-today.ts b/web/hooks/use-has-created-contract-today.ts
index 653049d2..3e9d6efe 100644
--- a/web/hooks/use-has-created-contract-today.ts
+++ b/web/hooks/use-has-created-contract-today.ts
@@ -1,6 +1,5 @@
import { listContracts } from '../lib/firebase/contracts'
import { useEffect, useState } from 'react'
-import dayjs from 'dayjs'
import { User } from '../../common/user'
export const useHasCreatedContractToday = (user: User | null | undefined) => {
@@ -8,7 +7,8 @@ export const useHasCreatedContractToday = (user: User | null | undefined) => {
useEffect(() => {
// Uses utc time like the server.
- const todayAtMidnight = dayjs.utc().startOf('day').valueOf()
+ const utcTimeString = new Date().toISOString()
+ const todayAtMidnight = new Date(utcTimeString).setUTCHours(0, 0, 0, 0)
async function listUserContractsForToday() {
if (!user) return
From 760681f95844e26630972329831c060d1d4a5413 Mon Sep 17 00:00:00 2001
From: Ian Philips
Date: Thu, 28 Apr 2022 19:39:55 -0600
Subject: [PATCH 12/23] Default has created one to prevent flash
---
web/hooks/use-has-created-contract-today.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/hooks/use-has-created-contract-today.ts b/web/hooks/use-has-created-contract-today.ts
index 3e9d6efe..43fe42f9 100644
--- a/web/hooks/use-has-created-contract-today.ts
+++ b/web/hooks/use-has-created-contract-today.ts
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'
import { User } from '../../common/user'
export const useHasCreatedContractToday = (user: User | null | undefined) => {
- const [hasCreatedContractToday, setHasCreatedContractToday] = useState(false)
+ const [hasCreatedContractToday, setHasCreatedContractToday] = useState(true)
useEffect(() => {
// Uses utc time like the server.
From 2ddd95e9049daaa97426b8b89c2eb9a35f2064f4 Mon Sep 17 00:00:00 2001
From: Marshall Polaris
Date: Thu, 28 Apr 2022 22:39:39 -0700
Subject: [PATCH 13/23] Make tags page filter on server side (#108)
---
web/lib/firebase/contracts.ts | 12 ++++++++++++
web/pages/tag/[tag].tsx | 36 +++++++++++++++--------------------
2 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts
index 1646bd09..f41d6902 100644
--- a/web/lib/firebase/contracts.ts
+++ b/web/lib/firebase/contracts.ts
@@ -115,6 +115,18 @@ export async function listContracts(creatorId: string): Promise {
return snapshot.docs.map((doc) => doc.data() as Contract)
}
+export async function listTaggedContractsCaseInsensitive(
+ tag: string
+): Promise {
+ const q = query(
+ contractCollection,
+ where('lowercaseTags', 'array-contains', tag.toLowerCase()),
+ orderBy('createdTime', 'desc')
+ )
+ const snapshot = await getDocs(q)
+ return snapshot.docs.map((doc) => doc.data() as Contract)
+}
+
export async function listAllContracts(): Promise {
const q = query(contractCollection, orderBy('createdTime', 'desc'))
const snapshot = await getDocs(q)
diff --git a/web/pages/tag/[tag].tsx b/web/pages/tag/[tag].tsx
index df9fd943..34c8f138 100644
--- a/web/pages/tag/[tag].tsx
+++ b/web/pages/tag/[tag].tsx
@@ -1,39 +1,33 @@
+import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { SearchableGrid } from '../../components/contract/contracts-list'
import { Page } from '../../components/page'
import { Title } from '../../components/title'
import { useContracts } from '../../hooks/use-contracts'
-import { Contract, listAllContracts } from '../../lib/firebase/contracts'
-
-export async function getStaticProps() {
- const contracts = await listAllContracts().catch((_) => [])
- return {
- props: {
- contracts,
- },
-
- revalidate: 60, // regenerate after a minute
- }
-}
-
-export async function getStaticPaths() {
- return { paths: [], fallback: 'blocking' }
-}
+import {
+ Contract,
+ listTaggedContractsCaseInsensitive,
+} from '../../lib/firebase/contracts'
export default function TagPage(props: { contracts: Contract[] }) {
const router = useRouter()
const { tag } = router.query as { tag: string }
- const contracts = useContracts()
+ // mqp: i wrote this in a panic to make the page literally work at all so if you
+ // want to e.g. listen for new contracts you may want to fix it up
+ const [contracts, setContracts] = useState('loading')
+ useEffect(() => {
+ if (tag != null) {
+ listTaggedContractsCaseInsensitive(tag).then(setContracts)
+ }
+ }, [tag])
- const taggedContracts = (contracts ?? props.contracts).filter((contract) =>
- contract.lowercaseTags.includes(tag.toLowerCase())
- )
+ if (contracts === 'loading') return <>>
return (
-
+
)
}
From ca8420d61be323b3317a27484bbca1dfa1b6d770 Mon Sep 17 00:00:00 2001
From: Ian Philips
Date: Fri, 29 Apr 2022 07:34:36 -0600
Subject: [PATCH 14/23] Allow free daily market with M-zsh
---
functions/src/create-contract.ts | 19 +++++++++----------
web/pages/create.tsx | 2 +-
2 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts
index e45e7497..781337f7 100644
--- a/functions/src/create-contract.ts
+++ b/functions/src/create-contract.ts
@@ -72,11 +72,19 @@ export const createContract = functions
return { status: 'error', message: 'Invalid initial probability' }
const ante = FIXED_ANTE // data.ante
+ // uses utc time on server:
+ const today = new Date().setHours(0, 0, 0, 0)
+ const userContractsCreatedTodaySnapshot = await firestore
+ .collection(`contracts`)
+ .where('creatorId', '==', userId)
+ .where('createdTime', '>=', today)
+ .get()
+ const isFree = userContractsCreatedTodaySnapshot.size === 0
if (
ante === undefined ||
ante < MINIMUM_ANTE ||
- ante > creator.balance ||
+ (ante > creator.balance && !isFree) ||
isNaN(ante) ||
!isFinite(ante)
)
@@ -108,15 +116,6 @@ export const createContract = functions
tags ?? []
)
- // uses utc time on server:
- const today = new Date().setHours(0, 0, 0, 0)
- const userContractsCreatedTodaySnapshot = await firestore
- .collection(`contracts`)
- .where('creatorId', '==', userId)
- .where('createdTime', '>=', today)
- .get()
- const isFree = userContractsCreatedTodaySnapshot.size === 0
-
if (!isFree && ante) await chargeUser(creator.id, ante)
await contractRef.create(contract)
diff --git a/web/pages/create.tsx b/web/pages/create.tsx
index 68a92260..ab614167 100644
--- a/web/pages/create.tsx
+++ b/web/pages/create.tsx
@@ -99,7 +99,7 @@ export function NewContract(props: { question: string; tag?: string }) {
ante !== undefined &&
ante !== null &&
ante >= MINIMUM_ANTE &&
- ante <= balance &&
+ (ante <= balance || deservesDailyFreeMarket) &&
// closeTime must be in the future
closeTime &&
closeTime > Date.now()
From 7e9007aad1c7d481752292f555c683cf23554b88 Mon Sep 17 00:00:00 2001
From: James Grugett
Date: Fri, 29 Apr 2022 09:53:51 -0400
Subject: [PATCH 15/23] Fetch markets client side on explore page (to avoid
vercel 5MB limit for static props)
---
web/pages/markets.tsx | 29 +++++++++--------------------
1 file changed, 9 insertions(+), 20 deletions(-)
diff --git a/web/pages/markets.tsx b/web/pages/markets.tsx
index 6149c8b5..81032199 100644
--- a/web/pages/markets.tsx
+++ b/web/pages/markets.tsx
@@ -2,26 +2,16 @@ import {
ContractsGrid,
SearchableGrid,
} from '../components/contract/contracts-list'
+import { LoadingIndicator } from '../components/loading-indicator'
import { Page } from '../components/page'
import { SEO } from '../components/SEO'
import { Title } from '../components/title'
import { useContracts } from '../hooks/use-contracts'
-import { Contract, listAllContracts } from '../lib/firebase/contracts'
-
-export async function getStaticProps() {
- const contracts = await listAllContracts().catch((_) => [])
- return {
- props: {
- contracts,
- },
-
- revalidate: 60, // regenerate after a minute
- }
-}
+import { Contract } from '../lib/firebase/contracts'
// TODO: Rename endpoint to "Explore"
-export default function Markets(props: { contracts: Contract[] }) {
- const contracts = useContracts() ?? props.contracts ?? []
+export default function Markets() {
+ const contracts = useContracts()
return (
@@ -30,12 +20,11 @@ export default function Markets(props: { contracts: Contract[] }) {
description="Discover what's new, trending, or soon-to-close. Or search among our hundreds of markets."
url="/markets"
/>
- {/*
-
-
- */}
-
-
+ {contracts === undefined ? (
+
+ ) : (
+
+ )}
)
}
From fa8ebe36bd8dd6dfd512ebc2e0283fd938e3ca83 Mon Sep 17 00:00:00 2001
From: James Grugett
Date: Fri, 29 Apr 2022 10:05:32 -0400
Subject: [PATCH 16/23] Move loading indicator into SearchableGrid.
---
web/components/contract/contracts-list.tsx | 9 ++++++---
web/pages/markets.tsx | 7 +------
web/pages/tag/[tag].tsx | 7 ++-----
3 files changed, 9 insertions(+), 14 deletions(-)
diff --git a/web/components/contract/contracts-list.tsx b/web/components/contract/contracts-list.tsx
index d6bef2e7..dab8613d 100644
--- a/web/components/contract/contracts-list.tsx
+++ b/web/components/contract/contracts-list.tsx
@@ -18,6 +18,7 @@ import {
useQueryAndSortParams,
} from '../../hooks/use-sort-and-query-params'
import { Answer } from '../../../common/answer'
+import { LoadingIndicator } from '../loading-indicator'
export function ContractsGrid(props: {
contracts: Contract[]
@@ -213,7 +214,7 @@ function TagContractsGrid(props: { contracts: Contract[] }) {
const MAX_CONTRACTS_DISPLAYED = 99
export function SearchableGrid(props: {
- contracts: Contract[]
+ contracts: Contract[] | undefined
byOneCreator?: boolean
querySortOptions?: {
defaultSort: Sort
@@ -230,7 +231,7 @@ export function SearchableGrid(props: {
return queryWords.every((word) => corpus.toLowerCase().includes(word))
}
- let matches = contracts.filter(
+ let matches = (contracts ?? []).filter(
(c) =>
check(c.question) ||
check(c.description) ||
@@ -324,7 +325,9 @@ export function SearchableGrid(props: {
- {sort === 'tag' ? (
+ {contracts === undefined ? (
+
+ ) : sort === 'tag' ? (
) : !byOneCreator && sort === 'creator' ? (
diff --git a/web/pages/markets.tsx b/web/pages/markets.tsx
index 81032199..23ff2adf 100644
--- a/web/pages/markets.tsx
+++ b/web/pages/markets.tsx
@@ -2,7 +2,6 @@ import {
ContractsGrid,
SearchableGrid,
} from '../components/contract/contracts-list'
-import { LoadingIndicator } from '../components/loading-indicator'
import { Page } from '../components/page'
import { SEO } from '../components/SEO'
import { Title } from '../components/title'
@@ -20,11 +19,7 @@ export default function Markets() {
description="Discover what's new, trending, or soon-to-close. Or search among our hundreds of markets."
url="/markets"
/>
- {contracts === undefined ? (
-
- ) : (
-
- )}
+
)
}
diff --git a/web/pages/tag/[tag].tsx b/web/pages/tag/[tag].tsx
index 34c8f138..cabc6c80 100644
--- a/web/pages/tag/[tag].tsx
+++ b/web/pages/tag/[tag].tsx
@@ -3,27 +3,24 @@ import { useRouter } from 'next/router'
import { SearchableGrid } from '../../components/contract/contracts-list'
import { Page } from '../../components/page'
import { Title } from '../../components/title'
-import { useContracts } from '../../hooks/use-contracts'
import {
Contract,
listTaggedContractsCaseInsensitive,
} from '../../lib/firebase/contracts'
-export default function TagPage(props: { contracts: Contract[] }) {
+export default function TagPage() {
const router = useRouter()
const { tag } = router.query as { tag: string }
// mqp: i wrote this in a panic to make the page literally work at all so if you
// want to e.g. listen for new contracts you may want to fix it up
- const [contracts, setContracts] = useState('loading')
+ const [contracts, setContracts] = useState()
useEffect(() => {
if (tag != null) {
listTaggedContractsCaseInsensitive(tag).then(setContracts)
}
}, [tag])
- if (contracts === 'loading') return <>>
-
return (
From c59e444d0ad5d70bd744b259d369a67597c54e10 Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Fri, 29 Apr 2022 13:30:27 -0400
Subject: [PATCH 17/23] Manifold CLA
---
functions/.github/CONTRIBUTING.md | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
create mode 100644 functions/.github/CONTRIBUTING.md
diff --git a/functions/.github/CONTRIBUTING.md b/functions/.github/CONTRIBUTING.md
new file mode 100644
index 00000000..4651b133
--- /dev/null
+++ b/functions/.github/CONTRIBUTING.md
@@ -0,0 +1,27 @@
+# Manifold CLA
+
+**Manifold Markets Contributor License Agreement**
+
+(Thanks to Beeminder and Discourse.org whose CLA we modeled this on!)
+
+## Unofficial Summary
+
+- Manifold can use your contributions
+- Manifold can sell things involving your contributions
+- You’re legally able to agree to the above
+- You’re the one who created these contributions
+- Manifold decides what gets included in Manifold
+- Manifold does not promise any support
+
+## Official Agreement
+
+The document below clarifies the terms under which You (the copyright owner or legal entity authorized by the copyright owner), may make "The Contributions" (software, bug fixes, configuration changes, documentation, or any other materials) to "The Work" (Manifold Markets). This license protects You, "The Company" (Manifold Markets, Inc.) and licensees; it does not change your rights to use your own contributions for any other purpose.
+
+You and "The Company" (Manifold Markets, Inc.) agree:
+
+- You grant to "The Company" (Manifold Markets, Inc.) a non-exclusive, irrevocable, worldwide, royalty-free, sublicenseable, relicenseable, transferable license under all of Your relevant intellectual property rights, to use, copy, prepare derivative works of, distribute and publicly perform and display "The Contributions" on any licensing terms, including without limitation: (a) open source licenses like the GNU General Public (v2.0) license; and (b) binary, proprietary, or commercial licenses. Except for the licenses granted herein, You reserve all right, title, and interest in and to "The Contributions".
+- You grant to "The Company" a non-exclusive, irrevocable (except as stated in this section), worldwide, royalty-free, sublicenseable, transferable patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer "The Work", where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with "The Work" to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or "The Work" to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
+- You are able to grant us these rights. You represent that You are legally entitled to grant the above license(s). If Your employer has rights to intellectual property that You create, You represent that You have received permission to make "The Contributions" on behalf of that employer, or that Your employer has waived such rights for "The Contributions".
+- "The Contributions" are your original work. You represent that "The Contributions" are Your original works of authorship, and to Your knowledge, no other person claims, or has the right to claim, any right in any invention or patent related to "The Contributions". You also represent that You are not legally obligated, whether by entering into an agreement or otherwise, in any way that conflicts with the terms of this license. For example, if you have signed an agreement requiring you to assign the intellectual property rights in "The Contributions" to an employer or customer, that would conflict with the terms of this license.
+- We, as authoritative representatives of "The Company" determine the code that is in "The Work". You understand that the decision to include "The Contribution(s)" in any project or source repository is entirely that of "The Company", and this agreement does not guarantee that "The Contributions" will be included in any product.
+- No Implied Warranties. "The Company" acknowledges that, except as explicitly described in this Agreement, the Contribution is provided on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
From be7e4a5c03927b6cb431109f31af1f5d6f77c514 Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Fri, 29 Apr 2022 13:39:52 -0400
Subject: [PATCH 18/23] Manifold CLA: readme message, move to top level
---
{functions/.github => .github}/CONTRIBUTING.md | 0
README.md | 2 ++
2 files changed, 2 insertions(+)
rename {functions/.github => .github}/CONTRIBUTING.md (100%)
diff --git a/functions/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
similarity index 100%
rename from functions/.github/CONTRIBUTING.md
rename to .github/CONTRIBUTING.md
diff --git a/README.md b/README.md
index 56dceae0..98387abe 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,8 @@ Also: Our docs are currently in [a separate repo](https://github.com/manifoldmar
Since we are just now open-sourcing things, we will see how things go. Feel free to open issues, submit PRs, and chat about the process on [Discord][discord]. We would prefer [small PRs][small-prs] that we can effectively evaluate and review -- maybe check in with us first if you are thinking to work on a big change.
+By contributing to this codebase, you are agreeing to the terms of the [Manifold CLA](https://github.com/manifoldmarkets/manifold/.github/CONTRIBUTING.md).
+
If you need additional access to any infrastructure in order to work on something (e.g. Vercel, Firebase) let us know about that on Discord as well.
[vercel]: https://vercel.com/
From f078ce4fa9b8b607f2c783e708df58bc46f0ac1e Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Fri, 29 Apr 2022 13:40:44 -0400
Subject: [PATCH 19/23] CLA: fix link
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 98387abe..b480f60c 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ Also: Our docs are currently in [a separate repo](https://github.com/manifoldmar
Since we are just now open-sourcing things, we will see how things go. Feel free to open issues, submit PRs, and chat about the process on [Discord][discord]. We would prefer [small PRs][small-prs] that we can effectively evaluate and review -- maybe check in with us first if you are thinking to work on a big change.
-By contributing to this codebase, you are agreeing to the terms of the [Manifold CLA](https://github.com/manifoldmarkets/manifold/.github/CONTRIBUTING.md).
+By contributing to this codebase, you are agreeing to the terms of the [Manifold CLA](https://github.com/manifoldmarkets/manifold/blob/main/.github/CONTRIBUTING.md).
If you need additional access to any infrastructure in order to work on something (e.g. Vercel, Firebase) let us know about that on Discord as well.
From c2832039b8ee81b9b21970f1066e9960afa588eb Mon Sep 17 00:00:00 2001
From: Austin Chen
Date: Fri, 29 Apr 2022 13:45:10 -0400
Subject: [PATCH 20/23] Link to beeminder & discourse
---
.github/CONTRIBUTING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 4651b133..0d4dbd32 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -2,7 +2,7 @@
**Manifold Markets Contributor License Agreement**
-(Thanks to Beeminder and Discourse.org whose CLA we modeled this on!)
+(Thanks to [Beeminder](http://bmndr.co/cla) and [Discourse.org](https://cla-assistant.io/discourse/discourse) whose CLAs we modeled this on!)
## Unofficial Summary
From 5f86637ca56d16889bc3edd09d979196d825637e Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Fri, 29 Apr 2022 15:53:13 -0400
Subject: [PATCH 21/23] raise ante to M$100
---
common/antes.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/antes.ts b/common/antes.ts
index 9a3f48c6..a443f508 100644
--- a/common/antes.ts
+++ b/common/antes.ts
@@ -5,7 +5,7 @@ import { User } from './user'
import { LiquidityProvision } from './liquidity-provision'
import { noFees } from './fees'
-export const FIXED_ANTE = 50
+export const FIXED_ANTE = 100
// deprecated
export const PHANTOM_ANTE = 0.001
From 5cb6ee3bca43a4ad071e1146a00805a75c482a5b Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Fri, 29 Apr 2022 15:58:01 -0400
Subject: [PATCH 22/23] daily free markets' liquidity provided by
@ManifoldMarkets
---
common/antes.ts | 6 ++++--
functions/src/create-contract.ts | 5 ++++-
functions/src/scripts/migrate-to-cfmm.ts | 2 +-
3 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/common/antes.ts b/common/antes.ts
index a443f508..c77308f4 100644
--- a/common/antes.ts
+++ b/common/antes.ts
@@ -11,8 +11,10 @@ export const FIXED_ANTE = 100
export const PHANTOM_ANTE = 0.001
export const MINIMUM_ANTE = 50
+export const HOUSE_LIQUIDITY_PROVIDER_ID = 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2' // @ManifoldMarkets' id
+
export function getCpmmInitialLiquidity(
- creator: User,
+ providerId: string,
contract: FullContract,
anteId: string,
amount: number
@@ -21,7 +23,7 @@ export function getCpmmInitialLiquidity(
const lp: LiquidityProvision = {
id: anteId,
- userId: creator.id,
+ userId: providerId,
contractId: contract.id,
createdTime,
isAnte: true,
diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts
index 781337f7..ae7b0ad8 100644
--- a/functions/src/create-contract.ts
+++ b/functions/src/create-contract.ts
@@ -22,6 +22,7 @@ import {
getAnteBets,
getCpmmInitialLiquidity,
getFreeAnswerAnte,
+ HOUSE_LIQUIDITY_PROVIDER_ID,
MINIMUM_ANTE,
} from '../../common/antes'
import { getNoneAnswer } from '../../common/answer'
@@ -144,8 +145,10 @@ export const createContract = functions
.collection(`contracts/${contract.id}/liquidity`)
.doc()
+ const providerId = isFree ? HOUSE_LIQUIDITY_PROVIDER_ID : creator.id
+
const lp = getCpmmInitialLiquidity(
- creator,
+ providerId,
contract as FullContract,
liquidityDoc.id,
ante
diff --git a/functions/src/scripts/migrate-to-cfmm.ts b/functions/src/scripts/migrate-to-cfmm.ts
index cd9177a3..874011ca 100644
--- a/functions/src/scripts/migrate-to-cfmm.ts
+++ b/functions/src/scripts/migrate-to-cfmm.ts
@@ -106,7 +106,7 @@ async function recalculateContract(contractRef: DocRef, isCommit = false) {
const liquidityDocRef = contractRef.collection('liquidity').doc()
const lp = getCpmmInitialLiquidity(
- { id: 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2' } as User, // use @ManifoldMarkets' id
+ 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2', // use @ManifoldMarkets' id
{
...contract,
...contractUpdate,
From 78997c1e45afcbb8b10dc7198306af4eb342d253 Mon Sep 17 00:00:00 2001
From: Boa
Date: Fri, 29 Apr 2022 15:11:04 -0600
Subject: [PATCH 23/23] Show comments position (#110)
* Add betting activity back to feed
* Show position in bin. markets, no comments on bets
* Degroup bets on Bets tab
* Show users position or recent bet with comments
* Add tooltip on answer to FR comments
* Style improvements
* Only use bets by current user for comment input
---
web/components/feed/activity-items.ts | 91 ++++---
web/components/feed/feed-items.tsx | 328 +++++++++++++++++-------
web/components/outcome-label.tsx | 34 ++-
web/pages/[username]/[contractSlug].tsx | 2 +-
4 files changed, 308 insertions(+), 147 deletions(-)
diff --git a/web/components/feed/activity-items.ts b/web/components/feed/activity-items.ts
index a12c4e0a..7879b637 100644
--- a/web/components/feed/activity-items.ts
+++ b/web/components/feed/activity-items.ts
@@ -31,6 +31,8 @@ type BaseActivityItem = {
export type CommentInputItem = BaseActivityItem & {
type: 'commentInput'
+ betsByCurrentUser: Bet[]
+ comments: Comment[]
}
export type DescriptionItem = BaseActivityItem & {
@@ -48,12 +50,13 @@ export type BetItem = BaseActivityItem & {
bet: Bet
hideOutcome: boolean
smallAvatar: boolean
+ hideComment?: boolean
}
export type CommentItem = BaseActivityItem & {
type: 'comment'
comment: Comment
- bet: Bet | undefined
+ betsBySameUser: Bet[]
hideOutcome: boolean
truncate: boolean
smallAvatar: boolean
@@ -129,7 +132,7 @@ function groupBets(
type: 'comment' as const,
id: bet.id,
comment,
- bet,
+ betsBySameUser: [bet],
contract,
hideOutcome,
truncate: abbreviated,
@@ -280,7 +283,7 @@ function groupBetsAndComments(
id: comment.id,
contract: contract,
comment,
- bet: undefined,
+ betsBySameUser: [],
truncate: abbreviated,
hideOutcome: true,
smallAvatar,
@@ -308,6 +311,27 @@ function groupBetsAndComments(
return abbrItems
}
+function getCommentsWithPositions(
+ bets: Bet[],
+ comments: Comment[],
+ contract: Contract
+) {
+ const betsByUserId = _.groupBy(bets, (bet) => bet.userId)
+
+ const items = comments.map((comment) => ({
+ type: 'comment' as const,
+ id: comment.id,
+ contract: contract,
+ comment,
+ betsBySameUser: bets.length === 0 ? [] : betsByUserId[comment.userId] ?? [],
+ truncate: true,
+ hideOutcome: false,
+ smallAvatar: false,
+ }))
+
+ return items
+}
+
export function getAllContractActivityItems(
contract: Contract,
bets: Bet[],
@@ -361,6 +385,8 @@ export function getAllContractActivityItems(
type: 'commentInput',
id: 'commentInput',
contract,
+ betsByCurrentUser: [],
+ comments: [],
})
} else {
items.push(
@@ -385,6 +411,8 @@ export function getAllContractActivityItems(
type: 'commentInput',
id: 'commentInput',
contract,
+ betsByCurrentUser: [],
+ comments: [],
})
}
@@ -432,24 +460,13 @@ export function getRecentContractActivityItems(
)
)
} else {
- const onlyUsersBetsOrBetsWithComments = bets.filter((bet) =>
- comments.some(
- (comment) => comment.betId === bet.id || bet.userId === user?.id
- )
- )
items.push(
- ...groupBetsAndComments(
- onlyUsersBetsOrBetsWithComments,
- comments,
- contract,
- user?.id,
- {
- hideOutcome: false,
- abbreviated: true,
- smallAvatar: false,
- reversed: true,
- }
- )
+ ...groupBetsAndComments(bets, comments, contract, user?.id, {
+ hideOutcome: false,
+ abbreviated: true,
+ smallAvatar: false,
+ reversed: true,
+ })
)
}
@@ -471,37 +488,29 @@ export function getSpecificContractActivityItems(
switch (mode) {
case 'bets':
items.push(
- ...groupBets(bets, comments, contract, user?.id, {
+ ...bets.map((bet) => ({
+ type: 'bet' as const,
+ id: bet.id,
+ bet,
+ contract,
hideOutcome: false,
- abbreviated: false,
smallAvatar: false,
- reversed: false,
- })
+ hideComment: true,
+ }))
)
break
case 'comments':
- const onlyBetsWithComments = bets.filter((bet) =>
- comments.some((comment) => comment.betId === bet.id)
- )
- items.push(
- ...groupBetsAndComments(
- onlyBetsWithComments,
- comments,
- contract,
- user?.id,
- {
- hideOutcome: false,
- abbreviated: false,
- smallAvatar: false,
- reversed: false,
- }
- )
- )
+ items.push(...getCommentsWithPositions(bets, comments, contract))
+
items.push({
type: 'commentInput',
id: 'commentInput',
contract,
+ betsByCurrentUser: user
+ ? bets.filter((bet) => bet.userId === user.id)
+ : [],
+ comments: comments,
})
break
}
diff --git a/web/components/feed/feed-items.tsx b/web/components/feed/feed-items.tsx
index 7518c998..584907d9 100644
--- a/web/components/feed/feed-items.tsx
+++ b/web/components/feed/feed-items.tsx
@@ -39,7 +39,13 @@ import BetRow from '../bet-row'
import { Avatar } from '../avatar'
import { Answer } from '../../../common/answer'
import { ActivityItem } from './activity-items'
-import { FreeResponse, FullContract } from '../../../common/contract'
+import {
+ Binary,
+ CPMM,
+ DPM,
+ FreeResponse,
+ FullContract,
+} from '../../../common/contract'
import { BuyButton } from '../yes-no-selector'
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
import { AnswerBetPanel } from '../answers/answer-bet-panel'
@@ -50,6 +56,7 @@ import { trackClick } from '../../lib/firebase/tracking'
import { firebaseLogin } from '../../lib/firebase/users'
import { DAY_MS } from '../../../common/util/time'
import NewContractBadge from '../new-contract-badge'
+import { calculateCpmmSale } from '../../../common/calculate-cpmm'
export function FeedItems(props: {
contract: Contract
@@ -123,21 +130,38 @@ function FeedItem(props: { item: ActivityItem }) {
export function FeedComment(props: {
contract: Contract
comment: Comment
- bet: Bet | undefined
+ betsBySameUser: Bet[]
hideOutcome: boolean
truncate: boolean
smallAvatar: boolean
}) {
- const { contract, comment, bet, hideOutcome, truncate, smallAvatar } = props
- let money: string | undefined
- let outcome: string | undefined
- let bought: string | undefined
- if (bet) {
- outcome = bet.outcome
- bought = bet.amount >= 0 ? 'bought' : 'sold'
- money = formatMoney(Math.abs(bet.amount))
- }
+ const {
+ contract,
+ comment,
+ betsBySameUser,
+ hideOutcome,
+ truncate,
+ smallAvatar,
+ } = props
const { text, userUsername, userName, userAvatarUrl, createdTime } = comment
+ let outcome: string | undefined,
+ bought: string | undefined,
+ money: string | undefined
+
+ const matchedBet = betsBySameUser.find((bet) => bet.id === comment.betId)
+ if (matchedBet) {
+ outcome = matchedBet.outcome
+ bought = matchedBet.amount >= 0 ? 'bought' : 'sold'
+ money = formatMoney(Math.abs(matchedBet.amount))
+ }
+
+ // Only calculated if they don't have a matching bet
+ const { userPosition, userPositionMoney, yesFloorShares, noFloorShares } =
+ getBettorsPosition(
+ contract,
+ comment.createdTime,
+ matchedBet ? [] : betsBySameUser
+ )
return (
<>
@@ -155,18 +179,33 @@ export function FeedComment(props: {
username={userUsername}
name={userName}
/>{' '}
- {bought} {money}
- {!hideOutcome && (
+ {!matchedBet && userPosition > 0 && (
<>
- {' '}
- of{' '}
-
+ {'with ' + userPositionMoney + ' '}
+ <>
+ {' of '}
+ noFloorShares ? 'YES' : 'NO'}
+ contract={contract}
+ truncate="short"
+ />
+ >
>
)}
+ <>
+ {bought} {money}
+ {outcome && !hideOutcome && (
+ <>
+ {' '}
+ of{' '}
+
+ >
+ )}
+ >
@@ -180,20 +219,12 @@ export function FeedComment(props: {
)
}
-function RelativeTimestamp(props: { time: number }) {
- const { time } = props
- return (
-
-
- {fromNow(time)}
-
-
- )
-}
-
-export function CommentInput(props: { contract: Contract }) {
- // see if we can comment input on any bet:
- const { contract } = props
+export function CommentInput(props: {
+ contract: Contract
+ betsByCurrentUser: Bet[]
+ comments: Comment[]
+}) {
+ const { contract, betsByCurrentUser, comments } = props
const user = useUser()
const [comment, setComment] = useState('')
@@ -206,14 +237,50 @@ export function CommentInput(props: { contract: Contract }) {
setComment('')
}
+ // Should this be oldest bet or most recent bet?
+ const mostRecentCommentableBet = betsByCurrentUser
+ .filter(
+ (bet) =>
+ canCommentOnBet(bet.userId, bet.createdTime, user) &&
+ !comments.some((comment) => comment.betId == bet.id)
+ )
+ .sort((b1, b2) => b1.createdTime - b2.createdTime)
+ .pop()
+
+ if (mostRecentCommentableBet) {
+ return (
+
+ )
+ }
+ const { userPosition, userPositionMoney, yesFloorShares, noFloorShares } =
+ getBettorsPosition(contract, Date.now(), betsByCurrentUser)
+
return (
<>
-
+