Updates to experimental home (#858)

* Line clamp question in prob change table

* Tweaks

* Expand option for daily movers

* Snap scrolling for carousel

* Add arrows to section headers

* Remove carousel from experimental/home

* React querify fetching your groups

* Edit home is its own page

* Add daily profit and balance

* Merge branch 'main' into new-home

* Make experimental search by your followed groups/creators

* Just submit, allow xs on pills

* Weigh in

* Use next/future/image component to optimize avatar images

* Inga/challenge icon (#857)

* changed challenge icon to custom icon
* fixed tip button alignment

* weighing in and trading "weigh in" for "trade"

Co-authored-by: Ian Philips <iansphilips@gmail.com>
Co-authored-by: Austin Chen <akrolsmir@gmail.com>
Co-authored-by: ingawei <46611122+ingawei@users.noreply.github.com>
Co-authored-by: mantikoros <sgrugett@gmail.com>
This commit is contained in:
James Grugett 2022-09-08 01:36:34 -05:00 committed by GitHub
parent edbebb7e67
commit 54c227cf6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 265 additions and 192 deletions

View File

@ -116,12 +116,12 @@ const calculateProfitForPeriod = (
return currentProfit return currentProfit
} }
const startingProfit = calculateTotalProfit(startingPortfolio) const startingProfit = calculatePortfolioProfit(startingPortfolio)
return currentProfit - startingProfit return currentProfit - startingProfit
} }
const calculateTotalProfit = (portfolio: PortfolioMetrics) => { export const calculatePortfolioProfit = (portfolio: PortfolioMetrics) => {
return portfolio.investmentValue + portfolio.balance - portfolio.totalDeposits return portfolio.investmentValue + portfolio.balance - portfolio.totalDeposits
} }
@ -129,7 +129,7 @@ export const calculateNewProfit = (
portfolioHistory: PortfolioMetrics[], portfolioHistory: PortfolioMetrics[],
newPortfolio: PortfolioMetrics newPortfolio: PortfolioMetrics
) => { ) => {
const allTimeProfit = calculateTotalProfit(newPortfolio) const allTimeProfit = calculatePortfolioProfit(newPortfolio)
const descendingPortfolio = sortBy( const descendingPortfolio = sortBy(
portfolioHistory, portfolioHistory,
(p) => p.timestamp (p) => p.timestamp

View File

@ -12,7 +12,7 @@ import { User } from 'common/user'
import { Group } from 'common/group' import { Group } from 'common/group'
export function ArrangeHome(props: { export function ArrangeHome(props: {
user: User | null user: User | null | undefined
homeSections: { visible: string[]; hidden: string[] } homeSections: { visible: string[]; hidden: string[] }
setHomeSections: (homeSections: { setHomeSections: (homeSections: {
visible: string[] visible: string[]
@ -30,7 +30,6 @@ export function ArrangeHome(props: {
return ( return (
<DragDropContext <DragDropContext
onDragEnd={(e) => { onDragEnd={(e) => {
console.log('drag end', e)
const { destination, source, draggableId } = e const { destination, source, draggableId } = e
if (!destination) return if (!destination) return

View File

@ -38,7 +38,7 @@ export function Carousel(props: {
return ( return (
<div className={clsx('relative', className)}> <div className={clsx('relative', className)}>
<Row <Row
className="scrollbar-hide w-full gap-4 overflow-x-auto scroll-smooth" className="scrollbar-hide w-full snap-x gap-4 overflow-x-auto scroll-smooth"
ref={ref} ref={ref}
onScroll={onScroll} onScroll={onScroll}
> >

View File

@ -69,6 +69,7 @@ type AdditionalFilter = {
excludeContractIds?: string[] excludeContractIds?: string[]
groupSlug?: string groupSlug?: string
yourBets?: boolean yourBets?: boolean
followed?: boolean
} }
export function ContractSearch(props: { export function ContractSearch(props: {
@ -88,6 +89,7 @@ export function ContractSearch(props: {
useQueryUrlParam?: boolean useQueryUrlParam?: boolean
isWholePage?: boolean isWholePage?: boolean
noControls?: boolean noControls?: boolean
maxResults?: number
renderContracts?: ( renderContracts?: (
contracts: Contract[] | undefined, contracts: Contract[] | undefined,
loadMore: () => void loadMore: () => void
@ -107,6 +109,7 @@ export function ContractSearch(props: {
useQueryUrlParam, useQueryUrlParam,
isWholePage, isWholePage,
noControls, noControls,
maxResults,
renderContracts, renderContracts,
} = props } = props
@ -189,7 +192,8 @@ export function ContractSearch(props: {
const contracts = state.pages const contracts = state.pages
.flat() .flat()
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id)) .filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
const renderedContracts = state.pages.length === 0 ? undefined : contracts const renderedContracts =
state.pages.length === 0 ? undefined : contracts.slice(0, maxResults)
if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) { if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) {
return <ContractSearchFirestore additionalFilter={additionalFilter} /> return <ContractSearchFirestore additionalFilter={additionalFilter} />
@ -292,6 +296,19 @@ function ContractSearchControls(props: {
const pillGroups: { name: string; slug: string }[] = const pillGroups: { name: string; slug: string }[] =
memberPillGroups.length > 0 ? memberPillGroups : DEFAULT_CATEGORY_GROUPS memberPillGroups.length > 0 ? memberPillGroups : DEFAULT_CATEGORY_GROUPS
const personalFilters = user
? [
// Show contracts in groups that the user is a member of.
memberGroupSlugs
.map((slug) => `groupLinks.slug:${slug}`)
// Or, show contracts created by users the user follows
.concat(follows?.map((followId) => `creatorId:${followId}`) ?? []),
// Subtract contracts you bet on, to show new ones.
`uniqueBettorIds:-${user.id}`,
]
: []
const additionalFilters = [ const additionalFilters = [
additionalFilter?.creatorId additionalFilter?.creatorId
? `creatorId:${additionalFilter.creatorId}` ? `creatorId:${additionalFilter.creatorId}`
@ -304,6 +321,7 @@ function ContractSearchControls(props: {
? // Show contracts bet on by the user ? // Show contracts bet on by the user
`uniqueBettorIds:${user.id}` `uniqueBettorIds:${user.id}`
: '', : '',
...(additionalFilter?.followed ? personalFilters : []),
] ]
const facetFilters = query const facetFilters = query
? additionalFilters ? additionalFilters
@ -320,17 +338,7 @@ function ContractSearchControls(props: {
state.pillFilter !== 'your-bets' state.pillFilter !== 'your-bets'
? `groupLinks.slug:${state.pillFilter}` ? `groupLinks.slug:${state.pillFilter}`
: '', : '',
state.pillFilter === 'personal' ...(state.pillFilter === 'personal' ? personalFilters : []),
? // Show contracts in groups that the user is a member of
memberGroupSlugs
.map((slug) => `groupLinks.slug:${slug}`)
// Show contracts created by users the user follows
.concat(follows?.map((followId) => `creatorId:${followId}`) ?? [])
: '',
// Subtract contracts you bet on from For you.
state.pillFilter === 'personal' && user
? `uniqueBettorIds:-${user.id}`
: '',
state.pillFilter === 'your-bets' && user state.pillFilter === 'your-bets' && user
? // Show contracts bet on by the user ? // Show contracts bet on by the user
`uniqueBettorIds:${user.id}` `uniqueBettorIds:${user.id}`

View File

@ -3,52 +3,74 @@ import { contractPath } from 'web/lib/firebase/contracts'
import { CPMMContract } from 'common/contract' import { CPMMContract } from 'common/contract'
import { formatPercent } from 'common/util/format' import { formatPercent } from 'common/util/format'
import { useProbChanges } from 'web/hooks/use-prob-changes' import { useProbChanges } from 'web/hooks/use-prob-changes'
import { SiteLink } from '../site-link' import { linkClass, SiteLink } from '../site-link'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { Row } from '../layout/row' import { Row } from '../layout/row'
import { useState } from 'react'
export function ProbChangeTable(props: { userId: string | undefined }) { export function ProbChangeTable(props: { userId: string | undefined }) {
const { userId } = props const { userId } = props
const changes = useProbChanges(userId ?? '') const changes = useProbChanges(userId ?? '')
const [expanded, setExpanded] = useState(false)
if (!changes) { if (!changes) {
return null return null
} }
const { positiveChanges, negativeChanges } = changes const count = expanded ? 16 : 4
const count = 3 const { positiveChanges, negativeChanges } = changes
const filteredPositiveChanges = positiveChanges.slice(0, count / 2)
const filteredNegativeChanges = negativeChanges.slice(0, count / 2)
const filteredChanges = [
...filteredPositiveChanges,
...filteredNegativeChanges,
]
return ( return (
<Row className="w-full flex-wrap divide-x-2 rounded bg-white shadow-md"> <Col>
<Col className="min-w-[300px] flex-1 divide-y"> <Col className="mb-4 w-full divide-x-2 divide-y rounded-lg bg-white shadow-md md:flex-row md:divide-y-0">
{positiveChanges.slice(0, count).map((contract) => ( <Col className="flex-1 divide-y">
<Row className="hover:bg-gray-100"> {filteredChanges.slice(0, count / 2).map((contract) => (
<ProbChange className="p-4 text-right" contract={contract} /> <Row className="items-center hover:bg-gray-100">
<SiteLink <ProbChange
className="p-4 font-semibold text-indigo-700" className="p-4 text-right text-xl"
href={contractPath(contract)} contract={contract}
> />
{contract.question} <SiteLink
</SiteLink> className="p-4 pl-2 font-semibold text-indigo-700"
</Row> href={contractPath(contract)}
))} >
<span className="line-clamp-2">{contract.question}</span>
</SiteLink>
</Row>
))}
</Col>
<Col className="flex-1 divide-y">
{filteredChanges.slice(count / 2).map((contract) => (
<Row className="items-center hover:bg-gray-100">
<ProbChange
className="p-4 text-right text-xl"
contract={contract}
/>
<SiteLink
className="p-4 pl-2 font-semibold text-indigo-700"
href={contractPath(contract)}
>
<span className="line-clamp-2">{contract.question}</span>
</SiteLink>
</Row>
))}
</Col>
</Col> </Col>
<Col className="justify-content-stretch min-w-[300px] flex-1 divide-y"> <div
{negativeChanges.slice(0, count).map((contract) => ( className={clsx(linkClass, 'cursor-pointer self-end')}
<Row className="hover:bg-gray-100"> onClick={() => setExpanded(!expanded)}
<ProbChange className="p-4 text-right" contract={contract} /> >
<SiteLink {expanded ? 'Show less' : 'Show more'}
className="p-4 font-semibold text-indigo-700" </div>
href={contractPath(contract)} </Col>
>
{contract.question}
</SiteLink>
</Row>
))}
</Col>
</Row>
) )
} }
@ -63,9 +85,9 @@ export function ProbChange(props: {
const color = const color =
change > 0 change > 0
? 'text-green-600' ? 'text-green-500'
: change < 0 : change < 0
? 'text-red-600' ? 'text-red-500'
: 'text-gray-600' : 'text-gray-600'
const str = const str =

View File

@ -7,7 +7,6 @@ import { Col } from 'web/components/layout/col'
export function DoubleCarousel(props: { export function DoubleCarousel(props: {
contracts: Contract[] contracts: Contract[]
seeMoreUrl?: string
showTime?: ShowTime showTime?: ShowTime
loadMore?: () => void loadMore?: () => void
}) { }) {
@ -19,7 +18,7 @@ export function DoubleCarousel(props: {
? range(0, Math.floor(contracts.length / 2)).map((col) => { ? range(0, Math.floor(contracts.length / 2)).map((col) => {
const i = col * 2 const i = col * 2
return ( return (
<Col key={contracts[i].id}> <Col className="snap-start scroll-m-4" key={contracts[i].id}>
<ContractCard <ContractCard
contract={contracts[i]} contract={contracts[i]}
className="mb-2 w-96 shrink-0" className="mb-2 w-96 shrink-0"

View File

@ -2,13 +2,13 @@ import { useEffect, useState } from 'react'
import { Group } from 'common/group' import { Group } from 'common/group'
import { User } from 'common/user' import { User } from 'common/user'
import { import {
getMemberGroups,
GroupMemberDoc, GroupMemberDoc,
groupMembers, groupMembers,
listenForGroup, listenForGroup,
listenForGroupContractDocs, listenForGroupContractDocs,
listenForGroups, listenForGroups,
listenForMemberGroupIds, listenForMemberGroupIds,
listenForMemberGroups,
listenForOpenGroups, listenForOpenGroups,
listGroups, listGroups,
} from 'web/lib/firebase/groups' } from 'web/lib/firebase/groups'
@ -17,6 +17,7 @@ import { filterDefined } from 'common/util/array'
import { Contract } from 'common/contract' import { Contract } from 'common/contract'
import { uniq } from 'lodash' import { uniq } from 'lodash'
import { listenForValues } from 'web/lib/firebase/utils' import { listenForValues } from 'web/lib/firebase/utils'
import { useQuery } from 'react-query'
export const useGroup = (groupId: string | undefined) => { export const useGroup = (groupId: string | undefined) => {
const [group, setGroup] = useState<Group | null | undefined>() const [group, setGroup] = useState<Group | null | undefined>()
@ -49,12 +50,10 @@ export const useOpenGroups = () => {
} }
export const useMemberGroups = (userId: string | null | undefined) => { export const useMemberGroups = (userId: string | null | undefined) => {
const [memberGroups, setMemberGroups] = useState<Group[] | undefined>() const result = useQuery(['member-groups', userId ?? ''], () =>
useEffect(() => { getMemberGroups(userId ?? '')
if (userId) )
return listenForMemberGroups(userId, (groups) => setMemberGroups(groups)) return result.data
}, [userId])
return memberGroups
} }
// Note: We cache member group ids in localstorage to speed up the initial load // Note: We cache member group ids in localstorage to speed up the initial load

View File

@ -32,7 +32,7 @@ export const groupMembers = (groupId: string) =>
export const groupContracts = (groupId: string) => export const groupContracts = (groupId: string) =>
collection(groups, groupId, 'groupContracts') collection(groups, groupId, 'groupContracts')
const openGroupsQuery = query(groups, where('anyoneCanJoin', '==', true)) const openGroupsQuery = query(groups, where('anyoneCanJoin', '==', true))
const memberGroupsQuery = (userId: string) => export const memberGroupsQuery = (userId: string) =>
query(collectionGroup(db, 'groupMembers'), where('userId', '==', userId)) query(collectionGroup(db, 'groupMembers'), where('userId', '==', userId))
export function groupPath( export function groupPath(
@ -113,6 +113,15 @@ export function listenForGroup(
return listenForValue(doc(groups, groupId), setGroup) return listenForValue(doc(groups, groupId), setGroup)
} }
export async function getMemberGroups(userId: string) {
const snapshot = await getDocs(memberGroupsQuery(userId))
const groupIds = filterDefined(
snapshot.docs.map((doc) => doc.ref.parent.parent?.id)
)
const groups = await Promise.all(groupIds.map(getGroup))
return filterDefined(groups)
}
export function listenForMemberGroupIds( export function listenForMemberGroupIds(
userId: string, userId: string,
setGroupIds: (groupIds: string[]) => void setGroupIds: (groupIds: string[]) => void

View File

@ -0,0 +1,60 @@
import clsx from 'clsx'
import { useState } from 'react'
import { ArrangeHome } from 'web/components/arrange-home'
import { Button } from 'web/components/button'
import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row'
import { Page } from 'web/components/page'
import { SiteLink } from 'web/components/site-link'
import { Title } from 'web/components/title'
import { useTracking } from 'web/hooks/use-tracking'
import { useUser } from 'web/hooks/use-user'
import { updateUser } from 'web/lib/firebase/users'
export default function Home() {
const user = useUser()
useTracking('edit home')
const [homeSections, setHomeSections] = useState(
user?.homeSections ?? { visible: [], hidden: [] }
)
const updateHomeSections = (newHomeSections: {
visible: string[]
hidden: string[]
}) => {
if (!user) return
updateUser(user.id, { homeSections: newHomeSections })
setHomeSections(newHomeSections)
}
return (
<Page>
<Col className="pm:mx-10 gap-4 px-4 pb-12">
<Row className={'w-full items-center justify-between'}>
<Title text="Edit your home page" />
<DoneButton />
</Row>
<ArrangeHome
user={user}
homeSections={homeSections}
setHomeSections={updateHomeSections}
/>
</Col>
</Page>
)
}
function DoneButton(props: { className?: string }) {
const { className } = props
return (
<SiteLink href="/experimental/home">
<Button size="lg" color="blue" className={clsx(className, 'flex')}>
Done
</Button>
</SiteLink>
)
}

View File

@ -1,40 +1,36 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import Router from 'next/router' import Router from 'next/router'
import { PencilIcon, PlusSmIcon } from '@heroicons/react/solid' import {
PencilIcon,
PlusSmIcon,
ArrowSmRightIcon,
} from '@heroicons/react/solid'
import clsx from 'clsx'
import { Page } from 'web/components/page' import { Page } from 'web/components/page'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
import { ContractSearch, SORTS } from 'web/components/contract-search' import { ContractSearch, SORTS } from 'web/components/contract-search'
import { User } from 'common/user' import { User } from 'common/user'
import { getUserAndPrivateUser, updateUser } from 'web/lib/firebase/users'
import { useTracking } from 'web/hooks/use-tracking' import { useTracking } from 'web/hooks/use-tracking'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { authenticateOnServer } from 'web/lib/firebase/server-auth'
import { useSaveReferral } from 'web/hooks/use-save-referral' import { useSaveReferral } from 'web/hooks/use-save-referral'
import { GetServerSideProps } from 'next'
import { Sort } from 'web/components/contract-search' import { Sort } from 'web/components/contract-search'
import { Group } from 'common/group' import { Group } from 'common/group'
import { LoadingIndicator } from 'web/components/loading-indicator'
import { GroupLinkItem } from '../../groups'
import { SiteLink } from 'web/components/site-link' import { SiteLink } from 'web/components/site-link'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { useMemberGroups } from 'web/hooks/use-group' import { useMemberGroups } from 'web/hooks/use-group'
import { DoubleCarousel } from '../../../components/double-carousel'
import clsx from 'clsx'
import { Button } from 'web/components/button' import { Button } from 'web/components/button'
import { ArrangeHome, getHomeItems } from '../../../components/arrange-home' import { getHomeItems } from '../../../components/arrange-home'
import { Title } from 'web/components/title' import { Title } from 'web/components/title'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { ProbChangeTable } from 'web/components/contract/prob-change-table' import { ProbChangeTable } from 'web/components/contract/prob-change-table'
import { groupPath } from 'web/lib/firebase/groups'
import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
import { calculatePortfolioProfit } from 'common/calculate-metrics'
import { formatMoney } from 'common/util/format'
export const getServerSideProps: GetServerSideProps = async (ctx) => { const Home = () => {
const creds = await authenticateOnServer(ctx) const user = useUser()
const auth = creds ? await getUserAndPrivateUser(creds.uid) : null
return { props: { auth } }
}
const Home = (props: { auth: { user: User } | null }) => {
const user = useUser() ?? props.auth?.user ?? null
useTracking('view home') useTracking('view home')
@ -42,76 +38,54 @@ const Home = (props: { auth: { user: User } | null }) => {
const groups = useMemberGroups(user?.id) ?? [] const groups = useMemberGroups(user?.id) ?? []
const [homeSections, setHomeSections] = useState( const [homeSections] = useState(
user?.homeSections ?? { visible: [], hidden: [] } user?.homeSections ?? { visible: [], hidden: [] }
) )
const { visibleItems } = getHomeItems(groups, homeSections) const { visibleItems } = getHomeItems(groups, homeSections)
const updateHomeSections = (newHomeSections: {
visible: string[]
hidden: string[]
}) => {
if (!user) return
updateUser(user.id, { homeSections: newHomeSections })
setHomeSections(newHomeSections)
}
const [isEditing, setIsEditing] = useState(false)
return ( return (
<Page> <Page>
<Col className="pm:mx-10 gap-4 px-4 pb-12 xl:w-[125%]"> <Col className="pm:mx-10 gap-4 px-4 pb-12">
<Row className={'w-full items-center justify-between'}> <Row className={'w-full items-center justify-between'}>
<Title text={isEditing ? 'Edit your home page' : 'Home'} /> <Title className="!mb-0" text="Home" />
<EditDoneButton isEditing={isEditing} setIsEditing={setIsEditing} /> <EditButton />
</Row> </Row>
{isEditing ? ( <DailyProfitAndBalance userId={user?.id} />
<>
<ArrangeHome
user={user}
homeSections={homeSections}
setHomeSections={updateHomeSections}
/>
</>
) : (
<>
<div className="text-xl text-gray-800">Daily movers</div>
<ProbChangeTable userId={user?.id} />
{visibleItems.map((item) => { <div className="text-xl text-gray-800">Daily movers</div>
const { id } = item <ProbChangeTable userId={user?.id} />
if (id === 'your-bets') {
return (
<SearchSection
key={id}
label={'Your trades'}
sort={'prob-change-day'}
user={user}
yourBets
/>
)
}
const sort = SORTS.find((sort) => sort.value === id)
if (sort)
return (
<SearchSection
key={id}
label={sort.label}
sort={sort.value}
user={user}
/>
)
const group = groups.find((g) => g.id === id) {visibleItems.map((item) => {
if (group) const { id } = item
return <GroupSection key={id} group={group} user={user} /> if (id === 'your-bets') {
return (
<SearchSection
key={id}
label={'Your trades'}
sort={'newest'}
user={user}
yourBets
/>
)
}
const sort = SORTS.find((sort) => sort.value === id)
if (sort)
return (
<SearchSection
key={id}
label={sort.label}
sort={sort.value}
user={user}
/>
)
return null const group = groups.find((g) => g.id === id)
})} if (group) return <GroupSection key={id} group={group} user={user} />
</>
)} return null
})}
</Col> </Col>
<button <button
type="button" type="button"
@ -129,7 +103,7 @@ const Home = (props: { auth: { user: User } | null }) => {
function SearchSection(props: { function SearchSection(props: {
label: string label: string
user: User | null user: User | null | undefined
sort: Sort sort: Sort
yourBets?: boolean yourBets?: boolean
}) { }) {
@ -139,88 +113,91 @@ function SearchSection(props: {
return ( return (
<Col> <Col>
<SiteLink className="mb-2 text-xl" href={href}> <SiteLink className="mb-2 text-xl" href={href}>
{label} {label}{' '}
<ArrowSmRightIcon
className="mb-0.5 inline h-6 w-6 text-gray-500"
aria-hidden="true"
/>
</SiteLink> </SiteLink>
<ContractSearch <ContractSearch
user={user} user={user}
defaultSort={sort} defaultSort={sort}
additionalFilter={yourBets ? { yourBets: true } : undefined} additionalFilter={yourBets ? { yourBets: true } : { followed: true }}
noControls noControls
// persistPrefix={`experimental-home-${sort}`} maxResults={6}
renderContracts={(contracts, loadMore) => persistPrefix={`experimental-home-${sort}`}
contracts ? (
<DoubleCarousel
contracts={contracts}
seeMoreUrl={href}
showTime={
sort === 'close-date' || sort === 'resolve-date'
? sort
: undefined
}
loadMore={loadMore}
/>
) : (
<LoadingIndicator />
)
}
/> />
</Col> </Col>
) )
} }
function GroupSection(props: { group: Group; user: User | null }) { function GroupSection(props: { group: Group; user: User | null | undefined }) {
const { group, user } = props const { group, user } = props
return ( return (
<Col> <Col>
<GroupLinkItem className="mb-2 text-xl" group={group} /> <SiteLink className="mb-2 text-xl" href={groupPath(group.slug)}>
{group.name}{' '}
<ArrowSmRightIcon
className="mb-0.5 inline h-6 w-6 text-gray-500"
aria-hidden="true"
/>
</SiteLink>
<ContractSearch <ContractSearch
user={user} user={user}
defaultSort={'score'} defaultSort={'score'}
additionalFilter={{ groupSlug: group.slug }} additionalFilter={{ groupSlug: group.slug }}
noControls noControls
// persistPrefix={`experimental-home-${group.slug}`} maxResults={6}
renderContracts={(contracts, loadMore) => persistPrefix={`experimental-home-${group.slug}`}
contracts ? (
contracts.length == 0 ? (
<div className="m-2 text-gray-500">No open markets</div>
) : (
<DoubleCarousel
contracts={contracts}
seeMoreUrl={`/group/${group.slug}`}
loadMore={loadMore}
/>
)
) : (
<LoadingIndicator />
)
}
/> />
</Col> </Col>
) )
} }
function EditDoneButton(props: { function EditButton(props: { className?: string }) {
isEditing: boolean const { className } = props
setIsEditing: (isEditing: boolean) => void
className?: string
}) {
const { isEditing, setIsEditing, className } = props
return ( return (
<Button <SiteLink href="/experimental/home/edit">
size="lg" <Button size="lg" color="gray-white" className={clsx(className, 'flex')}>
color={isEditing ? 'blue' : 'gray-white'} <PencilIcon className={clsx('mr-2 h-[24px] w-5')} aria-hidden="true" />{' '}
className={clsx(className, 'flex')} Edit
onClick={() => { </Button>
setIsEditing(!isEditing) </SiteLink>
}} )
> }
{!isEditing && (
<PencilIcon className={clsx('mr-2 h-[24px] w-5')} aria-hidden="true" /> function DailyProfitAndBalance(props: {
)} userId: string | null | undefined
{isEditing ? 'Done' : 'Edit'} className?: string
</Button> }) {
const { userId, className } = props
const metrics = usePortfolioHistory(userId ?? '', 'daily') ?? []
const [first, last] = [metrics[0], metrics[metrics.length - 1]]
if (first === undefined || last === undefined) return null
const profit =
calculatePortfolioProfit(last) - calculatePortfolioProfit(first)
const balanceChange = last.balance - first.balance
return (
<div className={clsx(className, 'text-lg')}>
<span className={clsx(profit >= 0 ? 'text-green-500' : 'text-red-500')}>
{profit >= 0 ? '+' : '-'}
{formatMoney(profit)}
</span>{' '}
profit and{' '}
<span
className={clsx(balanceChange >= 0 ? 'text-green-500' : 'text-red-500')}
>
{balanceChange >= 0 ? '+' : '-'}
{formatMoney(balanceChange)}
</span>{' '}
balance today
</div>
) )
} }

View File

@ -237,7 +237,7 @@ const MarketCarousel = (props: { slug: string }) => {
key={m.id} key={m.id}
contract={m} contract={m}
hideGroupLink hideGroupLink
className="mb-2 max-h-[200px] w-96 shrink-0" className="mb-2 max-h-[200px] w-96 shrink-0 snap-start scroll-m-4 md:snap-align-none"
questionClass="line-clamp-3" questionClass="line-clamp-3"
trackingPostfix=" tournament" trackingPostfix=" tournament"
/> />