Clean up and unify persistent state stores
This commit is contained in:
parent
f643636822
commit
01075a7e1f
|
@ -1,15 +1,9 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import algoliasearch from 'algoliasearch/lite'
|
||||
import { SearchOptions } from '@algolia/client-search'
|
||||
|
||||
import { useRouter } from 'next/router'
|
||||
import { Contract } from 'common/contract'
|
||||
import { User } from 'common/user'
|
||||
import {
|
||||
SORTS,
|
||||
Sort,
|
||||
useQuery,
|
||||
useSort,
|
||||
} from '../hooks/use-sort-and-query-params'
|
||||
import {
|
||||
ContractHighlightOptions,
|
||||
ContractsGrid,
|
||||
|
@ -20,12 +14,12 @@ import { useEffect, useLayoutEffect, useRef, useMemo } from 'react'
|
|||
import { ENV, IS_PRIVATE_MANIFOLD } from 'common/envs/constants'
|
||||
import { useFollows } from 'web/hooks/use-follows'
|
||||
import {
|
||||
getKey,
|
||||
saveState,
|
||||
loadState,
|
||||
storageStore,
|
||||
historyStore,
|
||||
urlParamsStore,
|
||||
usePersistentState,
|
||||
} from 'web/hooks/use-persistent-state'
|
||||
import { safeLocalStorage, safeSessionStorage } from 'web/lib/util/local'
|
||||
import { 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'
|
||||
|
@ -44,6 +38,19 @@ const searchClient = algoliasearch(
|
|||
const indexPrefix = ENV === 'DEV' ? 'dev-' : ''
|
||||
const searchIndexName = ENV === 'DEV' ? 'dev-contracts' : 'contractsIndex'
|
||||
|
||||
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 filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all'
|
||||
|
||||
type SearchParameters = {
|
||||
|
@ -97,29 +104,25 @@ export function ContractSearch(props: {
|
|||
noControls,
|
||||
} = props
|
||||
|
||||
const store = safeSessionStorage()
|
||||
const persistAs = (name: string) => {
|
||||
return persistPrefix ? { prefix: persistPrefix, name, store } : undefined
|
||||
}
|
||||
|
||||
const [state, setState] = usePersistentState(
|
||||
{
|
||||
numPages: 1,
|
||||
pages: [] as Contract[][],
|
||||
showTime: null as ShowTime | null,
|
||||
},
|
||||
persistAs('state')
|
||||
{ key: `${persistPrefix}-search`, store: historyStore() }
|
||||
)
|
||||
|
||||
const searchParameters = useRef<SearchParameters | null>(null)
|
||||
const searchParams = useRef<SearchParameters | null>(null)
|
||||
const searchParamsStore = historyStore<SearchParameters>()
|
||||
const requestId = useRef(0)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (persistPrefix && store) {
|
||||
const parameters = loadState(getKey(persistPrefix, 'parameters'), store)
|
||||
if (parameters !== undefined) {
|
||||
console.log('Restoring search parameters: ', parameters)
|
||||
searchParameters.current = parameters as SearchParameters
|
||||
if (persistPrefix) {
|
||||
const params = searchParamsStore.get(`${persistPrefix}-params`)
|
||||
if (params !== undefined) {
|
||||
console.log('Restoring search parameters: ', params)
|
||||
searchParams.current = params
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
@ -131,11 +134,10 @@ export function ContractSearch(props: {
|
|||
|
||||
const performQuery = async (freshQuery?: boolean) => {
|
||||
console.log('Performing query.')
|
||||
if (searchParameters.current == null) {
|
||||
if (searchParams.current == null) {
|
||||
return
|
||||
}
|
||||
const { query, sort, openClosedFilter, facetFilters } =
|
||||
searchParameters.current
|
||||
const { query, sort, openClosedFilter, facetFilters } = searchParams.current
|
||||
const id = ++requestId.current
|
||||
const requestedPage = freshQuery ? 0 : state.pages.length
|
||||
if (freshQuery || requestedPage < state.numPages) {
|
||||
|
@ -159,10 +161,8 @@ export function ContractSearch(props: {
|
|||
const newPage = results.hits as any as Contract[]
|
||||
const showTime =
|
||||
sort === 'close-date' || sort === 'resolve-date' ? sort : null
|
||||
setState((curr) => {
|
||||
const pages = freshQuery ? [newPage] : [...curr.pages, newPage]
|
||||
return { numPages: results.nbPages, pages, showTime }
|
||||
})
|
||||
const pages = freshQuery ? [newPage] : [...state.pages, newPage]
|
||||
setState({ numPages: results.nbPages, pages, showTime })
|
||||
if (freshQuery && isWholePage) window.scrollTo(0, 0)
|
||||
}
|
||||
}
|
||||
|
@ -170,12 +170,12 @@ export function ContractSearch(props: {
|
|||
|
||||
const onSearchParametersChanged = useRef(
|
||||
debounce((params) => {
|
||||
if (!isEqual(searchParameters.current, params)) {
|
||||
console.log('Old vs new:', searchParameters.current, params)
|
||||
if (persistPrefix && store) {
|
||||
saveState(getKey(persistPrefix, 'parameters'), params, store)
|
||||
if (!isEqual(searchParams.current, params)) {
|
||||
console.log('Old vs new:', searchParams.current, params)
|
||||
if (persistPrefix) {
|
||||
searchParamsStore.set(`${persistPrefix}-params`, params)
|
||||
}
|
||||
searchParameters.current = params
|
||||
searchParams.current = params
|
||||
performQuery(true)
|
||||
}
|
||||
}, 100)
|
||||
|
@ -244,28 +244,21 @@ function ContractSearchControls(props: {
|
|||
noControls,
|
||||
} = props
|
||||
|
||||
const localStore = safeLocalStorage()
|
||||
const sessionStore = safeSessionStorage()
|
||||
const persistAs = (name: string, store?: Storage) => {
|
||||
return persistPrefix ? { prefix: persistPrefix, name, store } : undefined
|
||||
}
|
||||
|
||||
const initialSort = defaultSort ?? 'score'
|
||||
const [sort, setSort] = useSort(initialSort, {
|
||||
useUrl: !!useQuerySortUrlParams,
|
||||
persist: persistAs('sort', localStore),
|
||||
const router = useRouter()
|
||||
const [query, setQuery] = usePersistentState('', {
|
||||
key: 'q',
|
||||
store: urlParamsStore(router),
|
||||
})
|
||||
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 [state, setState] = usePersistentState(
|
||||
{
|
||||
sort: defaultSort ?? 'score',
|
||||
filter: defaultFilter ?? 'open',
|
||||
pillFilter: null as string | null,
|
||||
},
|
||||
{
|
||||
key: `${persistPrefix}-params`,
|
||||
store: storageStore(safeSessionStorage()),
|
||||
}
|
||||
)
|
||||
|
||||
const follows = useFollows(user?.id)
|
||||
|
@ -300,14 +293,16 @@ function ContractSearchControls(props: {
|
|||
...additionalFilters,
|
||||
additionalFilter ? '' : 'visibility:public',
|
||||
|
||||
filter === 'open' ? 'isResolved:false' : '',
|
||||
filter === 'closed' ? 'isResolved:false' : '',
|
||||
filter === 'resolved' ? 'isResolved:true' : '',
|
||||
state.filter === 'open' ? 'isResolved:false' : '',
|
||||
state.filter === 'closed' ? 'isResolved:false' : '',
|
||||
state.filter === 'resolved' ? 'isResolved:true' : '',
|
||||
|
||||
pillFilter && pillFilter !== 'personal' && pillFilter !== 'your-bets'
|
||||
? `groupLinks.slug:${pillFilter}`
|
||||
state.pillFilter &&
|
||||
state.pillFilter !== 'personal' &&
|
||||
state.pillFilter !== 'your-bets'
|
||||
? `groupLinks.slug:${state.pillFilter}`
|
||||
: '',
|
||||
pillFilter === 'personal'
|
||||
state.pillFilter === 'personal'
|
||||
? // Show contracts in groups that the user is a member of
|
||||
memberGroupSlugs
|
||||
.map((slug) => `groupLinks.slug:${slug}`)
|
||||
|
@ -319,18 +314,24 @@ function ContractSearchControls(props: {
|
|||
)
|
||||
: '',
|
||||
// Subtract contracts you bet on from For you.
|
||||
pillFilter === 'personal' && user ? `uniqueBettorIds:-${user.id}` : '',
|
||||
pillFilter === 'your-bets' && user
|
||||
state.pillFilter === 'personal' && user
|
||||
? `uniqueBettorIds:-${user.id}`
|
||||
: '',
|
||||
state.pillFilter === 'your-bets' && user
|
||||
? // Show contracts bet on by the user
|
||||
`uniqueBettorIds:${user.id}`
|
||||
: '',
|
||||
].filter((f) => f)
|
||||
|
||||
const openClosedFilter =
|
||||
filter === 'open' ? 'open' : filter === 'closed' ? 'closed' : undefined
|
||||
state.filter === 'open'
|
||||
? 'open'
|
||||
: state.filter === 'closed'
|
||||
? 'closed'
|
||||
: undefined
|
||||
|
||||
const selectPill = (pill: string | null) => () => {
|
||||
setPillFilter(pill)
|
||||
setState({ ...state, pillFilter: pill })
|
||||
track('select search category', { category: pill ?? 'all' })
|
||||
}
|
||||
|
||||
|
@ -340,25 +341,25 @@ function ContractSearchControls(props: {
|
|||
}
|
||||
|
||||
const selectFilter = (newFilter: filter) => {
|
||||
if (newFilter === filter) return
|
||||
setFilter(newFilter)
|
||||
if (newFilter === state.filter) return
|
||||
setState({ ...state, filter: newFilter })
|
||||
track('select search filter', { filter: newFilter })
|
||||
}
|
||||
|
||||
const selectSort = (newSort: Sort) => {
|
||||
if (newSort === sort) return
|
||||
setSort(newSort)
|
||||
if (newSort === state.sort) return
|
||||
setState({ ...state, sort: newSort })
|
||||
track('select search sort', { sort: newSort })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onSearchParametersChanged({
|
||||
query: query,
|
||||
sort: sort,
|
||||
sort: state.sort,
|
||||
openClosedFilter: openClosedFilter,
|
||||
facetFilters: facetFilters,
|
||||
})
|
||||
}, [query, sort, openClosedFilter, JSON.stringify(facetFilters)])
|
||||
}, [query, state.sort, openClosedFilter, JSON.stringify(facetFilters)])
|
||||
|
||||
if (noControls) {
|
||||
return <></>
|
||||
|
@ -373,14 +374,14 @@ function ContractSearchControls(props: {
|
|||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => updateQuery(e.target.value)}
|
||||
onBlur={trackCallback('search', { query })}
|
||||
onBlur={trackCallback('search', { query: query })}
|
||||
placeholder={'Search'}
|
||||
className="input input-bordered w-full"
|
||||
/>
|
||||
{!query && (
|
||||
<select
|
||||
className="select select-bordered"
|
||||
value={filter}
|
||||
value={state.filter}
|
||||
onChange={(e) => selectFilter(e.target.value as filter)}
|
||||
>
|
||||
<option value="open">Open</option>
|
||||
|
@ -392,7 +393,7 @@ function ContractSearchControls(props: {
|
|||
{!hideOrderSelector && !query && (
|
||||
<select
|
||||
className="select select-bordered"
|
||||
value={sort}
|
||||
value={state.sort}
|
||||
onChange={(e) => selectSort(e.target.value as Sort)}
|
||||
>
|
||||
{SORTS.map((option) => (
|
||||
|
@ -408,14 +409,14 @@ function ContractSearchControls(props: {
|
|||
<Row className="scrollbar-hide items-start gap-2 overflow-x-auto">
|
||||
<PillButton
|
||||
key={'all'}
|
||||
selected={pillFilter === undefined}
|
||||
selected={state.pillFilter === undefined}
|
||||
onSelect={selectPill(null)}
|
||||
>
|
||||
All
|
||||
</PillButton>
|
||||
<PillButton
|
||||
key={'personal'}
|
||||
selected={pillFilter === 'personal'}
|
||||
selected={state.pillFilter === 'personal'}
|
||||
onSelect={selectPill('personal')}
|
||||
>
|
||||
{user ? 'For you' : 'Featured'}
|
||||
|
@ -424,7 +425,7 @@ function ContractSearchControls(props: {
|
|||
{user && (
|
||||
<PillButton
|
||||
key={'your-bets'}
|
||||
selected={pillFilter === 'your-bets'}
|
||||
selected={state.pillFilter === 'your-bets'}
|
||||
onSelect={selectPill('your-bets')}
|
||||
>
|
||||
Your bets
|
||||
|
@ -435,7 +436,7 @@ function ContractSearchControls(props: {
|
|||
return (
|
||||
<PillButton
|
||||
key={slug}
|
||||
selected={pillFilter === slug}
|
||||
selected={state.pillFilter === slug}
|
||||
onSelect={selectPill(slug)}
|
||||
>
|
||||
{name}
|
||||
|
|
|
@ -1,88 +1,105 @@
|
|||
import { useLayoutEffect, useEffect, useState } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { useStateCheckEquality } from './use-state-check-equality'
|
||||
import { NextRouter } from 'next/router'
|
||||
|
||||
export type PersistenceOptions = {
|
||||
store?: Storage
|
||||
prefix: string
|
||||
name: string
|
||||
export type PersistenceOptions<T> = { key: string; store: PersistentStore<T> }
|
||||
|
||||
export interface PersistentStore<T> {
|
||||
get: (k: string) => T | undefined
|
||||
set: (k: string, v: T | undefined) => void
|
||||
}
|
||||
|
||||
export const getKey = (prefix: string, name: string) => `${prefix}-${name}`
|
||||
|
||||
export const saveState = (key: string, val: unknown, store: Storage) => {
|
||||
if (val === undefined) {
|
||||
store.removeItem(key)
|
||||
const withURLParam = (location: Location, k: string, v?: string) => {
|
||||
const newParams = new URLSearchParams(location.search)
|
||||
if (!v) {
|
||||
newParams.delete(k)
|
||||
} else {
|
||||
store.setItem(key, JSON.stringify(val))
|
||||
newParams.set(k, v)
|
||||
}
|
||||
const newUrl = new URL(location.href)
|
||||
newUrl.search = newParams.toString()
|
||||
return newUrl
|
||||
}
|
||||
|
||||
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)
|
||||
export const storageStore = <T>(storage?: Storage): PersistentStore<T> => ({
|
||||
get: (k: string) => {
|
||||
if (!storage) {
|
||||
return undefined
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
const saved = storage.getItem(k)
|
||||
if (typeof saved === 'string') {
|
||||
try {
|
||||
return JSON.parse(saved) as T
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
set: (k: string, v: T | undefined) => {
|
||||
if (storage) {
|
||||
if (v === undefined) {
|
||||
storage.removeItem(k)
|
||||
} else {
|
||||
storage.setItem(k, JSON.stringify(v))
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const STATE_KEY = '__manifold'
|
||||
export const urlParamsStore = (router: NextRouter) => ({
|
||||
get: (k: string) => {
|
||||
const v = router.query[k]
|
||||
return typeof v === 'string' ? v : undefined
|
||||
},
|
||||
set: (k: string, v: string | undefined) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// see relevant discussion here https://github.com/vercel/next.js/discussions/18072
|
||||
const url = withURLParam(window.location, k, v).toString()
|
||||
const updatedState = { ...window.history.state, as: url, url }
|
||||
window.history.replaceState(updatedState, '', url)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const getHistoryState = <T>(k: string) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.history.state?.options?.[STATE_KEY]?.[k] as T | undefined
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const setHistoryState = (k: string, v: any) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const state = window.history.state ?? {}
|
||||
const options = state.options ?? {}
|
||||
const inner = options[STATE_KEY] ?? {}
|
||||
window.history.replaceState(
|
||||
{ ...state, options: { ...options, [STATE_KEY]: { ...inner, [k]: v } } },
|
||||
''
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const useHistoryState = <T>(key: string, initialValue: T) => {
|
||||
const [state, setState] = useState(getHistoryState<T>(key) ?? initialValue)
|
||||
const setter = (val: T) => {
|
||||
console.log('Setting state: ', val)
|
||||
setHistoryState(key, val)
|
||||
setState(val)
|
||||
}
|
||||
return [state, setter] as const
|
||||
}
|
||||
export const historyStore = <T>(prefix = '__manifold'): PersistentStore<T> => ({
|
||||
get: (k: string) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.history.state?.options?.[prefix]?.[k] as T | undefined
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
set: (k: string, v: T | undefined) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const state = window.history.state ?? {}
|
||||
const options = state.options ?? {}
|
||||
const inner = options[prefix] ?? {}
|
||||
window.history.replaceState(
|
||||
{
|
||||
...state,
|
||||
options: { ...options, [prefix]: { ...inner, [k]: v } },
|
||||
},
|
||||
''
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const usePersistentState = <T>(
|
||||
initial: T,
|
||||
persist?: PersistenceOptions
|
||||
persist?: PersistenceOptions<T>
|
||||
) => {
|
||||
const store = persist?.store
|
||||
const key = persist ? getKey(persist.prefix, persist.name) : null
|
||||
useLayoutEffect(() => {
|
||||
if (key != null && store != null) {
|
||||
const saved = loadState(key, store) as T
|
||||
console.log('Loading state for: ', key, saved)
|
||||
if (saved !== undefined) {
|
||||
setState(saved)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
const [state, setState] = useStateCheckEquality(initial)
|
||||
const key = persist?.key
|
||||
const savedValue = key != null && store != null ? store.get(key) : undefined
|
||||
const [state, setState] = useStateCheckEquality(savedValue ?? initial)
|
||||
useEffect(() => {
|
||||
if (key != null && store != null) {
|
||||
console.log('Saving state for: ', key, state)
|
||||
saveState(key, state, store)
|
||||
store.set(key, state)
|
||||
}
|
||||
}, [key, state, store])
|
||||
}, [key, state])
|
||||
return [state, setState] as const
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
import { useState } from 'react'
|
||||
import {
|
||||
usePersistentState,
|
||||
PersistenceOptions,
|
||||
} from 'web/hooks/use-persistent-state'
|
||||
import { NextRouter, useRouter } from 'next/router'
|
||||
|
||||
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; persist?: PersistenceOptions }
|
||||
|
||||
function withURLParams(location: Location, params: UpdatedQueryParams) {
|
||||
const newParams = new URLSearchParams(location.search)
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
if (!v) {
|
||||
newParams.delete(k)
|
||||
} else {
|
||||
newParams.set(k, v)
|
||||
}
|
||||
}
|
||||
const newUrl = new URL(location.href)
|
||||
newUrl.search = newParams.toString()
|
||||
return newUrl
|
||||
}
|
||||
|
||||
function updateURL(params: UpdatedQueryParams) {
|
||||
// see relevant discussion here https://github.com/vercel/next.js/discussions/18072
|
||||
const url = withURLParams(window.location, params).toString()
|
||||
const updatedState = { ...window.history.state, as: url, url }
|
||||
window.history.replaceState(updatedState, '', url)
|
||||
}
|
||||
|
||||
function getStringURLParam(router: NextRouter, k: string) {
|
||||
const v = router.query[k]
|
||||
return typeof v === 'string' ? v : null
|
||||
}
|
||||
|
||||
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] = usePersistentState(
|
||||
initialQuery ?? defaultQuery,
|
||||
opts?.persist
|
||||
)
|
||||
if (!useUrl) {
|
||||
return [query, setQuery] as const
|
||||
} else {
|
||||
return [query, (q: string) => (setQuery(q), updateURL({ q }))] as const
|
||||
}
|
||||
}
|
||||
|
||||
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] = usePersistentState(
|
||||
initialSort ?? defaultSort,
|
||||
opts?.persist
|
||||
)
|
||||
if (!useUrl) {
|
||||
return [sort, setSort] as const
|
||||
} else {
|
||||
return [sort, (s: Sort) => (setSort(s), updateURL({ s }))] as const
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import { Answer } from 'common/answer'
|
||||
import { searchInAny } from 'common/util/parse'
|
||||
import { sortBy } from 'lodash'
|
||||
import { ContractsGrid } from 'web/components/contract/contracts-grid'
|
||||
import { useContracts } from 'web/hooks/use-contracts'
|
||||
import { Sort, useQuery, useSort } from 'web/hooks/use-sort-and-query-params'
|
||||
import {
|
||||
usePersistentState,
|
||||
urlParamsStore,
|
||||
} from 'web/hooks/use-persistent-state'
|
||||
|
||||
const MAX_CONTRACTS_RENDERED = 100
|
||||
|
||||
|
@ -17,8 +21,10 @@ export default function ContractSearchFirestore(props: {
|
|||
}) {
|
||||
const contracts = useContracts()
|
||||
const { additionalFilter } = props
|
||||
const [query, setQuery] = useQuery('', { useUrl: true })
|
||||
const [sort, setSort] = useSort('score', { useUrl: true })
|
||||
const router = useRouter()
|
||||
const store = urlParamsStore(router)
|
||||
const [query, setQuery] = usePersistentState('', { key: 'q', store })
|
||||
const [sort, setSort] = usePersistentState('score', { key: 'sort', store })
|
||||
|
||||
let matches = (contracts ?? []).filter((c) =>
|
||||
searchInAny(
|
||||
|
@ -91,7 +97,7 @@ export default function ContractSearchFirestore(props: {
|
|||
<select
|
||||
className="select select-bordered"
|
||||
value={sort}
|
||||
onChange={(e) => setSort(e.target.value as Sort)}
|
||||
onChange={(e) => setSort(e.target.value)}
|
||||
>
|
||||
<option value="score">Trending</option>
|
||||
<option value="newest">Newest</option>
|
||||
|
|
|
@ -12,7 +12,7 @@ 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/hooks/use-sort-and-query-params'
|
||||
import { Sort } from 'web/components/contract-search'
|
||||
import { Button } from 'web/components/button'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { useMemberGroups } from 'web/hooks/use-group'
|
||||
|
|
Loading…
Reference in New Issue
Block a user