import { FC, useContext, useEffect, useRef, useMemo } from 'react' import { useMediaQuery } from 'react-responsive' import { Text, Flex, TableCell, TableRow, HeaderRow, FormatCryptoCurrency, Anchor, Button, Box, Tooltip, } from '../primitives' import { useIntersectionObserver } from 'usehooks-ts' import LoadingSpinner from '../common/LoadingSpinner' import { useBids } from '@reservoir0x/reservoir-kit-ui' import Link from 'next/link' import { MutatorCallback } from 'swr' import { useMarketplaceChain, useTimeSince } from 'hooks' import CancelBid from 'components/buttons/CancelBid' import { Address } from 'wagmi' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faGasPump, faHand } from '@fortawesome/free-solid-svg-icons' import { NAVBAR_HEIGHT } from 'components/navbar' import { ChainContext } from 'context/ChainContextProvider' import Img from 'components/primitives/Img' import optimizeImage from 'utils/optimizeImage' import { formatNumber } from 'utils/numbers' type Props = { address: Address | undefined isOwner: boolean } const desktopTemplateColumns = '1.25fr .75fr repeat(4, 1fr)' export const OffersTable: FC = ({ address, isOwner }) => { const loadMoreRef = useRef(null) const loadMoreObserver = useIntersectionObserver(loadMoreRef, {}) let bidsQuery: Parameters['0'] = { maker: address, includeCriteriaMetadata: true, includeRawData: true, } const { chain } = useContext(ChainContext) if (chain.community) bidsQuery.community = chain.community const { data: offers, fetchNextPage, mutate, isValidating, isFetchingPage, } = useBids(bidsQuery) useEffect(() => { const isVisible = !!loadMoreObserver?.isIntersecting if (isVisible) { fetchNextPage() } }, [loadMoreObserver?.isIntersecting]) return ( <> {!isValidating && !isFetchingPage && offers && offers.length === 0 ? ( No offers made yet ) : ( {offers.map((offer, i) => { return ( ) })} )} {isValidating && ( )} ) } type OfferTableRowProps = { offer: ReturnType['data'][0] isOwner: boolean mutate?: MutatorCallback } const OfferTableRow: FC = ({ offer, isOwner, mutate }) => { const isSmallDevice = useMediaQuery({ maxWidth: 900 }) const { routePrefix } = useMarketplaceChain() const expiration = useTimeSince(offer?.expiration) const isOracleOrder = offer?.isNativeOffChainCancellable const isCollectionOffer = offer?.criteria?.kind !== 'token' // @ts-ignore const attribute = offer?.criteria?.data?.attribute const attributeQueryParam = attribute ? `?attributes[${attribute.key}]=${attribute.value}` : '' const attributeDisplayText = attribute ? `${attribute?.key}: ${attribute?.value}` : '' let criteriaData = offer?.criteria?.data const imageSrc = useMemo(() => { return optimizeImage( criteriaData?.token?.tokenId ? criteriaData?.token?.image || criteriaData?.collection?.image : criteriaData?.collection?.image, 250 ) }, [ criteriaData?.token?.tokenId, criteriaData?.token?.image, criteriaData?.collection?.image, ]) if (isSmallDevice) { return ( src} src={imageSrc} alt={`${offer?.id}`} width={36} height={36} /> {criteriaData?.collection?.name} {isCollectionOffer ? attributeDisplayText : `#${criteriaData?.token?.tokenId}`} {offer?.quantityRemaining && offer?.quantityRemaining > 1 ? ( x{formatNumber(offer.quantityRemaining, 0, true)} ) : null} Net Amount } > {`${offer?.source?.name}`} {expiration} {isOwner ? ( {!isOracleOrder ? ( Cancelling this order requires gas. } > ) : ( )} } /> ) : null} ) } return ( src} src={imageSrc} alt={`${criteriaData?.token?.name}`} width={48} height={48} /> {criteriaData?.collection?.name} {isCollectionOffer ? attributeDisplayText : `#${criteriaData?.token?.tokenId}`} Net Amount } > {formatNumber(offer.quantityRemaining, 0, true)} {expiration} {`${offer?.source?.name}`} {offer?.source?.name as string} {isOwner ? ( {!isOracleOrder ? ( Cancelling this order requires gas. } > ) : ( )} } /> ) : null} ) } const headings = [ 'Items', 'Offer Amount', 'Quantity', 'Expiration', 'Marketplace', '', ] const TableHeading = () => ( {headings.map((heading) => ( {heading} ))} )