Multichain support for profile page

This commit is contained in:
pedromcunha
2023-01-09 13:46:45 -05:00
parent df93a69ec4
commit aa18641f26
12 changed files with 76 additions and 142 deletions
-1
View File
@@ -1 +0,0 @@
#NEXT_PUBLIC_RESERVOIR_API_BASE=https://api-goerli.reservoir.tools
-1
View File
@@ -1 +0,0 @@
NEXT_PUBLIC_RESERVOIR_API_BASE=https://api.reservoir.tools
+4 -4
View File
@@ -224,14 +224,14 @@ const ActivityTableRow: FC<ActivityTableRowProps> = ({ activity }) => {
<Flex align="center" justify="between">
<Link href={href} passHref>
<Flex align="center">
<Image
{imageSrc && <Image
style={{ borderRadius: '4px', objectFit: 'cover' }}
loader={({ src }) => src}
src={imageSrc}
alt={`${activity.token?.tokenName} Token Image`}
width={48}
height={48}
/>
/>}
<Text ellipsify css={{ ml: '$2', fontSize: '14px' }}>
{activity.token?.tokenName ||
activity.token?.tokenId ||
@@ -332,14 +332,14 @@ const ActivityTableRow: FC<ActivityTableRowProps> = ({ activity }) => {
<TableCell>
<Link href={href} passHref>
<Flex align="center">
<Image
{imageSrc && <Image
style={{ borderRadius: '4px', objectFit: 'cover' }}
loader={({ src }) => src}
src={imageSrc}
alt={`${activity.token?.tokenName} Token Image`}
width={48}
height={48}
/>
/>}
<Text ellipsify css={{ ml: '$2', fontSize: '14px' }}>
{activity.token?.tokenName ||
activity.token?.tokenId ||
+1 -1
View File
@@ -16,7 +16,7 @@ export const TrendingCollectionItem: FC<Props> = ({ rank, collection }) => {
return (
<Link
href={`/collection/${routePrefix}/${collection.id}`}
style={{ display: 'inline-block' }}
style={{ display: 'inline-block', minWidth: 0 }}
>
<Flex align="center" css={{ cursor: 'pointer' }}>
<Text css={{ mr: '$4' }} style="subtitle3">
+2 -2
View File
@@ -126,14 +126,14 @@ export const MobileTokenFilters: FC<Props> = ({
}
}}
>
<Image
{collection?.collection?.image && <Image
style={{ borderRadius: '4px', objectFit: 'cover' }}
loader={({ src }) => src}
src={collection?.collection?.image as string}
alt={collection?.collection?.name as string}
width={24}
height={24}
/>
/>}
<Text
style="body1"
css={{
+10 -8
View File
@@ -81,14 +81,16 @@ export const TokenFilters: FC<Props> = ({
scrollToTop()
}}
>
<Image
style={{ borderRadius: '4px', objectFit: 'cover' }}
loader={({ src }) => src}
src={collection?.collection?.image as string}
alt={collection?.collection?.name as string}
width={24}
height={24}
/>
{collection?.collection?.image && (
<Image
style={{ borderRadius: '4px', objectFit: 'cover' }}
loader={({ src }) => src}
src={collection?.collection?.image as string}
alt={collection?.collection?.name as string}
width={24}
height={24}
/>
)}
<Text
style="body1"
css={{
-1
View File
@@ -1,6 +1,5 @@
export { default as useMounted } from './useMounted'
export { default as useTimeSince } from './useTimeSince'
export { default as useUserActivity } from './useUserActivity'
export { default as useUserCollections } from './useUserCollections'
export { default as useChainCurrency } from './useChainCurrency'
export { default as useMarketplaceChain } from './useMarketplaceChain'
-91
View File
@@ -1,91 +0,0 @@
import { useReservoirClient } from '@reservoir0x/reservoir-kit-ui'
import { paths, setParams } from '@reservoir0x/reservoir-sdk'
import useSWRInfinite, { SWRInfiniteConfiguration } from 'swr/infinite'
type CollectionResponse =
paths['/collections/v5']['get']['responses']['200']['schema']
type CollectionsQuery = paths['/collections/v5']['get']['parameters']['query']
export default function (
options?: CollectionsQuery | false,
swrOptions: SWRInfiniteConfiguration = {}
) {
const client = useReservoirClient()
const { data, mutate, error, isValidating, size, setSize } =
useSWRInfinite<CollectionResponse>(
(pageIndex, previousPageData) => {
if (!options) {
return null
}
const url = new URL(`${client?.apiBase}/collections/v5`)
let query: CollectionsQuery = { ...options }
if (previousPageData && !previousPageData.continuation) {
return null
} else if (previousPageData && pageIndex > 0) {
query.continuation = previousPageData.continuation
}
if (
query.normalizeRoyalties === undefined &&
client?.normalizeRoyalties !== undefined
) {
query.normalizeRoyalties = client.normalizeRoyalties
}
setParams(url, query)
return url.href
},
(resource: string) => {
const headers: HeadersInit = {
'x-rkui-version': client?.version || '',
}
if (client?.apiKey) {
headers['x-api-key'] = client?.apiKey
}
if (client?.version) {
headers['x-rkc-version'] = client?.version
}
return fetch(resource, {
headers,
})
.then((res) => res.json())
.catch((e) => {
throw e
})
},
{
revalidateOnMount: true,
revalidateFirstPage: false,
...swrOptions,
}
)
const collections = data?.flatMap((page) => page?.collections || []) ?? []
const hasNextPage = Boolean(data?.[size - 1]?.continuation)
const isFetchingInitialData = !data && !error
const isFetchingPage =
isFetchingInitialData ||
(size > 0 && data && typeof data[size - 1] === 'undefined')
const fetchNextPage = () => {
if (!isFetchingPage && hasNextPage) {
setSize((size) => size + 1)
}
}
return {
response: data,
data: collections,
hasNextPage,
isFetchingInitialData,
isFetchingPage,
fetchNextPage,
setSize,
mutate,
error,
isValidating,
}
}
+1 -1
View File
@@ -25,7 +25,7 @@
"@radix-ui/react-toggle-group": "^1.0.1",
"@radix-ui/react-tooltip": "^1.0.2",
"@rainbow-me/rainbowkit": "^0.8.1",
"@reservoir0x/reservoir-kit-ui": "0.8.2",
"@reservoir0x/reservoir-kit-ui": "0.8.4",
"dayjs": "^1.11.6",
"ethers": "^5.6.9",
"framer-motion": "^6.3.6",
+7 -9
View File
@@ -3,7 +3,6 @@ import { Text, Flex, Box, Button } from 'components/primitives'
import TrendingCollectionsList from 'components/home/TrendingCollectionsList'
import Layout from 'components/Layout'
import { useState } from 'react'
import usePaginatedCollections from 'hooks/usePaginatedCollections'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import TrendingCollectionsTimeToggle, {
CollectionsSortingOption,
@@ -13,12 +12,14 @@ import { useMediaQuery } from 'react-responsive'
import { useMarketplaceChain, useMounted } from 'hooks'
import LoadingSpinner from 'components/common/LoadingSpinner'
import { paths } from '@reservoir0x/reservoir-sdk'
import { useCollections } from '@reservoir0x/reservoir-kit-ui'
import fetcher from 'utils/fetcher'
import supportedChains from 'utils/chains'
type Props = InferGetStaticPropsType<typeof getStaticProps>
const IndexPage: NextPage<Props> = ({ ssr }) => {
const isSSR = typeof window === 'undefined'
const isMounted = useMounted()
const compactToggleNames = useMediaQuery({ query: '(max-width: 800px)' })
const [sortByTime, setSortByTime] =
@@ -31,18 +32,17 @@ const IndexPage: NextPage<Props> = ({ ssr }) => {
isFetchingPage,
isValidating,
isFetchingInitialData,
} = usePaginatedCollections(
} = useCollections(
{
limit: 20,
limit: 12,
sortBy: sortByTime,
},
{
fallbackData: [ssr.collections[marketplaceChain.id]],
}
)
let collections = data || []
const showViewAllButton = collections.length <= 20 && hasNextPage
const showViewAllButton = collections.length <= 12 && hasNextPage
if (showViewAllButton) {
collections = collections?.slice(0, 12)
}
@@ -91,7 +91,7 @@ const IndexPage: NextPage<Props> = ({ ssr }) => {
}}
/>
</Flex>
{isValidating || isFetchingInitialData ? null : (
{isSSR || !isMounted || isFetchingInitialData ? null : (
<TrendingCollectionsList collections={collections} />
)}
{!isFetchingInitialData && (isFetchingPage || isValidating) && (
@@ -134,7 +134,7 @@ export const getStaticProps: GetStaticProps<{
let collectionQuery: paths['/collections/v5']['get']['parameters']['query'] =
{
sortBy: 'allTimeVolume',
limit: 20,
limit: 12,
}
const promises: ReturnType<typeof fetcher>[] = []
@@ -151,8 +151,6 @@ export const getStaticProps: GetStaticProps<{
}
})
console.log(collections)
return {
props: { ssr: { collections } },
revalidate: 5,
+47 -19
View File
@@ -26,7 +26,7 @@ import {
useUserTokens,
} from '@reservoir0x/reservoir-kit-ui'
import TokenCard from 'components/collections/TokenCard'
import { useMounted } from '../../hooks'
import { useMounted, useMarketplaceChain } from '../../hooks'
import { TokenFilters } from 'components/profile/TokenFilters'
import { FilterButton } from 'components/common/FilterButton'
import { UserAcivityTable } from 'components/profile/UserActivityTable'
@@ -35,6 +35,7 @@ import { ActivityFilters } from 'components/common/ActivityFilters'
import { MobileTokenFilters } from 'components/profile/MobileTokenFilters'
import LoadingCard from 'components/common/LoadingCard'
import { NAVBAR_HEIGHT } from 'components/navbar'
import supportedChains, { DefaultChain } from 'utils/chains'
type Props = InferGetStaticPropsType<typeof getStaticProps>
@@ -63,6 +64,7 @@ const IndexPage: NextPage<Props> = ({ address, ssr }) => {
>()
const isMounted = useMounted()
const [activityTypes, setActivityTypes] = useState<ActivityTypes>(['sale'])
const marketplaceChain = useMarketplaceChain()
const scrollRef = useRef<HTMLDivElement | null>(null)
@@ -80,6 +82,7 @@ const IndexPage: NextPage<Props> = ({ address, ssr }) => {
limit: 20,
collection: filterCollection,
}
const ssrTokens = ssr.tokens[marketplaceChain.id]
const {
data: tokens,
mutate,
@@ -88,7 +91,7 @@ const IndexPage: NextPage<Props> = ({ address, ssr }) => {
hasNextPage,
isFetchingPage,
} = useUserTokens(address || '', tokenQuery, {
fallbackData: filterCollection ? undefined : [ssr.tokens],
fallbackData: filterCollection ? undefined : [ssrTokens],
})
useEffect(() => {
@@ -98,7 +101,8 @@ const IndexPage: NextPage<Props> = ({ address, ssr }) => {
}
}, [loadMoreObserver?.isIntersecting])
const collections = ssr.collections.collections
const ssrCollections = ssr.collections[marketplaceChain.id]
const collections = ssrCollections?.collections || []
if (!isMounted) {
return null
@@ -316,38 +320,62 @@ export const getStaticPaths: GetStaticPaths = async () => {
}
}
type UserTokensSchema =
paths['/users/{user}/tokens/v6']['get']['responses']['200']['schema']
type UserCollectionsSchema =
paths['/users/{user}/collections/v2']['get']['responses']['200']['schema']
export const getStaticProps: GetStaticProps<{
ssr: {
tokens: paths['/users/{user}/tokens/v6']['get']['responses']['200']['schema']
collections: paths['/users/{user}/collections/v2']['get']['responses']['200']['schema']
tokens: Record<number, UserTokensSchema>
collections: Record<number, UserCollectionsSchema>
}
address: string | undefined
}> = async ({ params }) => {
const address = params?.address?.toString()
let tokensQuery: paths['/users/{user}/tokens/v6']['get']['parameters']['query'] =
const tokensQuery: paths['/users/{user}/tokens/v6']['get']['parameters']['query'] =
{
limit: 20,
}
//todo fetch all chains and return as options
const tokensResponse = await fetcher(
`users/${address}/tokens/v6`,
tokensQuery
)
const tokens: Props['ssr']['tokens'] = tokensResponse['data']
let collectionsQuery: paths['/users/{user}/collections/v2']['get']['parameters']['query'] =
const collectionsQuery: paths['/users/{user}/collections/v2']['get']['parameters']['query'] =
{
limit: 100,
}
const collectionsResponse = await fetcher(
`users/${address}/collections/v2`,
collectionsQuery
)
const chainMap: Record<string, typeof supportedChains[0]> = {}
const promises: ReturnType<typeof fetcher>[] = []
supportedChains.forEach((chain) => {
chainMap[chain.reservoirBaseUrl] = chain
const tokensPromise = fetcher(
`${chain.reservoirBaseUrl}/users/${address}/tokens/v6`,
tokensQuery
)
const collectionsPromise = fetcher(
`${chain.reservoirBaseUrl}/users/${address}/collections/v2`,
collectionsQuery
)
promises.push(tokensPromise)
promises.push(collectionsPromise)
})
const collections: Props['ssr']['collections'] = collectionsResponse['data']
const responses = await Promise.allSettled(promises)
const collections: Record<number, any> = {}
const tokens: Record<number, any> = {}
responses.forEach((response) => {
if (response.status === 'fulfilled') {
const url = new URL(response.value.response.url)
const chain = chainMap[url.origin]
if (chain) {
if (url.pathname.includes('collections')) {
collections[chain.id] = response.value.data
} else if (url.pathname.includes('tokens')) {
tokens[chain.id] = response.value.data
}
}
}
})
return {
props: { ssr: { tokens, collections }, address },
+4 -4
View File
@@ -1750,10 +1750,10 @@
dependencies:
"@react-hookz/deep-equal" "^1.0.3"
"@reservoir0x/[email protected].2":
version "0.8.2"
resolved "https://registry.yarnpkg.com/@reservoir0x/reservoir-kit-ui/-/reservoir-kit-ui-0.8.2.tgz#dd27c6a809c9a76952bbf2669a932814d0d15322"
integrity sha512-sBPxzjnsFRnNkTDAs9ppDRMor6AEtZGYby8SNQdrm0sGXPl2PcZIgtnPSovr9lmIieL0TFlpXW6bvRuy65NFfw==
"@reservoir0x/[email protected].4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@reservoir0x/reservoir-kit-ui/-/reservoir-kit-ui-0.8.4.tgz#3fcf2b78d37f870b452e694f5e88a7c02df2f21b"
integrity sha512-yuzHU26C1ra9K5UMcWCmLMZaCQNt8hfou58IuOghw05VrBbMoXJnFlzo/mZ1tH++kO4P4na5M3pVe4IqPn27VQ==
dependencies:
"@fortawesome/fontawesome-svg-core" "^6.1.1"
"@fortawesome/free-solid-svg-icons" "^6.1.1"