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:
James Grugett 2022-05-17 12:56:10 -05:00 committed by GitHub
parent 1bf2073e61
commit 7da46050e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 171 additions and 106 deletions

View File

@ -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>
<ContractSearchInner
querySortOptions={querySortOptions}
filter={filter}
/>
<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 && (
<ContractsGrid
contracts={contracts}
loadMore={showMore}
hasMore={!isLastPage}
showCloseTime={index === 'contracts-closing-soon'}
/>
)}
</div>
return (
<ContractsGrid
contracts={contracts}
loadMore={showMore}
hasMore={!isLastPage}
showCloseTime={index === 'contracts-closing-soon'}
/>
)
}
@ -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) => {

View File

@ -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}
/>
)
}

View File

@ -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
)}
>

View File

@ -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>
<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>
{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">

View File

@ -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
View 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}
/>
)}
</>
)
}

View File

@ -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}
/>
<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}
<Page assertUser="signed-in">
<Col className="mx-auto w-full">
<ContractSearch
querySortOptions={{
shouldLoadFromStorage: false,
defaultSort: '24-hour-vol',
}}
showCategorySelector
/>
)}
</>
</Col>
</Page>
)
}

View File

@ -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>
)
}

View File

@ -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>
)