Quick back navigation into feed

This commit is contained in:
James Grugett 2022-04-11 16:13:26 -05:00
parent 9f2ac17ffb
commit e53cde5b34
9 changed files with 124 additions and 93 deletions

View File

@ -1,7 +1,6 @@
import _ from 'lodash'
import { useLayoutEffect, useState } from 'react'
import { Answer } from '../../../common/answer'
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
import { Col } from '../layout/col'
import { formatPercent } from '../../../common/util/format'
@ -15,12 +14,11 @@ import { AnswerResolvePanel } from './answer-resolve-panel'
export function AnswersPanel(props: {
contract: FullContract<DPM, FreeResponse>
answers: Answer[]
}) {
const { contract } = props
const { creatorId, resolution, resolutions, totalBets } = contract
const answers = useAnswers(contract.id) ?? props.answers
const answers = useAnswers(contract.id) ?? contract.answers
const [winningAnswers, otherAnswers] = _.partition(
answers.filter(
(answer) => answer.id !== '0' && totalBets[answer.id] > 0.000000001

View File

@ -1,13 +1,10 @@
import _ from 'lodash'
import clsx from 'clsx'
import { Contract, tradingAllowed } from '../../lib/firebase/contracts'
import { Contract } from '../../lib/firebase/contracts'
import { Comment } from '../../lib/firebase/comments'
import { Col } from '../layout/col'
import { Bet } from '../../../common/bet'
import { useUser } from '../../hooks/use-user'
import BetRow from '../bet-row'
import { FeedQuestion } from './feed-items'
import { ContractActivity } from './contract-activity'
export function ActivityFeed(props: {
@ -15,8 +12,9 @@ export function ActivityFeed(props: {
recentBets: Bet[]
recentComments: Comment[]
mode: 'only-recent' | 'abbreviated' | 'all'
getContractPath?: (contract: Contract) => string
}) {
const { contracts, recentBets, recentComments, mode } = props
const { contracts, recentBets, recentComments, mode, getContractPath } = props
const user = useUser()
@ -36,23 +34,13 @@ export function ActivityFeed(props: {
bets={groupedBets[contract.id] ?? []}
comments={groupedComments[contract.id] ?? []}
mode={mode}
contractPath={getContractPath ? getContractPath(contract) : undefined}
/>
)}
/>
)
}
export function SummaryActivityFeed(props: { contracts: Contract[] }) {
const { contracts } = props
return (
<FeedContainer
contracts={contracts}
renderContract={(contract) => <ContractSummary contract={contract} />}
/>
)
}
function FeedContainer(props: {
contracts: Contract[]
renderContract: (contract: Contract) => any
@ -73,27 +61,3 @@ function FeedContainer(props: {
</Col>
)
}
function ContractSummary(props: {
contract: Contract
betRowClassName?: string
}) {
const { contract, betRowClassName } = props
const { outcomeType } = contract
const isBinary = outcomeType === 'BINARY'
return (
<div className="flow-root pr-2 md:pr-0">
<div className={clsx(tradingAllowed(contract) ? '' : '-mb-8')}>
<div className="relative pb-8">
<div className="relative flex items-start space-x-3">
<FeedQuestion contract={contract} showDescription />
</div>
</div>
</div>
{isBinary && tradingAllowed(contract) && (
<BetRow contract={contract} className={clsx('mb-2', betRowClassName)} />
)}
</div>
)
}

View File

@ -36,6 +36,7 @@ export type DescriptionItem = BaseActivityItem & {
export type QuestionItem = BaseActivityItem & {
type: 'question'
showDescription: boolean
contractPath?: string
}
export type BetItem = BaseActivityItem & {
@ -280,7 +281,14 @@ export function getAllContractActivityItems(
filterToOutcome && answer
? [{ type: 'createanswer', id: answer.id, contract, answer }]
: abbreviated
? [{ type: 'question', id: '0', contract, showDescription: false }]
? [
{
type: 'question',
id: '0',
contract,
showDescription: false,
},
]
: [{ type: 'description', id: '0', contract }]
items.push(
@ -325,8 +333,12 @@ export function getRecentContractActivityItems(
contract: Contract,
bets: Bet[],
comments: Comment[],
user: User | null | undefined
user: User | null | undefined,
options: {
contractPath?: string
}
) {
const { contractPath } = options
bets = bets
.filter((bet) => !bet.isRedemption)
.sort((b1, b2) => b1.createdTime - b2.createdTime)
@ -337,6 +349,7 @@ export function getRecentContractActivityItems(
id: '0',
contract,
showDescription: false,
contractPath,
}
const items =

View File

@ -19,11 +19,19 @@ export function ContractActivity(props: {
user: User | null | undefined
mode: 'only-recent' | 'abbreviated' | 'all'
filterToOutcome?: string // Which multi-category outcome to filter
contractPath?: string
className?: string
betRowClassName?: string
}) {
const { contract, user, filterToOutcome, mode, className, betRowClassName } =
props
const {
contract,
user,
filterToOutcome,
mode,
contractPath,
className,
betRowClassName,
} = props
const updatedComments =
// eslint-disable-next-line react-hooks/rules-of-hooks
@ -36,7 +44,9 @@ export function ContractActivity(props: {
const items =
mode === 'only-recent'
? getRecentContractActivityItems(contract, bets, comments, user)
? getRecentContractActivityItems(contract, bets, comments, user, {
contractPath,
})
: getAllContractActivityItems(
contract,
bets,

View File

@ -295,7 +295,8 @@ function TruncatedComment(props: {
export function FeedQuestion(props: {
contract: Contract
showDescription?: boolean
showDescription: boolean
contractPath?: string
}) {
const { contract, showDescription } = props
const { creatorName, creatorUsername, question, resolution, outcomeType } =
@ -335,7 +336,9 @@ export function FeedQuestion(props: {
<Col className="items-start justify-between gap-2 sm:flex-row sm:gap-4">
<Col>
<SiteLink
href={contractPath(contract)}
href={
props.contractPath ? props.contractPath : contractPath(contract)
}
className="text-lg text-indigo-700 sm:text-xl"
>
{question}

View File

@ -6,9 +6,10 @@ export function Page(props: {
margin?: boolean
assertUser?: 'signed-in' | 'signed-out'
rightSidebar?: React.ReactNode
suspend?: boolean
children?: any
}) {
const { margin, assertUser, children, rightSidebar } = props
const { margin, assertUser, children, rightSidebar, suspend } = props
return (
<div>
@ -17,6 +18,7 @@ export function Page(props: {
'mx-auto w-full pb-14 lg:grid lg:grid-cols-12 lg:gap-8 lg:pt-6 xl:max-w-7xl',
margin && 'px-4'
)}
style={suspend ? visuallyHiddenStyle : undefined}
>
<div className="hidden lg:col-span-2 lg:block">
<Sidebar />
@ -41,3 +43,15 @@ export function Page(props: {
</div>
)
}
const visuallyHiddenStyle = {
clip: 'rect(0 0 0 0)',
clipPath: 'inset(50%)',
height: 1,
margin: -1,
overflow: 'hidden',
padding: 0,
position: 'absolute',
width: 1,
whiteSpace: 'nowrap',
} as const

View File

@ -28,6 +28,10 @@ export function contractPath(contract: Contract) {
return `/${contract.creatorUsername}/${contract.slug}`
}
export function homeContractPath(contract: Contract) {
return `/home?c=${contract.slug}`
}
export function contractMetrics(contract: Contract) {
const { createdTime, resolutionTime, isResolved } = contract

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react'
import { ArrowLeftIcon } from '@heroicons/react/outline'
import { useContractWithPreload } from '../../hooks/use-contract'
import { ContractOverview } from '../../components/contract/contract-overview'
@ -21,10 +22,6 @@ import { contractTextDetails } from '../../components/contract/contract-card'
import { Bet, listAllBets } from '../../lib/firebase/bets'
import { Comment, listAllComments } from '../../lib/firebase/comments'
import Custom404 from '../404'
import { getFoldsByTags } from '../../lib/firebase/folds'
import { Fold } from '../../../common/fold'
import { listAllAnswers } from '../../lib/firebase/answers'
import { Answer } from '../../../common/answer'
import { AnswersPanel } from '../../components/answers/answers-panel'
import { fromPropz, usePropz } from '../../hooks/use-propz'
import { Leaderboard } from '../../components/leaderboard'
@ -34,6 +31,8 @@ import { formatMoney } from '../../../common/util/format'
import { FeedBet, FeedComment } from '../../components/feed/feed-items'
import { useUserById } from '../../hooks/use-users'
import { ContractTabs } from '../../components/contract/contract-tabs'
import { FirstArgument } from '../../../common/util/types'
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: {
@ -43,18 +42,11 @@ export async function getStaticPropz(props: {
const contract = (await getContractFromSlug(contractSlug)) || null
const contractId = contract?.id
const foldsPromise = getFoldsByTags(contract?.tags ?? [])
const [bets, comments, answers] = await Promise.all([
const [bets, comments] = await Promise.all([
contractId ? listAllBets(contractId) : [],
contractId ? listAllComments(contractId) : [],
contractId && contract.outcomeType === 'FREE_RESPONSE'
? listAllAnswers(contractId)
: [],
])
const folds = await foldsPromise
return {
props: {
contract,
@ -62,8 +54,6 @@ export async function getStaticPropz(props: {
slug: contractSlug,
bets,
comments,
answers,
folds,
},
revalidate: 60, // regenerate after a minute
@ -79,19 +69,22 @@ export default function ContractPage(props: {
username: string
bets: Bet[]
comments: Comment[]
answers: Answer[]
slug: string
folds: Fold[]
backToHome?: () => void
}) {
props = usePropz(props, getStaticPropz) ?? {
contract: null,
username: '',
comments: [],
answers: [],
bets: [],
slug: '',
folds: [],
}
return <ContractPageContent {...props} />
}
export function ContractPageContent(props: FirstArgument<typeof ContractPage>) {
const { backToHome } = props
const user = useUser()
const contract = useContractWithPreload(props.contract)
@ -136,6 +129,16 @@ export default function ContractPage(props: {
)}
<Col className="w-full justify-between rounded border-0 border-gray-100 bg-white px-2 py-6 md:px-6 md:py-8">
{backToHome && (
<button
className="btn btn-sm mb-4 items-center gap-2 self-start border-0 border-gray-700 bg-white normal-case text-gray-700 hover:bg-white hover:text-gray-700 lg:hidden"
onClick={backToHome}
>
<ArrowLeftIcon className="h-5 w-5 text-gray-700" />
Back
</button>
)}
<ContractOverview
contract={contract}
bets={bets ?? []}
@ -145,8 +148,7 @@ export default function ContractPage(props: {
<>
<Spacer h={4} />
<AnswersPanel
contract={contract as any}
answers={props.answers}
contract={contract as FullContract<DPM, FreeResponse>}
/>
<Spacer h={4} />
</>

View File

@ -1,5 +1,5 @@
import React from 'react'
import Router from 'next/router'
import React, { useEffect } from 'react'
import Router, { useRouter } from 'next/router'
import _ from 'lodash'
import { Page } from '../components/page'
@ -13,6 +13,7 @@ import { useRecentBets } from '../hooks/use-bets'
import { useActiveContracts } from '../hooks/use-contracts'
import { useRecentComments } from '../hooks/use-comments'
import { useAlgoFeed } from '../hooks/use-algo-feed'
import { ContractPageContent } from './[username]/[contractSlug]'
const Home = () => {
const user = useUser()
@ -29,33 +30,55 @@ const Home = () => {
(contract) => contractsDict[contract.id] ?? contract
)
const router = useRouter()
const slug = router.query.c as string | undefined
const contract = feedContracts.find((c) => c.slug === slug)
useEffect(() => {
if (slug && !contract) {
delete router.query.c
router.replace(router, undefined, { shallow: true })
}
})
if (user === null) {
Router.replace('/')
return <></>
}
const activityContent =
contracts && recentBets && recentComments ? (
<ActivityFeed
contracts={updatedContracts}
recentBets={recentBets}
recentComments={recentComments}
mode="only-recent"
/>
) : (
<LoadingIndicator className="mt-4" />
)
return (
<Page assertUser="signed-in">
<Col className="items-center">
<Col className="w-full max-w-[700px]">
<FeedCreate user={user ?? undefined} />
<Spacer h={10} />
{activityContent}
<>
<Page assertUser="signed-in" suspend={!!contract}>
<Col className="items-center">
<Col className="w-full max-w-[700px]">
<FeedCreate user={user ?? undefined} />
<Spacer h={10} />
{contracts && recentBets && recentComments ? (
<ActivityFeed
contracts={updatedContracts}
recentBets={recentBets}
recentComments={recentComments}
mode="only-recent"
getContractPath={(c) => `home?c=${c.slug}`}
/>
) : (
<LoadingIndicator className="mt-4" />
)}
</Col>
</Col>
</Col>
</Page>
</Page>
{contract && (
<ContractPageContent
contract={contract}
username={contract.creatorUsername}
slug={contract.slug}
bets={[]}
comments={[]}
backToHome={router.back}
/>
)}
</>
)
}