Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
I
interface
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
LuckySwap
interface
Commits
b63e9538
Unverified
Commit
b63e9538
authored
Sep 22, 2022
by
aballerr
Committed by
GitHub
Sep 22, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: Merging in search, sort (#4675)
* Porting over search and porting over sort
parent
ef8fba1d
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
450 additions
and
76 deletions
+450
-76
CollectionNfts.tsx
src/nft/components/collection/CollectionNfts.tsx
+147
-29
CollectionSearch.tsx
src/nft/components/collection/CollectionSearch.tsx
+28
-0
Filters.tsx
src/nft/components/collection/Filters.tsx
+2
-2
PriceRange.tsx
src/nft/components/collection/PriceRange.tsx
+5
-6
TraitSelect.tsx
src/nft/components/collection/TraitSelect.tsx
+6
-6
index.ts
src/nft/components/collection/index.ts
+1
-0
index.css.ts
src/nft/pages/collection/index.css.ts
+6
-0
index.tsx
src/nft/pages/collection/index.tsx
+45
-33
asset.ts
src/nft/utils/asset.ts
+17
-0
urlParams.ts
src/nft/utils/urlParams.ts
+193
-0
No files found.
src/nft/components/collection/CollectionNfts.tsx
View file @
b63e9538
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
:
debouncedMax
Price
,
debouncedMin
Price
,
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,14 +250,16 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf
...
@@ -134,14 +250,16 @@ export const CollectionNfts = ({ contractAddress, rarityVerified }: CollectionNf
})
}
})
}
</
div
>
</
div
>
)
:
(
)
:
(
<
Center
width=
"full"
color=
"darkGray"
style=
{
{
height
:
'
60vh
'
}
}
>
!
isLoading
&&
(
<
div
style=
{
{
display
:
'
block
'
,
textAlign
:
'
center
'
}
}
>
<
Center
width=
"full"
color=
"darkGray"
style=
{
{
height
:
'
60vh
'
}
}
>
<
p
className=
{
header2
}
>
No NFTS found
</
p
>
<
div
style=
{
{
display
:
'
block
'
,
textAlign
:
'
center
'
}
}
>
<
Box
className=
{
clsx
(
bodySmall
,
buttonTextMedium
)
}
color=
"blue"
cursor=
"pointer"
>
<
p
className=
{
header2
}
>
No NFTS found
</
p
>
View full collection
<
Box
className=
{
clsx
(
bodySmall
,
buttonTextMedium
)
}
color=
"blue"
cursor=
"pointer"
>
</
Box
>
View full collection
</
div
>
</
Box
>
</
Center
>
</
div
>
</
Center
>
)
)
}
)
}
</
InfiniteScroll
>
</
InfiniteScroll
>
</>
</>
...
...
src/nft/components/collection/CollectionSearch.tsx
0 → 100644
View file @
b63e9538
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
)
}
}
/>
)
}
src/nft/components/collection/Filters.tsx
View file @
b63e9538
...
@@ -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
'
...
...
src/nft/components/collection/PriceRange.tsx
View file @
b63e9538
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
)
...
...
src/nft/components/collection/TraitSelect.tsx
View file @
b63e9538
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
=
({
...
...
src/nft/components/collection/index.ts
View file @
b63e9538
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
'
src/nft/pages/collection/index.css.ts
View file @
b63e9538
...
@@ -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
'
,
})
src/nft/pages/collection/index.tsx
View file @
b63e9538
...
@@ -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,40 +45,52 @@ const Collection = () => {
...
@@ -45,40 +45,52 @@ const Collection = () => {
return
(
return
(
<
Column
width=
"full"
>
<
Column
width=
"full"
>
<
Box
width=
"full"
height=
"160"
>
{
collectionStats
&&
contractAddress
?
(
<
Box
<>
as=
"img"
{
'
'
}
maxHeight=
"full"
<
Box
width=
"full"
height=
"160"
>
width=
"full"
<
Box
src=
{
collectionStats
?.
bannerImageUrl
}
as=
"img"
className=
{
`${styles.bannerImage}`
}
maxHeight=
"full"
/>
width=
"full"
</
Box
>
src=
{
collectionStats
?.
bannerImageUrl
}
className=
{
`${styles.bannerImage}`
}
{
collectionStats
&&
(
/>
<
Row
paddingLeft=
"32"
paddingRight=
"32"
>
</
Box
>
<
CollectionStats
stats=
{
collectionStats
}
isMobile=
{
isMobile
}
/>
{
collectionStats
&&
(
</
Row
>
<
Row
paddingLeft=
"32"
paddingRight=
"32"
>
)
}
<
CollectionStats
stats=
{
collectionStats
}
isMobile=
{
isMobile
}
/>
<
Row
alignItems=
"flex-start"
position=
"relative"
paddingX=
"48"
>
</
Row
>
<
Box
position=
"sticky"
top=
"72"
width=
"0"
>
{
isFiltersExpanded
&&
(
<
Filters
traitsByAmount=
{
collectionStats
?.
numTraitsByAmount
??
[]
}
traits=
{
collectionStats
?.
traits
??
[]
}
/>
)
}
)
}
</
Box
>
<
Row
alignItems=
"flex-start"
position=
"relative"
paddingX=
"48"
>
<
Box
position=
"sticky"
top=
"72"
width=
"0"
>
{
isFiltersExpanded
&&
(
<
Filters
traitsByAmount=
{
collectionStats
?.
numTraitsByAmount
??
[]
}
traits=
{
collectionStats
?.
traits
??
[]
}
/>
)
}
</
Box
>
{
/* @ts-ignore: https://github.com/microsoft/TypeScript/issues/34933 */
}
{
/* @ts-ignore: https://github.com/microsoft/TypeScript/issues/34933 */
}
<
AnimatedBox
<
AnimatedBox
style=
{
{
style=
{
{
transform
:
gridX
.
interpolate
((
x
)
=>
`translate(${x as number}px)`
),
transform
:
gridX
.
interpolate
((
x
)
=>
`translate(${x as number}px)`
),
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
}
</
AnimatedBox
>
rarityVerified=
{
collectionStats
?.
rarityVerified
}
</
Row
>
/>
</
AnimatedBox
>
</
Row
>
</>
)
:
(
// TODO: Put no collection asset page here
!
isLoading
&&
<
div
className=
{
styles
.
noCollectionAssets
}
>
No collection assets exist at this address
</
div
>
)
}
</
Column
>
</
Column
>
)
)
}
}
...
...
src/nft/utils/asset.ts
View file @
b63e9538
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
}
`
:
''
}
`
}
}
src/nft/utils/urlParams.ts
0 → 100644
View file @
b63e9538
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
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment