WIP persistence work
This commit is contained in:
parent
1d948821ca
commit
3b49cfcea9
|
@ -1,44 +1,42 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import algoliasearch, { SearchIndex } from 'algoliasearch/lite'
|
||||
import algoliasearch from 'algoliasearch/lite'
|
||||
import { SearchOptions } from '@algolia/client-search'
|
||||
|
||||
import { Contract } from 'common/contract'
|
||||
import { User } from 'common/user'
|
||||
import { Sort, useQuery, useSort } from '../hooks/use-sort-and-query-params'
|
||||
import {
|
||||
SORTS,
|
||||
Sort,
|
||||
useQuery,
|
||||
useSort,
|
||||
} from '../hooks/use-sort-and-query-params'
|
||||
import {
|
||||
ContractHighlightOptions,
|
||||
ContractsGrid,
|
||||
} from './contract/contracts-grid'
|
||||
import { ShowTime } from './contract/contract-details'
|
||||
import { Row } from './layout/row'
|
||||
import { useEffect, useRef, useMemo, useState } from 'react'
|
||||
import { useEffect, useLayoutEffect, useRef, useMemo } from 'react'
|
||||
import { unstable_batchedUpdates } from 'react-dom'
|
||||
import { ENV, IS_PRIVATE_MANIFOLD } from 'common/envs/constants'
|
||||
import { useFollows } from 'web/hooks/use-follows'
|
||||
import {
|
||||
getKey,
|
||||
saveState,
|
||||
loadState,
|
||||
usePersistentState,
|
||||
} from 'web/hooks/use-persistent-state'
|
||||
import { safeLocalStorage, safeSessionStorage } from 'web/lib/util/local'
|
||||
import { track, trackCallback } from 'web/lib/service/analytics'
|
||||
import ContractSearchFirestore from 'web/pages/contract-search-firestore'
|
||||
import { useMemberGroups } from 'web/hooks/use-group'
|
||||
import { NEW_USER_GROUP_SLUGS } from 'common/group'
|
||||
import { PillButton } from './buttons/pill-button'
|
||||
import { debounce, sortBy } from 'lodash'
|
||||
import { debounce, isEqual, sortBy } from 'lodash'
|
||||
import { DEFAULT_CATEGORY_GROUPS } from 'common/categories'
|
||||
import { Col } from './layout/col'
|
||||
import { safeLocalStorage } from 'web/lib/util/local'
|
||||
import clsx from 'clsx'
|
||||
|
||||
// TODO: this obviously doesn't work with SSR, common sense would suggest
|
||||
// that we should save things like this in cookies so the server has them
|
||||
|
||||
const MARKETS_SORT = 'markets_sort'
|
||||
|
||||
function setSavedSort(s: Sort) {
|
||||
safeLocalStorage()?.setItem(MARKETS_SORT, s)
|
||||
}
|
||||
|
||||
function getSavedSort() {
|
||||
return safeLocalStorage()?.getItem(MARKETS_SORT) as Sort | null | undefined
|
||||
}
|
||||
|
||||
const searchClient = algoliasearch(
|
||||
'GJQPAYENIF',
|
||||
'75c28fc084a80e1129d427d470cf41a3'
|
||||
|
@ -47,22 +45,11 @@ const searchClient = algoliasearch(
|
|||
const indexPrefix = ENV === 'DEV' ? 'dev-' : ''
|
||||
const searchIndexName = ENV === 'DEV' ? 'dev-contracts' : 'contractsIndex'
|
||||
|
||||
const sortOptions = [
|
||||
{ label: 'Newest', value: 'newest' },
|
||||
{ label: 'Trending', value: 'score' },
|
||||
{ label: 'Most traded', value: 'most-traded' },
|
||||
{ label: '24h volume', value: '24-hour-vol' },
|
||||
{ label: 'Last updated', value: 'last-updated' },
|
||||
{ label: 'Subsidy', value: 'liquidity' },
|
||||
{ label: 'Close date', value: 'close-date' },
|
||||
{ label: 'Resolve date', value: 'resolve-date' },
|
||||
]
|
||||
|
||||
type filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all'
|
||||
|
||||
type SearchParameters = {
|
||||
index: SearchIndex
|
||||
query: string
|
||||
sort: Sort
|
||||
numericFilters: SearchOptions['numericFilters']
|
||||
facetFilters: SearchOptions['facetFilters']
|
||||
showTime?: ShowTime
|
||||
|
@ -88,6 +75,7 @@ export function ContractSearch(props: {
|
|||
hideQuickBet?: boolean
|
||||
}
|
||||
headerClassName?: string
|
||||
persistPrefix?: string
|
||||
useQuerySortLocalStorage?: boolean
|
||||
useQuerySortUrlParams?: boolean
|
||||
isWholePage?: boolean
|
||||
|
@ -104,29 +92,76 @@ export function ContractSearch(props: {
|
|||
cardHideOptions,
|
||||
highlightOptions,
|
||||
headerClassName,
|
||||
useQuerySortLocalStorage,
|
||||
persistPrefix,
|
||||
useQuerySortUrlParams,
|
||||
isWholePage,
|
||||
maxItems,
|
||||
noControls,
|
||||
} = props
|
||||
|
||||
const [numPages, setNumPages] = useState(1)
|
||||
const [pages, setPages] = useState<Contract[][]>([])
|
||||
const [showTime, setShowTime] = useState<ShowTime | undefined>()
|
||||
const store = safeSessionStorage()
|
||||
const persistAs = (name: string) => {
|
||||
return persistPrefix ? { prefix: persistPrefix, name, store } : undefined
|
||||
}
|
||||
|
||||
const searchParameters = useRef<SearchParameters | undefined>()
|
||||
const [numPages, setNumPages] = usePersistentState(1, persistAs('numPages'))
|
||||
const [pages, setPages] = usePersistentState<Contract[][]>(
|
||||
[],
|
||||
persistAs('pages')
|
||||
)
|
||||
const [showTime, setShowTime] = usePersistentState<ShowTime | null>(
|
||||
null,
|
||||
persistAs('showTime')
|
||||
)
|
||||
|
||||
const searchParameters = useRef<SearchParameters | null>(null)
|
||||
const requestId = useRef(0)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (persistPrefix && store) {
|
||||
const parameters = loadState(getKey(persistPrefix, 'parameters'), store)
|
||||
const scrollY = loadState(getKey(persistPrefix, 'scrollY'), store)
|
||||
if (parameters !== undefined) {
|
||||
console.log('Restoring search parameters: ', parameters)
|
||||
searchParameters.current = parameters as SearchParameters
|
||||
}
|
||||
if (scrollY !== undefined) {
|
||||
console.log('Restoring scroll position: ', scrollY)
|
||||
window.scrollTo(0, scrollY as number)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (persistPrefix && store) {
|
||||
const handleScroll = (e: Event) => {
|
||||
const scrollY = (e.currentTarget as Window).scrollY
|
||||
console.log('Saving scroll position: ', scrollY)
|
||||
saveState(getKey(persistPrefix, 'scrollY'), scrollY, store)
|
||||
}
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const searchIndex = useMemo(
|
||||
() => searchClient.initIndex(searchIndexName),
|
||||
[searchIndexName]
|
||||
)
|
||||
|
||||
const performQuery = async (freshQuery?: boolean) => {
|
||||
if (searchParameters.current === undefined) {
|
||||
console.log('Performing query.')
|
||||
if (searchParameters.current == null) {
|
||||
return
|
||||
}
|
||||
const params = searchParameters.current
|
||||
const id = ++requestId.current
|
||||
const requestedPage = freshQuery ? 0 : pages.length
|
||||
if (freshQuery || requestedPage < numPages) {
|
||||
const results = await params.index.search(params.query, {
|
||||
const index = params.query
|
||||
? searchIndex
|
||||
: searchClient.initIndex(`${indexPrefix}contracts-${params.sort}`)
|
||||
const results = await index.search(params.query, {
|
||||
facetFilters: params.facetFilters,
|
||||
numericFilters: params.numericFilters,
|
||||
page: requestedPage,
|
||||
|
@ -135,11 +170,16 @@ export function ContractSearch(props: {
|
|||
// if there's a more recent request, forget about this one
|
||||
if (id === requestId.current) {
|
||||
const newPage = results.hits as any as Contract[]
|
||||
const showTime =
|
||||
params.sort === 'close-date' || params.sort === 'resolve-date'
|
||||
? params.sort
|
||||
: null
|
||||
|
||||
// this spooky looking function is the easiest way to get react to
|
||||
// batch this and not do multiple renders. we can throw it out in react 18.
|
||||
// see https://github.com/reactwg/react-18/discussions/21
|
||||
unstable_batchedUpdates(() => {
|
||||
setShowTime(params.showTime)
|
||||
setShowTime(showTime)
|
||||
setNumPages(results.nbPages)
|
||||
if (freshQuery) {
|
||||
setPages([newPage])
|
||||
|
@ -154,8 +194,14 @@ export function ContractSearch(props: {
|
|||
|
||||
const onSearchParametersChanged = useRef(
|
||||
debounce((params) => {
|
||||
searchParameters.current = params
|
||||
performQuery(true)
|
||||
if (!isEqual(searchParameters.current, params)) {
|
||||
console.log('Old vs new:', searchParameters.current, params)
|
||||
if (persistPrefix && store) {
|
||||
saveState(getKey(persistPrefix, 'parameters'), params, store)
|
||||
}
|
||||
searchParameters.current = params
|
||||
performQuery(true)
|
||||
}
|
||||
}, 100)
|
||||
).current
|
||||
|
||||
|
@ -177,7 +223,7 @@ export function ContractSearch(props: {
|
|||
defaultFilter={defaultFilter}
|
||||
additionalFilter={additionalFilter}
|
||||
hideOrderSelector={hideOrderSelector}
|
||||
useQuerySortLocalStorage={useQuerySortLocalStorage}
|
||||
persistPrefix={persistPrefix ? `${persistPrefix}-controls` : undefined}
|
||||
useQuerySortUrlParams={useQuerySortUrlParams}
|
||||
user={user}
|
||||
onSearchParametersChanged={onSearchParametersChanged}
|
||||
|
@ -186,7 +232,7 @@ export function ContractSearch(props: {
|
|||
<ContractsGrid
|
||||
contracts={renderedContracts}
|
||||
loadMore={noControls ? undefined : performQuery}
|
||||
showTime={showTime}
|
||||
showTime={showTime ?? undefined}
|
||||
onContractClick={onContractClick}
|
||||
highlightOptions={highlightOptions}
|
||||
cardHideOptions={cardHideOptions}
|
||||
|
@ -202,7 +248,7 @@ function ContractSearchControls(props: {
|
|||
additionalFilter?: AdditionalFilter
|
||||
hideOrderSelector?: boolean
|
||||
onSearchParametersChanged: (params: SearchParameters) => void
|
||||
useQuerySortLocalStorage?: boolean
|
||||
persistPrefix?: string
|
||||
useQuerySortUrlParams?: boolean
|
||||
user?: User | null
|
||||
noControls?: boolean
|
||||
|
@ -214,25 +260,35 @@ function ContractSearchControls(props: {
|
|||
additionalFilter,
|
||||
hideOrderSelector,
|
||||
onSearchParametersChanged,
|
||||
useQuerySortLocalStorage,
|
||||
persistPrefix,
|
||||
useQuerySortUrlParams,
|
||||
user,
|
||||
noControls,
|
||||
} = props
|
||||
|
||||
const savedSort = useQuerySortLocalStorage ? getSavedSort() : null
|
||||
const initialSort = savedSort ?? defaultSort ?? 'score'
|
||||
const querySortOpts = { useUrl: !!useQuerySortUrlParams }
|
||||
const [sort, setSort] = useSort(initialSort, querySortOpts)
|
||||
const [query, setQuery] = useQuery('', querySortOpts)
|
||||
const [filter, setFilter] = useState<filter>(defaultFilter ?? 'open')
|
||||
const [pillFilter, setPillFilter] = useState<string | undefined>(undefined)
|
||||
const localStore = safeLocalStorage()
|
||||
const sessionStore = safeSessionStorage()
|
||||
const persistAs = (name: string, store?: Storage) => {
|
||||
return persistPrefix ? { prefix: persistPrefix, name, store } : undefined
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (useQuerySortLocalStorage) {
|
||||
setSavedSort(sort)
|
||||
}
|
||||
}, [sort])
|
||||
const initialSort = defaultSort ?? 'score'
|
||||
const [sort, setSort] = useSort(initialSort, {
|
||||
useUrl: !!useQuerySortUrlParams,
|
||||
persist: persistAs('sort', localStore),
|
||||
})
|
||||
const [query, setQuery] = useQuery('', {
|
||||
useUrl: !!useQuerySortUrlParams,
|
||||
persist: persistAs('query', sessionStore),
|
||||
})
|
||||
const [filter, setFilter] = usePersistentState<filter>(
|
||||
defaultFilter ?? 'open',
|
||||
persistAs('filter', sessionStore)
|
||||
)
|
||||
const [pillFilter, setPillFilter] = usePersistentState<string | null>(
|
||||
null,
|
||||
persistAs('pillFilter', sessionStore)
|
||||
)
|
||||
|
||||
const follows = useFollows(user?.id)
|
||||
const memberGroups = (useMemberGroups(user?.id) ?? []).filter(
|
||||
|
@ -299,7 +355,7 @@ function ContractSearchControls(props: {
|
|||
filter === 'closed' ? `closeTime <= ${Date.now()}` : '',
|
||||
].filter((f) => f)
|
||||
|
||||
const selectPill = (pill: string | undefined) => () => {
|
||||
const selectPill = (pill: string | null) => () => {
|
||||
setPillFilter(pill)
|
||||
track('select search category', { category: pill ?? 'all' })
|
||||
}
|
||||
|
@ -320,23 +376,14 @@ function ContractSearchControls(props: {
|
|||
track('select search sort', { sort: newSort })
|
||||
}
|
||||
|
||||
const indexName = `${indexPrefix}contracts-${sort}`
|
||||
const index = useMemo(() => searchClient.initIndex(indexName), [indexName])
|
||||
const searchIndex = useMemo(
|
||||
() => searchClient.initIndex(searchIndexName),
|
||||
[searchIndexName]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
onSearchParametersChanged({
|
||||
index: query ? searchIndex : index,
|
||||
query: query,
|
||||
sort: sort,
|
||||
numericFilters: numericFilters,
|
||||
facetFilters: facetFilters,
|
||||
showTime:
|
||||
sort === 'close-date' || sort === 'resolve-date' ? sort : undefined,
|
||||
})
|
||||
}, [query, index, searchIndex, filter, JSON.stringify(facetFilters)])
|
||||
}, [query, sort, filter, JSON.stringify(facetFilters)])
|
||||
|
||||
if (noControls) {
|
||||
return <></>
|
||||
|
@ -373,7 +420,7 @@ function ContractSearchControls(props: {
|
|||
value={sort}
|
||||
onChange={(e) => selectSort(e.target.value as Sort)}
|
||||
>
|
||||
{sortOptions.map((option) => (
|
||||
{SORTS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
|
@ -387,7 +434,7 @@ function ContractSearchControls(props: {
|
|||
<PillButton
|
||||
key={'all'}
|
||||
selected={pillFilter === undefined}
|
||||
onSelect={selectPill(undefined)}
|
||||
onSelect={selectPill(null)}
|
||||
>
|
||||
All
|
||||
</PillButton>
|
||||
|
|
54
web/hooks/use-persistent-state.ts
Normal file
54
web/hooks/use-persistent-state.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
|
||||
export type PersistenceOptions = {
|
||||
store?: Storage
|
||||
prefix: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const getKey = (prefix: string, name: string) => `${prefix}-${name}`
|
||||
|
||||
export const saveState = (key: string, val: unknown, store: Storage) => {
|
||||
if (val === undefined) {
|
||||
store.removeItem(key)
|
||||
} else {
|
||||
store.setItem(key, JSON.stringify(val))
|
||||
}
|
||||
}
|
||||
|
||||
export const loadState = (key: string, store: Storage) => {
|
||||
const saved = store.getItem(key)
|
||||
if (typeof saved === 'string') {
|
||||
try {
|
||||
return JSON.parse(saved) as unknown
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const usePersistentState = <T>(
|
||||
defaultValue: T,
|
||||
persist?: PersistenceOptions
|
||||
) => {
|
||||
const store = persist?.store
|
||||
const key = persist ? getKey(persist.prefix, persist.name) : null
|
||||
let initialValue
|
||||
if (key != null && store != null) {
|
||||
const saved = loadState(key, store) as T
|
||||
console.log('Loading state for: ', key, saved)
|
||||
if (saved !== undefined) {
|
||||
initialValue = saved
|
||||
}
|
||||
}
|
||||
const [state, setState] = useState<T>(initialValue ?? defaultValue)
|
||||
useEffect(() => {
|
||||
if (key != null && store != null) {
|
||||
console.log('Saving state for: ', key, state)
|
||||
saveState(key, state, store)
|
||||
}
|
||||
}, [key, state, store])
|
||||
return [state, setState] as const
|
||||
}
|
|
@ -1,18 +1,25 @@
|
|||
import { useState } from 'react'
|
||||
import {
|
||||
usePersistentState,
|
||||
PersistenceOptions,
|
||||
} from 'web/hooks/use-persistent-state'
|
||||
import { NextRouter, useRouter } from 'next/router'
|
||||
|
||||
export type Sort =
|
||||
| 'newest'
|
||||
| 'oldest'
|
||||
| 'most-traded'
|
||||
| '24-hour-vol'
|
||||
| 'close-date'
|
||||
| 'resolve-date'
|
||||
| 'last-updated'
|
||||
| 'score'
|
||||
export const SORTS = [
|
||||
{ label: 'Newest', value: 'newest' },
|
||||
{ label: 'Trending', value: 'score' },
|
||||
{ label: 'Most traded', value: 'most-traded' },
|
||||
{ label: '24h volume', value: '24-hour-vol' },
|
||||
{ label: 'Last updated', value: 'last-updated' },
|
||||
{ label: 'Subsidy', value: 'liquidity' },
|
||||
{ label: 'Close date', value: 'close-date' },
|
||||
{ label: 'Resolve date', value: 'resolve-date' },
|
||||
] as const
|
||||
|
||||
export type Sort = typeof SORTS[number]['value']
|
||||
|
||||
type UpdatedQueryParams = { [k: string]: string }
|
||||
type QuerySortOpts = { useUrl: boolean }
|
||||
type QuerySortOpts = { useUrl: boolean; persist?: PersistenceOptions }
|
||||
|
||||
function withURLParams(location: Location, params: UpdatedQueryParams) {
|
||||
const newParams = new URLSearchParams(location.search)
|
||||
|
@ -44,7 +51,10 @@ export function useQuery(defaultQuery: string, opts?: QuerySortOpts) {
|
|||
const useUrl = opts?.useUrl ?? false
|
||||
const router = useRouter()
|
||||
const initialQuery = useUrl ? getStringURLParam(router, 'q') : null
|
||||
const [query, setQuery] = useState(initialQuery ?? defaultQuery)
|
||||
const [query, setQuery] = usePersistentState(
|
||||
initialQuery ?? defaultQuery,
|
||||
opts?.persist
|
||||
)
|
||||
if (!useUrl) {
|
||||
return [query, setQuery] as const
|
||||
} else {
|
||||
|
@ -56,7 +66,10 @@ export function useSort(defaultSort: Sort, opts?: QuerySortOpts) {
|
|||
const useUrl = opts?.useUrl ?? false
|
||||
const router = useRouter()
|
||||
const initialSort = useUrl ? (getStringURLParam(router, 's') as Sort) : null
|
||||
const [sort, setSort] = useState(initialSort ?? defaultSort)
|
||||
const [sort, setSort] = usePersistentState(
|
||||
initialSort ?? defaultSort,
|
||||
opts?.persist
|
||||
)
|
||||
if (!useUrl) {
|
||||
return [sort, setSort] as const
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
export const safeLocalStorage = () => (isLocalStorage() ? localStorage : null)
|
||||
export const safeLocalStorage = () =>
|
||||
isLocalStorage() ? localStorage : undefined
|
||||
export const safeSessionStorage = () =>
|
||||
isSessionStorage() ? sessionStorage : undefined
|
||||
|
||||
const isLocalStorage = () => {
|
||||
try {
|
||||
|
@ -9,3 +12,13 @@ const isLocalStorage = () => {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const isSessionStorage = () => {
|
||||
try {
|
||||
sessionStorage.getItem('test')
|
||||
sessionStorage.setItem('hi', 'mom')
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,8 +34,6 @@ export default function ContractSearchFirestore(props: {
|
|||
matches.sort((a, b) => b.createdTime - a.createdTime)
|
||||
} else if (sort === 'resolve-date') {
|
||||
matches = sortBy(matches, (contract) => -1 * (contract.resolutionTime ?? 0))
|
||||
} else if (sort === 'oldest') {
|
||||
matches.sort((a, b) => a.createdTime - b.createdTime)
|
||||
} else if (sort === 'close-date') {
|
||||
matches = sortBy(matches, ({ volume24Hours }) => -1 * volume24Hours)
|
||||
matches = sortBy(matches, (contract) => contract.closeTime ?? Infinity)
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { PencilAltIcon } 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 { Contract } from 'common/contract'
|
||||
import { User } from 'common/user'
|
||||
import { ContractPageContent } from './[username]/[contractSlug]'
|
||||
import { getContractFromSlug } from 'web/lib/firebase/contracts'
|
||||
import { getUserAndPrivateUser } from 'web/lib/firebase/users'
|
||||
import { useTracking } from 'web/hooks/use-tracking'
|
||||
import { track } from 'web/lib/service/analytics'
|
||||
|
@ -25,8 +21,6 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|||
|
||||
const Home = (props: { auth: { user: User } | null }) => {
|
||||
const user = props.auth ? props.auth.user : null
|
||||
const [contract, setContract] = useContractPage()
|
||||
|
||||
const router = useRouter()
|
||||
useTracking('view home')
|
||||
|
||||
|
@ -35,19 +29,13 @@ const Home = (props: { auth: { user: User } | null }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Page className={contract ? 'sr-only' : ''}>
|
||||
<Page>
|
||||
<Col className="mx-auto w-full p-2">
|
||||
<ContractSearch
|
||||
user={user}
|
||||
persistPrefix="home-search"
|
||||
useQuerySortLocalStorage={true}
|
||||
useQuerySortUrlParams={true}
|
||||
onContractClick={(c) => {
|
||||
// Show contract without navigating to contract page.
|
||||
setContract(c)
|
||||
// Update the url without switching pages in Nextjs.
|
||||
history.pushState(null, '', `/${c.creatorUsername}/${c.slug}`)
|
||||
}}
|
||||
isWholePage
|
||||
/>
|
||||
</Col>
|
||||
<button
|
||||
|
@ -61,81 +49,8 @@ const Home = (props: { auth: { user: User } | null }) => {
|
|||
<PencilAltIcon className="h-7 w-7" aria-hidden="true" />
|
||||
</button>
|
||||
</Page>
|
||||
|
||||
{contract && (
|
||||
<ContractPageContent
|
||||
contract={contract}
|
||||
user={user}
|
||||
username={contract.creatorUsername}
|
||||
slug={contract.slug}
|
||||
bets={[]}
|
||||
comments={[]}
|
||||
backToHome={() => {
|
||||
history.back()
|
||||
}}
|
||||
recommendedContracts={[]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const useContractPage = () => {
|
||||
const [contract, setContract] = useState<Contract | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
const updateContract = () => {
|
||||
const path = location.pathname.split('/').slice(1)
|
||||
if (path[0] === 'home') setContract(undefined)
|
||||
else {
|
||||
const [username, contractSlug] = path
|
||||
if (!username || !contractSlug) setContract(undefined)
|
||||
else {
|
||||
// Show contract if route is to a contract: '/[username]/[contractSlug]'.
|
||||
getContractFromSlug(contractSlug).then((contract) => {
|
||||
const path = location.pathname.split('/').slice(1)
|
||||
const [_username, contractSlug] = path
|
||||
// Make sure we're still on the same contract.
|
||||
if (contract?.slug === contractSlug) setContract(contract)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener('popstate', updateContract)
|
||||
|
||||
const { pushState, replaceState } = window.history
|
||||
|
||||
window.history.pushState = function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
const args = [...(arguments as any)] as any
|
||||
// Discard NextJS router state.
|
||||
args[0] = null
|
||||
pushState.apply(history, args)
|
||||
updateContract()
|
||||
}
|
||||
|
||||
window.history.replaceState = function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
const args = [...(arguments as any)] as any
|
||||
// Discard NextJS router state.
|
||||
args[0] = null
|
||||
replaceState.apply(history, args)
|
||||
updateContract()
|
||||
}
|
||||
|
||||
return () => {
|
||||
removeEventListener('popstate', updateContract)
|
||||
window.history.pushState = pushState
|
||||
window.history.replaceState = replaceState
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (contract) window.scrollTo(0, 0)
|
||||
}, [contract])
|
||||
|
||||
return [contract, setContract] as const
|
||||
}
|
||||
|
||||
export default Home
|
||||
|
|
Loading…
Reference in New Issue
Block a user