Commit b63e9538 authored by aballerr's avatar aballerr Committed by GitHub

chore: Merging in search, sort (#4675)

* Porting over search and porting over sort
parent ef8fba1d
import clsx from 'clsx' import clsx from 'clsx'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import { AnimatedBox, Box } from 'nft/components/Box' import { AnimatedBox, Box } from 'nft/components/Box'
import { FilterButton } from 'nft/components/collection' import { CollectionSearch, FilterButton } from 'nft/components/collection'
import { CollectionAsset } from 'nft/components/collection/CollectionAsset' import { CollectionAsset } from 'nft/components/collection/CollectionAsset'
import * as styles from 'nft/components/collection/CollectionNfts.css' import * as styles from 'nft/components/collection/CollectionNfts.css'
import { Row } from 'nft/components/Flex' import { SortDropdown } from 'nft/components/common/SortDropdown'
import { Center } from 'nft/components/Flex' import { Center, Row } from 'nft/components/Flex'
import { NonRarityIcon, RarityIcon } from 'nft/components/icons'
import { bodySmall, buttonTextMedium, header2 } from 'nft/css/common.css' import { bodySmall, buttonTextMedium, header2 } from 'nft/css/common.css'
import { useCollectionFilters, useFiltersExpanded, useIsMobile } from 'nft/hooks' import { vars } from 'nft/css/sprinkles.css'
import {
CollectionFilters,
initialCollectionFilterState,
SortBy,
useCollectionFilters,
useFiltersExpanded,
useIsMobile,
} from 'nft/hooks'
import { AssetsFetcher } from 'nft/queries' import { AssetsFetcher } from 'nft/queries'
import { UniformHeight, UniformHeights } from 'nft/types' import { DropDownOption, GenieCollection, UniformHeight, UniformHeights } from 'nft/types'
import { useEffect, useMemo, useState } from 'react' import { getRarityStatus } from 'nft/utils/asset'
import { applyFiltersFromURL, syncLocalFiltersWithURL } from 'nft/utils/urlParams'
import { useEffect, useMemo, useRef, useState } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component' import InfiniteScroll from 'react-infinite-scroll-component'
import { useInfiniteQuery } from 'react-query' import { useInfiniteQuery } from 'react-query'
import { useLocation } from 'react-router-dom'
interface CollectionNftsProps { interface CollectionNftsProps {
contractAddress: string contractAddress: string
collectionStats: GenieCollection
rarityVerified?: boolean rarityVerified?: boolean
} }
export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNftsProps) => { const rarityStatusCache = new Map<string, boolean>()
export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerified }: CollectionNftsProps) => {
const traits = useCollectionFilters((state) => state.traits) const traits = useCollectionFilters((state) => state.traits)
const minPrice = useCollectionFilters((state) => state.minPrice) const minPrice = useCollectionFilters((state) => state.minPrice)
const maxPrice = useCollectionFilters((state) => state.maxPrice) const maxPrice = useCollectionFilters((state) => state.maxPrice)
const markets = useCollectionFilters((state) => state.markets) const markets = useCollectionFilters((state) => state.markets)
const sortBy = useCollectionFilters((state) => state.sortBy)
const searchByNameText = useCollectionFilters((state) => state.search)
const setMarketCount = useCollectionFilters((state) => state.setMarketCount)
const setSortBy = useCollectionFilters((state) => state.setSortBy)
const buyNow = useCollectionFilters((state) => state.buyNow) const buyNow = useCollectionFilters((state) => state.buyNow)
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
const isMobile = useIsMobile()
const debouncedMinPrice = useDebounce(minPrice, 500) const debouncedMinPrice = useDebounce(minPrice, 500)
const debouncedMaxPrice = useDebounce(maxPrice, 500) const debouncedMaxPrice = useDebounce(maxPrice, 500)
const debouncedSearchByNameText = useDebounce(searchByNameText, 500)
const { const {
data: collectionAssets, data: collectionAssets,
isSuccess: AssetsFetchSuccess, isSuccess: AssetsFetchSuccess,
isLoading,
fetchNextPage, fetchNextPage,
hasNextPage, hasNextPage,
} = useInfiniteQuery( } = useInfiniteQuery(
...@@ -44,18 +63,37 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf ...@@ -44,18 +63,37 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf
contractAddress, contractAddress,
markets, markets,
notForSale: !buyNow, notForSale: !buyNow,
price: { sortBy,
low: debouncedMinPrice, searchByNameText,
high: debouncedMaxPrice, debouncedMinPrice,
symbol: 'ETH', debouncedMaxPrice,
}, searchText: debouncedSearchByNameText,
}, },
], ],
async ({ pageParam = 0 }) => { async ({ pageParam = 0 }) => {
let sort = undefined
switch (sortBy) {
case SortBy.HighToLow: {
sort = { currentEthPrice: 'desc' }
break
}
case SortBy.RareToCommon: {
sort = { 'rarity.providers.0.rank': 1 }
break
}
case SortBy.CommonToRare: {
sort = { 'rarity.providers.0.rank': -1 }
break
}
default:
}
return await AssetsFetcher({ return await AssetsFetcher({
contractAddress, contractAddress,
sort,
markets, markets,
notForSale: !buyNow, notForSale: !buyNow,
searchText: debouncedSearchByNameText,
pageParam, pageParam,
traits, traits,
price: { price: {
...@@ -78,6 +116,9 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf ...@@ -78,6 +116,9 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf
const [uniformHeight, setUniformHeight] = useState<UniformHeight>(UniformHeights.unset) const [uniformHeight, setUniformHeight] = useState<UniformHeight>(UniformHeights.unset)
const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState<string | undefined>() const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState<string | undefined>()
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
const oldStateRef = useRef<CollectionFilters | null>(null)
const isMobile = useIsMobile()
const collectionNfts = useMemo(() => { const collectionNfts = useMemo(() => {
if (!collectionAssets || !AssetsFetchSuccess) return undefined if (!collectionAssets || !AssetsFetchSuccess) return undefined
...@@ -85,14 +126,88 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf ...@@ -85,14 +126,88 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf
return collectionAssets.pages.flat() return collectionAssets.pages.flat()
}, [collectionAssets, AssetsFetchSuccess]) }, [collectionAssets, AssetsFetchSuccess])
const hasRarity = getRarityStatus(rarityStatusCache, collectionStats?.address, collectionNfts)
const sortDropDownOptions: DropDownOption[] = useMemo(
() =>
hasRarity
? [
{
displayText: 'Low to High',
onClick: () => setSortBy(SortBy.LowToHigh),
icon: <NonRarityIcon width="28" height="28" color={vars.color.blue400} />,
reverseIndex: 2,
},
{
displayText: 'High to Low',
onClick: () => setSortBy(SortBy.HighToLow),
icon: <NonRarityIcon width="28" height="28" color={vars.color.blue400} />,
reverseIndex: 1,
},
{
displayText: 'Rare to Common',
onClick: () => setSortBy(SortBy.RareToCommon),
icon: <RarityIcon width="28" height="28" color={vars.color.blue400} />,
reverseIndex: 4,
},
{
displayText: 'Common to Rare',
onClick: () => setSortBy(SortBy.CommonToRare),
icon: <RarityIcon width="28" height="28" color={vars.color.blue400} />,
reverseIndex: 3,
},
]
: [
{
displayText: 'Low to High',
onClick: () => setSortBy(SortBy.LowToHigh),
icon: <NonRarityIcon width="28" height="28" color={vars.color.blue400} />,
reverseIndex: 2,
},
{
displayText: 'High to Low',
onClick: () => setSortBy(SortBy.HighToLow),
icon: <NonRarityIcon width="28" height="28" color={vars.color.blue400} />,
reverseIndex: 1,
},
],
[hasRarity, setSortBy]
)
useEffect(() => { useEffect(() => {
setUniformHeight(UniformHeights.unset) setUniformHeight(UniformHeights.unset)
return () => {
useCollectionFilters.setState(initialCollectionFilterState)
}
}, [contractAddress]) }, [contractAddress])
if (!collectionNfts) { useEffect(() => {
// TODO: collection unavailable page const marketCount: any = {}
return <div>No CollectionAssets</div> collectionStats?.marketplaceCount?.forEach(({ marketplace, count }) => {
marketCount[marketplace] = count
})
setMarketCount(marketCount)
oldStateRef.current = useCollectionFilters.getState()
}, [collectionStats?.marketplaceCount, setMarketCount])
const location = useLocation()
// Applying filters from URL to local state
useEffect(() => {
if (collectionStats?.traits) {
const modifiedQuery = applyFiltersFromURL(location, collectionStats)
requestAnimationFrame(() => {
useCollectionFilters.setState(modifiedQuery as any)
})
useCollectionFilters.subscribe((state) => {
if (JSON.stringify(oldStateRef.current) !== JSON.stringify(state)) {
syncLocalFiltersWithURL(state)
oldStateRef.current = state
}
})
} }
}, [collectionStats, location])
return ( return (
<> <>
...@@ -105,18 +220,19 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf ...@@ -105,18 +220,19 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf
onClick={() => setFiltersExpanded(!isFiltersExpanded)} onClick={() => setFiltersExpanded(!isFiltersExpanded)}
collectionCount={collectionNfts?.[0]?.totalCount ?? 0} collectionCount={collectionNfts?.[0]?.totalCount ?? 0}
/> />
<SortDropdown dropDownOptions={sortDropDownOptions} />
<CollectionSearch />
</Row> </Row>
</Box> </Box>
</AnimatedBox> </AnimatedBox>
<InfiniteScroll <InfiniteScroll
next={fetchNextPage} next={fetchNextPage}
hasMore={hasNextPage ?? false} hasMore={hasNextPage ?? false}
loader={hasNextPage ? <p>Loading from scroll...</p> : null} loader={hasNextPage ? <p>Loading from scroll...</p> : null}
dataLength={collectionNfts.length} dataLength={collectionNfts?.length ?? 0}
style={{ overflow: 'unset' }} style={{ overflow: 'unset' }}
> >
{collectionNfts.length > 0 ? ( {collectionNfts && collectionNfts.length > 0 ? (
<div className={styles.assetList}> <div className={styles.assetList}>
{collectionNfts.map((asset) => { {collectionNfts.map((asset) => {
return asset ? ( return asset ? (
...@@ -134,6 +250,7 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf ...@@ -134,6 +250,7 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf
})} })}
</div> </div>
) : ( ) : (
!isLoading && (
<Center width="full" color="darkGray" style={{ height: '60vh' }}> <Center width="full" color="darkGray" style={{ height: '60vh' }}>
<div style={{ display: 'block', textAlign: 'center' }}> <div style={{ display: 'block', textAlign: 'center' }}>
<p className={header2}>No NFTS found</p> <p className={header2}>No NFTS found</p>
...@@ -142,6 +259,7 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf ...@@ -142,6 +259,7 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf
</Box> </Box>
</div> </div>
</Center> </Center>
)
)} )}
</InfiniteScroll> </InfiniteScroll>
</> </>
......
import { Box } from 'nft/components/Box'
import { useCollectionFilters } from 'nft/hooks/useCollectionFilters'
import { FormEvent } from 'react'
export const CollectionSearch = () => {
const setSearchByNameText = useCollectionFilters((state) => state.setSearch)
const searchByNameText = useCollectionFilters((state) => state.search)
return (
<Box
as="input"
borderColor={{ default: 'medGray', focus: 'genieBlue' }}
borderWidth="1px"
borderStyle="solid"
borderRadius="12"
padding="12"
backgroundColor="white"
fontSize="16"
height="44"
color={{ placeholder: 'darkGray', default: 'blackBlue' }}
value={searchByNameText}
placeholder={'Search by name'}
onChange={(e: FormEvent<HTMLInputElement>) => {
setSearchByNameText(e.currentTarget.value)
}}
/>
)
}
...@@ -5,11 +5,11 @@ import { PriceRange } from 'nft/components/collection/PriceRange' ...@@ -5,11 +5,11 @@ import { PriceRange } from 'nft/components/collection/PriceRange'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
import { Radio } from 'nft/components/layout/Radio' import { Radio } from 'nft/components/layout/Radio'
import { useCollectionFilters } from 'nft/hooks' import { useCollectionFilters } from 'nft/hooks'
import { Trait } from 'nft/hooks/useCollectionFilters'
import { groupBy } from 'nft/utils/groupBy'
import { FocusEventHandler, FormEvent, useMemo, useState } from 'react' import { FocusEventHandler, FormEvent, useMemo, useState } from 'react'
import { useReducer } from 'react' import { useReducer } from 'react'
import { Trait } from '../../hooks/useCollectionFilters'
import { groupBy } from '../../utils/groupBy'
import { Input } from '../layout/Input' import { Input } from '../layout/Input'
import { TraitSelect } from './TraitSelect' import { TraitSelect } from './TraitSelect'
......
import { Row } from 'nft/components/Flex'
import { NumericInput } from 'nft/components/layout/Input'
import { useIsMobile } from 'nft/hooks' import { useIsMobile } from 'nft/hooks'
import { useCollectionFilters } from 'nft/hooks/useCollectionFilters'
import { isNumber } from 'nft/utils/numbers'
import { scrollToTop } from 'nft/utils/scrollToTop'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { FocusEventHandler, FormEvent } from 'react' import { FocusEventHandler, FormEvent } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { useCollectionFilters } from '../../hooks/useCollectionFilters'
import { isNumber } from '../../utils/numbers'
import { scrollToTop } from '../../utils/scrollToTop'
import { Row } from '../Flex'
import { NumericInput } from '../layout/Input'
export const PriceRange = () => { export const PriceRange = () => {
const [placeholderText, setPlaceholderText] = useState('') const [placeholderText, setPlaceholderText] = useState('')
const setMinPrice = useCollectionFilters((state) => state.setMinPrice) const setMinPrice = useCollectionFilters((state) => state.setMinPrice)
......
import clsx from 'clsx' import clsx from 'clsx'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { ChevronUpIcon } from 'nft/components/icons'
import { Checkbox } from 'nft/components/layout/Checkbox'
import { subheadSmall } from 'nft/css/common.css'
import { Trait, useCollectionFilters } from 'nft/hooks/useCollectionFilters'
import { pluralize } from 'nft/utils/roundAndPluralize' import { pluralize } from 'nft/utils/roundAndPluralize'
import { scrollToTop } from 'nft/utils/scrollToTop' import { scrollToTop } from 'nft/utils/scrollToTop'
import { useMemo } from 'react' import { useMemo } from 'react'
import { FormEvent, MouseEvent } from 'react' import { FormEvent, MouseEvent } from 'react'
import { useEffect, useLayoutEffect, useState } from 'react' import { useEffect, useLayoutEffect, useState } from 'react'
import { subheadSmall } from '../../css/common.css'
import { Trait, useCollectionFilters } from '../../hooks/useCollectionFilters'
import { Box } from '../Box'
import { Column, Row } from '../Flex'
import { ChevronUpIcon } from '../icons'
import { Checkbox } from '../layout/Checkbox'
import * as styles from './Filters.css' import * as styles from './Filters.css'
const TraitItem = ({ const TraitItem = ({
......
export { CollectionNfts } from './CollectionNfts' export { CollectionNfts } from './CollectionNfts'
export { CollectionSearch } from './CollectionSearch'
export { CollectionStats } from './CollectionStats' export { CollectionStats } from './CollectionStats'
export { FilterButton } from './FilterButton' export { FilterButton } from './FilterButton'
export { Filters } from './Filters' export { Filters } from './Filters'
...@@ -45,3 +45,9 @@ export const selectedActivitySwitcherToggle = style([ ...@@ -45,3 +45,9 @@ export const selectedActivitySwitcherToggle = style([
}, },
}, },
]) ])
export const noCollectionAssets = sprinkles({
display: 'flex',
justifyContent: 'center',
marginTop: '40',
})
...@@ -20,7 +20,7 @@ const Collection = () => { ...@@ -20,7 +20,7 @@ const Collection = () => {
const setMarketCount = useCollectionFilters((state) => state.setMarketCount) const setMarketCount = useCollectionFilters((state) => state.setMarketCount)
const isBagExpanded = useBag((state) => state.bagExpanded) const isBagExpanded = useBag((state) => state.bagExpanded)
const { data: collectionStats } = useQuery(['collectionStats', contractAddress], () => const { data: collectionStats, isLoading } = useQuery(['collectionStats', contractAddress], () =>
CollectionStatsFetcher(contractAddress as string) CollectionStatsFetcher(contractAddress as string)
) )
...@@ -45,6 +45,9 @@ const Collection = () => { ...@@ -45,6 +45,9 @@ const Collection = () => {
return ( return (
<Column width="full"> <Column width="full">
{collectionStats && contractAddress ? (
<>
{' '}
<Box width="full" height="160"> <Box width="full" height="160">
<Box <Box
as="img" as="img"
...@@ -54,7 +57,6 @@ const Collection = () => { ...@@ -54,7 +57,6 @@ const Collection = () => {
className={`${styles.bannerImage}`} className={`${styles.bannerImage}`}
/> />
</Box> </Box>
{collectionStats && ( {collectionStats && (
<Row paddingLeft="32" paddingRight="32"> <Row paddingLeft="32" paddingRight="32">
<CollectionStats stats={collectionStats} isMobile={isMobile} /> <CollectionStats stats={collectionStats} isMobile={isMobile} />
...@@ -63,7 +65,10 @@ const Collection = () => { ...@@ -63,7 +65,10 @@ const Collection = () => {
<Row alignItems="flex-start" position="relative" paddingX="48"> <Row alignItems="flex-start" position="relative" paddingX="48">
<Box position="sticky" top="72" width="0"> <Box position="sticky" top="72" width="0">
{isFiltersExpanded && ( {isFiltersExpanded && (
<Filters traitsByAmount={collectionStats?.numTraitsByAmount ?? []} traits={collectionStats?.traits ?? []} /> <Filters
traitsByAmount={collectionStats?.numTraitsByAmount ?? []}
traits={collectionStats?.traits ?? []}
/>
)} )}
</Box> </Box>
...@@ -74,11 +79,18 @@ const Collection = () => { ...@@ -74,11 +79,18 @@ const Collection = () => {
width: gridWidthOffset.interpolate((x) => `calc(100% - ${x as number}px)`), width: gridWidthOffset.interpolate((x) => `calc(100% - ${x as number}px)`),
}} }}
> >
{contractAddress && ( <CollectionNfts
<CollectionNfts contractAddress={contractAddress} rarityVerified={collectionStats?.rarityVerified} /> collectionStats={collectionStats}
)} contractAddress={contractAddress}
rarityVerified={collectionStats?.rarityVerified}
/>
</AnimatedBox> </AnimatedBox>
</Row> </Row>
</>
) : (
// TODO: Put no collection asset page here
!isLoading && <div className={styles.noCollectionAssets}>No collection assets exist at this address</div>
)}
</Column> </Column>
) )
} }
......
import { DetailsOrigin, GenieAsset } from 'nft/types' import { DetailsOrigin, GenieAsset } from 'nft/types'
export function getRarityStatus(
rarityStatusCache: Map<string, boolean>,
id: string,
assets?: (GenieAsset | undefined)[]
) {
if (rarityStatusCache.has(id)) {
return rarityStatusCache.get(id)
}
const hasRarity = assets && Array.from(assets).reduce((reducer, asset) => !!(reducer || asset?.rarity), false)
if (hasRarity) {
rarityStatusCache.set(id, hasRarity)
}
return hasRarity
}
export const getAssetHref = (asset: GenieAsset, origin?: DetailsOrigin) => { export const getAssetHref = (asset: GenieAsset, origin?: DetailsOrigin) => {
return `/nfts/asset/${asset.address}/${asset.tokenId}${origin ? `?origin=${origin}` : ''}` return `/nfts/asset/${asset.address}/${asset.tokenId}${origin ? `?origin=${origin}` : ''}`
} }
import { CollectionFilters, initialCollectionFilterState, SortByPointers, Trait } from 'nft/hooks'
import { GenieCollection } from 'nft/types'
import qs from 'query-string'
import { Location } from 'react-router-dom'
const trimTraitStr = (trait: string) => {
return trait.substring(1, trait.length - 1)
}
const urlParamsUtils = {
removeDefaults: (query: Record<string, any>) => {
const clonedQuery: Record<string, any> = { ...query }
// Leveraging default values & not showing them on URL
for (const key in clonedQuery) {
const valueInQuery = clonedQuery[key]
const initialValue = initialCollectionFilterState[key as keyof typeof initialCollectionFilterState]
if (JSON.stringify(valueInQuery) === JSON.stringify(initialValue)) {
delete clonedQuery[key]
}
}
// Doing this one manually due to name mismatch - "all" in url, "buyNow" in state
if (clonedQuery['all'] !== initialCollectionFilterState.buyNow) {
delete clonedQuery['all']
}
const defaultSortByPointer = SortByPointers[initialCollectionFilterState.sortBy]
if (clonedQuery['sort'] === defaultSortByPointer) {
delete clonedQuery['sort']
}
return clonedQuery
},
// Making values in our URL more state-friendly
buildQuery: (query: Record<string, any>, collectionStats: GenieCollection) => {
const clonedQuery: Record<string, any> = { ...query }
const filters = ['traits', 'markets']
filters.forEach((key) => {
if (!clonedQuery[key]) {
clonedQuery[key] = []
}
/*
query-string package treats arrays with one value as a string.
Here we're making sure that we have an array, not a string. Example:
const foo = 'hey' // => ['hey']
*/
if (clonedQuery[key] && typeof clonedQuery[key] === 'string') {
clonedQuery[key] = [clonedQuery[key]]
}
})
try {
const { buyNow: initialBuyNow, search: initialSearchText } = initialCollectionFilterState
Object.entries(SortByPointers).forEach(([key, value]) => {
if (value === clonedQuery['sort']) {
clonedQuery['sortBy'] = Number(key)
}
})
clonedQuery['buyNow'] = !(clonedQuery['all'] === undefined ? !initialBuyNow : clonedQuery['all'])
clonedQuery['search'] = clonedQuery['search'] === undefined ? initialSearchText : String(clonedQuery['search'])
/*
Handling an edge case caused by query-string's bad array parsing, when user
only selects one trait and reloads the page.
Here's the general data-structure for our traits in URL:
`traits=("trait_type","trait_value"),("trait_type","trait_value")`
Expected behavior: When user selects one trait, there should be an array
containing one element.
Actual behavior: It creates an array with two elements, first element being
trait_type & the other trait_value. This causes confusion since we don't know
whether user has selected two traits (cause we have two elements in our array)
or it's only one.
Using this block of code, we'll identify if that's the case.
*/
if (clonedQuery['traits'].length === 2) {
const [trait_type, trait_value] = clonedQuery['traits'] as [string, string]
const fullTrait = `${trait_type}${trait_value}`
if (!fullTrait.includes(',')) {
if (
trait_type.startsWith('(') &&
!trait_type.endsWith(')') &&
trait_value.endsWith(')') &&
!trait_value.startsWith('(')
)
clonedQuery['traits'] = [`${trait_type},${trait_value}`]
}
}
clonedQuery['traits'] = clonedQuery['traits'].map((queryTrait: string) => {
const modifiedTrait = trimTraitStr(queryTrait.replace(/(")/g, ''))
const [trait_type, trait_value] = modifiedTrait.split(',')
const traitInStats = collectionStats.traits.find(
(item) => item.trait_type === trait_type && item.trait_value === trait_value
)
/*
For most cases, `traitInStats` is assigned. In case the trait
does not exist in our store, e.g "Number of traits", we have to
manually create an object for it.
*/
const trait = traitInStats ?? { trait_type, trait_value, trait_count: 0 }
return trait as Trait
})
} catch (err) {
clonedQuery['traits'] = []
}
return clonedQuery
},
}
export const syncLocalFiltersWithURL = (state: CollectionFilters) => {
const urlFilterItems = [
'markets',
'maxPrice',
'maxRarity',
'minPrice',
'minRarity',
'traits',
'all',
'search',
'sort',
] as const
const query: Record<string, any> = {}
urlFilterItems.forEach((key) => {
switch (key) {
case 'traits':
const traits = state.traits.map(({ trait_type, trait_value }) => `("${trait_type}","${trait_value}")`)
query['traits'] = traits
break
case 'all':
query['all'] = !state.buyNow
break
case 'sort':
query['sort'] = SortByPointers[state.sortBy]
break
default:
query[key] = state[key]
break
}
})
const modifiedQuery = urlParamsUtils.removeDefaults(query)
// Applying local state changes to URL
const url = window.location.href.split('?')[0]
const stringifiedQuery = qs.stringify(modifiedQuery, { arrayFormat: 'comma' })
// Using pushState on purpose here. router.push() will trigger re-renders & API calls.
window.history.pushState({}, ``, `${url}${stringifiedQuery && `?${stringifiedQuery}`}`)
}
export const applyFiltersFromURL = (location: Location, collectionStats: GenieCollection) => {
if (!location.search) return
const query = qs.parse(location.search, {
arrayFormat: 'comma',
parseNumbers: true,
parseBooleans: true,
}) as {
maxPrice: string
maxRarity: string
minPrice: string
minRarity: string
search: string
sort: string
sortBy: number
all: boolean
buyNow: boolean
traits: string[]
markets: string[]
}
const modifiedQuery = urlParamsUtils.buildQuery(query, collectionStats)
return modifiedQuery
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment