Merge branch 'manifoldmarkets:main' into main

This commit is contained in:
marsteralex 2022-09-20 15:03:39 -07:00 committed by GitHub
commit eea173a016
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 126 additions and 143 deletions

View File

@ -4,8 +4,14 @@ import * as utc from 'dayjs/plugin/utc'
dayjs.extend(utc) dayjs.extend(utc)
import { getPrivateUser } from './utils' import { getPrivateUser } from './utils'
import { User } from '../../common/user' import { User } from 'common/user'
import { sendPersonalFollowupEmail, sendWelcomeEmail } from './emails' import {
sendCreatorGuideEmail,
sendInterestingMarketsEmail,
sendPersonalFollowupEmail,
sendWelcomeEmail,
} from './emails'
import { getTrendingContracts } from './weekly-markets-emails'
export const onCreateUser = functions export const onCreateUser = functions
.runWith({ secrets: ['MAILGUN_KEY'] }) .runWith({ secrets: ['MAILGUN_KEY'] })
@ -19,4 +25,21 @@ export const onCreateUser = functions
const followupSendTime = dayjs().add(48, 'hours').toString() const followupSendTime = dayjs().add(48, 'hours').toString()
await sendPersonalFollowupEmail(user, privateUser, followupSendTime) await sendPersonalFollowupEmail(user, privateUser, followupSendTime)
const guideSendTime = dayjs().add(96, 'hours').toString()
await sendCreatorGuideEmail(user, privateUser, guideSendTime)
// skip email if weekly email is about to go out
const day = dayjs().utc().day()
if (day === 0 || (day === 1 && dayjs().utc().hour() <= 19)) return
const contracts = await getTrendingContracts()
const marketsSendTime = dayjs().add(24, 'hours').toString()
await sendInterestingMarketsEmail(
user,
privateUser,
contracts,
marketsSendTime
)
}) })

View File

@ -1,5 +1,5 @@
import { Bet } from 'common/bet' import { Bet } from 'common/bet'
import { Contract, CPMMBinaryContract } from 'common/contract' import { Contract } from 'common/contract'
import { ContractComment } from 'common/comment' import { ContractComment } from 'common/comment'
import { PAST_BETS, User } from 'common/user' import { PAST_BETS, User } from 'common/user'
import { import {
@ -11,13 +11,9 @@ import { ContractBetsTable, BetsSummary } from '../bets-list'
import { Spacer } from '../layout/spacer' import { Spacer } from '../layout/spacer'
import { Tabs } from '../layout/tabs' import { Tabs } from '../layout/tabs'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { tradingAllowed } from 'web/lib/firebase/contracts'
import { CommentTipMap } from 'web/hooks/use-tip-txns' import { CommentTipMap } from 'web/hooks/use-tip-txns'
import { useComments } from 'web/hooks/use-comments' import { useComments } from 'web/hooks/use-comments'
import { useLiquidity } from 'web/hooks/use-liquidity' import { useLiquidity } from 'web/hooks/use-liquidity'
import { BetSignUpPrompt } from '../sign-up-prompt'
import { PlayMoneyDisclaimer } from '../play-money-disclaimer'
import BetButton from '../bet-button'
import { capitalize } from 'lodash' import { capitalize } from 'lodash'
import { import {
DEV_HOUSE_LIQUIDITY_PROVIDER_ID, DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
@ -123,44 +119,28 @@ export function ContractTabs(props: {
) )
return ( return (
<> <Tabs
<Tabs currentPageForAnalytics={'contract'}
currentPageForAnalytics={'contract'} tabs={[
tabs={[ {
{ title: 'Comments',
title: 'Comments', content: commentActivity,
content: commentActivity, badge: `${comments.length}`,
badge: `${comments.length}`, },
}, {
{ title: capitalize(PAST_BETS),
title: capitalize(PAST_BETS), content: betActivity,
content: betActivity, badge: `${visibleBets.length + visibleLps.length}`,
badge: `${visibleBets.length + visibleLps.length}`, },
}, ...(!user || !userBets?.length
...(!user || !userBets?.length ? []
? [] : [
: [ {
{ title: isMobile ? `You` : `Your ${PAST_BETS}`,
title: isMobile ? `You` : `Your ${PAST_BETS}`, content: yourTrades,
content: yourTrades, },
}, ]),
]), ]}
]} />
/>
{!user ? (
<Col className="mt-4 max-w-sm items-center xl:hidden">
<BetSignUpPrompt />
<PlayMoneyDisclaimer />
</Col>
) : (
outcomeType === 'BINARY' &&
tradingAllowed(contract) && (
<BetButton
contract={contract as CPMMBinaryContract}
className="mb-2 !mt-0 xl:hidden"
/>
)
)}
</>
) )
} }

View File

@ -11,7 +11,6 @@ import clsx from 'clsx'
import { import {
ContractCommentInput, ContractCommentInput,
FeedComment, FeedComment,
getMostRecentCommentableBet,
} from 'web/components/feed/feed-comments' } from 'web/components/feed/feed-comments'
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time' import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@ -49,28 +48,6 @@ export function FeedAnswerCommentGroup(props: {
const answerElementId = `answer-${answer.id}` const answerElementId = `answer-${answer.id}`
const commentsByCurrentUser = (user && commentsByUserId[user.id]) ?? [] const commentsByCurrentUser = (user && commentsByUserId[user.id]) ?? []
const isFreeResponseContractPage = !!commentsByCurrentUser
const mostRecentCommentableBet = getMostRecentCommentableBet(
betsByCurrentUser,
commentsByCurrentUser,
user,
answer.number.toString()
)
const [usersMostRecentBetTimeAtLoad, setUsersMostRecentBetTimeAtLoad] =
useState<number | undefined>(
!user ? undefined : mostRecentCommentableBet?.createdTime ?? 0
)
useEffect(() => {
if (user && usersMostRecentBetTimeAtLoad === undefined)
setUsersMostRecentBetTimeAtLoad(
mostRecentCommentableBet?.createdTime ?? 0
)
}, [
mostRecentCommentableBet?.createdTime,
user,
usersMostRecentBetTimeAtLoad,
])
const scrollAndOpenReplyInput = useEvent( const scrollAndOpenReplyInput = useEvent(
(comment?: ContractComment, answer?: Answer) => { (comment?: ContractComment, answer?: Answer) => {
@ -85,19 +62,6 @@ export function FeedAnswerCommentGroup(props: {
} }
) )
useEffect(() => {
// Only show one comment input for a bet at a time
if (
betsByCurrentUser.length > 1 &&
// inputRef?.textContent?.length === 0 && //TODO: editor.isEmpty
betsByCurrentUser.sort((a, b) => b.createdTime - a.createdTime)[0]
?.outcome !== answer.number.toString()
)
setShowReply(false)
// Even if we pass memoized bets this still runs on every render, which we don't want
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [betsByCurrentUser.length, user, answer.number])
useEffect(() => { useEffect(() => {
if (router.asPath.endsWith(`#${answerElementId}`)) { if (router.asPath.endsWith(`#${answerElementId}`)) {
setHighlighted(true) setHighlighted(true)
@ -105,10 +69,7 @@ export function FeedAnswerCommentGroup(props: {
}, [answerElementId, router.asPath]) }, [answerElementId, router.asPath])
return ( return (
<Col <Col className="relative flex-1 items-stretch gap-3">
className={'relative flex-1 items-stretch gap-3'}
key={answer.id + 'comment'}
>
<Row <Row
className={clsx( className={clsx(
'gap-3 space-x-3 pt-4 transition-all duration-1000', 'gap-3 space-x-3 pt-4 transition-all duration-1000',
@ -133,28 +94,23 @@ export function FeedAnswerCommentGroup(props: {
<span className="whitespace-pre-line text-lg"> <span className="whitespace-pre-line text-lg">
<Linkify text={text} /> <Linkify text={text} />
</span> </span>
<div className="sm:hidden">
{isFreeResponseContractPage && (
<div className={'sm:hidden'}>
<button
className={'text-xs font-bold text-gray-500 hover:underline'}
onClick={() => scrollAndOpenReplyInput(undefined, answer)}
>
Reply
</button>
</div>
)}
</Col>
{isFreeResponseContractPage && (
<div className={'justify-initial hidden sm:block'}>
<button <button
className={'text-xs font-bold text-gray-500 hover:underline'} className="text-xs font-bold text-gray-500 hover:underline"
onClick={() => scrollAndOpenReplyInput(undefined, answer)} onClick={() => scrollAndOpenReplyInput(undefined, answer)}
> >
Reply Reply
</button> </button>
</div> </div>
)} </Col>
<div className="justify-initial hidden sm:block">
<button
className="text-xs font-bold text-gray-500 hover:underline"
onClick={() => scrollAndOpenReplyInput(undefined, answer)}
>
Reply
</button>
</div>
</Col> </Col>
</Row> </Row>
<Col className="gap-3 pl-1"> <Col className="gap-3 pl-1">
@ -170,7 +126,7 @@ export function FeedAnswerCommentGroup(props: {
))} ))}
</Col> </Col>
{showReply && ( {showReply && (
<div className={'relative ml-7'}> <div className="relative ml-7">
<span <span
className="absolute -left-1 -ml-[1px] mt-[1.25rem] h-2 w-0.5 rotate-90 bg-gray-200" className="absolute -left-1 -ml-[1px] mt-[1.25rem] h-2 w-0.5 rotate-90 bg-gray-200"
aria-hidden="true" aria-hidden="true"

View File

@ -14,6 +14,8 @@ import { Col } from './layout/col'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { InfoTooltip } from './info-tooltip' import { InfoTooltip } from './info-tooltip'
import { BETTORS, PRESENT_BET } from 'common/user' import { BETTORS, PRESENT_BET } from 'common/user'
import { buildArray } from 'common/util/array'
import { useAdmin } from 'web/hooks/use-admin'
export function LiquidityPanel(props: { contract: CPMMContract }) { export function LiquidityPanel(props: { contract: CPMMContract }) {
const { contract } = props const { contract } = props
@ -28,31 +30,32 @@ export function LiquidityPanel(props: { contract: CPMMContract }) {
setShowWithdrawal(true) setShowWithdrawal(true)
}, [showWithdrawal, lpShares]) }, [showWithdrawal, lpShares])
const isCreator = user?.id === contract.creatorId
const isAdmin = useAdmin()
if (!showWithdrawal && !isAdmin && !isCreator) return <></>
return ( return (
<Tabs <Tabs
tabs={[ tabs={buildArray(
{ (isCreator || isAdmin) && {
title: 'Subsidize', title: (isAdmin ? '[Admin] ' : '') + 'Subsidize',
content: <AddLiquidityPanel contract={contract} />, content: <AddLiquidityPanel contract={contract} />,
}, },
...(showWithdrawal showWithdrawal && {
? [ title: 'Withdraw',
{ content: (
title: 'Withdraw', <WithdrawLiquidityPanel
content: ( contract={contract}
<WithdrawLiquidityPanel lpShares={lpShares as { YES: number; NO: number }}
contract={contract} />
lpShares={lpShares as { YES: number; NO: number }} ),
/> },
),
},
]
: []),
{ {
title: 'Pool', title: 'Pool',
content: <ViewLiquidityPanel contract={contract} />, content: <ViewLiquidityPanel contract={contract} />,
}, }
]} )}
/> />
) )
} }

View File

@ -45,6 +45,10 @@ import { ContractsGrid } from 'web/components/contract/contracts-grid'
import { Title } from 'web/components/title' import { Title } from 'web/components/title'
import { usePrefetch } from 'web/hooks/use-prefetch' import { usePrefetch } from 'web/hooks/use-prefetch'
import { useAdmin } from 'web/hooks/use-admin' import { useAdmin } from 'web/hooks/use-admin'
import { BetSignUpPrompt } from 'web/components/sign-up-prompt'
import { PlayMoneyDisclaimer } from 'web/components/play-money-disclaimer'
import BetButton from 'web/components/bet-button'
import dayjs from 'dayjs' import dayjs from 'dayjs'
export const getStaticProps = fromPropz(getStaticPropz) export const getStaticProps = fromPropz(getStaticPropz)
@ -205,18 +209,6 @@ export function ContractPageContent(
setShowConfetti(shouldSeeConfetti) setShowConfetti(shouldSeeConfetti)
}, [contract, user]) }, [contract, user])
const [recommendedContracts, setRecommendedContracts] = useState<Contract[]>(
[]
)
useEffect(() => {
if (contract && user) {
getRecommendedContracts(contract, user.id, 6).then(
setRecommendedContracts
)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [contract.id, user?.id])
const { isResolved, question, outcomeType } = contract const { isResolved, question, outcomeType } = contract
const allowTrade = tradingAllowed(contract) const allowTrade = tradingAllowed(contract)
@ -300,17 +292,46 @@ export function ContractPageContent(
tips={tips} tips={tips}
comments={comments} comments={comments}
/> />
{!user ? (
<Col className="mt-4 max-w-sm items-center xl:hidden">
<BetSignUpPrompt />
<PlayMoneyDisclaimer />
</Col>
) : (
outcomeType === 'BINARY' &&
allowTrade && (
<BetButton
contract={contract as CPMMBinaryContract}
className="mb-2 !mt-0 xl:hidden"
/>
)
)}
</Col> </Col>
<RecommendedContractsWidget contract={contract} />
{recommendedContracts.length > 0 && (
<Col className="mt-2 gap-2 px-2 sm:px-0">
<Title className="text-gray-700" text="Recommended" />
<ContractsGrid
contracts={recommendedContracts}
trackingPostfix=" recommended"
/>
</Col>
)}
</Page> </Page>
) )
} }
function RecommendedContractsWidget(props: { contract: Contract }) {
const { contract } = props
const user = useUser()
const [recommendations, setRecommendations] = useState<Contract[]>([])
useEffect(() => {
if (user) {
getRecommendedContracts(contract, user.id, 6).then(setRecommendations)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [contract.id, user?.id])
if (recommendations.length === 0) {
return null
}
return (
<Col className="mt-2 gap-2 px-2 sm:px-0">
<Title className="text-gray-700" text="Recommended" />
<ContractsGrid
contracts={recommendations}
trackingPostfix=" recommended"
/>
</Col>
)
}