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