🔍 Algolia search (#136)
* Add algolia and instantsearch packages * Switch to hooks-web package * Implement algolia search! * Fix types * Fix tags page * Closed sort option * Implement select for filtering on open, closed, resolved, all. * Support search in dev environment * Fix runtime error in landing page
This commit is contained in:
parent
3fc159f10b
commit
e8ab863557
|
@ -2,7 +2,7 @@ import { DEV_CONFIG } from './dev'
|
|||
import { EnvConfig, PROD_CONFIG } from './prod'
|
||||
import { THEOREMONE_CONFIG } from './theoremone'
|
||||
|
||||
const ENV = process.env.NEXT_PUBLIC_FIREBASE_ENV ?? 'PROD'
|
||||
export const ENV = process.env.NEXT_PUBLIC_FIREBASE_ENV ?? 'PROD'
|
||||
|
||||
const CONFIGS = {
|
||||
PROD: PROD_CONFIG,
|
||||
|
|
|
@ -9,9 +9,7 @@ const formatter = new Intl.NumberFormat('en-US', {
|
|||
|
||||
export function formatMoney(amount: number) {
|
||||
const newAmount = Math.round(amount) === 0 ? 0 : Math.floor(amount) // handle -0 case
|
||||
return (
|
||||
ENV_CONFIG.moneyMoniker + formatter.format(newAmount).replace('$', '')
|
||||
)
|
||||
return ENV_CONFIG.moneyMoniker + formatter.format(newAmount).replace('$', '')
|
||||
}
|
||||
|
||||
export function formatWithCommas(amount: number) {
|
||||
|
|
221
web/components/contract-search.tsx
Normal file
221
web/components/contract-search.tsx
Normal file
|
@ -0,0 +1,221 @@
|
|||
import algoliasearch from 'algoliasearch/lite'
|
||||
import {
|
||||
InstantSearch,
|
||||
SearchBox,
|
||||
SortBy,
|
||||
useInfiniteHits,
|
||||
useRange,
|
||||
useRefinementList,
|
||||
useSortBy,
|
||||
useToggleRefinement,
|
||||
} from 'react-instantsearch-hooks-web'
|
||||
import { Contract } from '../../common/contract'
|
||||
import {
|
||||
Sort,
|
||||
useInitialQueryAndSort,
|
||||
useUpdateQueryAndSort,
|
||||
} from '../hooks/use-sort-and-query-params'
|
||||
import { ContractsGrid } from './contract/contracts-list'
|
||||
import { Row } from './layout/row'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Spacer } from './layout/spacer'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ENV } from 'common/envs/constants'
|
||||
|
||||
const searchClient = algoliasearch(
|
||||
'GJQPAYENIF',
|
||||
'75c28fc084a80e1129d427d470cf41a3'
|
||||
)
|
||||
|
||||
const indexPrefix = ENV === 'DEV' ? 'dev-' : ''
|
||||
console.log('env', ENV, indexPrefix)
|
||||
|
||||
const sortIndexes = [
|
||||
{ label: 'Newest', value: indexPrefix + 'contracts-newest' },
|
||||
{ label: 'Oldest', value: indexPrefix + 'contracts-oldest' },
|
||||
{ label: 'Most traded', value: indexPrefix + 'contracts-most-traded' },
|
||||
{ label: '24h volume', value: indexPrefix + 'contracts-24-hour-vol' },
|
||||
{ label: 'Close date', value: indexPrefix + 'contracts-close-date' },
|
||||
{ label: 'Resolve date', value: indexPrefix + 'contracts-resolve-date' },
|
||||
]
|
||||
|
||||
type filter = 'open' | 'closed' | 'resolved' | 'all'
|
||||
|
||||
export function ContractSearch(props: {
|
||||
querySortOptions?: {
|
||||
defaultSort: Sort
|
||||
filter?: {
|
||||
creatorId?: string
|
||||
tag?: string
|
||||
}
|
||||
shouldLoadFromStorage?: boolean
|
||||
}
|
||||
}) {
|
||||
const { querySortOptions } = props
|
||||
|
||||
const { initialSort } = useInitialQueryAndSort(querySortOptions)
|
||||
|
||||
const sort = sortIndexes
|
||||
.map(({ value }) => value)
|
||||
.includes(`${indexPrefix}contracts-${initialSort ?? ''}`)
|
||||
? initialSort
|
||||
: querySortOptions?.defaultSort
|
||||
|
||||
const [filter, setFilter] = useState<filter>('open')
|
||||
|
||||
if (!sort) return <></>
|
||||
return (
|
||||
<InstantSearch
|
||||
searchClient={searchClient}
|
||||
indexName={`${indexPrefix}contracts-${sort}`}
|
||||
>
|
||||
<Row className="flex-wrap gap-2">
|
||||
<SearchBox
|
||||
className="flex-1"
|
||||
classNames={{
|
||||
form: 'before:top-6',
|
||||
input: '!pl-10 !input !input-bordered shadow-none',
|
||||
resetIcon: 'mt-2',
|
||||
}}
|
||||
placeholder="Search markets"
|
||||
/>
|
||||
<Row className="mt-2 gap-2 sm:mt-0">
|
||||
<select
|
||||
className="!select !select-bordered"
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value as filter)}
|
||||
>
|
||||
<option value="open">Open</option>
|
||||
<option value="closed">Closed</option>
|
||||
<option value="resolved">Resolved</option>
|
||||
<option value="all">All</option>
|
||||
</select>
|
||||
<SortBy
|
||||
items={sortIndexes}
|
||||
classNames={{
|
||||
select: '!select !select-bordered',
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
</Row>
|
||||
<ContractSearchInner
|
||||
querySortOptions={querySortOptions}
|
||||
filter={filter}
|
||||
/>
|
||||
</InstantSearch>
|
||||
)
|
||||
}
|
||||
|
||||
export function ContractSearchInner(props: {
|
||||
querySortOptions?: {
|
||||
defaultSort: Sort
|
||||
filter?: {
|
||||
creatorId?: string
|
||||
tag?: string
|
||||
}
|
||||
shouldLoadFromStorage?: boolean
|
||||
}
|
||||
filter: filter
|
||||
}) {
|
||||
const { querySortOptions, filter } = props
|
||||
const { initialQuery } = useInitialQueryAndSort(querySortOptions)
|
||||
|
||||
const { query, setQuery, setSort } = useUpdateQueryAndSort({
|
||||
shouldLoadFromStorage: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
console.log('initial query', initialQuery)
|
||||
setQuery(initialQuery)
|
||||
}, [initialQuery])
|
||||
|
||||
const { currentRefinement: index } = useSortBy({
|
||||
items: [],
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
console.log('setting query', query)
|
||||
setQuery(query)
|
||||
}, [query])
|
||||
|
||||
useEffect(() => {
|
||||
console.log('effect sort', 'curr', index)
|
||||
const sort = index.split('contracts-')[1] as Sort
|
||||
if (sort) {
|
||||
setSort(sort)
|
||||
}
|
||||
}, [index])
|
||||
|
||||
const creatorId = querySortOptions?.filter?.creatorId
|
||||
useFilterCreator(creatorId)
|
||||
|
||||
const tag = querySortOptions?.filter?.tag
|
||||
useFilterTag(tag)
|
||||
|
||||
useFilterClosed(
|
||||
filter === 'closed'
|
||||
? true
|
||||
: filter === 'all' || filter === 'resolved'
|
||||
? undefined
|
||||
: false
|
||||
)
|
||||
useFilterResolved(
|
||||
filter === 'resolved' ? true : filter === 'all' ? undefined : false
|
||||
)
|
||||
|
||||
const { showMore, hits, isLastPage } = useInfiniteHits()
|
||||
const contracts = hits as any as Contract[]
|
||||
|
||||
const router = useRouter()
|
||||
const hasLoaded = contracts.length > 0 || router.isReady
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Spacer h={8} />
|
||||
|
||||
{hasLoaded && (
|
||||
<ContractsGrid
|
||||
contracts={contracts}
|
||||
loadMore={showMore}
|
||||
hasMore={!isLastPage}
|
||||
showCloseTime={index === 'contracts-closing-soon'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const useFilterCreator = (creatorId: string | undefined) => {
|
||||
const { refine } = useRefinementList({ attribute: 'creatorId' })
|
||||
useEffect(() => {
|
||||
if (creatorId) refine(creatorId)
|
||||
}, [creatorId, refine])
|
||||
}
|
||||
|
||||
const useFilterTag = (tag: string | undefined) => {
|
||||
const { refine } = useRefinementList({ attribute: 'lowercaseTags' })
|
||||
useEffect(() => {
|
||||
if (tag) refine(tag.toLowerCase())
|
||||
}, [tag, refine])
|
||||
}
|
||||
|
||||
const useFilterClosed = (value: boolean | undefined) => {
|
||||
const [now] = useState(Date.now())
|
||||
useRange({
|
||||
attribute: 'closeTime',
|
||||
min: value === false ? now : undefined,
|
||||
max: value ? now : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const useFilterResolved = (value: boolean | undefined) => {
|
||||
// Note (James): I don't know why this works.
|
||||
const { refine: refineResolved } = useToggleRefinement({
|
||||
attribute: value === undefined ? 'non-existant-field' : 'isResolved',
|
||||
on: true,
|
||||
off: value === undefined ? undefined : false,
|
||||
})
|
||||
useEffect(() => {
|
||||
refineResolved({ isRefined: !value })
|
||||
}, [value])
|
||||
}
|
|
@ -1,41 +1,19 @@
|
|||
import _ from 'lodash'
|
||||
import Link from 'next/link'
|
||||
import clsx from 'clsx'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import {
|
||||
contractMetrics,
|
||||
Contract,
|
||||
listContracts,
|
||||
getBinaryProb,
|
||||
} from 'web/lib/firebase/contracts'
|
||||
import { User } from 'web/lib/firebase/users'
|
||||
import { Contract } from '../../lib/firebase/contracts'
|
||||
import { User } from '../../lib/firebase/users'
|
||||
import { Col } from '../layout/col'
|
||||
import { SiteLink } from '../site-link'
|
||||
import { ContractCard } from './contract-card'
|
||||
import {
|
||||
Sort,
|
||||
useQueryAndSortParams,
|
||||
} from 'web/hooks/use-sort-and-query-params'
|
||||
import { Answer } from 'common/answer'
|
||||
import { LoadingIndicator } from '../loading-indicator'
|
||||
import { ContractSearch } from '../contract-search'
|
||||
|
||||
export function ContractsGrid(props: {
|
||||
contracts: Contract[]
|
||||
showHotVolume?: boolean
|
||||
loadMore: () => void
|
||||
hasMore: boolean
|
||||
showCloseTime?: boolean
|
||||
}) {
|
||||
const { showCloseTime } = props
|
||||
const PAGE_SIZE = 100
|
||||
const [page, setPage] = useState(1)
|
||||
|
||||
const [resolvedContracts, activeContracts] = _.partition(
|
||||
props.contracts,
|
||||
(c) => c.isResolved
|
||||
)
|
||||
const allContracts = [...activeContracts, ...resolvedContracts]
|
||||
const showMore = allContracts.length > PAGE_SIZE * page
|
||||
const contracts = allContracts.slice(0, PAGE_SIZE * page)
|
||||
const { contracts, showCloseTime, hasMore, loadMore } = props
|
||||
|
||||
if (contracts.length === 0) {
|
||||
return (
|
||||
|
@ -49,306 +27,38 @@ export function ContractsGrid(props: {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Col className="gap-8">
|
||||
<ul className="grid w-full grid-cols-1 gap-6 md:grid-cols-2">
|
||||
{contracts.map((contract) => (
|
||||
<ContractCard
|
||||
contract={contract}
|
||||
key={contract.id}
|
||||
// showHotVolume={showHotVolume}
|
||||
showCloseTime={showCloseTime}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{/* Show a link that increases the page num when clicked */}
|
||||
{showMore && (
|
||||
{hasMore && (
|
||||
<button
|
||||
className="btn btn-link float-right normal-case"
|
||||
onClick={() => setPage(page + 1)}
|
||||
className="btn btn-primary self-center normal-case"
|
||||
onClick={loadMore}
|
||||
>
|
||||
Show more...
|
||||
Show more
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const MAX_GROUPED_CONTRACTS_DISPLAYED = 6
|
||||
|
||||
function CreatorContractsGrid(props: { contracts: Contract[] }) {
|
||||
const { contracts } = props
|
||||
|
||||
const byCreator = _.groupBy(contracts, (contract) => contract.creatorId)
|
||||
const creator7DayVol = _.mapValues(byCreator, (contracts) =>
|
||||
_.sumBy(contracts, (contract) => contract.volume7Days)
|
||||
)
|
||||
const creatorIds = _.sortBy(
|
||||
Object.keys(byCreator),
|
||||
(creatorId) => -1 * creator7DayVol[creatorId]
|
||||
)
|
||||
|
||||
let numContracts = 0
|
||||
let maxIndex = 0
|
||||
for (; maxIndex < creatorIds.length; maxIndex++) {
|
||||
numContracts += Math.min(
|
||||
MAX_GROUPED_CONTRACTS_DISPLAYED,
|
||||
byCreator[creatorIds[maxIndex]].length
|
||||
)
|
||||
if (numContracts > MAX_CONTRACTS_DISPLAYED) break
|
||||
}
|
||||
|
||||
const creatorIdsSubset = creatorIds.slice(0, maxIndex)
|
||||
|
||||
return (
|
||||
<Col className="gap-6">
|
||||
{creatorIdsSubset.map((creatorId) => {
|
||||
const { creatorUsername, creatorName } = byCreator[creatorId][0]
|
||||
|
||||
return (
|
||||
<Col className="gap-4" key={creatorUsername}>
|
||||
<SiteLink className="text-lg" href={`/${creatorUsername}`}>
|
||||
{creatorName}
|
||||
</SiteLink>
|
||||
|
||||
<ul role="list" className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
{byCreator[creatorId]
|
||||
.slice(0, MAX_GROUPED_CONTRACTS_DISPLAYED)
|
||||
.map((contract) => (
|
||||
<ContractCard contract={contract} key={contract.id} />
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{byCreator[creatorId].length > MAX_GROUPED_CONTRACTS_DISPLAYED ? (
|
||||
<Link href={`/${creatorUsername}`}>
|
||||
<a
|
||||
className={clsx(
|
||||
'self-end hover:underline hover:decoration-indigo-400 hover:decoration-2'
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
See all
|
||||
</a>
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function TagContractsGrid(props: { contracts: Contract[] }) {
|
||||
const { contracts } = props
|
||||
|
||||
const contractTags = _.flatMap(contracts, (contract) => {
|
||||
const { tags } = contract
|
||||
return tags.map((tag) => ({
|
||||
tag,
|
||||
contract,
|
||||
}))
|
||||
})
|
||||
const groupedByTag = _.groupBy(contractTags, ({ tag }) => tag)
|
||||
const byTag = _.mapValues(groupedByTag, (contractTags) =>
|
||||
contractTags.map(({ contract }) => contract)
|
||||
)
|
||||
const tag7DayVol = _.mapValues(byTag, (contracts) =>
|
||||
_.sumBy(contracts, (contract) => contract.volume7Days)
|
||||
)
|
||||
const tags = _.sortBy(
|
||||
Object.keys(byTag),
|
||||
(creatorId) => -1 * tag7DayVol[creatorId]
|
||||
)
|
||||
|
||||
let numContracts = 0
|
||||
let maxIndex = 0
|
||||
for (; maxIndex < tags.length; maxIndex++) {
|
||||
numContracts += Math.min(
|
||||
MAX_GROUPED_CONTRACTS_DISPLAYED,
|
||||
byTag[tags[maxIndex]].length
|
||||
)
|
||||
if (numContracts > MAX_CONTRACTS_DISPLAYED) break
|
||||
}
|
||||
|
||||
const tagsSubset = tags.slice(0, maxIndex)
|
||||
export function CreatorContractsList(props: { creator: User }) {
|
||||
const { creator } = props
|
||||
|
||||
return (
|
||||
<Col className="gap-6">
|
||||
{tagsSubset.map((tag) => {
|
||||
return (
|
||||
<Col className="gap-4" key={tag}>
|
||||
<SiteLink className="text-lg" href={`/tag/${tag}`}>
|
||||
#{tag}
|
||||
</SiteLink>
|
||||
|
||||
<ul role="list" className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
{byTag[tag]
|
||||
.slice(0, MAX_GROUPED_CONTRACTS_DISPLAYED)
|
||||
.map((contract) => (
|
||||
<ContractCard contract={contract} key={contract.id} />
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{byTag[tag].length > MAX_GROUPED_CONTRACTS_DISPLAYED ? (
|
||||
<Link href={`/tag/${tag}`}>
|
||||
<a
|
||||
className={clsx(
|
||||
'self-end hover:underline hover:decoration-indigo-400 hover:decoration-2'
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
See all
|
||||
</a>
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
const MAX_CONTRACTS_DISPLAYED = 99
|
||||
|
||||
export function SearchableGrid(props: {
|
||||
contracts: Contract[] | undefined
|
||||
byOneCreator?: boolean
|
||||
querySortOptions?: {
|
||||
defaultSort: Sort
|
||||
shouldLoadFromStorage?: boolean
|
||||
}
|
||||
}) {
|
||||
const { contracts, byOneCreator, querySortOptions } = props
|
||||
|
||||
const { query, setQuery, sort, setSort } =
|
||||
useQueryAndSortParams(querySortOptions)
|
||||
|
||||
const queryWords = query.toLowerCase().split(' ')
|
||||
function check(corpus: String) {
|
||||
return queryWords.every((word) => corpus.toLowerCase().includes(word))
|
||||
}
|
||||
|
||||
let matches = (contracts ?? []).filter(
|
||||
(c) =>
|
||||
check(c.question) ||
|
||||
check(c.description) ||
|
||||
check(c.creatorName) ||
|
||||
check(c.creatorUsername) ||
|
||||
check(c.lowercaseTags.map((tag) => `#${tag}`).join(' ')) ||
|
||||
check(
|
||||
((c as any).answers ?? [])
|
||||
.map((answer: Answer) => answer.text)
|
||||
.join(' ')
|
||||
)
|
||||
)
|
||||
|
||||
if (sort === 'newest' || sort === 'all') {
|
||||
matches.sort((a, b) => b.createdTime - a.createdTime)
|
||||
} else if (sort === 'resolved') {
|
||||
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' || sort === 'closed') {
|
||||
matches = _.sortBy(matches, ({ volume24Hours }) => -1 * volume24Hours)
|
||||
matches = _.sortBy(
|
||||
matches,
|
||||
(contract) =>
|
||||
(sort === 'closed' ? -1 : 1) * (contract.closeTime ?? Infinity)
|
||||
)
|
||||
const hideClosed = sort === 'closed'
|
||||
matches = matches.filter(
|
||||
({ closeTime }) => closeTime && closeTime > Date.now() !== hideClosed
|
||||
)
|
||||
} else if (sort === 'most-traded') {
|
||||
matches.sort((a, b) => b.volume - a.volume)
|
||||
} else if (sort === '24-hour-vol') {
|
||||
// Use lodash for stable sort, so previous sort breaks all ties.
|
||||
matches = _.sortBy(matches, ({ volume7Days }) => -1 * volume7Days)
|
||||
matches = _.sortBy(matches, ({ volume24Hours }) => -1 * volume24Hours)
|
||||
} else if (sort === 'creator' || sort === 'tag') {
|
||||
matches.sort((a, b) => b.volume7Days - a.volume7Days)
|
||||
} else if (sort === 'most-likely') {
|
||||
matches = _.sortBy(matches, (contract) => -getBinaryProb(contract))
|
||||
} else if (sort === 'least-likely') {
|
||||
// Exclude non-binary contracts
|
||||
matches = matches.filter((contract) => getBinaryProb(contract) !== 0)
|
||||
matches = _.sortBy(matches, (contract) => getBinaryProb(contract))
|
||||
}
|
||||
|
||||
if (sort !== 'all') {
|
||||
// Filter for (or filter out) resolved contracts
|
||||
matches = matches.filter((c) =>
|
||||
sort === 'resolved' ? c.resolution : !c.resolution
|
||||
)
|
||||
|
||||
// Filter out closed contracts.
|
||||
if (sort !== 'closed' && sort !== 'resolved') {
|
||||
matches = matches.filter((c) => !c.closeTime || c.closeTime > Date.now())
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Show a search input next to a sort dropdown */}
|
||||
<div className="mt-2 mb-8 flex justify-between gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search markets"
|
||||
className="input input-bordered w-full"
|
||||
/>
|
||||
<select
|
||||
className="select select-bordered"
|
||||
value={sort}
|
||||
onChange={(e) => setSort(e.target.value as Sort)}
|
||||
>
|
||||
<option value="most-traded">Most traded</option>
|
||||
<option value="24-hour-vol">24h volume</option>
|
||||
<option value="close-date">Closing soon</option>
|
||||
<option value="closed">Closed</option>
|
||||
<option value="newest">Newest</option>
|
||||
<option value="oldest">Oldest</option>
|
||||
<option value="most-likely">Most likely</option>
|
||||
<option value="least-likely">Least likely</option>
|
||||
|
||||
<option value="tag">By tag</option>
|
||||
{!byOneCreator && <option value="creator">By creator</option>}
|
||||
<option value="resolved">Resolved</option>
|
||||
{byOneCreator && <option value="all">All markets</option>}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{contracts === undefined ? (
|
||||
<LoadingIndicator />
|
||||
) : sort === 'tag' ? (
|
||||
<TagContractsGrid contracts={matches} />
|
||||
) : !byOneCreator && sort === 'creator' ? (
|
||||
<CreatorContractsGrid contracts={matches} />
|
||||
) : (
|
||||
<ContractsGrid
|
||||
contracts={matches}
|
||||
showCloseTime={['close-date', 'closed'].includes(sort)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CreatorContractsList(props: { contracts: Contract[] }) {
|
||||
const { contracts } = props
|
||||
return (
|
||||
<SearchableGrid
|
||||
contracts={contracts}
|
||||
byOneCreator
|
||||
<ContractSearch
|
||||
querySortOptions={{
|
||||
defaultSort: 'all',
|
||||
filter: {
|
||||
creatorId: creator.id,
|
||||
},
|
||||
defaultSort: 'newest',
|
||||
shouldLoadFromStorage: false,
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import _ from 'lodash'
|
||||
import { SparklesIcon, XIcon } from '@heroicons/react/solid'
|
||||
import { Avatar } from './avatar'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
@ -9,8 +10,7 @@ import { Contract, MAX_QUESTION_LENGTH } from 'common/contract'
|
|||
import { Col } from './layout/col'
|
||||
import clsx from 'clsx'
|
||||
import { Row } from './layout/row'
|
||||
import { ENV_CONFIG } from 'common/envs/constants'
|
||||
import _ from 'lodash'
|
||||
import { ENV_CONFIG } from '../../common/envs/constants'
|
||||
import { SiteLink } from './site-link'
|
||||
|
||||
export function FeedPromo(props: { hotContracts: Contract[] }) {
|
||||
|
@ -50,7 +50,8 @@ export function FeedPromo(props: { hotContracts: Contract[] }) {
|
|||
</Row>
|
||||
<ContractsGrid
|
||||
contracts={hotContracts?.slice(0, 10) || []}
|
||||
showHotVolume
|
||||
loadMore={() => {}}
|
||||
hasMore={false}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -201,7 +201,7 @@ export function UserPage(props: {
|
|||
tabs={[
|
||||
{
|
||||
title: 'Markets',
|
||||
content: <CreatorContractsList contracts={usersContracts} />,
|
||||
content: <CreatorContractsList creator={user} />,
|
||||
tabIcon: (
|
||||
<div className="px-0.5 font-bold">
|
||||
{usersContracts.length}
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import _ from 'lodash'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useSearchBox } from 'react-instantsearch-hooks-web'
|
||||
|
||||
const MARKETS_SORT = 'markets_sort'
|
||||
|
||||
export type Sort =
|
||||
| 'creator'
|
||||
| 'tag'
|
||||
| 'newest'
|
||||
| 'oldest'
|
||||
| 'most-traded'
|
||||
| '24-hour-vol'
|
||||
| 'close-date'
|
||||
| 'closing-soon'
|
||||
| 'closed'
|
||||
| 'resolved'
|
||||
| 'all'
|
||||
|
||||
export function useQueryAndSortParams(options?: {
|
||||
export function useInitialQueryAndSort(options?: {
|
||||
defaultSort: Sort
|
||||
shouldLoadFromStorage?: boolean
|
||||
}) {
|
||||
|
@ -26,24 +24,58 @@ export function useQueryAndSortParams(options?: {
|
|||
})
|
||||
const router = useRouter()
|
||||
|
||||
const [initialSort, setInitialSort] = useState<Sort | undefined>(undefined)
|
||||
const [initialQuery, setInitialQuery] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
// If there's no sort option, then set the one from localstorage
|
||||
if (router.isReady) {
|
||||
const { s: sort, q: query } = router.query as {
|
||||
q?: string
|
||||
s?: Sort
|
||||
}
|
||||
|
||||
setInitialQuery(query ?? '')
|
||||
|
||||
if (!sort && shouldLoadFromStorage) {
|
||||
console.log('ready loading from storage ', sort ?? defaultSort)
|
||||
const localSort = localStorage.getItem(MARKETS_SORT) as Sort
|
||||
if (localSort) {
|
||||
router.query.s = localSort
|
||||
// Use replace to not break navigating back.
|
||||
router.replace(router, undefined, { shallow: true })
|
||||
}
|
||||
setInitialSort(localSort ?? defaultSort)
|
||||
} else {
|
||||
console.log('ready setting to ', sort ?? defaultSort)
|
||||
setInitialSort(sort ?? defaultSort)
|
||||
}
|
||||
}
|
||||
}, [defaultSort, router.isReady, shouldLoadFromStorage])
|
||||
|
||||
return {
|
||||
initialSort,
|
||||
initialQuery,
|
||||
}
|
||||
}
|
||||
|
||||
export function useUpdateQueryAndSort(props: {
|
||||
shouldLoadFromStorage: boolean
|
||||
}) {
|
||||
const { shouldLoadFromStorage } = props
|
||||
const router = useRouter()
|
||||
|
||||
const setSort = (sort: Sort | undefined) => {
|
||||
if (sort !== router.query.s) {
|
||||
router.query.s = sort
|
||||
router.push(router, undefined, { shallow: true })
|
||||
if (shouldLoadFromStorage) {
|
||||
localStorage.setItem(MARKETS_SORT, sort || '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [queryState, setQueryState] = useState(query)
|
||||
|
||||
useEffect(() => {
|
||||
setQueryState(query)
|
||||
}, [query])
|
||||
const { query, refine } = useSearchBox()
|
||||
|
||||
// Debounce router query update.
|
||||
const pushQuery = useMemo(
|
||||
|
@ -60,26 +92,13 @@ export function useQueryAndSortParams(options?: {
|
|||
)
|
||||
|
||||
const setQuery = (query: string | undefined) => {
|
||||
setQueryState(query)
|
||||
refine(query ?? '')
|
||||
pushQuery(query)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// If there's no sort option, then set the one from localstorage
|
||||
if (router.isReady && !sort && shouldLoadFromStorage) {
|
||||
const localSort = localStorage.getItem(MARKETS_SORT) as Sort
|
||||
if (localSort) {
|
||||
router.query.s = localSort
|
||||
// Use replace to not break navigating back.
|
||||
router.replace(router, undefined, { shallow: true })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
sort: sort ?? defaultSort,
|
||||
query: queryState ?? '',
|
||||
setSort,
|
||||
setQuery,
|
||||
query,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"@heroicons/react": "1.0.5",
|
||||
"@nivo/core": "0.74.0",
|
||||
"@nivo/line": "0.74.0",
|
||||
"algoliasearch": "4.13.0",
|
||||
"clsx": "1.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"daisyui": "1.16.4",
|
||||
|
@ -31,9 +32,10 @@
|
|||
"lodash": "4.17.21",
|
||||
"next": "12.1.2",
|
||||
"react": "17.0.2",
|
||||
"react-confetti": "^6.0.1",
|
||||
"react-confetti": "6.0.1",
|
||||
"react-dom": "17.0.2",
|
||||
"react-expanding-textarea": "2.3.5"
|
||||
"react-expanding-textarea": "2.3.5",
|
||||
"react-instantsearch-hooks-web": "6.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "0.4.0",
|
||||
|
|
|
@ -18,6 +18,13 @@ export default function Document() {
|
|||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/instantsearch.css@7.4.5/themes/satellite-min.css"
|
||||
integrity="sha256-TehzF/2QvNKhGQrrNpoOb2Ck4iGZ1J/DI4pkd2oUsBc="
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-SSFK1Q138D"
|
||||
|
|
|
@ -10,17 +10,15 @@ import {
|
|||
foldPath,
|
||||
getFoldBySlug,
|
||||
getFoldContracts,
|
||||
} from 'web/lib/firebase/folds'
|
||||
import { ActivityFeed } from 'web/components/feed/activity-feed'
|
||||
import { TagsList } from 'web/components/tags-list'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { UserLink } from 'web/components/user-page'
|
||||
import { getUser, User } from 'web/lib/firebase/users'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { useFold } from 'web/hooks/use-fold'
|
||||
import { SearchableGrid } from 'web/components/contract/contracts-list'
|
||||
} from '../../../lib/firebase/folds'
|
||||
import { TagsList } from '../../../components/tags-list'
|
||||
import { Row } from '../../../components/layout/row'
|
||||
import { UserLink } from '../../../components/user-page'
|
||||
import { getUser, User } from '../../../lib/firebase/users'
|
||||
import { Spacer } from '../../../components/layout/spacer'
|
||||
import { Col } from '../../../components/layout/col'
|
||||
import { useUser } from '../../../hooks/use-user'
|
||||
import { useFold } from '../../../hooks/use-fold'
|
||||
import { useRouter } from 'next/router'
|
||||
import { scoreCreators, scoreTraders } from 'common/scoring'
|
||||
import { Leaderboard } from 'web/components/leaderboard'
|
||||
|
@ -209,7 +207,7 @@ export default function FoldPage(props: {
|
|||
tabs={[
|
||||
{
|
||||
title: 'Markets',
|
||||
content: <SearchableGrid contracts={contracts} />,
|
||||
content: <div>This view is deprecated.</div>,
|
||||
href: foldPath(fold, 'markets'),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@ export default function LandingPage(props: { hotContracts: Contract[] }) {
|
|||
<div>
|
||||
<Hero />
|
||||
<FeaturesSection />
|
||||
<ExploreMarketsSection hotContracts={hotContracts} />
|
||||
{/* <ExploreMarketsSection hotContracts={hotContracts} /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -158,7 +158,11 @@ function ExploreMarketsSection(props: { hotContracts: Contract[] }) {
|
|||
Today's top markets
|
||||
</p>
|
||||
|
||||
<ContractsGrid contracts={hotContracts} />
|
||||
<ContractsGrid
|
||||
contracts={hotContracts}
|
||||
loadMore={() => {}}
|
||||
hasMore={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
import {
|
||||
ContractsGrid,
|
||||
SearchableGrid,
|
||||
} from 'web/components/contract/contracts-list'
|
||||
import { Page } from 'web/components/page'
|
||||
import { SEO } from 'web/components/SEO'
|
||||
import { Title } from 'web/components/title'
|
||||
import { useContracts } from 'web/hooks/use-contracts'
|
||||
import { Contract } from 'web/lib/firebase/contracts'
|
||||
import { ContractSearch } from '../components/contract-search'
|
||||
import { Page } from '../components/page'
|
||||
import { SEO } from '../components/SEO'
|
||||
|
||||
// TODO: Rename endpoint to "Explore"
|
||||
export default function Markets() {
|
||||
const contracts = useContracts()
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<SEO
|
||||
|
@ -19,31 +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"
|
||||
/>
|
||||
<SearchableGrid contracts={contracts} />
|
||||
<ContractSearch />
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export const HotMarkets = (props: { contracts: Contract[] }) => {
|
||||
const { contracts } = props
|
||||
if (contracts.length === 0) return <></>
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-lg border-2 border-indigo-100 bg-indigo-50 p-6 shadow-md">
|
||||
<Title className="!mt-0" text="🔥 Markets" />
|
||||
<ContractsGrid contracts={contracts} showHotVolume />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ClosingSoonMarkets = (props: { contracts: Contract[] }) => {
|
||||
const { contracts } = props
|
||||
if (contracts.length === 0) return <></>
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-lg border-2 border-green-100 bg-green-50 p-6 shadow-md">
|
||||
<Title className="!mt-0" text="⏰ Closing soon" />
|
||||
<ContractsGrid contracts={contracts} showCloseTime />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { SearchableGrid } from 'web/components/contract/contracts-list'
|
||||
import { Page } from 'web/components/page'
|
||||
import { Title } from 'web/components/title'
|
||||
import {
|
||||
Contract,
|
||||
listTaggedContractsCaseInsensitive,
|
||||
} from 'web/lib/firebase/contracts'
|
||||
import { ContractSearch } from '../../components/contract-search'
|
||||
import { Page } from '../../components/page'
|
||||
import { Title } from '../../components/title'
|
||||
|
||||
export default function TagPage() {
|
||||
const router = useRouter()
|
||||
const { tag } = router.query as { tag: string }
|
||||
|
||||
// mqp: i wrote this in a panic to make the page literally work at all so if you
|
||||
// want to e.g. listen for new contracts you may want to fix it up
|
||||
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
||||
useEffect(() => {
|
||||
if (tag != null) {
|
||||
listTaggedContractsCaseInsensitive(tag).then(setContracts)
|
||||
}
|
||||
}, [tag])
|
||||
|
||||
// TODO: Fix error: The provided `href` (/tag/[tag]?s=newest) value is missing query values (tag)
|
||||
return (
|
||||
<Page>
|
||||
<Title text={`#${tag}`} />
|
||||
<SearchableGrid contracts={contracts} />
|
||||
<ContractSearch
|
||||
querySortOptions={{
|
||||
filter: { tag },
|
||||
defaultSort: 'newest',
|
||||
shouldLoadFromStorage: false,
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
|
242
yarn.lock
242
yarn.lock
|
@ -2,6 +2,115 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@algolia/cache-browser-local-storage@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.13.0.tgz#f8aa4fe31104b19d616ea392f9ed5c2ea847d964"
|
||||
integrity sha512-nj1vHRZauTqP/bluwkRIgEADEimqojJgoTRCel5f6q8WCa9Y8QeI4bpDQP28FoeKnDRYa3J5CauDlN466jqRhg==
|
||||
dependencies:
|
||||
"@algolia/cache-common" "4.13.0"
|
||||
|
||||
"@algolia/cache-common@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.13.0.tgz#27b83fd3939d08d72261b36a07eeafc4cb4d2113"
|
||||
integrity sha512-f9mdZjskCui/dA/fA/5a+6hZ7xnHaaZI5tM/Rw9X8rRB39SUlF/+o3P47onZ33n/AwkpSbi5QOyhs16wHd55kA==
|
||||
|
||||
"@algolia/cache-in-memory@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.13.0.tgz#10801a74550cbabb64b59ff08c56bce9c278ff2d"
|
||||
integrity sha512-hHdc+ahPiMM92CQMljmObE75laYzNFYLrNOu0Q3/eyvubZZRtY2SUsEEgyUEyzXruNdzrkcDxFYa7YpWBJYHAg==
|
||||
dependencies:
|
||||
"@algolia/cache-common" "4.13.0"
|
||||
|
||||
"@algolia/client-account@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.13.0.tgz#f8646dd40d1e9e3353e10abbd5d6c293ea92a8e2"
|
||||
integrity sha512-FzFqFt9b0g/LKszBDoEsW+dVBuUe1K3scp2Yf7q6pgHWM1WqyqUlARwVpLxqyc+LoyJkTxQftOKjyFUqddnPKA==
|
||||
dependencies:
|
||||
"@algolia/client-common" "4.13.0"
|
||||
"@algolia/client-search" "4.13.0"
|
||||
"@algolia/transporter" "4.13.0"
|
||||
|
||||
"@algolia/client-analytics@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.13.0.tgz#a00bd02df45d71becb9dd4c5c993d805f2e1786d"
|
||||
integrity sha512-klmnoq2FIiiMHImkzOm+cGxqRLLu9CMHqFhbgSy9wtXZrqb8BBUIUE2VyBe7azzv1wKcxZV2RUyNOMpFqmnRZA==
|
||||
dependencies:
|
||||
"@algolia/client-common" "4.13.0"
|
||||
"@algolia/client-search" "4.13.0"
|
||||
"@algolia/requester-common" "4.13.0"
|
||||
"@algolia/transporter" "4.13.0"
|
||||
|
||||
"@algolia/client-common@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.13.0.tgz#8bc373d164dbdcce38b4586912bbe162492bcb86"
|
||||
integrity sha512-GoXfTp0kVcbgfSXOjfrxx+slSipMqGO9WnNWgeMmru5Ra09MDjrcdunsiiuzF0wua6INbIpBQFTC2Mi5lUNqGA==
|
||||
dependencies:
|
||||
"@algolia/requester-common" "4.13.0"
|
||||
"@algolia/transporter" "4.13.0"
|
||||
|
||||
"@algolia/client-personalization@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.13.0.tgz#10fb7af356422551f11a67222b39c52306f1512c"
|
||||
integrity sha512-KneLz2WaehJmNfdr5yt2HQETpLaCYagRdWwIwkTqRVFCv4DxRQ2ChPVW9jeTj4YfAAhfzE6F8hn7wkQ/Jfj6ZA==
|
||||
dependencies:
|
||||
"@algolia/client-common" "4.13.0"
|
||||
"@algolia/requester-common" "4.13.0"
|
||||
"@algolia/transporter" "4.13.0"
|
||||
|
||||
"@algolia/client-search@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.13.0.tgz#2d8ff8e755c4a37ec89968f3f9b358eed005c7f0"
|
||||
integrity sha512-blgCKYbZh1NgJWzeGf+caKE32mo3j54NprOf0LZVCubQb3Kx37tk1Hc8SDs9bCAE8hUvf3cazMPIg7wscSxspA==
|
||||
dependencies:
|
||||
"@algolia/client-common" "4.13.0"
|
||||
"@algolia/requester-common" "4.13.0"
|
||||
"@algolia/transporter" "4.13.0"
|
||||
|
||||
"@algolia/events@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950"
|
||||
integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==
|
||||
|
||||
"@algolia/logger-common@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.13.0.tgz#be2606e71aae618a1ff1ea9a1b5f5a74284b35a8"
|
||||
integrity sha512-8yqXk7rMtmQJ9wZiHOt/6d4/JDEg5VCk83gJ39I+X/pwUPzIsbKy9QiK4uJ3aJELKyoIiDT1hpYVt+5ia+94IA==
|
||||
|
||||
"@algolia/logger-console@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.13.0.tgz#f28028a760e3d9191e28a10b12925e48f6c9afde"
|
||||
integrity sha512-YepRg7w2/87L0vSXRfMND6VJ5d6699sFJBRWzZPOlek2p5fLxxK7O0VncYuc/IbVHEgeApvgXx0WgCEa38GVuQ==
|
||||
dependencies:
|
||||
"@algolia/logger-common" "4.13.0"
|
||||
|
||||
"@algolia/requester-browser-xhr@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.13.0.tgz#e2483f4e8d7f09e27cd0daf6c77711d15c5a919f"
|
||||
integrity sha512-Dj+bnoWR5MotrnjblzGKZ2kCdQi2cK/VzPURPnE616NU/il7Ypy6U6DLGZ/ZYz+tnwPa0yypNf21uqt84fOgrg==
|
||||
dependencies:
|
||||
"@algolia/requester-common" "4.13.0"
|
||||
|
||||
"@algolia/requester-common@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.13.0.tgz#47fb3464cfb26b55ba43676d13f295d812830596"
|
||||
integrity sha512-BRTDj53ecK+gn7ugukDWOOcBRul59C4NblCHqj4Zm5msd5UnHFjd/sGX+RLOEoFMhetILAnmg6wMrRrQVac9vw==
|
||||
|
||||
"@algolia/requester-node-http@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.13.0.tgz#7d981bbd31492f51dd11820a665f9d8906793c37"
|
||||
integrity sha512-9b+3O4QFU4azLhGMrZAr/uZPydvzOR4aEZfSL8ZrpLZ7fbbqTO0S/5EVko+QIgglRAtVwxvf8UJ1wzTD2jvKxQ==
|
||||
dependencies:
|
||||
"@algolia/requester-common" "4.13.0"
|
||||
|
||||
"@algolia/transporter@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.13.0.tgz#f6379e5329efa2127da68c914d1141f5f21dbd07"
|
||||
integrity sha512-8tSQYE+ykQENAdeZdofvtkOr5uJ9VcQSWgRhQ9h01AehtBIPAczk/b2CLrMsw5yQZziLs5cZ3pJ3478yI+urhA==
|
||||
dependencies:
|
||||
"@algolia/cache-common" "4.13.0"
|
||||
"@algolia/logger-common" "4.13.0"
|
||||
"@algolia/requester-common" "4.13.0"
|
||||
|
||||
"@babel/code-frame@7.12.11":
|
||||
version "7.12.11"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
|
||||
|
@ -38,6 +147,13 @@
|
|||
core-js-pure "^3.20.2"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.1.2":
|
||||
version "7.17.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
|
||||
integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
|
||||
|
@ -988,6 +1104,16 @@
|
|||
"@types/express-serve-static-core" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/google.maps@^3.45.3":
|
||||
version "3.48.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/google.maps/-/google.maps-3.48.7.tgz#2291e7fd41af797d4827c0f19e5609b1c07bf42e"
|
||||
integrity sha512-mssJhNv6jwk+zyh0MpYWWTsL99l84nEz7m+l0p4MY2f2iqdkOYl2kDFdB7g7ME0ih41V0kaZXsjntkq5ul/UyQ==
|
||||
|
||||
"@types/hogan.js@^3.0.0":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hogan.js/-/hogan.js-3.0.1.tgz#64c54407b30da359763e14877f5702b8ae85d61c"
|
||||
integrity sha512-D03i/2OY7kGyMq9wdQ7oD8roE49z/ZCZThe/nbahtvuqCNZY9T2MfedOWyeBdbEpY2W8Gnh/dyJLdFtUCOkYbg==
|
||||
|
||||
"@types/json5@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
|
@ -1041,7 +1167,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
|
||||
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
|
||||
|
||||
"@types/qs@*":
|
||||
"@types/qs@*", "@types/qs@^6.5.3":
|
||||
version "6.9.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
||||
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
|
||||
|
@ -1117,6 +1243,11 @@
|
|||
"@typescript-eslint/types" "4.33.0"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
||||
|
||||
abort-controller@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
|
||||
|
@ -1205,6 +1336,33 @@ ajv@^8.0.1:
|
|||
require-from-string "^2.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
algoliasearch-helper@^3.7.4, algoliasearch-helper@^3.8.2:
|
||||
version "3.8.2"
|
||||
resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.8.2.tgz#35726dc6d211f49dbab0bf6d37b4658165539523"
|
||||
integrity sha512-AXxiF0zT9oYwl8ZBgU/eRXvfYhz7cBA5YrLPlw9inZHdaYF0QEya/f1Zp1mPYMXc1v6VkHwBq4pk6/vayBLICg==
|
||||
dependencies:
|
||||
"@algolia/events" "^4.0.1"
|
||||
|
||||
algoliasearch@4.13.0:
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.13.0.tgz#e36611fda82b1fc548c156ae7929a7f486e4b663"
|
||||
integrity sha512-oHv4faI1Vl2s+YC0YquwkK/TsaJs79g2JFg5FDm2rKN12VItPTAeQ7hyJMHarOPPYuCnNC5kixbtcqvb21wchw==
|
||||
dependencies:
|
||||
"@algolia/cache-browser-local-storage" "4.13.0"
|
||||
"@algolia/cache-common" "4.13.0"
|
||||
"@algolia/cache-in-memory" "4.13.0"
|
||||
"@algolia/client-account" "4.13.0"
|
||||
"@algolia/client-analytics" "4.13.0"
|
||||
"@algolia/client-common" "4.13.0"
|
||||
"@algolia/client-personalization" "4.13.0"
|
||||
"@algolia/client-search" "4.13.0"
|
||||
"@algolia/logger-common" "4.13.0"
|
||||
"@algolia/logger-console" "4.13.0"
|
||||
"@algolia/requester-browser-xhr" "4.13.0"
|
||||
"@algolia/requester-common" "4.13.0"
|
||||
"@algolia/requester-node-http" "4.13.0"
|
||||
"@algolia/transporter" "4.13.0"
|
||||
|
||||
ansi-colors@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||
|
@ -1533,6 +1691,11 @@ chokidar@^3.5.2:
|
|||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
classnames@^2.2.5:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
||||
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||
|
@ -1962,6 +2125,11 @@ depd@~1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
dequal@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
|
||||
integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==
|
||||
|
||||
destroy@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||
|
@ -2985,6 +3153,14 @@ hash-stream-validation@^0.2.2:
|
|||
resolved "https://registry.yarnpkg.com/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz#ee68b41bf822f7f44db1142ec28ba9ee7ccb7512"
|
||||
integrity sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==
|
||||
|
||||
hogan.js@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
|
||||
integrity sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=
|
||||
dependencies:
|
||||
mkdirp "0.3.0"
|
||||
nopt "1.0.10"
|
||||
|
||||
http-errors@1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
|
||||
|
@ -3117,6 +3293,22 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
|
|||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
instantsearch.js@^4.40.1:
|
||||
version "4.40.5"
|
||||
resolved "https://registry.yarnpkg.com/instantsearch.js/-/instantsearch.js-4.40.5.tgz#17b3c2ed493bee80101b3eb9a48187b487d275c3"
|
||||
integrity sha512-6YL4WGJ4mGz+b1mbAd/k+g3foz0W0HhZA7wSz0CeuNoJHnCcCcJAoAFdwLoWGRBKjxV7FoXFou0NRkLWXEmBrg==
|
||||
dependencies:
|
||||
"@algolia/events" "^4.0.1"
|
||||
"@types/google.maps" "^3.45.3"
|
||||
"@types/hogan.js" "^3.0.0"
|
||||
"@types/qs" "^6.5.3"
|
||||
algoliasearch-helper "^3.8.2"
|
||||
classnames "^2.2.5"
|
||||
hogan.js "^3.0.2"
|
||||
preact "^10.6.0"
|
||||
qs "^6.5.1 < 6.10"
|
||||
search-insights "^2.1.0"
|
||||
|
||||
internal-slot@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
|
||||
|
@ -3752,6 +3944,11 @@ minimist@^1.2.6:
|
|||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mkdirp@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
|
||||
integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=
|
||||
|
||||
mri@^1.1.5:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
||||
|
@ -3869,6 +4066,13 @@ node-releases@^2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
|
||||
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
|
||||
|
||||
nopt@1.0.10:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
|
||||
integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=
|
||||
dependencies:
|
||||
abbrev "1"
|
||||
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
|
@ -4249,6 +4453,11 @@ preact@^10.5.12:
|
|||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.6.5.tgz#726d8bd12903a0d51cdd17e2e1b90cc539403e0c"
|
||||
integrity sha512-i+LXM6JiVjQXSt2jG2vZZFapGpCuk1fl8o6ii3G84MA3xgj686FKjs4JFDkmUVhtxyq21+4ay74zqPykz9hU6w==
|
||||
|
||||
preact@^10.6.0:
|
||||
version "10.7.1"
|
||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.7.1.tgz#bdd2b2dce91a5842c3b9b34dfe050e5401068c9e"
|
||||
integrity sha512-MufnRFz39aIhs9AMFisonjzTud1PK1bY+jcJLo6m2T9Uh8AqjD77w11eAAawmjUogoGOnipECq7e/1RClIKsxg==
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
|
@ -4407,6 +4616,11 @@ qs@6.9.6:
|
|||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee"
|
||||
integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==
|
||||
|
||||
"qs@^6.5.1 < 6.10":
|
||||
version "6.9.7"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
|
||||
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
|
||||
|
||||
qs@^6.6.0:
|
||||
version "6.10.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
|
||||
|
@ -4446,7 +4660,7 @@ raw-body@2.4.2, raw-body@^2.2.0:
|
|||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
react-confetti@^6.0.1:
|
||||
react-confetti@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.0.1.tgz#d4f57b5a021dd908a6243b8f63b6009b00818d10"
|
||||
integrity sha512-ZpOTBrqSNhWE4rRXCZ6E6U+wGd7iYHF5MGrqwikoiBpgBq9Akdu0DcLW+FdFnLjyZYC+VfAiV2KeFgYRMyMrkA==
|
||||
|
@ -4471,6 +4685,25 @@ react-expanding-textarea@2.3.5:
|
|||
react-with-forwarded-ref "^0.3.3"
|
||||
tslib "^2.0.3"
|
||||
|
||||
react-instantsearch-hooks-web@6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/react-instantsearch-hooks-web/-/react-instantsearch-hooks-web-6.24.1.tgz#392be70c584583f3cd9fe22eda5a59f7449e5ac9"
|
||||
integrity sha512-UX7zaxDE834LxxUNHB5lBg8ujO0KFyOjg8iRZs+WVEBsYdQskGEAHiLlyaSjcfDZS0tPBsZA/ybYjwpGmOMqKg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
instantsearch.js "^4.40.1"
|
||||
react-instantsearch-hooks "6.24.1"
|
||||
|
||||
react-instantsearch-hooks@6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/react-instantsearch-hooks/-/react-instantsearch-hooks-6.24.1.tgz#59869c4492a5dffdb9ad979e141ec754d87fa668"
|
||||
integrity sha512-X7Wg4thRsybYNYpKFKr2YpZeN8eDH/sIhxxUz4uHhO2U0DD7tnZKa7fJVhCMHZf8ci3lTx9Lz6MBbCK1mr+pvQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
algoliasearch-helper "^3.7.4"
|
||||
dequal "^2.0.0"
|
||||
instantsearch.js "^4.40.1"
|
||||
|
||||
react-is@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
|
@ -4681,6 +4914,11 @@ scheduler@^0.20.2:
|
|||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
search-insights@^2.1.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-2.2.1.tgz#9c93344fbae5fbf2f88c1a81b46b4b5d888c11f7"
|
||||
integrity sha512-JDfVGZbKqTtiKVZjAVbkNw9C9f0ib80yx6Ea17M3z4RvPmuD0GYWXuFwA9++dpbreBEMH4TC3lQ29Zq7O4b5oA==
|
||||
|
||||
selenium-webdriver@4.0.0-rc-1:
|
||||
version "4.0.0-rc-1"
|
||||
resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-rc-1.tgz#b1e7e5821298c8a071e988518dd6b759f0c41281"
|
||||
|
|
Loading…
Reference in New Issue
Block a user