Merge branch 'manifoldmarkets:main' into main
This commit is contained in:
commit
eea173a016
|
@ -4,8 +4,14 @@ import * as utc from 'dayjs/plugin/utc'
|
|||
dayjs.extend(utc)
|
||||
|
||||
import { getPrivateUser } from './utils'
|
||||
import { User } from '../../common/user'
|
||||
import { sendPersonalFollowupEmail, sendWelcomeEmail } from './emails'
|
||||
import { User } from 'common/user'
|
||||
import {
|
||||
sendCreatorGuideEmail,
|
||||
sendInterestingMarketsEmail,
|
||||
sendPersonalFollowupEmail,
|
||||
sendWelcomeEmail,
|
||||
} from './emails'
|
||||
import { getTrendingContracts } from './weekly-markets-emails'
|
||||
|
||||
export const onCreateUser = functions
|
||||
.runWith({ secrets: ['MAILGUN_KEY'] })
|
||||
|
@ -19,4 +25,21 @@ export const onCreateUser = functions
|
|||
|
||||
const followupSendTime = dayjs().add(48, 'hours').toString()
|
||||
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
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Bet } from 'common/bet'
|
||||
import { Contract, CPMMBinaryContract } from 'common/contract'
|
||||
import { Contract } from 'common/contract'
|
||||
import { ContractComment } from 'common/comment'
|
||||
import { PAST_BETS, User } from 'common/user'
|
||||
import {
|
||||
|
@ -11,13 +11,9 @@ import { ContractBetsTable, BetsSummary } from '../bets-list'
|
|||
import { Spacer } from '../layout/spacer'
|
||||
import { Tabs } from '../layout/tabs'
|
||||
import { Col } from '../layout/col'
|
||||
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||
import { useComments } from 'web/hooks/use-comments'
|
||||
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 {
|
||||
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
|
||||
|
@ -123,44 +119,28 @@ export function ContractTabs(props: {
|
|||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
currentPageForAnalytics={'contract'}
|
||||
tabs={[
|
||||
{
|
||||
title: 'Comments',
|
||||
content: commentActivity,
|
||||
badge: `${comments.length}`,
|
||||
},
|
||||
{
|
||||
title: capitalize(PAST_BETS),
|
||||
content: betActivity,
|
||||
badge: `${visibleBets.length + visibleLps.length}`,
|
||||
},
|
||||
...(!user || !userBets?.length
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: isMobile ? `You` : `Your ${PAST_BETS}`,
|
||||
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"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
<Tabs
|
||||
currentPageForAnalytics={'contract'}
|
||||
tabs={[
|
||||
{
|
||||
title: 'Comments',
|
||||
content: commentActivity,
|
||||
badge: `${comments.length}`,
|
||||
},
|
||||
{
|
||||
title: capitalize(PAST_BETS),
|
||||
content: betActivity,
|
||||
badge: `${visibleBets.length + visibleLps.length}`,
|
||||
},
|
||||
...(!user || !userBets?.length
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: isMobile ? `You` : `Your ${PAST_BETS}`,
|
||||
content: yourTrades,
|
||||
},
|
||||
]),
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import clsx from 'clsx'
|
|||
import {
|
||||
ContractCommentInput,
|
||||
FeedComment,
|
||||
getMostRecentCommentableBet,
|
||||
} from 'web/components/feed/feed-comments'
|
||||
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
||||
import { useRouter } from 'next/router'
|
||||
|
@ -49,28 +48,6 @@ export function FeedAnswerCommentGroup(props: {
|
|||
|
||||
const answerElementId = `answer-${answer.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(
|
||||
(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(() => {
|
||||
if (router.asPath.endsWith(`#${answerElementId}`)) {
|
||||
setHighlighted(true)
|
||||
|
@ -105,10 +69,7 @@ export function FeedAnswerCommentGroup(props: {
|
|||
}, [answerElementId, router.asPath])
|
||||
|
||||
return (
|
||||
<Col
|
||||
className={'relative flex-1 items-stretch gap-3'}
|
||||
key={answer.id + 'comment'}
|
||||
>
|
||||
<Col className="relative flex-1 items-stretch gap-3">
|
||||
<Row
|
||||
className={clsx(
|
||||
'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">
|
||||
<Linkify text={text} />
|
||||
</span>
|
||||
|
||||
{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'}>
|
||||
<div className="sm:hidden">
|
||||
<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)}
|
||||
>
|
||||
Reply
|
||||
</button>
|
||||
</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>
|
||||
</Row>
|
||||
<Col className="gap-3 pl-1">
|
||||
|
@ -170,7 +126,7 @@ export function FeedAnswerCommentGroup(props: {
|
|||
))}
|
||||
</Col>
|
||||
{showReply && (
|
||||
<div className={'relative ml-7'}>
|
||||
<div className="relative ml-7">
|
||||
<span
|
||||
className="absolute -left-1 -ml-[1px] mt-[1.25rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
|
|
|
@ -14,6 +14,8 @@ import { Col } from './layout/col'
|
|||
import { track } from 'web/lib/service/analytics'
|
||||
import { InfoTooltip } from './info-tooltip'
|
||||
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 }) {
|
||||
const { contract } = props
|
||||
|
@ -28,31 +30,32 @@ export function LiquidityPanel(props: { contract: CPMMContract }) {
|
|||
setShowWithdrawal(true)
|
||||
}, [showWithdrawal, lpShares])
|
||||
|
||||
const isCreator = user?.id === contract.creatorId
|
||||
const isAdmin = useAdmin()
|
||||
|
||||
if (!showWithdrawal && !isAdmin && !isCreator) return <></>
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
tabs={[
|
||||
{
|
||||
title: 'Subsidize',
|
||||
tabs={buildArray(
|
||||
(isCreator || isAdmin) && {
|
||||
title: (isAdmin ? '[Admin] ' : '') + 'Subsidize',
|
||||
content: <AddLiquidityPanel contract={contract} />,
|
||||
},
|
||||
...(showWithdrawal
|
||||
? [
|
||||
{
|
||||
title: 'Withdraw',
|
||||
content: (
|
||||
<WithdrawLiquidityPanel
|
||||
contract={contract}
|
||||
lpShares={lpShares as { YES: number; NO: number }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
showWithdrawal && {
|
||||
title: 'Withdraw',
|
||||
content: (
|
||||
<WithdrawLiquidityPanel
|
||||
contract={contract}
|
||||
lpShares={lpShares as { YES: number; NO: number }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Pool',
|
||||
content: <ViewLiquidityPanel contract={contract} />,
|
||||
},
|
||||
]}
|
||||
}
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ import { ContractsGrid } from 'web/components/contract/contracts-grid'
|
|||
import { Title } from 'web/components/title'
|
||||
import { usePrefetch } from 'web/hooks/use-prefetch'
|
||||
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'
|
||||
|
||||
export const getStaticProps = fromPropz(getStaticPropz)
|
||||
|
@ -205,18 +209,6 @@ export function ContractPageContent(
|
|||
setShowConfetti(shouldSeeConfetti)
|
||||
}, [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 allowTrade = tradingAllowed(contract)
|
||||
|
@ -300,17 +292,46 @@ export function ContractPageContent(
|
|||
tips={tips}
|
||||
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>
|
||||
|
||||
{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>
|
||||
)}
|
||||
<RecommendedContractsWidget contract={contract} />
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user