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:
parent
edbebb7e67
commit
54c227cf6c
|
@ -116,12 +116,12 @@ const calculateProfitForPeriod = (
|
|||
return currentProfit
|
||||
}
|
||||
|
||||
const startingProfit = calculateTotalProfit(startingPortfolio)
|
||||
const startingProfit = calculatePortfolioProfit(startingPortfolio)
|
||||
|
||||
return currentProfit - startingProfit
|
||||
}
|
||||
|
||||
const calculateTotalProfit = (portfolio: PortfolioMetrics) => {
|
||||
export const calculatePortfolioProfit = (portfolio: PortfolioMetrics) => {
|
||||
return portfolio.investmentValue + portfolio.balance - portfolio.totalDeposits
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ export const calculateNewProfit = (
|
|||
portfolioHistory: PortfolioMetrics[],
|
||||
newPortfolio: PortfolioMetrics
|
||||
) => {
|
||||
const allTimeProfit = calculateTotalProfit(newPortfolio)
|
||||
const allTimeProfit = calculatePortfolioProfit(newPortfolio)
|
||||
const descendingPortfolio = sortBy(
|
||||
portfolioHistory,
|
||||
(p) => p.timestamp
|
||||
|
|
|
@ -12,7 +12,7 @@ import { User } from 'common/user'
|
|||
import { Group } from 'common/group'
|
||||
|
||||
export function ArrangeHome(props: {
|
||||
user: User | null
|
||||
user: User | null | undefined
|
||||
homeSections: { visible: string[]; hidden: string[] }
|
||||
setHomeSections: (homeSections: {
|
||||
visible: string[]
|
||||
|
@ -30,7 +30,6 @@ export function ArrangeHome(props: {
|
|||
return (
|
||||
<DragDropContext
|
||||
onDragEnd={(e) => {
|
||||
console.log('drag end', e)
|
||||
const { destination, source, draggableId } = e
|
||||
if (!destination) return
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ export function Carousel(props: {
|
|||
return (
|
||||
<div className={clsx('relative', className)}>
|
||||
<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}
|
||||
onScroll={onScroll}
|
||||
>
|
||||
|
|
|
@ -69,6 +69,7 @@ type AdditionalFilter = {
|
|||
excludeContractIds?: string[]
|
||||
groupSlug?: string
|
||||
yourBets?: boolean
|
||||
followed?: boolean
|
||||
}
|
||||
|
||||
export function ContractSearch(props: {
|
||||
|
@ -88,6 +89,7 @@ export function ContractSearch(props: {
|
|||
useQueryUrlParam?: boolean
|
||||
isWholePage?: boolean
|
||||
noControls?: boolean
|
||||
maxResults?: number
|
||||
renderContracts?: (
|
||||
contracts: Contract[] | undefined,
|
||||
loadMore: () => void
|
||||
|
@ -107,6 +109,7 @@ export function ContractSearch(props: {
|
|||
useQueryUrlParam,
|
||||
isWholePage,
|
||||
noControls,
|
||||
maxResults,
|
||||
renderContracts,
|
||||
} = props
|
||||
|
||||
|
@ -189,7 +192,8 @@ export function ContractSearch(props: {
|
|||
const contracts = state.pages
|
||||
.flat()
|
||||
.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) {
|
||||
return <ContractSearchFirestore additionalFilter={additionalFilter} />
|
||||
|
@ -292,6 +296,19 @@ function ContractSearchControls(props: {
|
|||
const pillGroups: { name: string; slug: string }[] =
|
||||
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 = [
|
||||
additionalFilter?.creatorId
|
||||
? `creatorId:${additionalFilter.creatorId}`
|
||||
|
@ -304,6 +321,7 @@ function ContractSearchControls(props: {
|
|||
? // Show contracts bet on by the user
|
||||
`uniqueBettorIds:${user.id}`
|
||||
: '',
|
||||
...(additionalFilter?.followed ? personalFilters : []),
|
||||
]
|
||||
const facetFilters = query
|
||||
? additionalFilters
|
||||
|
@ -320,17 +338,7 @@ function ContractSearchControls(props: {
|
|||
state.pillFilter !== 'your-bets'
|
||||
? `groupLinks.slug:${state.pillFilter}`
|
||||
: '',
|
||||
state.pillFilter === 'personal'
|
||||
? // 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 === 'personal' ? personalFilters : []),
|
||||
state.pillFilter === 'your-bets' && user
|
||||
? // Show contracts bet on by the user
|
||||
`uniqueBettorIds:${user.id}`
|
||||
|
|
|
@ -3,52 +3,74 @@ import { contractPath } from 'web/lib/firebase/contracts'
|
|||
import { CPMMContract } from 'common/contract'
|
||||
import { formatPercent } from 'common/util/format'
|
||||
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 { Row } from '../layout/row'
|
||||
import { useState } from 'react'
|
||||
|
||||
export function ProbChangeTable(props: { userId: string | undefined }) {
|
||||
const { userId } = props
|
||||
|
||||
const changes = useProbChanges(userId ?? '')
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
if (!changes) {
|
||||
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 (
|
||||
<Row className="w-full flex-wrap divide-x-2 rounded bg-white shadow-md">
|
||||
<Col className="min-w-[300px] flex-1 divide-y">
|
||||
{positiveChanges.slice(0, count).map((contract) => (
|
||||
<Row className="hover:bg-gray-100">
|
||||
<ProbChange className="p-4 text-right" contract={contract} />
|
||||
<SiteLink
|
||||
className="p-4 font-semibold text-indigo-700"
|
||||
href={contractPath(contract)}
|
||||
>
|
||||
{contract.question}
|
||||
</SiteLink>
|
||||
</Row>
|
||||
))}
|
||||
<Col>
|
||||
<Col className="mb-4 w-full divide-x-2 divide-y rounded-lg bg-white shadow-md md:flex-row md:divide-y-0">
|
||||
<Col className="flex-1 divide-y">
|
||||
{filteredChanges.slice(0, 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 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 className="justify-content-stretch min-w-[300px] flex-1 divide-y">
|
||||
{negativeChanges.slice(0, count).map((contract) => (
|
||||
<Row className="hover:bg-gray-100">
|
||||
<ProbChange className="p-4 text-right" contract={contract} />
|
||||
<SiteLink
|
||||
className="p-4 font-semibold text-indigo-700"
|
||||
href={contractPath(contract)}
|
||||
>
|
||||
{contract.question}
|
||||
</SiteLink>
|
||||
</Row>
|
||||
))}
|
||||
</Col>
|
||||
</Row>
|
||||
<div
|
||||
className={clsx(linkClass, 'cursor-pointer self-end')}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
{expanded ? 'Show less' : 'Show more'}
|
||||
</div>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -63,9 +85,9 @@ export function ProbChange(props: {
|
|||
|
||||
const color =
|
||||
change > 0
|
||||
? 'text-green-600'
|
||||
? 'text-green-500'
|
||||
: change < 0
|
||||
? 'text-red-600'
|
||||
? 'text-red-500'
|
||||
: 'text-gray-600'
|
||||
|
||||
const str =
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Col } from 'web/components/layout/col'
|
|||
|
||||
export function DoubleCarousel(props: {
|
||||
contracts: Contract[]
|
||||
seeMoreUrl?: string
|
||||
showTime?: ShowTime
|
||||
loadMore?: () => void
|
||||
}) {
|
||||
|
@ -19,7 +18,7 @@ export function DoubleCarousel(props: {
|
|||
? range(0, Math.floor(contracts.length / 2)).map((col) => {
|
||||
const i = col * 2
|
||||
return (
|
||||
<Col key={contracts[i].id}>
|
||||
<Col className="snap-start scroll-m-4" key={contracts[i].id}>
|
||||
<ContractCard
|
||||
contract={contracts[i]}
|
||||
className="mb-2 w-96 shrink-0"
|
||||
|
|
|
@ -2,13 +2,13 @@ import { useEffect, useState } from 'react'
|
|||
import { Group } from 'common/group'
|
||||
import { User } from 'common/user'
|
||||
import {
|
||||
getMemberGroups,
|
||||
GroupMemberDoc,
|
||||
groupMembers,
|
||||
listenForGroup,
|
||||
listenForGroupContractDocs,
|
||||
listenForGroups,
|
||||
listenForMemberGroupIds,
|
||||
listenForMemberGroups,
|
||||
listenForOpenGroups,
|
||||
listGroups,
|
||||
} from 'web/lib/firebase/groups'
|
||||
|
@ -17,6 +17,7 @@ import { filterDefined } from 'common/util/array'
|
|||
import { Contract } from 'common/contract'
|
||||
import { uniq } from 'lodash'
|
||||
import { listenForValues } from 'web/lib/firebase/utils'
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
export const useGroup = (groupId: string | undefined) => {
|
||||
const [group, setGroup] = useState<Group | null | undefined>()
|
||||
|
@ -49,12 +50,10 @@ export const useOpenGroups = () => {
|
|||
}
|
||||
|
||||
export const useMemberGroups = (userId: string | null | undefined) => {
|
||||
const [memberGroups, setMemberGroups] = useState<Group[] | undefined>()
|
||||
useEffect(() => {
|
||||
if (userId)
|
||||
return listenForMemberGroups(userId, (groups) => setMemberGroups(groups))
|
||||
}, [userId])
|
||||
return memberGroups
|
||||
const result = useQuery(['member-groups', userId ?? ''], () =>
|
||||
getMemberGroups(userId ?? '')
|
||||
)
|
||||
return result.data
|
||||
}
|
||||
|
||||
// Note: We cache member group ids in localstorage to speed up the initial load
|
||||
|
|
|
@ -32,7 +32,7 @@ export const groupMembers = (groupId: string) =>
|
|||
export const groupContracts = (groupId: string) =>
|
||||
collection(groups, groupId, 'groupContracts')
|
||||
const openGroupsQuery = query(groups, where('anyoneCanJoin', '==', true))
|
||||
const memberGroupsQuery = (userId: string) =>
|
||||
export const memberGroupsQuery = (userId: string) =>
|
||||
query(collectionGroup(db, 'groupMembers'), where('userId', '==', userId))
|
||||
|
||||
export function groupPath(
|
||||
|
@ -113,6 +113,15 @@ export function listenForGroup(
|
|||
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(
|
||||
userId: string,
|
||||
setGroupIds: (groupIds: string[]) => void
|
||||
|
|
60
web/pages/experimental/home/edit.tsx
Normal file
60
web/pages/experimental/home/edit.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -1,40 +1,36 @@
|
|||
import React, { useState } from 'react'
|
||||
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 { Col } from 'web/components/layout/col'
|
||||
import { ContractSearch, SORTS } from 'web/components/contract-search'
|
||||
import { User } from 'common/user'
|
||||
import { getUserAndPrivateUser, updateUser } from 'web/lib/firebase/users'
|
||||
import { useTracking } from 'web/hooks/use-tracking'
|
||||
import { track } from 'web/lib/service/analytics'
|
||||
import { authenticateOnServer } from 'web/lib/firebase/server-auth'
|
||||
import { useSaveReferral } from 'web/hooks/use-save-referral'
|
||||
import { GetServerSideProps } from 'next'
|
||||
import { Sort } from 'web/components/contract-search'
|
||||
import { Group } from 'common/group'
|
||||
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||
import { GroupLinkItem } from '../../groups'
|
||||
import { SiteLink } from 'web/components/site-link'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { useMemberGroups } from 'web/hooks/use-group'
|
||||
import { DoubleCarousel } from '../../../components/double-carousel'
|
||||
import clsx from 'clsx'
|
||||
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 { Row } from 'web/components/layout/row'
|
||||
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 creds = await authenticateOnServer(ctx)
|
||||
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
|
||||
const Home = () => {
|
||||
const user = useUser()
|
||||
|
||||
useTracking('view home')
|
||||
|
||||
|
@ -42,76 +38,54 @@ const Home = (props: { auth: { user: User } | null }) => {
|
|||
|
||||
const groups = useMemberGroups(user?.id) ?? []
|
||||
|
||||
const [homeSections, setHomeSections] = useState(
|
||||
const [homeSections] = useState(
|
||||
user?.homeSections ?? { visible: [], hidden: [] }
|
||||
)
|
||||
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 (
|
||||
<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'}>
|
||||
<Title text={isEditing ? 'Edit your home page' : 'Home'} />
|
||||
<Title className="!mb-0" text="Home" />
|
||||
|
||||
<EditDoneButton isEditing={isEditing} setIsEditing={setIsEditing} />
|
||||
<EditButton />
|
||||
</Row>
|
||||
|
||||
{isEditing ? (
|
||||
<>
|
||||
<ArrangeHome
|
||||
user={user}
|
||||
homeSections={homeSections}
|
||||
setHomeSections={updateHomeSections}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-xl text-gray-800">Daily movers</div>
|
||||
<ProbChangeTable userId={user?.id} />
|
||||
<DailyProfitAndBalance userId={user?.id} />
|
||||
|
||||
{visibleItems.map((item) => {
|
||||
const { id } = item
|
||||
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}
|
||||
/>
|
||||
)
|
||||
<div className="text-xl text-gray-800">Daily movers</div>
|
||||
<ProbChangeTable userId={user?.id} />
|
||||
|
||||
const group = groups.find((g) => g.id === id)
|
||||
if (group)
|
||||
return <GroupSection key={id} group={group} user={user} />
|
||||
{visibleItems.map((item) => {
|
||||
const { id } = item
|
||||
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>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -129,7 +103,7 @@ const Home = (props: { auth: { user: User } | null }) => {
|
|||
|
||||
function SearchSection(props: {
|
||||
label: string
|
||||
user: User | null
|
||||
user: User | null | undefined
|
||||
sort: Sort
|
||||
yourBets?: boolean
|
||||
}) {
|
||||
|
@ -139,88 +113,91 @@ function SearchSection(props: {
|
|||
return (
|
||||
<Col>
|
||||
<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>
|
||||
<ContractSearch
|
||||
user={user}
|
||||
defaultSort={sort}
|
||||
additionalFilter={yourBets ? { yourBets: true } : undefined}
|
||||
additionalFilter={yourBets ? { yourBets: true } : { followed: true }}
|
||||
noControls
|
||||
// persistPrefix={`experimental-home-${sort}`}
|
||||
renderContracts={(contracts, loadMore) =>
|
||||
contracts ? (
|
||||
<DoubleCarousel
|
||||
contracts={contracts}
|
||||
seeMoreUrl={href}
|
||||
showTime={
|
||||
sort === 'close-date' || sort === 'resolve-date'
|
||||
? sort
|
||||
: undefined
|
||||
}
|
||||
loadMore={loadMore}
|
||||
/>
|
||||
) : (
|
||||
<LoadingIndicator />
|
||||
)
|
||||
}
|
||||
maxResults={6}
|
||||
persistPrefix={`experimental-home-${sort}`}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function GroupSection(props: { group: Group; user: User | null }) {
|
||||
function GroupSection(props: { group: Group; user: User | null | undefined }) {
|
||||
const { group, user } = props
|
||||
|
||||
return (
|
||||
<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
|
||||
user={user}
|
||||
defaultSort={'score'}
|
||||
additionalFilter={{ groupSlug: group.slug }}
|
||||
noControls
|
||||
// persistPrefix={`experimental-home-${group.slug}`}
|
||||
renderContracts={(contracts, loadMore) =>
|
||||
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 />
|
||||
)
|
||||
}
|
||||
maxResults={6}
|
||||
persistPrefix={`experimental-home-${group.slug}`}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function EditDoneButton(props: {
|
||||
isEditing: boolean
|
||||
setIsEditing: (isEditing: boolean) => void
|
||||
className?: string
|
||||
}) {
|
||||
const { isEditing, setIsEditing, className } = props
|
||||
function EditButton(props: { className?: string }) {
|
||||
const { className } = props
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="lg"
|
||||
color={isEditing ? 'blue' : 'gray-white'}
|
||||
className={clsx(className, 'flex')}
|
||||
onClick={() => {
|
||||
setIsEditing(!isEditing)
|
||||
}}
|
||||
>
|
||||
{!isEditing && (
|
||||
<PencilIcon className={clsx('mr-2 h-[24px] w-5')} aria-hidden="true" />
|
||||
)}
|
||||
{isEditing ? 'Done' : 'Edit'}
|
||||
</Button>
|
||||
<SiteLink href="/experimental/home/edit">
|
||||
<Button size="lg" color="gray-white" className={clsx(className, 'flex')}>
|
||||
<PencilIcon className={clsx('mr-2 h-[24px] w-5')} aria-hidden="true" />{' '}
|
||||
Edit
|
||||
</Button>
|
||||
</SiteLink>
|
||||
)
|
||||
}
|
||||
|
||||
function DailyProfitAndBalance(props: {
|
||||
userId: string | null | undefined
|
||||
className?: string
|
||||
}) {
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -237,7 +237,7 @@ const MarketCarousel = (props: { slug: string }) => {
|
|||
key={m.id}
|
||||
contract={m}
|
||||
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"
|
||||
trackingPostfix=" tournament"
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue
Block a user