Swap home and explore (#244)
* Add activity page. Copy explore page into home * Update navbar with activity. Show explore instead if signed out. * Move category selector into contract search * Make algolia filter by category * Default tag page to all filter
This commit is contained in:
parent
1bf2073e61
commit
7da46050e5
|
@ -3,6 +3,7 @@ import {
|
|||
InstantSearch,
|
||||
SearchBox,
|
||||
SortBy,
|
||||
useCurrentRefinements,
|
||||
useInfiniteHits,
|
||||
useRange,
|
||||
useRefinementList,
|
||||
|
@ -21,6 +22,8 @@ import { useEffect, useRef, useState } from 'react'
|
|||
import { Spacer } from './layout/spacer'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ENV } from 'common/envs/constants'
|
||||
import { CategorySelector } from './feed/category-selector'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
|
||||
const searchClient = algoliasearch(
|
||||
'GJQPAYENIF',
|
||||
|
@ -44,15 +47,18 @@ export function ContractSearch(props: {
|
|||
querySortOptions?: {
|
||||
defaultSort: Sort
|
||||
defaultFilter?: filter
|
||||
filter?: {
|
||||
creatorId?: string
|
||||
tag?: string
|
||||
}
|
||||
shouldLoadFromStorage?: boolean
|
||||
}
|
||||
additionalFilter?: {
|
||||
creatorId?: string
|
||||
tag?: string
|
||||
category?: string
|
||||
}
|
||||
showCategorySelector: boolean
|
||||
}) {
|
||||
const { querySortOptions } = props
|
||||
const { querySortOptions, additionalFilter, showCategorySelector } = props
|
||||
|
||||
const user = useUser()
|
||||
const { initialSort } = useInitialQueryAndSort(querySortOptions)
|
||||
|
||||
const sort = sortIndexes
|
||||
|
@ -65,15 +71,15 @@ export function ContractSearch(props: {
|
|||
querySortOptions?.defaultFilter ?? 'open'
|
||||
)
|
||||
|
||||
const [category, setCategory] = useState<string>('all')
|
||||
|
||||
if (!sort) return <></>
|
||||
return (
|
||||
<InstantSearch
|
||||
searchClient={searchClient}
|
||||
indexName={`${indexPrefix}contracts-${sort}`}
|
||||
key={`search-${
|
||||
querySortOptions?.filter?.tag ??
|
||||
querySortOptions?.filter?.creatorId ??
|
||||
''
|
||||
additionalFilter?.tag ?? additionalFilter?.creatorId ?? ''
|
||||
}`}
|
||||
>
|
||||
<Row className="flex-wrap gap-2">
|
||||
|
@ -105,10 +111,25 @@ export function ContractSearch(props: {
|
|||
/>
|
||||
</Row>
|
||||
</Row>
|
||||
<div>
|
||||
{showCategorySelector && (
|
||||
<>
|
||||
<Spacer h={4} />
|
||||
<CategorySelector
|
||||
user={user}
|
||||
category={category}
|
||||
setCategory={setCategory}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Spacer h={4} />
|
||||
|
||||
<ContractSearchInner
|
||||
querySortOptions={querySortOptions}
|
||||
filter={filter}
|
||||
additionalFilter={{ category, ...additionalFilter }}
|
||||
/>
|
||||
</div>
|
||||
</InstantSearch>
|
||||
)
|
||||
}
|
||||
|
@ -116,15 +137,16 @@ export function ContractSearch(props: {
|
|||
export function ContractSearchInner(props: {
|
||||
querySortOptions?: {
|
||||
defaultSort: Sort
|
||||
filter?: {
|
||||
creatorId?: string
|
||||
tag?: string
|
||||
}
|
||||
shouldLoadFromStorage?: boolean
|
||||
}
|
||||
filter: filter
|
||||
additionalFilter: {
|
||||
creatorId?: string
|
||||
tag?: string
|
||||
category?: string
|
||||
}
|
||||
}) {
|
||||
const { querySortOptions, filter } = props
|
||||
const { querySortOptions, filter, additionalFilter } = props
|
||||
const { initialQuery } = useInitialQueryAndSort(querySortOptions)
|
||||
|
||||
const { query, setQuery, setSort } = useUpdateQueryAndSort({
|
||||
|
@ -156,11 +178,11 @@ export function ContractSearchInner(props: {
|
|||
}
|
||||
}, [index])
|
||||
|
||||
const creatorId = querySortOptions?.filter?.creatorId
|
||||
const { creatorId, category, tag } = additionalFilter
|
||||
|
||||
useFilterCreator(creatorId)
|
||||
|
||||
const tag = querySortOptions?.filter?.tag
|
||||
useFilterTag(tag)
|
||||
useFilterTag(tag ?? (category === 'all' ? undefined : category))
|
||||
|
||||
useFilterClosed(
|
||||
filter === 'closed'
|
||||
|
@ -173,25 +195,21 @@ export function ContractSearchInner(props: {
|
|||
filter === 'resolved' ? true : filter === 'all' ? undefined : false
|
||||
)
|
||||
|
||||
const { showMore, hits, isLastPage } = useInfiniteHits()
|
||||
const { showMore, hits, isLastPage, results } = useInfiniteHits()
|
||||
const contracts = hits as any as Contract[]
|
||||
|
||||
const router = useRouter()
|
||||
const hasLoaded = contracts.length > 0 || router.isReady
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Spacer h={8} />
|
||||
if (!hasLoaded || !results) return <></>
|
||||
|
||||
{hasLoaded && (
|
||||
return (
|
||||
<ContractsGrid
|
||||
contracts={contracts}
|
||||
loadMore={showMore}
|
||||
hasMore={!isLastPage}
|
||||
showCloseTime={index === 'contracts-closing-soon'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -203,10 +221,15 @@ const useFilterCreator = (creatorId: string | undefined) => {
|
|||
}
|
||||
|
||||
const useFilterTag = (tag: string | undefined) => {
|
||||
const { items, refine: deleteRefinement } = useCurrentRefinements({
|
||||
includedAttributes: ['lowercaseTags'],
|
||||
})
|
||||
const { refine } = useRefinementList({ attribute: 'lowercaseTags' })
|
||||
useEffect(() => {
|
||||
const refinements = items[0]?.refinements ?? []
|
||||
if (tag) refine(tag.toLowerCase())
|
||||
}, [tag, refine])
|
||||
if (refinements[0]) deleteRefinement(refinements[0])
|
||||
}, [tag])
|
||||
}
|
||||
|
||||
const useFilterClosed = (value: boolean | undefined) => {
|
||||
|
|
|
@ -53,13 +53,14 @@ export function CreatorContractsList(props: { creator: User }) {
|
|||
return (
|
||||
<ContractSearch
|
||||
querySortOptions={{
|
||||
filter: {
|
||||
creatorId: creator.id,
|
||||
},
|
||||
defaultSort: 'newest',
|
||||
defaultFilter: 'all',
|
||||
shouldLoadFromStorage: false,
|
||||
}}
|
||||
additionalFilter={{
|
||||
creatorId: creator.id,
|
||||
}}
|
||||
showCategorySelector={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ export function CategorySelector(props: {
|
|||
return (
|
||||
<Row
|
||||
className={clsx(
|
||||
'carousel mr-2 items-center space-x-2 space-y-2 overflow-x-scroll pt-4 pb-4 sm:flex-wrap',
|
||||
'carousel mr-2 items-center space-x-2 space-y-2 overflow-x-scroll pb-4 sm:flex-wrap',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
MenuAlt3Icon,
|
||||
PresentationChartLineIcon,
|
||||
SearchIcon,
|
||||
ChatAltIcon,
|
||||
XIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import { Transition, Dialog } from '@headlessui/react'
|
||||
|
@ -29,12 +30,21 @@ export function BottomNavBar() {
|
|||
</a>
|
||||
</Link>
|
||||
|
||||
{user === null ? (
|
||||
<Link href="/markets">
|
||||
<a className="block w-full py-1 px-3 text-center hover:bg-indigo-200 hover:text-indigo-700">
|
||||
<SearchIcon className="my-1 mx-auto h-6 w-6" aria-hidden="true" />
|
||||
Explore
|
||||
</a>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href="/activity">
|
||||
<a className="block w-full py-1 px-3 text-center hover:bg-indigo-200 hover:text-indigo-700">
|
||||
<ChatAltIcon className="my-1 mx-auto h-6 w-6" aria-hidden="true" />
|
||||
Activity
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{user !== null && (
|
||||
<Link href="/portfolio">
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
CashIcon,
|
||||
HeartIcon,
|
||||
PresentationChartLineIcon,
|
||||
ChatAltIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import clsx from 'clsx'
|
||||
import _ from 'lodash'
|
||||
|
@ -29,7 +30,7 @@ function IconFromUrl(url: string): React.ComponentType<{ className?: string }> {
|
|||
|
||||
const navigation = [
|
||||
{ name: 'Home', href: '/home', icon: HomeIcon },
|
||||
{ name: 'Explore', href: '/markets', icon: SearchIcon },
|
||||
{ name: 'Activity', href: '/activity', icon: ChatAltIcon },
|
||||
{ name: 'Portfolio', href: '/portfolio', icon: PresentationChartLineIcon },
|
||||
{ name: 'Charity', href: '/charity', icon: HeartIcon },
|
||||
]
|
||||
|
|
74
web/pages/activity.tsx
Normal file
74
web/pages/activity.tsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import Router, { useRouter } from 'next/router'
|
||||
import { Page } from 'web/components/page'
|
||||
import { ActivityFeed } from 'web/components/feed/activity-feed'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||
import { useAlgoFeed } from 'web/hooks/use-algo-feed'
|
||||
import { ContractPageContent } from './[username]/[contractSlug]'
|
||||
import { CategorySelector } from '../components/feed/category-selector'
|
||||
|
||||
export default function Activity() {
|
||||
const user = useUser()
|
||||
const [category, setCategory] = useState<string>('all')
|
||||
|
||||
const feed = useAlgoFeed(user, category)
|
||||
|
||||
const router = useRouter()
|
||||
const { u: username, s: slug } = router.query
|
||||
const contract = feed?.find(
|
||||
({ contract }) => contract.slug === slug
|
||||
)?.contract
|
||||
|
||||
useEffect(() => {
|
||||
// If the page initially loads with query params, redirect to the contract page.
|
||||
if (router.isReady && slug && username) {
|
||||
Router.replace(`/${username}/${slug}`)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady])
|
||||
|
||||
if (user === null) {
|
||||
Router.replace('/')
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Page assertUser="signed-in" suspend={!!contract}>
|
||||
<Col className="mx-auto w-full max-w-[700px]">
|
||||
<CategorySelector
|
||||
user={user}
|
||||
category={category}
|
||||
setCategory={setCategory}
|
||||
/>
|
||||
<Spacer h={1} />
|
||||
{feed ? (
|
||||
<ActivityFeed
|
||||
feed={feed}
|
||||
mode="only-recent"
|
||||
getContractPath={(c) =>
|
||||
`activity?u=${c.creatorUsername}&s=${c.slug}`
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<LoadingIndicator className="mt-4" />
|
||||
)}
|
||||
</Col>
|
||||
</Page>
|
||||
|
||||
{contract && (
|
||||
<ContractPageContent
|
||||
contract={contract}
|
||||
username={contract.creatorUsername}
|
||||
slug={contract.slug}
|
||||
bets={[]}
|
||||
comments={[]}
|
||||
backToHome={router.back}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,35 +1,12 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import Router, { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
import { Page } from 'web/components/page'
|
||||
import { ActivityFeed } from 'web/components/feed/activity-feed'
|
||||
import FeedCreate from 'web/components/feed-create'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||
import { useAlgoFeed } from 'web/hooks/use-algo-feed'
|
||||
import { ContractPageContent } from './[username]/[contractSlug]'
|
||||
import { CategorySelector } from '../components/feed/category-selector'
|
||||
import { ContractSearch } from 'web/components/contract-search'
|
||||
|
||||
const Home = () => {
|
||||
const user = useUser()
|
||||
const [category, setCategory] = useState<string>('all')
|
||||
|
||||
const feed = useAlgoFeed(user, category)
|
||||
|
||||
const router = useRouter()
|
||||
const { u: username, s: slug } = router.query
|
||||
const contract = feed?.find(
|
||||
({ contract }) => contract.slug === slug
|
||||
)?.contract
|
||||
|
||||
useEffect(() => {
|
||||
// If the page initially loads with query params, redirect to the contract page.
|
||||
if (router.isReady && slug && username) {
|
||||
Router.replace(`/${username}/${slug}`)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady])
|
||||
|
||||
if (user === null) {
|
||||
Router.replace('/')
|
||||
|
@ -37,40 +14,17 @@ const Home = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Page assertUser="signed-in" suspend={!!contract}>
|
||||
<Col className="mx-auto w-full max-w-[700px]">
|
||||
<FeedCreate user={user ?? undefined} />
|
||||
<Spacer h={2} />
|
||||
<CategorySelector
|
||||
user={user}
|
||||
category={category}
|
||||
setCategory={setCategory}
|
||||
<Page assertUser="signed-in">
|
||||
<Col className="mx-auto w-full">
|
||||
<ContractSearch
|
||||
querySortOptions={{
|
||||
shouldLoadFromStorage: false,
|
||||
defaultSort: '24-hour-vol',
|
||||
}}
|
||||
showCategorySelector
|
||||
/>
|
||||
<Spacer h={1} />
|
||||
{feed ? (
|
||||
<ActivityFeed
|
||||
feed={feed}
|
||||
mode="only-recent"
|
||||
getContractPath={(c) => `home?u=${c.creatorUsername}&s=${c.slug}`}
|
||||
/>
|
||||
) : (
|
||||
<LoadingIndicator className="mt-4" />
|
||||
)}
|
||||
</Col>
|
||||
</Page>
|
||||
|
||||
{contract && (
|
||||
<ContractPageContent
|
||||
contract={contract}
|
||||
username={contract.creatorUsername}
|
||||
slug={contract.slug}
|
||||
bets={[]}
|
||||
comments={[]}
|
||||
backToHome={router.back}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ export default function Markets() {
|
|||
description="Discover what's new, trending, or soon-to-close. Or search among our hundreds of markets."
|
||||
url="/markets"
|
||||
/>
|
||||
<ContractSearch />
|
||||
<ContractSearch showCategorySelector />
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,10 +13,12 @@ export default function TagPage() {
|
|||
<Title text={`#${tag}`} />
|
||||
<ContractSearch
|
||||
querySortOptions={{
|
||||
filter: { tag },
|
||||
defaultSort: 'newest',
|
||||
defaultFilter: 'all',
|
||||
shouldLoadFromStorage: false,
|
||||
}}
|
||||
additionalFilter={{ tag }}
|
||||
showCategorySelector={false}
|
||||
/>
|
||||
</Page>
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue
Block a user