From 28af2063c3f735af9311559cc9895f743c88ab93 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Wed, 7 Sep 2022 14:45:04 -0500 Subject: [PATCH 01/55] "bet" => "trade" --- functions/src/place-bet.ts | 2 +- web/components/answers/answer-bet-panel.tsx | 2 +- web/components/arrange-home.tsx | 2 +- web/components/bet-button.tsx | 2 +- web/components/bet-panel.tsx | 12 ++++++------ web/components/contract-search.tsx | 2 +- web/components/contract/contract-details.tsx | 2 +- web/components/contract/contract-info-dialog.tsx | 2 +- web/components/contract/contract-leaderboard.tsx | 2 +- web/components/contract/contract-tabs.tsx | 4 ++-- web/components/play-money-disclaimer.tsx | 2 +- web/components/profile/loans-modal.tsx | 2 +- web/components/resolution-panel.tsx | 4 ++-- web/components/user-page.tsx | 2 +- web/components/yes-no-selector.tsx | 2 +- web/pages/experimental/home/index.tsx | 2 +- web/pages/notifications.tsx | 4 ++-- 17 files changed, 25 insertions(+), 25 deletions(-) diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts index 404fda50..d98430c1 100644 --- a/functions/src/place-bet.ts +++ b/functions/src/place-bet.ts @@ -135,7 +135,7 @@ export const placebet = newEndpoint({}, async (req, auth) => { !isFinite(newP) || Math.min(...Object.values(newPool ?? {})) < CPMM_MIN_POOL_QTY) ) { - throw new APIError(400, 'Bet too large for current liquidity pool.') + throw new APIError(400, 'Trade too large for current liquidity pool.') } const betDoc = contractDoc.collection('bets').doc() diff --git a/web/components/answers/answer-bet-panel.tsx b/web/components/answers/answer-bet-panel.tsx index ace06b6c..dbf7ff11 100644 --- a/web/components/answers/answer-bet-panel.tsx +++ b/web/components/answers/answer-bet-panel.tsx @@ -120,7 +120,7 @@ export function AnswerBetPanel(props: {
- Bet on {isModal ? `"${answer.text}"` : 'this answer'} + Buy answer: {isModal ? `"${answer.text}"` : 'this answer'}
{!isModal && ( diff --git a/web/components/arrange-home.tsx b/web/components/arrange-home.tsx index 2c43788c..2f49d144 100644 --- a/web/components/arrange-home.tsx +++ b/web/components/arrange-home.tsx @@ -112,7 +112,7 @@ export const getHomeItems = ( { label: 'Trending', id: 'score' }, { label: 'Newest', id: 'newest' }, { label: 'Close date', id: 'close-date' }, - { label: 'Your bets', id: 'your-bets' }, + { label: 'Your trades', id: 'your-bets' }, ...groups.map((g) => ({ label: g.name, id: g.id, diff --git a/web/components/bet-button.tsx b/web/components/bet-button.tsx index 7d84bbc0..77b17678 100644 --- a/web/components/bet-button.tsx +++ b/web/components/bet-button.tsx @@ -38,7 +38,7 @@ export default function BetButton(props: { className={clsx('my-auto inline-flex min-w-[75px] ', btnClassName)} onClick={() => setOpen(true)} > - Bet + Trade ) : ( diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index c48e92a9..1f2b9bd3 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -281,7 +281,7 @@ function BuyPanel(props: { title="Whoa, there!" text={`You might not want to spend ${formatPercent( bankrollFraction - )} of your balance on a single bet. \n\nCurrent balance: ${formatMoney( + )} of your balance on a single trade. \n\nCurrent balance: ${formatMoney( user?.balance ?? 0 )}`} /> @@ -379,11 +379,11 @@ function BuyPanel(props: { )} onClick={betDisabled ? undefined : submitBet} > - {isSubmitting ? 'Submitting...' : 'Submit bet'} + {isSubmitting ? 'Submitting...' : 'Submit trade'} )} - {wasSubmitted &&
Bet submitted!
} + {wasSubmitted &&
Trade submitted!
} ) } @@ -569,7 +569,7 @@ function LimitOrderPanel(props: {
- Bet {isPseudoNumeric ? : } up to + Buy {isPseudoNumeric ? : } up to
- Bet {isPseudoNumeric ? : } down to + Buy {isPseudoNumeric ? : } down to
-
Bet
+
Trade
{!hideToggle && ( - Your bets + Your trades )} diff --git a/web/components/contract/contract-details.tsx b/web/components/contract/contract-details.tsx index 48528029..c383d349 100644 --- a/web/components/contract/contract-details.tsx +++ b/web/components/contract/contract-details.tsx @@ -294,7 +294,7 @@ export function ExtraMobileContractDetails(props: { {volumeTranslation} diff --git a/web/components/contract/contract-info-dialog.tsx b/web/components/contract/contract-info-dialog.tsx index f376a04a..ae586725 100644 --- a/web/components/contract/contract-info-dialog.tsx +++ b/web/components/contract/contract-info-dialog.tsx @@ -135,7 +135,7 @@ export function ContractInfoDialog(props: { */} - Bettors + Traders {bettorsCount} diff --git a/web/components/contract/contract-leaderboard.tsx b/web/components/contract/contract-leaderboard.tsx index ce5c7da6..1eaf7043 100644 --- a/web/components/contract/contract-leaderboard.tsx +++ b/web/components/contract/contract-leaderboard.tsx @@ -49,7 +49,7 @@ export function ContractLeaderboard(props: { return users && users.length > 0 ? ( {!user ? ( diff --git a/web/components/play-money-disclaimer.tsx b/web/components/play-money-disclaimer.tsx index 6ee16c1e..a3bda242 100644 --- a/web/components/play-money-disclaimer.tsx +++ b/web/components/play-money-disclaimer.tsx @@ -4,6 +4,6 @@ export const PlayMoneyDisclaimer = () => ( ) diff --git a/web/components/profile/loans-modal.tsx b/web/components/profile/loans-modal.tsx index 46be649a..24b23e5b 100644 --- a/web/components/profile/loans-modal.tsx +++ b/web/components/profile/loans-modal.tsx @@ -11,7 +11,7 @@ export function LoansModal(props: { 🏦 - Daily loans on your bets + Daily loans on your trades • What are daily loans? diff --git a/web/components/resolution-panel.tsx b/web/components/resolution-panel.tsx index fe062d06..5a7b993e 100644 --- a/web/components/resolution-panel.tsx +++ b/web/components/resolution-panel.tsx @@ -83,14 +83,14 @@ export function ResolutionPanel(props: {
{outcome === 'YES' ? ( <> - Winnings will be paid out to YES bettors. + Winnings will be paid out to traders who bought YES. {/*

You will earn {earnedFees}. */} ) : outcome === 'NO' ? ( <> - Winnings will be paid out to NO bettors. + Winnings will be paid out to traders who bought NO. {/*

You will earn {earnedFees}. */} diff --git a/web/components/user-page.tsx b/web/components/user-page.tsx index 2d4db1eb..81aed562 100644 --- a/web/components/user-page.tsx +++ b/web/components/user-page.tsx @@ -260,7 +260,7 @@ export function UserPage(props: { user: User }) { ), }, { - title: 'Bets', + title: 'Trades', content: ( <> diff --git a/web/components/yes-no-selector.tsx b/web/components/yes-no-selector.tsx index aaf1764e..719308bf 100644 --- a/web/components/yes-no-selector.tsx +++ b/web/components/yes-no-selector.tsx @@ -193,7 +193,7 @@ export function BuyButton(props: { className?: string; onClick?: () => void }) { )} onClick={onClick} > - Bet + Buy ) } diff --git a/web/pages/experimental/home/index.tsx b/web/pages/experimental/home/index.tsx index 0f02b002..fb0b488d 100644 --- a/web/pages/experimental/home/index.tsx +++ b/web/pages/experimental/home/index.tsx @@ -86,7 +86,7 @@ const Home = (props: { auth: { user: User } | null }) => { return ( )} From b4e0e9ebc0b698bd4e36a2f0bf58107bb49be205 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Wed, 7 Sep 2022 15:01:02 -0500 Subject: [PATCH 02/55] "A market for every question" --- web/components/landing-page-panel.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/web/components/landing-page-panel.tsx b/web/components/landing-page-panel.tsx index 2e3d85e2..a5b46b08 100644 --- a/web/components/landing-page-panel.tsx +++ b/web/components/landing-page-panel.tsx @@ -27,23 +27,18 @@ export function LandingPagePanel(props: { hotContracts: Contract[] }) {

- Predict{' '} + A{' '} - anything! - + market + {' '} + for every question

- Create a play-money prediction market on any topic you care about - and bet with your friends on what will happen! + Create a play-money prediction market on any topic you care about. + Trade with your friends to forecast the future.
- {/*
- Sign up and get {formatMoney(1000)} - worth $10 to your{' '} - - favorite charity. - -
*/}
From b3343c210a736d04470b6b0cc20ec71aed576333 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Wed, 7 Sep 2022 15:04:34 -0500 Subject: [PATCH 03/55] more "bet" => "trade" --- web/components/play-money-disclaimer.tsx | 2 +- web/components/sign-up-prompt.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/components/play-money-disclaimer.tsx b/web/components/play-money-disclaimer.tsx index a3bda242..860075d0 100644 --- a/web/components/play-money-disclaimer.tsx +++ b/web/components/play-money-disclaimer.tsx @@ -2,7 +2,7 @@ import { InfoBox } from './info-box' export const PlayMoneyDisclaimer = () => ( diff --git a/web/components/sign-up-prompt.tsx b/web/components/sign-up-prompt.tsx index 5ade4c1f..239fb4ad 100644 --- a/web/components/sign-up-prompt.tsx +++ b/web/components/sign-up-prompt.tsx @@ -19,7 +19,7 @@ export function BetSignUpPrompt(props: { size={size} color="gradient" > - {label ?? 'Sign up to bet!'} + {label ?? 'Sign up to trade!'} ) : null } From ce52f21ce97383c26ef6c232067e53c6761e59e1 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Wed, 7 Sep 2022 15:13:17 -0500 Subject: [PATCH 04/55] fix sidebar profile link to your trades --- web/components/nav/profile-menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/components/nav/profile-menu.tsx b/web/components/nav/profile-menu.tsx index aad17d84..e7cc056f 100644 --- a/web/components/nav/profile-menu.tsx +++ b/web/components/nav/profile-menu.tsx @@ -8,7 +8,7 @@ import { trackCallback } from 'web/lib/service/analytics' export function ProfileSummary(props: { user: User }) { const { user } = props return ( - + Date: Wed, 7 Sep 2022 23:09:20 +0100 Subject: [PATCH 05/55] Adds comments to posts (#844) * Adds comments to posts * Uncoupled CommentInput from Contracts * Fix nits --- common/comment.ts | 12 +- firestore.rules | 4 + web/components/comment-input.tsx | 175 +++++++++++ web/components/feed/contract-activity.tsx | 4 +- .../feed/feed-answer-comment-group.tsx | 4 +- web/components/feed/feed-comments.tsx | 273 +++++------------- web/components/tipper.tsx | 4 +- web/hooks/use-comments.ts | 18 +- web/hooks/use-tip-txns.ts | 7 +- web/lib/firebase/comments.ts | 109 +++++-- web/lib/firebase/txns.ts | 14 + web/pages/post/[...slugs]/index.tsx | 77 ++++- web/posts/post-comments.tsx | 172 +++++++++++ 13 files changed, 635 insertions(+), 238 deletions(-) create mode 100644 web/components/comment-input.tsx create mode 100644 web/posts/post-comments.tsx diff --git a/common/comment.ts b/common/comment.ts index 3a4bd9ac..7ecbb6d4 100644 --- a/common/comment.ts +++ b/common/comment.ts @@ -1,6 +1,6 @@ import type { JSONContent } from '@tiptap/core' -export type AnyCommentType = OnContract | OnGroup +export type AnyCommentType = OnContract | OnGroup | OnPost // Currently, comments are created after the bet, not atomically with the bet. // They're uniquely identified by the pair contractId/betId. @@ -20,7 +20,7 @@ export type Comment = { userAvatarUrl?: string } & T -type OnContract = { +export type OnContract = { commentType: 'contract' contractId: string answerOutcome?: string @@ -35,10 +35,16 @@ type OnContract = { betOutcome?: string } -type OnGroup = { +export type OnGroup = { commentType: 'group' groupId: string } +export type OnPost = { + commentType: 'post' + postId: string +} + export type ContractComment = Comment export type GroupComment = Comment +export type PostComment = Comment diff --git a/firestore.rules b/firestore.rules index 15b60d0f..30bf0ec9 100644 --- a/firestore.rules +++ b/firestore.rules @@ -203,6 +203,10 @@ service cloud.firestore { .affectedKeys() .hasOnly(['name', 'content']); allow delete: if isAdmin() || request.auth.uid == resource.data.creatorId; + match /comments/{commentId} { + allow read; + allow create: if request.auth != null && commentMatchesUser(request.auth.uid, request.resource.data) ; + } } } } diff --git a/web/components/comment-input.tsx b/web/components/comment-input.tsx new file mode 100644 index 00000000..1d0b3cc1 --- /dev/null +++ b/web/components/comment-input.tsx @@ -0,0 +1,175 @@ +import { PaperAirplaneIcon } from '@heroicons/react/solid' +import { Editor } from '@tiptap/react' +import clsx from 'clsx' +import { User } from 'common/user' +import { useEffect, useState } from 'react' +import { useUser } from 'web/hooks/use-user' +import { useWindowSize } from 'web/hooks/use-window-size' +import { MAX_COMMENT_LENGTH } from 'web/lib/firebase/comments' +import { Avatar } from './avatar' +import { TextEditor, useTextEditor } from './editor' +import { Row } from './layout/row' +import { LoadingIndicator } from './loading-indicator' + +export function CommentInput(props: { + replyToUser?: { id: string; username: string } + // Reply to a free response answer + parentAnswerOutcome?: string + // Reply to another comment + parentCommentId?: string + onSubmitComment?: (editor: Editor, betId: string | undefined) => void + className?: string + presetId?: string +}) { + const { + parentAnswerOutcome, + parentCommentId, + replyToUser, + onSubmitComment, + presetId, + } = props + const user = useUser() + + const { editor, upload } = useTextEditor({ + simple: true, + max: MAX_COMMENT_LENGTH, + placeholder: + !!parentCommentId || !!parentAnswerOutcome + ? 'Write a reply...' + : 'Write a comment...', + }) + + const [isSubmitting, setIsSubmitting] = useState(false) + + async function submitComment(betId: string | undefined) { + if (!editor || editor.isEmpty || isSubmitting) return + setIsSubmitting(true) + onSubmitComment?.(editor, betId) + setIsSubmitting(false) + } + + if (user?.isBannedFromPosting) return <> + + return ( + + +
+ +
+
+ ) +} + +export function CommentInputTextArea(props: { + user: User | undefined | null + replyToUser?: { id: string; username: string } + editor: Editor | null + upload: Parameters[0]['upload'] + submitComment: (id?: string) => void + isSubmitting: boolean + submitOnEnter?: boolean + presetId?: string +}) { + const { + user, + editor, + upload, + submitComment, + presetId, + isSubmitting, + submitOnEnter, + replyToUser, + } = props + const isMobile = (useWindowSize().width ?? 0) < 768 // TODO: base off input device (keybord vs touch) + + useEffect(() => { + editor?.setEditable(!isSubmitting) + }, [isSubmitting, editor]) + + const submit = () => { + submitComment(presetId) + editor?.commands?.clearContent() + } + + useEffect(() => { + if (!editor) { + return + } + // submit on Enter key + editor.setOptions({ + editorProps: { + handleKeyDown: (view, event) => { + if ( + submitOnEnter && + event.key === 'Enter' && + !event.shiftKey && + (!isMobile || event.ctrlKey || event.metaKey) && + // mention list is closed + !(view.state as any).mention$.active + ) { + submit() + event.preventDefault() + return true + } + return false + }, + }, + }) + // insert at mention and focus + if (replyToUser) { + editor + .chain() + .setContent({ + type: 'mention', + attrs: { label: replyToUser.username, id: replyToUser.id }, + }) + .insertContent(' ') + .focus() + .run() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editor]) + + return ( + <> + + {user && !isSubmitting && ( + + )} + + {isSubmitting && ( + + )} + + + {!user && ( + + )} + + + ) +} diff --git a/web/components/feed/contract-activity.tsx b/web/components/feed/contract-activity.tsx index 0878e570..55b8a958 100644 --- a/web/components/feed/contract-activity.tsx +++ b/web/components/feed/contract-activity.tsx @@ -6,7 +6,7 @@ import { getOutcomeProbability } from 'common/calculate' import { FeedBet } from './feed-bets' import { FeedLiquidity } from './feed-liquidity' import { FeedAnswerCommentGroup } from './feed-answer-comment-group' -import { FeedCommentThread, CommentInput } from './feed-comments' +import { FeedCommentThread, ContractCommentInput } from './feed-comments' import { User } from 'common/user' import { CommentTipMap } from 'web/hooks/use-tip-txns' import { LiquidityProvision } from 'common/liquidity-provision' @@ -72,7 +72,7 @@ export function ContractCommentsActivity(props: { return ( <> -