Rearrange home sections. Load more in carousel.
This commit is contained in:
parent
7dddff52b8
commit
849402ed70
|
@ -34,6 +34,7 @@ export type User = {
|
|||
followerCountCached: number
|
||||
|
||||
followedCategories?: string[]
|
||||
homeSections?: { visible: string[]; hidden: string[] }
|
||||
|
||||
referredByUserId?: string
|
||||
referredByContractId?: string
|
||||
|
|
|
@ -3,9 +3,14 @@ import clsx from 'clsx'
|
|||
import { throttle } from 'lodash'
|
||||
import { ReactNode, useRef, useState, useEffect } from 'react'
|
||||
import { Row } from './layout/row'
|
||||
import { VisibilityObserver } from 'web/components/visibility-observer'
|
||||
|
||||
export function Carousel(props: { children: ReactNode; className?: string }) {
|
||||
const { children, className } = props
|
||||
export function Carousel(props: {
|
||||
children: ReactNode
|
||||
loadMore?: () => void
|
||||
className?: string
|
||||
}) {
|
||||
const { children, loadMore, className } = props
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
|
@ -38,6 +43,13 @@ export function Carousel(props: { children: ReactNode; className?: string }) {
|
|||
onScroll={onScroll}
|
||||
>
|
||||
{children}
|
||||
|
||||
{loadMore && (
|
||||
<VisibilityObserver
|
||||
className="relative -left-96"
|
||||
onVisibilityUpdated={(visible) => visible && loadMore()}
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
{!atFront && (
|
||||
<div
|
||||
|
|
|
@ -38,7 +38,7 @@ const searchClient = algoliasearch(
|
|||
const indexPrefix = ENV === 'DEV' ? 'dev-' : ''
|
||||
const searchIndexName = ENV === 'DEV' ? 'dev-contracts' : 'contractsIndex'
|
||||
|
||||
const SORTS = [
|
||||
export const SORTS = [
|
||||
{ label: 'Newest', value: 'newest' },
|
||||
{ label: 'Trending', value: 'score' },
|
||||
{ label: 'Most traded', value: 'most-traded' },
|
||||
|
@ -83,9 +83,11 @@ export function ContractSearch(props: {
|
|||
persistPrefix?: string
|
||||
useQueryUrlParam?: boolean
|
||||
isWholePage?: boolean
|
||||
maxItems?: number
|
||||
noControls?: boolean
|
||||
renderContracts?: (contracts: Contract[] | undefined) => ReactNode
|
||||
renderContracts?: (
|
||||
contracts: Contract[] | undefined,
|
||||
loadMore: () => void
|
||||
) => ReactNode
|
||||
}) {
|
||||
const {
|
||||
user,
|
||||
|
@ -100,7 +102,6 @@ export function ContractSearch(props: {
|
|||
persistPrefix,
|
||||
useQueryUrlParam,
|
||||
isWholePage,
|
||||
maxItems,
|
||||
noControls,
|
||||
renderContracts,
|
||||
} = props
|
||||
|
@ -184,8 +185,7 @@ export function ContractSearch(props: {
|
|||
const contracts = state.pages
|
||||
.flat()
|
||||
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
|
||||
const renderedContracts =
|
||||
state.pages.length === 0 ? undefined : contracts.slice(0, maxItems)
|
||||
const renderedContracts = state.pages.length === 0 ? undefined : contracts
|
||||
|
||||
if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) {
|
||||
return <ContractSearchFirestore additionalFilter={additionalFilter} />
|
||||
|
@ -206,7 +206,7 @@ export function ContractSearch(props: {
|
|||
noControls={noControls}
|
||||
/>
|
||||
{renderContracts ? (
|
||||
renderContracts(renderedContracts)
|
||||
renderContracts(renderedContracts, performQuery)
|
||||
) : (
|
||||
<ContractsGrid
|
||||
contracts={renderedContracts}
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
"next": "12.2.5",
|
||||
"node-fetch": "3.2.4",
|
||||
"react": "17.0.2",
|
||||
"react-beautiful-dnd": "13.1.1",
|
||||
"react-confetti": "6.0.1",
|
||||
"react-dom": "17.0.2",
|
||||
"react-expanding-textarea": "2.3.5",
|
||||
|
@ -66,6 +67,7 @@
|
|||
"@types/lodash": "4.14.178",
|
||||
"@types/node": "16.11.11",
|
||||
"@types/react": "17.0.43",
|
||||
"@types/react-beautiful-dnd": "13.1.2",
|
||||
"@types/react-dom": "17.0.2",
|
||||
"@types/string-similarity": "^4.0.0",
|
||||
"autoprefixer": "10.2.6",
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
import { PlusSmIcon } from '@heroicons/react/solid'
|
||||
|
||||
import { Page } from 'web/components/page'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { ContractSearch } from 'web/components/contract-search'
|
||||
import { User } from 'common/user'
|
||||
import { getUserAndPrivateUser } 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 { Button } from 'web/components/button'
|
||||
import { useMemberGroups } from 'web/hooks/use-group'
|
||||
import { Group } from 'common/group'
|
||||
import { Carousel } from 'web/components/carousel'
|
||||
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||
import { ContractCard } from 'web/components/contract/contract-card'
|
||||
import { range } from 'lodash'
|
||||
import { Contract } from 'common/contract'
|
||||
import { ShowTime } from 'web/components/contract/contract-details'
|
||||
import { GroupLinkItem } from '../groups'
|
||||
import { SiteLink } from 'web/components/site-link'
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
const creds = await authenticateOnServer(ctx)
|
||||
const auth = creds ? await getUserAndPrivateUser(creds.user.uid) : null
|
||||
return { props: { auth } }
|
||||
}
|
||||
|
||||
const Home = (props: { auth: { user: User } | null }) => {
|
||||
const user = props.auth ? props.auth.user : null
|
||||
|
||||
useTracking('view home')
|
||||
|
||||
useSaveReferral()
|
||||
|
||||
const memberGroups = (useMemberGroups(user?.id) ?? []).filter(
|
||||
(group) => group.contractIds.length > 0
|
||||
)
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Col className="mx-4 mt-4 gap-4 sm:mx-10 xl:w-[125%]">
|
||||
<SearchSection label="Trending" sort="score" user={user} />
|
||||
<SearchSection label="Newest" sort="newest" user={user} />
|
||||
<SearchSection label="Closing soon" sort="close-date" user={user} />
|
||||
{memberGroups.map((group) => (
|
||||
<GroupSection key={group.id} group={group} user={user} />
|
||||
))}
|
||||
</Col>
|
||||
<button
|
||||
type="button"
|
||||
className="fixed bottom-[70px] right-3 inline-flex items-center rounded-full border border-transparent bg-indigo-600 p-3 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 lg:hidden"
|
||||
onClick={() => {
|
||||
Router.push('/create')
|
||||
track('mobile create button')
|
||||
}}
|
||||
>
|
||||
<PlusSmIcon className="h-8 w-8" aria-hidden="true" />
|
||||
</button>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchSection(props: {
|
||||
label: string
|
||||
user: User | null
|
||||
sort: Sort
|
||||
}) {
|
||||
const { label, user, sort } = props
|
||||
const href = `/home?s=${sort}`
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<SiteLink className="mb-2 text-xl" href={href}>
|
||||
{label}
|
||||
</SiteLink>
|
||||
<ContractSearch
|
||||
user={user}
|
||||
defaultSort={sort}
|
||||
maxItems={12}
|
||||
noControls
|
||||
renderContracts={(contracts) =>
|
||||
contracts ? (
|
||||
<DoubleCarousel
|
||||
contracts={contracts}
|
||||
seeMoreUrl={href}
|
||||
showTime={
|
||||
sort === 'close-date' || sort === 'resolve-date'
|
||||
? sort
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<LoadingIndicator />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function GroupSection(props: { group: Group; user: User | null }) {
|
||||
const { group, user } = props
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<GroupLinkItem className="mb-2 text-xl" group={group} />
|
||||
<ContractSearch
|
||||
user={user}
|
||||
defaultSort={'score'}
|
||||
additionalFilter={{ groupSlug: group.slug }}
|
||||
maxItems={12}
|
||||
noControls
|
||||
renderContracts={(contracts) =>
|
||||
contracts ? (
|
||||
<DoubleCarousel
|
||||
contracts={contracts}
|
||||
seeMoreUrl={`/group/${group.slug}`}
|
||||
/>
|
||||
) : (
|
||||
<LoadingIndicator />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function DoubleCarousel(props: {
|
||||
contracts: Contract[]
|
||||
seeMoreUrl?: string
|
||||
showTime?: ShowTime
|
||||
}) {
|
||||
const { contracts, seeMoreUrl, showTime } = props
|
||||
return (
|
||||
<Carousel className="-mx-4 mt-2 sm:-mx-10">
|
||||
<div className="shrink-0 sm:w-6" />
|
||||
{contracts.length >= 6
|
||||
? range(0, Math.floor(contracts.length / 2)).map((col) => {
|
||||
const i = col * 2
|
||||
return (
|
||||
<Col>
|
||||
<ContractCard
|
||||
key={contracts[i].id}
|
||||
contract={contracts[i]}
|
||||
className="mb-2 max-h-[200px] w-96 shrink-0"
|
||||
questionClass="line-clamp-3"
|
||||
trackingPostfix=" tournament"
|
||||
showTime={showTime}
|
||||
/>
|
||||
<ContractCard
|
||||
key={contracts[i + 1].id}
|
||||
contract={contracts[i + 1]}
|
||||
className="mb-2 max-h-[200px] w-96 shrink-0"
|
||||
questionClass="line-clamp-3"
|
||||
trackingPostfix=" tournament"
|
||||
showTime={showTime}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
})
|
||||
: contracts.map((c) => (
|
||||
<ContractCard
|
||||
key={c.id}
|
||||
contract={c}
|
||||
className="mb-2 max-h-[200px] w-96 shrink-0"
|
||||
questionClass="line-clamp-3"
|
||||
trackingPostfix=" tournament"
|
||||
showTime={showTime}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
className="self-center whitespace-nowrap"
|
||||
color="blue"
|
||||
size="sm"
|
||||
onClick={() => seeMoreUrl && Router.push(seeMoreUrl)}
|
||||
>
|
||||
See more
|
||||
</Button>
|
||||
</Carousel>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
127
web/pages/experimental/home/_arrange-home.tsx
Normal file
127
web/pages/experimental/home/_arrange-home.tsx
Normal file
|
@ -0,0 +1,127 @@
|
|||
import clsx from 'clsx'
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'
|
||||
import { MenuIcon } from '@heroicons/react/solid'
|
||||
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { Subtitle } from 'web/components/subtitle'
|
||||
import { useMemberGroups } from 'web/hooks/use-group'
|
||||
import { filterDefined } from 'common/util/array'
|
||||
import { keyBy } from 'lodash'
|
||||
import { User } from 'common/user'
|
||||
|
||||
export function ArrangeHome(props: {
|
||||
user: User | null
|
||||
homeSections: { visible: string[]; hidden: string[] }
|
||||
setHomeSections: (homeSections: {
|
||||
visible: string[]
|
||||
hidden: string[]
|
||||
}) => void
|
||||
}) {
|
||||
const {
|
||||
user,
|
||||
homeSections: { visible, hidden },
|
||||
setHomeSections,
|
||||
} = props
|
||||
|
||||
const memberGroups = useMemberGroups(user?.id) ?? []
|
||||
|
||||
const items = [
|
||||
{ label: 'Trending', id: 'score' },
|
||||
{ label: 'Newest', id: 'newest' },
|
||||
{ label: 'Close date', id: 'close-date' },
|
||||
...memberGroups.map((g) => ({
|
||||
label: g.name,
|
||||
id: g.id,
|
||||
})),
|
||||
]
|
||||
const itemsById = keyBy(items, 'id')
|
||||
|
||||
const [visibleItems, hiddenItems] = [
|
||||
filterDefined(visible.map((id) => itemsById[id])),
|
||||
filterDefined(hidden.map((id) => itemsById[id])),
|
||||
]
|
||||
|
||||
// Add unmentioned items to the visible list.
|
||||
visibleItems.push(
|
||||
...items.filter(
|
||||
(item) => !visibleItems.includes(item) && !hiddenItems.includes(item)
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
<DragDropContext
|
||||
onDragEnd={(e) => {
|
||||
console.log('drag end', e)
|
||||
const { destination, source, draggableId } = e
|
||||
if (!destination) return
|
||||
|
||||
const item = itemsById[draggableId]
|
||||
|
||||
const newHomeSections = {
|
||||
visible: visibleItems.map((item) => item.id),
|
||||
hidden: hiddenItems.map((item) => item.id),
|
||||
}
|
||||
|
||||
const sourceSection = source.droppableId as 'visible' | 'hidden'
|
||||
newHomeSections[sourceSection].splice(source.index, 1)
|
||||
|
||||
const destSection = destination.droppableId as 'visible' | 'hidden'
|
||||
newHomeSections[destSection].splice(destination.index, 0, item.id)
|
||||
|
||||
setHomeSections(newHomeSections)
|
||||
}}
|
||||
>
|
||||
<Row className="relative max-w-lg gap-4">
|
||||
<DraggableList items={visibleItems} title="Visible" />
|
||||
<DraggableList items={hiddenItems} title="Hidden" />
|
||||
</Row>
|
||||
</DragDropContext>
|
||||
)
|
||||
}
|
||||
|
||||
function DraggableList(props: {
|
||||
title: string
|
||||
items: { id: string; label: string }[]
|
||||
}) {
|
||||
const { title, items } = props
|
||||
return (
|
||||
<Droppable droppableId={title.toLowerCase()}>
|
||||
{(provided, snapshot) => (
|
||||
<Col
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
className={clsx(
|
||||
'width-[220px] flex-1 items-start rounded bg-gray-50 p-2',
|
||||
snapshot.isDraggingOver && 'bg-gray-100'
|
||||
)}
|
||||
>
|
||||
<Subtitle text={title} className="mx-2 !my-2" />
|
||||
{items.map((item, index) => (
|
||||
<Draggable key={item.id} draggableId={item.id} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={provided.draggableProps.style}
|
||||
className={clsx(
|
||||
'flex flex-row items-center gap-4 rounded bg-gray-50 p-2',
|
||||
snapshot.isDragging && 'z-[9000] bg-gray-300'
|
||||
)}
|
||||
>
|
||||
<MenuIcon
|
||||
className="h-5 w-5 flex-shrink-0 text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>{' '}
|
||||
{item.label}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Col>
|
||||
)}
|
||||
</Droppable>
|
||||
)
|
||||
}
|
52
web/pages/experimental/home/_double-carousel.tsx
Normal file
52
web/pages/experimental/home/_double-carousel.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Contract } from 'common/contract'
|
||||
import { range } from 'lodash'
|
||||
import { Carousel } from 'web/components/carousel'
|
||||
import { ContractCard } from 'web/components/contract/contract-card'
|
||||
import { ShowTime } from 'web/components/contract/contract-details'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
|
||||
export function DoubleCarousel(props: {
|
||||
contracts: Contract[]
|
||||
seeMoreUrl?: string
|
||||
showTime?: ShowTime
|
||||
loadMore?: () => void
|
||||
}) {
|
||||
const { contracts, showTime, loadMore } = props
|
||||
return (
|
||||
<Carousel className="-mx-4 mt-2 sm:-mx-10" loadMore={loadMore}>
|
||||
<div className="shrink-0 sm:w-6" />
|
||||
{contracts.length >= 6
|
||||
? range(0, Math.floor(contracts.length / 2)).map((col) => {
|
||||
const i = col * 2
|
||||
return (
|
||||
<Col key={contracts[i].id}>
|
||||
<ContractCard
|
||||
contract={contracts[i]}
|
||||
className="mb-2 w-96 shrink-0"
|
||||
questionClass="line-clamp-3"
|
||||
trackingPostfix=" tournament"
|
||||
showTime={showTime}
|
||||
/>
|
||||
<ContractCard
|
||||
contract={contracts[i + 1]}
|
||||
className="mb-2 w-96 shrink-0"
|
||||
questionClass="line-clamp-3"
|
||||
trackingPostfix=" tournament"
|
||||
showTime={showTime}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
})
|
||||
: contracts.map((c) => (
|
||||
<ContractCard
|
||||
key={c.id}
|
||||
contract={c}
|
||||
className="mb-2 max-h-[220px] w-96 shrink-0"
|
||||
questionClass="line-clamp-3"
|
||||
trackingPostfix=" tournament"
|
||||
showTime={showTime}
|
||||
/>
|
||||
))}
|
||||
</Carousel>
|
||||
)
|
||||
}
|
204
web/pages/experimental/home/index.tsx
Normal file
204
web/pages/experimental/home/index.tsx
Normal file
|
@ -0,0 +1,204 @@
|
|||
import React, { useState } from 'react'
|
||||
import Router from 'next/router'
|
||||
import { PencilIcon, PlusSmIcon } from '@heroicons/react/solid'
|
||||
|
||||
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 './_double-carousel'
|
||||
import clsx from 'clsx'
|
||||
import { Button } from 'web/components/button'
|
||||
import { ArrangeHome } from './_arrange-home'
|
||||
import { Title } from 'web/components/title'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
const creds = await authenticateOnServer(ctx)
|
||||
const auth = creds ? await getUserAndPrivateUser(creds.user.uid) : null
|
||||
return { props: { auth } }
|
||||
}
|
||||
|
||||
const Home = (props: { auth: { user: User } | null }) => {
|
||||
const user = useUser() ?? props.auth?.user ?? null
|
||||
|
||||
useTracking('view home')
|
||||
|
||||
useSaveReferral()
|
||||
|
||||
const memberGroups = useMemberGroups(user?.id) ?? []
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Col className="pm:mx-10 gap-4 px-4 pb-12 xl:w-[125%]">
|
||||
<Row className={'w-full items-center justify-between'}>
|
||||
<Title text={isEditing ? 'Edit your home page' : 'Home'} />
|
||||
|
||||
<EditDoneButton isEditing={isEditing} setIsEditing={setIsEditing} />
|
||||
</Row>
|
||||
|
||||
{isEditing ? (
|
||||
<>
|
||||
<ArrangeHome
|
||||
user={user}
|
||||
homeSections={homeSections}
|
||||
setHomeSections={updateHomeSections}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
homeSections.visible.map((id) => {
|
||||
const sort = SORTS.find((sort) => sort.value === id)
|
||||
if (sort)
|
||||
return (
|
||||
<SearchSection
|
||||
label={sort.label}
|
||||
sort={sort.value}
|
||||
user={user}
|
||||
/>
|
||||
)
|
||||
|
||||
const group = memberGroups.find((g) => g.id === id)
|
||||
if (group) return <GroupSection group={group} user={user} />
|
||||
|
||||
return null
|
||||
})
|
||||
)}
|
||||
</Col>
|
||||
<button
|
||||
type="button"
|
||||
className="fixed bottom-[70px] right-3 z-20 inline-flex items-center rounded-full border border-transparent bg-indigo-600 p-3 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 lg:hidden"
|
||||
onClick={() => {
|
||||
Router.push('/create')
|
||||
track('mobile create button')
|
||||
}}
|
||||
>
|
||||
<PlusSmIcon className="h-8 w-8" aria-hidden="true" />
|
||||
</button>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchSection(props: {
|
||||
label: string
|
||||
user: User | null
|
||||
sort: Sort
|
||||
}) {
|
||||
const { label, user, sort } = props
|
||||
const href = `/home?s=${sort}`
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<SiteLink className="mb-2 text-xl" href={href}>
|
||||
{label}
|
||||
</SiteLink>
|
||||
<ContractSearch
|
||||
user={user}
|
||||
defaultSort={sort}
|
||||
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 />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function GroupSection(props: { group: Group; user: User | null }) {
|
||||
const { group, user } = props
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<GroupLinkItem className="mb-2 text-xl" group={group} />
|
||||
<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 />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function EditDoneButton(props: {
|
||||
isEditing: boolean
|
||||
setIsEditing: (isEditing: boolean) => void
|
||||
className?: string
|
||||
}) {
|
||||
const { isEditing, setIsEditing, 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>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
95
yarn.lock
95
yarn.lock
|
@ -1310,6 +1310,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.15.4", "@babel/runtime@^7.9.2":
|
||||
version "7.18.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
|
||||
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.12.7", "@babel/template@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
|
||||
|
@ -3316,6 +3323,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/hogan.js/-/hogan.js-3.0.1.tgz#64c54407b30da359763e14877f5702b8ae85d61c"
|
||||
integrity sha512-D03i/2OY7kGyMq9wdQ7oD8roE49z/ZCZThe/nbahtvuqCNZY9T2MfedOWyeBdbEpY2W8Gnh/dyJLdFtUCOkYbg==
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.0":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/html-minifier-terser@^6.0.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
|
||||
|
@ -3428,6 +3443,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/react-beautiful-dnd@13.1.2":
|
||||
version "13.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz#510405abb09f493afdfd898bf83995dc6385c130"
|
||||
integrity sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-dom@17.0.2":
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.2.tgz#35654cf6c49ae162d5bc90843d5437dc38008d43"
|
||||
|
@ -3435,6 +3457,16 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-redux@^7.1.20":
|
||||
version "7.1.24"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.24.tgz#6caaff1603aba17b27d20f8ad073e4c077e975c0"
|
||||
integrity sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.0"
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-router-config@*":
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451"
|
||||
|
@ -4992,6 +5024,13 @@ crypto-random-string@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
||||
|
||||
css-box-model@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
||||
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
||||
dependencies:
|
||||
tiny-invariant "^1.0.6"
|
||||
|
||||
css-declaration-sorter@^6.2.2:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz#bfd2f6f50002d6a3ae779a87d3a0c5d5b10e0f02"
|
||||
|
@ -7071,7 +7110,7 @@ hogan.js@^3.0.2:
|
|||
mkdirp "0.3.0"
|
||||
nopt "1.0.10"
|
||||
|
||||
hoist-non-react-statics@^3.1.0:
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
|
@ -8377,6 +8416,11 @@ memfs@^3.1.2, memfs@^3.4.3:
|
|||
dependencies:
|
||||
fs-monkey "1.0.3"
|
||||
|
||||
memoize-one@^5.1.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||
|
||||
merge-descriptors@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
|
@ -9851,6 +9895,11 @@ quick-lru@^5.1.1:
|
|||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||
|
||||
raf-schd@^4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
||||
|
||||
raf@^3.1.0:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
||||
|
@ -9905,6 +9954,19 @@ react-base16-styling@^0.6.0:
|
|||
lodash.flow "^3.3.0"
|
||||
pure-color "^1.2.0"
|
||||
|
||||
react-beautiful-dnd@13.1.1:
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
|
||||
integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
css-box-model "^1.2.0"
|
||||
memoize-one "^5.1.1"
|
||||
raf-schd "^4.0.2"
|
||||
react-redux "^7.2.0"
|
||||
redux "^4.0.4"
|
||||
use-memo-one "^1.1.1"
|
||||
|
||||
react-confetti@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.0.1.tgz#d4f57b5a021dd908a6243b8f63b6009b00818d10"
|
||||
|
@ -10012,6 +10074,11 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-json-view@^1.21.3:
|
||||
version "1.21.3"
|
||||
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475"
|
||||
|
@ -10057,6 +10124,18 @@ react-query@3.39.0:
|
|||
broadcast-channel "^3.4.1"
|
||||
match-sorter "^6.0.2"
|
||||
|
||||
react-redux@^7.2.0:
|
||||
version "7.2.8"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
|
||||
integrity sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
"@types/react-redux" "^7.1.20"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^17.0.2"
|
||||
|
||||
react-router-config@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988"
|
||||
|
@ -10208,6 +10287,13 @@ recursive-readdir@^2.2.2:
|
|||
dependencies:
|
||||
minimatch "3.0.4"
|
||||
|
||||
redux@^4.0.0, redux@^4.0.4:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
|
||||
integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
|
||||
regenerate-unicode-properties@^10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
|
||||
|
@ -11306,7 +11392,7 @@ thunky@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
|
||||
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
|
||||
|
||||
tiny-invariant@^1.0.2:
|
||||
tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
|
||||
integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
|
||||
|
@ -11740,6 +11826,11 @@ use-latest@^1.2.1:
|
|||
dependencies:
|
||||
use-isomorphic-layout-effect "^1.1.1"
|
||||
|
||||
use-memo-one@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
|
||||
integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
|
||||
|
||||
use-sync-external-store@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||
|
|
Loading…
Reference in New Issue
Block a user