mirror of
https://github.com/luxfi/market.git
synced 2026-09-24 00:25:57 +00:00
Multichain support for profile page
This commit is contained in:
@@ -1 +0,0 @@
|
||||
#NEXT_PUBLIC_RESERVOIR_API_BASE=https://api-goerli.reservoir.tools
|
||||
@@ -1 +0,0 @@
|
||||
NEXT_PUBLIC_RESERVOIR_API_BASE=https://api.reservoir.tools
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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,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'
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 },
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user