import { NextPage } from 'next' import { Text, Flex, Box } from '../../components/primitives' import Layout from 'components/Layout' import { useMediaQuery } from 'react-responsive' import { useContext, useEffect, useMemo, useRef, useState } from 'react' import { useAccount } from 'wagmi' import { TabsList, TabsTrigger, TabsContent } from 'components/primitives/Tab' import * as Tabs from '@radix-ui/react-tabs' import { AcceptBidModal, AcceptBidStep, useUserCollections, useUserTokens, } from '@reservoir0x/reservoir-kit-ui' import { useENSResolver, useMounted } from '../../hooks' import { TokenTable, TokenTableRef } from 'components/portfolio/TokenTable' import { ConnectWalletButton } from 'components/ConnectWalletButton' import { MobileTokenFilters } from 'components/common/MobileTokenFilters' import { TokenFilters } from 'components/common/TokenFilters' import { FilterButton } from 'components/common/FilterButton' import { ListingsTable } from 'components/portfolio/ListingsTable' import { OffersTable } from 'components/portfolio/OffersTable' import { faCopy, faWallet } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import ChainToggle from 'components/common/ChainToggle' import { Head } from 'components/Head' import BatchActionsFooter from 'components/portfolio/BatchActionsFooter' import BatchListings from 'components/portfolio/BatchListings' import { ChainContext } from 'context/ChainContextProvider' import PortfolioSortDropdown, { PortfolioSortingOption, } from 'components/common/PortfolioSortDropdown' import { ActivityFilters } from 'components/common/ActivityFilters' import { MobileActivityFilters } from 'components/common/MobileActivityFilters' import { UserActivityTable } from 'components/portfolio/UserActivityTable' import { useCollectionActivity } from '@reservoir0x/reservoir-kit-ui' import { useRouter } from 'next/router' import { ItemView, ViewToggle } from 'components/portfolio/ViewToggle' import { ToastContext } from 'context/ToastContextProvider' import Blockies from 'react-blockies'; import { Avatar } from 'components/primitives/Avatar' import CopyText from 'components/common/CopyText' type ActivityTypes = Exclude< NonNullable< NonNullable< Exclude['0'], boolean> >['types'] >, string > export type UserToken = ReturnType['data'][0] const IndexPage: NextPage = () => { const router = useRouter() const { address: accountAddress, isConnected } = useAccount() const address = router.query.address ? (router.query.address[0] as `0x${string}`) : accountAddress const [tabValue, setTabValue] = useState('items') const [itemView, setItemView] = useState('list') const [activityTypes, setActivityTypes] = useState(['sale']) const [activityFiltersOpen, setActivityFiltersOpen] = useState(true) const [tokenFiltersOpen, setTokenFiltersOpen] = useState(false) const [hideSpam, setHideSpam] = useState(true) const [filterCollection, setFilterCollection] = useState( undefined ) const [sortByType, setSortByType] = useState('acquiredAt') const isSmallDevice = useMediaQuery({ maxWidth: 905 }) const isMounted = useMounted() const { addToast } = useContext(ToastContext) const isOwner = !router.query.address || router.query.address[0] === accountAddress const { avatar: ensAvatar, name: resolvedEnsName, shortAddress, } = useENSResolver(address) let collectionQuery: Parameters['1'] = { limit: 100, excludeSpam: hideSpam, collectionsSetId: 'b8ea588c49df7aba7b70a49ec1102b7809158fb6fa470c4fb49f9d588e8aa65a', } const { chain } = useContext(ChainContext) if (chain.collectionSetId) { collectionQuery.collectionsSetId = chain.collectionSetId } else if (chain.community) { collectionQuery.community = chain.community } const { data: collections, isLoading: collectionsLoading, fetchNextPage, } = useUserCollections(isMounted ? (address as string) : '', collectionQuery) console.log(collections) // Batch listing logic const [showListingPage, setShowListingPage] = useState(false) const [batchAcceptBidModalOpen, setBatchAcceptBidModalOpen] = useState(false) const [selectedItems, setSelectedItems] = useState([]) const sellableItems = useMemo( () => selectedItems .filter((item) => item.token?.topBid?.id !== null) .map((item) => ({ tokenId: item.token?.tokenId as string, collectionId: item.token?.collection?.id as string, })), [selectedItems] ) const tokenTableRef = useRef(null) useEffect(() => { setSelectedItems([]) }, [chain]) useEffect(() => { setSelectedItems([]) setShowListingPage(false) setBatchAcceptBidModalOpen(false) }, [address]) useEffect(() => { let tab = tabValue let deeplinkTab: string | null = null if (typeof window !== 'undefined') { const params = new URL(window.location.href).searchParams deeplinkTab = params.get('tab') } if (deeplinkTab) { switch (deeplinkTab) { case 'items': tab = 'items' break case 'collections': tab = 'collections' break case 'listings': tab = 'listings' break case 'offers': tab = 'offers' break case 'activity': tab = 'activity' break } } setTabValue(tab) }, [isSmallDevice, router.asPath]) useEffect(() => { if (router.query.tab !== tabValue) { const newQuery = { ...router.query, tab: tabValue }; router.push({ pathname: router.pathname, query: newQuery, }, undefined, { shallow: true }); } }, [tabValue]); useEffect(() => { // This function runs only once when the component mounts const handleRouteChange = (url: string) => { const regex = /^\/portfolio\/0x[a-fA-F0-9]{40}\?tab=items$/; if (regex.test(url)) { window.location.reload(); } }; // Listen for route changes to /portfolio/{connected_address}?tab=items, reload when detected router.events.on('routeChangeComplete', handleRouteChange); // Cleanup listener when component unmounts return () => { router.events.off('routeChangeComplete', handleRouteChange); }; }, [router]); if (!isMounted) { return null } return ( <> {!isOwner || isConnected ? ( <> {showListingPage && !isSmallDevice ? ( ) : ( <> {ensAvatar ? ( ) : ( )} {resolvedEnsName ? resolvedEnsName : shortAddress} {shortAddress} setTabValue(value)} > Items Listings Offers Made Activity {isSmallDevice ? ( ) : ( )} {isSmallDevice && ( { setSortByType(option) }} /> )} {!isSmallDevice && !collectionsLoading && collections.length > 0 && ( )} {!isSmallDevice && !collectionsLoading && ( { setSortByType(option) }} /> )} {!isSmallDevice && ( )} {!isSmallDevice && ( )} {isSmallDevice ? ( ) : ( )} )} ) : ( Sell your NFT instantly Connect wallet to instant sell your token across all major marketplaces. )} { if (tokenTableRef && currentStep == AcceptBidStep.Complete) { tokenTableRef.current?.mutate() setSelectedItems([]) } }} onBidAcceptError={(error: any) => { if (error?.type === 'price mismatch') { addToast?.({ title: 'Could not accept offer', description: 'Offer was lower than expected.', }) return } // Handle user rejection if (error?.code === 4001) { addToast?.({ title: 'User canceled transaction', description: 'You have canceled the transaction.', }) return } addToast?.({ title: 'Could not accept offer', description: 'The transaction was not completed.', }) }} /> ) } export default IndexPage