diff --git a/.eslintrc.gql.js b/.eslintrc.gql.js index ea90eb02..df2ce9bf 100644 --- a/.eslintrc.gql.js +++ b/.eslintrc.gql.js @@ -1,10 +1,6 @@ module.exports = { root: true, parser: '@graphql-eslint/eslint-plugin', - parserOptions: { - schema: 'node_modules/@nft/api-graphql/schema.graphql', - operations: '**/*.gql', - }, plugins: ['@graphql-eslint/eslint-plugin'], extends: ['plugin:@graphql-eslint/operations-recommended'], rules: { diff --git a/.graphqlrc.yml b/.graphqlrc.yml new file mode 100644 index 00000000..f04bf4a7 --- /dev/null +++ b/.graphqlrc.yml @@ -0,0 +1,2 @@ +schema: './node_modules/@nft/api-graphql/schema.graphql' +documents: '{components,hooks,pages}/**/*.{graphql,gql}' diff --git a/components/Cart/Step/Selection.gql b/components/Cart/Step/Selection.gql index b3a14fbb..7c70a2cb 100644 --- a/components/Cart/Step/Selection.gql +++ b/components/Cart/Step/Selection.gql @@ -34,10 +34,6 @@ query FetchCartItems($offerIds: [UUID!]!) { name image imageMimetype - unlockedContent { - url - mimetype - } animationUrl animationMimetype } diff --git a/components/Cart/Step/Transaction.tsx b/components/Cart/Step/Transaction.tsx index a31cdad0..b3cdf163 100644 --- a/components/Cart/Step/Transaction.tsx +++ b/components/Cart/Step/Transaction.tsx @@ -32,7 +32,7 @@ import useSigner from '../../../hooks/useSigner' import ConnectButtonWithNetworkSwitch from '../../Button/ConnectWithNetworkSwitch' import List, { ListItem } from '../../List/List' import BatchPurchaseModal from '../../Modal/BatchPurchase' -import Price, { formatPrice } from '../../Price/Price' +import Price from '../../Price/Price' import CartItemSummary from '../ItemSummary' type Props = { @@ -190,7 +190,7 @@ const CartStepTransaction: FC = ({ isLoading={approvalLoading[item.currency.id] || false} > {t('cart.step.transaction.button.approve', { - value: formatPrice(item.amount, item.currency), + value: item.currency.symbol, })} ) : balances[item.currency.id] && diff --git a/components/Filter/FilterBy/Trait.tsx b/components/Filter/FilterBy/Trait.tsx index 9452817b..782df787 100644 --- a/components/Filter/FilterBy/Trait.tsx +++ b/components/Filter/FilterBy/Trait.tsx @@ -65,8 +65,8 @@ const FilterByTrait: FC = ({ notifyOnNetworkStatusChange: true, }) - const traitsData = data?.collection?.traitsOfCollection.nodes - const hasNextPage = data?.collection?.traitsOfCollection.pageInfo.hasNextPage + const traitsData = data?.collectionTraits?.nodes + const hasNextPage = data?.collectionTraits?.pageInfo.hasNextPage const loadMore = useCallback(async () => { const newOffset = offset + PAGINATION_LIMIT @@ -76,22 +76,16 @@ const FilterByTrait: FC = ({ // Cannot use concatToQuery function because of nested cache // Nested cache comes from the shape of FetchCollectionTraits query above updateQuery: (prevResult, { fetchMoreResult }) => { - if ( - !fetchMoreResult || - !fetchMoreResult.collection?.traitsOfCollection - ) + if (!fetchMoreResult || !fetchMoreResult.collectionTraits) return prevResult return { ...fetchMoreResult, - collection: { - ...fetchMoreResult.collection, - traitsOfCollection: { - ...fetchMoreResult.collection.traitsOfCollection, - nodes: [ - ...(prevResult?.collection?.traitsOfCollection.nodes || []), - ...fetchMoreResult.collection.traitsOfCollection.nodes, - ], - }, + collectionTraits: { + ...fetchMoreResult.collectionTraits, + nodes: [ + ...(prevResult?.collectionTraits?.nodes || []), + ...fetchMoreResult.collectionTraits.nodes, + ], }, } }, diff --git a/components/History/HistoryList.gql b/components/History/HistoryList.gql index bc90533a..ca11dcbe 100644 --- a/components/History/HistoryList.gql +++ b/components/History/HistoryList.gql @@ -6,53 +6,51 @@ query FetchAssetHistory( $offset: Int! $filter: [AssetHistoryAction!] ) { - asset( - chainId: $chainId - collectionAddress: $collectionAddress - tokenId: $tokenId + assetHistories( + filter: { + chainId: { equalTo: $chainId } + collectionAddress: { equalTo: $collectionAddress } + tokenId: { equalTo: $tokenId } + action: { in: $filter } + } + orderBy: DATE_DESC + first: $limit + offset: $offset + includeWhenAssetDeleted: YES + includeWhenCurrencyDeleted: YES ) { - chainId - collectionAddress - tokenId - histories( - filter: { action: { in: $filter } } - orderBy: DATE_DESC - first: $limit - offset: $offset - ) { - pageInfo { - hasNextPage - hasPreviousPage - } - nodes { - action - date - unitPrice - quantity - fromAddress - from { - address - name - image - verification { - status - } - } - toAddress - to { - address - name - image - verification { - status - } + pageInfo { + hasNextPage + hasPreviousPage + } + nodes { + action + date + unitPrice + quantity + fromAddress + from { + address + name + image + verification { + status } - transactionHash - currency { - decimals - symbol + } + toAddress + to { + address + name + image + verification { + status } } + transactionHash + currency { + decimals + symbol + } } } } diff --git a/components/History/HistoryList.tsx b/components/History/HistoryList.tsx index 9cde8cf3..72ee147e 100644 --- a/components/History/HistoryList.tsx +++ b/components/History/HistoryList.tsx @@ -57,12 +57,10 @@ const HistoryList: FC = ({ chainId, collectionAddress, tokenId }) => { filter, }, }) - const histories = historyData?.asset?.histories.nodes + const histories = historyData?.assetHistories?.nodes const ListItem = ( - history: NonNullable< - FetchAssetHistoryQuery['asset'] - >['histories']['nodes'][0], + history: NonNullable['nodes'][0], i: number, ) => { switch (history.action) { @@ -140,9 +138,9 @@ const HistoryList: FC = ({ chainId, collectionAddress, tokenId }) => { = ({ date }) => { const assetsQuery = useFetchAssetsQuery({ variables: { - now: date, limit: PAGINATION_LIMIT, assetIds: assetIds || [], address: address || '', diff --git a/components/HomeSection/Banner.tsx b/components/HomeSection/Banner.tsx new file mode 100644 index 00000000..f9e202f2 --- /dev/null +++ b/components/HomeSection/Banner.tsx @@ -0,0 +1,65 @@ +import { Box, Button, Flex, Heading, VStack } from '@chakra-ui/react' +import Link from '../../components/Link/Link' +import MarkdownViewer from '../../components/MarkdownViewer' +import useEnvironment from '../../hooks/useEnvironment' +import Image from '../Image/Image' +import Slider from '../Slider/Slider' + +const BannerHomeSection = () => { + const { HOME_BANNERS } = useEnvironment() + + if (!HOME_BANNERS.length || HOME_BANNERS.length === 0) return null + return ( + 1 ? Slider : 'div'}> + {HOME_BANNERS.map((banner) => ( + + {banner.title} + + + {banner.title} + {banner.content && ( + + + + )} + + {banner.button && ( + + )} + + + ))} + + ) +} + +export default BannerHomeSection diff --git a/components/HomeSection/Featured.gql b/components/HomeSection/Featured.gql index eaa9ed13..dea12f1b 100644 --- a/components/HomeSection/Featured.gql +++ b/components/HomeSection/Featured.gql @@ -55,21 +55,15 @@ query FetchFeaturedAssets( } } } - bestBid: bids( - orderBy: [UNIT_PRICE_IN_REF_DESC, CREATED_AT_ASC] - filter: { expiredAt: { greaterThan: $now } } - first: 1 - ) { - nodes { - unitPrice - amount - currency { - image - name - id - decimals - symbol - } + bestBid { + unitPrice + amount + currency { + image + name + id + decimals + symbol } } sales( diff --git a/components/Link/Link.tsx b/components/Link/Link.tsx index 1ccd2fdf..4c8dab56 100644 --- a/components/Link/Link.tsx +++ b/components/Link/Link.tsx @@ -1,4 +1,4 @@ -import { Link as ChakraLink, Flex, LinkProps } from '@chakra-ui/react' +import { Box, Link as ChakraLink, Flex, LinkProps } from '@chakra-ui/react' import { HiOutlineExternalLink } from '@react-icons/all-files/hi/HiOutlineExternalLink' import NextLink, { LinkProps as NextLinkProps } from 'next/link' import { forwardRef } from 'react' @@ -6,13 +6,15 @@ import { forwardRef } from 'react' type IProps = Omit & Omit & { externalIcon?: boolean + condition?: boolean } & ( | { isExternal: true; href: LinkProps['href'] } | { isExternal?: false; href: NextLinkProps['href'] } ) const Link = forwardRef(function Link(props, ref) { - const { children, href, isExternal, externalIcon, ...rest } = props + const { children, href, isExternal, externalIcon, condition, ...rest } = props + if (condition === false) return {children} if (isExternal) { return ( ( }, ) -const MarkdownViewer: FC = ({ source, ...props }) => { +const MarkdownViewer: FC = ({ + noTruncate, + source, + ...props +}) => { const { t } = useTranslation('components') const { colorMode } = useColorMode() const [isMultiline, setMultiline] = useState(false) @@ -30,6 +34,7 @@ const MarkdownViewer: FC = ({ source, ...props }) => { const invisibleRef = useRef(null) useEffect(() => { + if (noTruncate) return setOpen(true) const element = invisibleRef.current const ONE_LINE_HEIGHT = 24 if ( @@ -40,7 +45,7 @@ const MarkdownViewer: FC = ({ source, ...props }) => { ) { setMultiline(true) } - }, [source]) + }, [noTruncate, source]) return ( diff --git a/components/Modal/Sign.tsx b/components/Modal/Sign.tsx new file mode 100644 index 00000000..befa44ac --- /dev/null +++ b/components/Modal/Sign.tsx @@ -0,0 +1,78 @@ +import { + Button, + Heading, + Icon, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, +} from '@chakra-ui/react' +import { FaFileSignature } from '@react-icons/all-files/fa/FaFileSignature' +import useTranslation from 'next-translate/useTranslation' +import { FC, useCallback, useState } from 'react' +import { Connector, useDisconnect } from 'wagmi' +import useAccount from '../../hooks/useAccount' + +type Props = { + connector?: Connector + isOpen: boolean + onClose: () => void +} + +const SignModal: FC = ({ connector, isOpen, onClose }) => { + const { t } = useTranslation('components') + const { sign } = useAccount() + const { disconnect } = useDisconnect() + const [isLoading, setLoading] = useState(false) + + const onSign = useCallback(async () => { + if (!connector) return + setLoading(true) + try { + await sign(connector) + } catch (e: any) { + disconnect() + } finally { + onClose() + setLoading(false) + } + }, [connector, disconnect, onClose, sign]) + + return ( + { + onClose() + disconnect() + }} + isCentered + size="sm" + > + + + + + {t('modal.signature.title')} + + + + + + {t('modal.signature.sub-title')} + {t('modal.signature.description')} + + + + + + + ) +} + +export default SignModal diff --git a/components/Navbar/Navbar.tsx b/components/Navbar/Navbar.tsx index 2ef1ccac..8dc6eaf4 100644 --- a/components/Navbar/Navbar.tsx +++ b/components/Navbar/Navbar.tsx @@ -340,6 +340,7 @@ const Navbar: FC<{ const { openConnectModal } = useConnectModal() const { items: cartItems } = useCart() const lastNotification = cookies[`lastNotification-${address}`] + const { data: accountData, refetch, @@ -351,10 +352,15 @@ const Navbar: FC<{ }, skip: !isLoggedIn && !address, }) + const account = isLoggedIn ? accountData?.account || previousAccountData?.account : undefined + const notifications = isLoggedIn + ? accountData?.notifications || previousAccountData?.notifications + : undefined + useEffect(() => { if (!isReady) return if (!query.search) return formValues.setValue('search', '') @@ -496,7 +502,7 @@ const Navbar: FC<{ > - {account.notifications.totalCount > 0 && ( + {notifications && notifications.totalCount > 0 && ( = ({ asset, currencies, onCreated }) => { () => asset.collection.standard === 'ERC1155', [asset.collection.standard], ) - const ownerAddress = useMemo( - () => asset.ownerships.nodes[0]?.ownerAddress, - [asset.ownerships.nodes], - ) const defaultExpirationValue = useMemo( () => @@ -171,11 +167,6 @@ const OfferFormBid: FC = ({ asset, currencies, onCreated }) => { chain: asset.chainId, collection: toAddress(asset.collectionAddress), token: asset.tokenId, - taker: isMultiple - ? undefined // Keep the bid open for anyone that can fill it - : ownerAddress - ? toAddress(ownerAddress) - : undefined, expiredAt: new Date(expiredAt), }) diff --git a/components/Offer/Summary.tsx b/components/Offer/Summary.tsx index df4e7806..4df406ca 100644 --- a/components/Offer/Summary.tsx +++ b/components/Offer/Summary.tsx @@ -12,7 +12,7 @@ type Props = { isSingle: boolean price: BigNumber quantity: BigNumber - feesOnTopPerTenThousand?: number | undefined + feesOnTopPerTenThousand?: BigNumber | undefined noFees?: boolean } @@ -66,7 +66,7 @@ const Summary: FC = ({ ) : ( {t('offer.summary.fees', { - value: feesOnTopPerTenThousand / 100, + value: feesOnTopPerTenThousand.div(100).toString(), })} = ({ asset, + sales, currencies, isHomepage, onOfferCanceled, @@ -73,7 +74,7 @@ const SaleDetail: FC = ({ () => asset.collection.standard === 'ERC721', [asset.collection.standard], ) - const directSales = asset.sales.nodes + const directSales = sales.nodes return ( diff --git a/components/Sales/Direct/CardFooter.tsx b/components/Sales/Direct/CardFooter.tsx index de10bc27..f523d7ed 100644 --- a/components/Sales/Direct/CardFooter.tsx +++ b/components/Sales/Direct/CardFooter.tsx @@ -1,6 +1,8 @@ import { Divider, Flex, HStack, Text } from '@chakra-ui/react' import useTranslation from 'next-translate/useTranslation' import { FC, useMemo } from 'react' +import useAccount from '../../../hooks/useAccount' +import { isSameAddress } from '../../../utils' import AddToCartButton from '../../Button/AddToCart' import Link from '../../Link/Link' import Price from '../../Price/Price' @@ -13,10 +15,12 @@ type Props = { decimals: number symbol: string } + maker: { + address: string + } } numberOfSales: number hasMultiCurrency: boolean - isOwner: boolean showButton?: boolean } @@ -24,10 +28,18 @@ const SaleDirectCardFooter: FC = ({ sale, numberOfSales, hasMultiCurrency, - isOwner, showButton = true, }) => { const { t } = useTranslation('components') + const { address } = useAccount() + + // TODO: we should have a modal if there is more than one sale like we have on detail page + // issue is tracked on https://github.com/liteflow-labs/starter-kit/issues/529 for this modal improvement + const isOwner = useMemo( + () => (address ? isSameAddress(sale.maker.address, address) : false), + [address, sale.maker], + ) + const chip = useMemo(() => { switch (numberOfSales) { case 0: @@ -94,7 +106,7 @@ const SaleDirectCardFooter: FC = ({ : chip} - {showButton && ( + {showButton && address && ( <> = ({ asset, currencies, onCreated }) => { ) : ( {t('sales.direct.form.fees', { - value: feesPerTenThousand / 100, + value: feesPerTenThousand.div(100).toString(), })} = ({ asset, currencies, onCreated }) => { ) : ( {t('sales.direct.form.total-fees', { - value: feesPerTenThousand / 100, + value: feesPerTenThousand.div(100).toString(), })} = ({ asset }) => { const [isHovered, setIsHovered] = useState(false) const media = useDetectAssetMedia(asset) - const sale = asset.firstSale?.nodes[0] - const bestBid = - asset.bestBid?.nodes?.length > 0 ? asset.bestBid.nodes[0] : undefined - const numberOfSales = asset.firstSale?.totalCount || 0 - const hasMultiCurrency = asset.firstSale?.totalCurrencyDistinctCount - ? asset.firstSale.totalCurrencyDistinctCount > 1 - : false + const sale = asset.firstSale + const bestBid = asset.bestBid + const numberOfSales = asset.totalSalesCount + const hasMultiCurrency = asset.totalSalesCurrencyDistinctCount > 1 const chainName = useMemo( () => CHAINS.find((x) => x.id === asset.collection.chainId)?.name, [asset.collection.chainId, CHAINS], @@ -107,7 +101,6 @@ const TokenCard: FC = ({ asset }) => { sale={sale} numberOfSales={numberOfSales} hasMultiCurrency={hasMultiCurrency} - isOwner={isOwner} showButton={isHovered} /> ) diff --git a/components/Token/Header.tsx b/components/Token/Header.tsx index 7de4e788..62183f4c 100644 --- a/components/Token/Header.tsx +++ b/components/Token/Header.tsx @@ -84,9 +84,14 @@ const TokenHeader: FC = ({ {asset.name} - + = ({ asset }) => { +const TokenMetadata: FC = ({ asset, sales, ownerships }) => { const { t } = useTranslation('components') const isOpenCollection = asset.collection.mintType === 'PUBLIC' - const numberOfOwners = asset.ownerships.totalCount - const saleSupply = BigNumber.from(asset.sales.totalAvailableQuantitySum) + const numberOfOwners = ownerships.totalCount + const saleSupply = BigNumber.from(sales.totalAvailableQuantitySum) const totalSupply = BigNumber.from(asset.quantity) - const owners = asset.ownerships.nodes + const owners = ownerships.nodes return ( @@ -81,7 +81,7 @@ const TokenMetadata: FC = ({ asset }) => { {t('token.metadata.owners')} - + )} {asset.collection.standard === 'ERC721' && ( diff --git a/components/Token/Owners/Modal.tsx b/components/Token/Owners/Modal.tsx index 399203e3..2e6ed96b 100644 --- a/components/Token/Owners/Modal.tsx +++ b/components/Token/Owners/Modal.tsx @@ -29,27 +29,27 @@ export type Props = { chainId: number collectionAddress: string tokenId: string - ownerships: { - totalCount: number - nodes: { - ownerAddress: string - quantity: string - owner: { - address: string - name: string | null - image: string | null - verification: { - status: AccountVerificationStatus - } | null - } - }[] - } + } + ownerships: { + totalCount: number + nodes: { + ownerAddress: string + quantity: string + owner: { + address: string + name: string | null + image: string | null + verification: { + status: AccountVerificationStatus + } | null + } + }[] } } const OwnerPaginationLimit = 8 -const OwnersModal: FC = ({ asset }) => { +const OwnersModal: FC = ({ asset, ownerships }) => { const { t } = useTranslation('components') const { isOpen, onOpen, onClose } = useDisclosure() const [page, setPage] = useState(1) @@ -76,10 +76,7 @@ const OwnersModal: FC = ({ asset }) => { return ( <> - + = ({ asset }) => { px={2.5} > - {asset.ownerships.totalCount} + {ownerships.totalCount} diff --git a/environment.ts b/environment.ts index af27992f..0c3e37a7 100644 --- a/environment.ts +++ b/environment.ts @@ -3,11 +3,15 @@ import { LRUCache } from 'lru-cache' import { createContext } from 'react' import invariant from 'ts-invariant' import { + arbitrum, + arbitrumSepolia, bsc, bscTestnet, Chain, goerli as ethereumGoerli, mainnet as ethereumMainnet, + neonDevnet, + neonMainnet, polygon, polygonMumbai, } from 'wagmi/chains' @@ -24,6 +28,17 @@ type RemoteConfig = { FAVICON: string BRAND_COLOR: string + HOME_BANNERS: { + image: string + title: string + content?: string + textColor?: string + button: { + text: string + href: string + isExternal?: boolean + } + }[] FEATURED_TOKEN: string[] HOME_COLLECTIONS: string[] HOME_USERS: string[] @@ -95,6 +110,33 @@ export type Environment = { * Home page configuration */ + // List of banners to be displayed on the homepage + HOME_BANNERS: { + // URL of the image to display + image: string + + // Title of the banner + title: string + + // (Optional) Content of the banner + content?: string + + // (Optional) Text color of the banner + textColor?: string + + // Button to display on the banner + button: { + // Text of the button + text: string + + // URL to redirect to when the button is clicked + href: string + + // (Optional) Whether the URL is external or not (default: false) + isExternal?: boolean + } + }[] + // Ordered list of tokens to be highlighted on the homepage with the following format: [chainId]-[contractAddress]-[tokenId] FEATURED_TOKEN: string[] @@ -199,6 +241,10 @@ const getEnvironment = async (): Promise => { bsc, polygon, polygonMumbai, + neonMainnet, + neonDevnet, + arbitrum, + arbitrumSepolia, { name: 'LightLink Phoenix', network: 'lightlink-phoenix', @@ -228,7 +274,7 @@ const getEnvironment = async (): Promise => { url: 'https://phoenix.lightlink.io', }, }, - } as Chain, + }, { name: 'LightLink Pegasus Testnet', network: 'lightlink-pegasus', @@ -259,13 +305,14 @@ const getEnvironment = async (): Promise => { url: 'https://pegasus.lightlink.io', }, }, - } as Chain, + }, ], WALLET_CONNECT_PROJECT_ID: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID, MAGIC_API_KEY: process.env.NEXT_PUBLIC_MAGIC_API_KEY, ALCHEMY_API_KEY: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY, // Home page configuration + HOME_BANNERS: metadata.HOME_BANNERS || [], FEATURED_TOKEN: metadata.FEATURED_TOKEN || [], HOME_COLLECTIONS: metadata.HOME_COLLECTIONS || [], HOME_USERS: metadata.HOME_USERS || [], diff --git a/hooks/useAccount.ts b/hooks/useAccount.ts index 31b50267..e7c9ae7b 100644 --- a/hooks/useAccount.ts +++ b/hooks/useAccount.ts @@ -1,6 +1,6 @@ import { useAuthenticate, useIsLoggedIn } from '@liteflow/react' import { JwtPayload, jwtDecode } from 'jwt-decode' -import { useCallback, useEffect, useMemo } from 'react' +import { useCallback, useMemo } from 'react' import { useCookies } from 'react-cookie' import { Connector, useAccount as useWagmiAccount } from 'wagmi' import { walletClientToSigner } from './useSigner' @@ -12,6 +12,7 @@ type AccountDetail = { jwtToken: string | null logout: () => Promise login: (connector: Connector) => Promise + sign: (connector: Connector) => Promise } export const COOKIE_JWT_TOKEN = 'jwt-token' @@ -57,14 +58,6 @@ export default function useAccount(): AccountDetail { [isLoggedInWhileReconnect, isLoggedInToAPI], ) - // Reconnect based on the token and mark as logged in - useEffect(() => { - if (isLoggedInToAPI) return - if (!isReconnecting) return - if (!jwt) return - setAuthenticationToken(jwt.token) - }, [isLoggedInToAPI, isReconnecting, jwt, setAuthenticationToken]) - const login = useCallback( async (connector: Connector) => { const wallet = await connector.getWalletClient() @@ -73,6 +66,14 @@ export default function useAccount(): AccountDetail { if (jwt && currentAddress === jwt.address) { return setAuthenticationToken(jwt.token) } + }, + [jwt, setAuthenticationToken], + ) + + const sign = useCallback( + async (connector: Connector) => { + const wallet = await connector.getWalletClient() + const signer = walletClientToSigner(wallet) const { jwtToken } = await authenticate(signer) const newJwt = jwtDecode(jwtToken) @@ -86,7 +87,7 @@ export default function useAccount(): AccountDetail { : {}), }) }, - [jwt, authenticate, setAuthenticationToken, setCookie], + [authenticate, setCookie], ) // Server side @@ -98,6 +99,7 @@ export default function useAccount(): AccountDetail { isConnected: !!jwt, logout, login, + sign, } } @@ -108,5 +110,6 @@ export default function useAccount(): AccountDetail { isConnected, logout, login, + sign, } } diff --git a/hooks/useAssetFilterFromQuery.ts b/hooks/useAssetFilterFromQuery.ts index 807d6acc..0c9d8d21 100644 --- a/hooks/useAssetFilterFromQuery.ts +++ b/hooks/useAssetFilterFromQuery.ts @@ -67,10 +67,8 @@ const traitFilter = (traits: TraitFilter[]): AssetFilter => const collectionFilter = (collection: string): AssetFilter => { const [chainId, address] = collection.split('-') return { - collection: { - chainId: { equalTo: chainId && parseInt(chainId, 10) }, - address: { equalTo: address }, - }, + chainId: { equalTo: chainId && parseInt(chainId, 10) }, + collectionAddress: { equalTo: address }, } as AssetFilter } diff --git a/hooks/useFees.gql b/hooks/useFees.gql index 9715e8dd..ceace729 100644 --- a/hooks/useFees.gql +++ b/hooks/useFees.gql @@ -14,6 +14,7 @@ query Fees( unitPrice: $unitPrice tokenId: $tokenId ) { - valuePerTenThousand + value + precision } } diff --git a/hooks/useFees.ts b/hooks/useFees.ts index 50f905ea..bb050525 100644 --- a/hooks/useFees.ts +++ b/hooks/useFees.ts @@ -64,10 +64,11 @@ export default function useFees({ tokenId, ]) - const feesPerTenThousand = useMemo( - () => (data?.orderFees || previousData?.orderFees)?.valuePerTenThousand, - [data?.orderFees, previousData?.orderFees], - ) + const feesPerTenThousand = useMemo(() => { + const value = (data?.orderFees || previousData?.orderFees)?.value + if (value === undefined) return undefined + return BigNumber.from(value) // TODO: take into account orderFees.precision + }, [data?.orderFees, previousData?.orderFees]) return { feesPerTenThousand, diff --git a/hooks/useFetchCollectionTraits.gql b/hooks/useFetchCollectionTraits.gql index bed95bfe..a37ab5c6 100644 --- a/hooks/useFetchCollectionTraits.gql +++ b/hooks/useFetchCollectionTraits.gql @@ -1,33 +1,33 @@ query FetchCollectionTraits( $chainId: Int! $address: Address! - $filter: CollectionTraitFilter! + $filter: [CollectionTraitFilter!] $limit: Int! $offset: Int! ) { - collection(chainId: $chainId, address: $address) { - chainId - address - traitsOfCollection( - orderBy: [TYPE_ASC] - first: $limit - filter: $filter - offset: $offset - ) { - pageInfo { - hasNextPage - } - nodes { - type - numberOfValues - values( - first: 50 # TODO: implement pagination - orderBy: [NUMBER_OF_ASSETS_DESC, VALUE_ASC] - ) { - nodes { - value - numberOfAssets - } + collectionTraits( + orderBy: [TYPE_ASC] + first: $limit + filter: { + chainId: { equalTo: $chainId } + collectionAddress: { equalTo: $address } + and: $filter + } + offset: $offset + ) { + pageInfo { + hasNextPage + } + nodes { + type + numberOfValues + values( + first: 50 # TODO: implement pagination + orderBy: [NUMBER_OF_ASSETS_DESC, VALUE_ASC] + ) { + nodes { + value + numberOfAssets } } } diff --git a/layouts/navbar.gql b/layouts/navbar.gql index 200d4666..5773837e 100644 --- a/layouts/navbar.gql +++ b/layouts/navbar.gql @@ -3,8 +3,15 @@ query NavbarAccount($account: Address!, $lastNotification: Datetime!) { address name image - notifications(filter: { createdAt: { greaterThan: $lastNotification } }) { - totalCount + } + notifications( + filter: { + accountAddress: { equalTo: $account } + createdAt: { greaterThan: $lastNotification } } + includeWhenCollectionDeleted: YES + includeWhenTradeDeleted: YES + ) { + totalCount } } diff --git a/locales/en/components.json b/locales/en/components.json index f01a4ec8..92d1703d 100644 --- a/locales/en/components.json +++ b/locales/en/components.json @@ -475,6 +475,12 @@ "title": "Sign in with your wallet", "description": "Connect with one of the following options.", "alternative": "Or sign in with" + }, + "signature": { + "title": "Signature", + "sub-title": "Verify your account", + "description": "To complete the connection, you must sign a message in your wallet to verify that you are the owner of this account.", + "action": "Send message" } }, "back": "Go back", diff --git a/locales/es-mx/components.json b/locales/es-mx/components.json index 9410f455..3971ced8 100644 --- a/locales/es-mx/components.json +++ b/locales/es-mx/components.json @@ -475,6 +475,12 @@ "title": "Inicia sesión con tu Wallet", "description": "Conéctate con alguna de las siguientes opciones:", "alternative": "O inicia sesión con:" + }, + "signature": { + "title": "Signature", + "sub-title": "Verify your account", + "description": "To complete the connection, you must sign a message in your wallet to verify that you are the owner of this account.", + "action": "Send message" } }, "back": "Volver", diff --git a/locales/ja/components.json b/locales/ja/components.json index 1e6fb175..69897be5 100644 --- a/locales/ja/components.json +++ b/locales/ja/components.json @@ -475,6 +475,12 @@ "title": "ウォレットでサインインする", "description": "次のいずれかのオプションを使用して接続します。", "alternative": "または、" + }, + "signature": { + "title": "Signature", + "sub-title": "Verify your account", + "description": "To complete the connection, you must sign a message in your wallet to verify that you are the owner of this account.", + "action": "Send message" } }, "back": "戻る", diff --git a/locales/zh-cn/components.json b/locales/zh-cn/components.json index 097fb1b4..54cd161e 100644 --- a/locales/zh-cn/components.json +++ b/locales/zh-cn/components.json @@ -475,6 +475,12 @@ "title": "使用钱包登录", "description": "使用以下选项之一进行连接。", "alternative": "或使用" + }, + "signature": { + "title": "Signature", + "sub-title": "Verify your account", + "description": "To complete the connection, you must sign a message in your wallet to verify that you are the owner of this account.", + "action": "Send message" } }, "back": "返回", diff --git a/package-lock.json b/package-lock.json index 87693320..d238a1de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@magiclabs/wagmi-connector": "^1.1.5", "@metamask/jazzicon": "^2.0.0", "@nft/chat": "^0.2.0", - "@nft/webhook": "^1.0.0-beta.49", + "@nft/webhook": "^1.0.0-beta.51", "@rainbow-me/rainbowkit": "^1.3.3", "@react-icons/all-files": "^4.1.0", "@uiw/react-markdown-preview": "^5.0.7", @@ -55,7 +55,7 @@ "@graphql-codegen/typescript-react-apollo": "^4.1.0", "@graphql-eslint/eslint-plugin": "^3.20.1", "@next/bundle-analyzer": "^14.0.4", - "@nft/api-graphql": "^1.0.0-beta.49", + "@nft/api-graphql": "^1.0.0-beta.52-prerelease-3", "@types/nodemailer": "^6.4.14", "@types/nprogress": "^0.2.3", "@types/react": "^18.2.48", @@ -6724,9 +6724,9 @@ } }, "node_modules/@nft/api-graphql": { - "version": "1.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@nft/api-graphql/-/api-graphql-1.0.0-beta.49.tgz", - "integrity": "sha512-WXvaU3HcHjD0hS2PZnKOcn5SMQtbzByAXbbo4k9VpXGzOe0ZIfmKDG4SGszBoASka7OTezQUqpQzY6eAXoQvnA==", + "version": "1.0.0-beta.52-prerelease-3", + "resolved": "https://registry.npmjs.org/@nft/api-graphql/-/api-graphql-1.0.0-beta.52-prerelease-3.tgz", + "integrity": "sha512-FCYJOEb3V9GdM/6AIlJi/AS31Ia6/MnDpq3SxnofDsEhPD8IppRVmBKf0j8hejdL/CHVsNZwnRKsYsGTleVaGA==", "dev": true }, "node_modules/@nft/chat": { @@ -6753,9 +6753,9 @@ } }, "node_modules/@nft/webhook": { - "version": "1.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@nft/webhook/-/webhook-1.0.0-beta.49.tgz", - "integrity": "sha512-aoc4OZ1oWZ2Nc/k95cBuX9kgvIwaWSjAYOAatZB/dTVvjdz/VEoa54VLhi9XQR5lZfpbMbFOeXnGxR1BIWf82Q==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@nft/webhook/-/webhook-1.0.0-beta.51.tgz", + "integrity": "sha512-gX9Rj+wBga2uGQ6yfY3ZcHu0dh5VSVMcfUcfZg5ObkeupJSvLBB7R42dY4DKz0CzfQVMtNnsu1J03gnmE8VCMQ==", "dependencies": { "raw-body": "^2.5.1", "ts-invariant": "^0.9.4" @@ -26699,9 +26699,9 @@ "optional": true }, "@nft/api-graphql": { - "version": "1.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@nft/api-graphql/-/api-graphql-1.0.0-beta.49.tgz", - "integrity": "sha512-WXvaU3HcHjD0hS2PZnKOcn5SMQtbzByAXbbo4k9VpXGzOe0ZIfmKDG4SGszBoASka7OTezQUqpQzY6eAXoQvnA==", + "version": "1.0.0-beta.52-prerelease-3", + "resolved": "https://registry.npmjs.org/@nft/api-graphql/-/api-graphql-1.0.0-beta.52-prerelease-3.tgz", + "integrity": "sha512-FCYJOEb3V9GdM/6AIlJi/AS31Ia6/MnDpq3SxnofDsEhPD8IppRVmBKf0j8hejdL/CHVsNZwnRKsYsGTleVaGA==", "dev": true }, "@nft/chat": { @@ -26724,9 +26724,9 @@ } }, "@nft/webhook": { - "version": "1.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@nft/webhook/-/webhook-1.0.0-beta.49.tgz", - "integrity": "sha512-aoc4OZ1oWZ2Nc/k95cBuX9kgvIwaWSjAYOAatZB/dTVvjdz/VEoa54VLhi9XQR5lZfpbMbFOeXnGxR1BIWf82Q==", + "version": "1.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@nft/webhook/-/webhook-1.0.0-beta.51.tgz", + "integrity": "sha512-gX9Rj+wBga2uGQ6yfY3ZcHu0dh5VSVMcfUcfZg5ObkeupJSvLBB7R42dY4DKz0CzfQVMtNnsu1J03gnmE8VCMQ==", "requires": { "raw-body": "^2.5.1", "ts-invariant": "^0.9.4" diff --git a/package.json b/package.json index f023de59..acdcd019 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@magiclabs/wagmi-connector": "^1.1.5", "@metamask/jazzicon": "^2.0.0", "@nft/chat": "^0.2.0", - "@nft/webhook": "^1.0.0-beta.49", + "@nft/webhook": "^1.0.0-beta.51", "@rainbow-me/rainbowkit": "^1.3.3", "@react-icons/all-files": "^4.1.0", "@uiw/react-markdown-preview": "^5.0.7", @@ -63,7 +63,7 @@ "@graphql-codegen/typescript-react-apollo": "^4.1.0", "@graphql-eslint/eslint-plugin": "^3.20.1", "@next/bundle-analyzer": "^14.0.4", - "@nft/api-graphql": "^1.0.0-beta.49", + "@nft/api-graphql": "^1.0.0-beta.52-prerelease-3", "@types/nodemailer": "^6.4.14", "@types/nprogress": "^0.2.3", "@types/react": "^18.2.48", diff --git a/pages/_app.tsx b/pages/_app.tsx index 26c9752f..08a91ca1 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,7 +1,7 @@ import { ApolloProvider } from '@apollo/client' import Bugsnag from '@bugsnag/js' import BugsnagPluginReact from '@bugsnag/plugin-react' -import { Box, ChakraProvider } from '@chakra-ui/react' +import { Box, ChakraProvider, useDisclosure } from '@chakra-ui/react' import { LiteflowProvider } from '@liteflow/react' import { RainbowKitProvider, lightTheme } from '@rainbow-me/rainbowkit' import '@rainbow-me/rainbowkit/styles.css' @@ -22,7 +22,7 @@ import React, { useMemo, useState, } from 'react' -import { Cookies, CookiesProvider } from 'react-cookie' +import { Cookies, CookiesProvider, useCookies } from 'react-cookie' import { WagmiConfig, useDisconnect, @@ -31,6 +31,7 @@ import { import getClient from '../client' import CartContext from '../components/CartContext' import Footer from '../components/Footer/Footer' +import SignModal from '../components/Modal/Sign' import Navbar from '../components/Navbar/Navbar' import connectors from '../connectors' import getEnvironment, { Environment, EnvironmentContext } from '../environment' @@ -125,14 +126,18 @@ function AccountProvider({ onError, }: PropsWithChildren<{ onError: (code: number) => void }>) { const { LITEFLOW_API_KEY, BASE_URL } = useEnvironment() - const { login, jwtToken, logout } = useAccount() + const { jwtToken, login, logout } = useAccount() const { disconnect } = useDisconnect() + const { isOpen, onOpen, onClose } = useDisclosure() + const [cookies] = useCookies([COOKIE_JWT_TOKEN]) const { connector } = useWagmiAccount({ async onConnect({ connector }) { if (!connector) return try { await login(connector) + // jwtToken from useAccount is null on refresh, so we need to check the cookies + if (!cookies[COOKIE_JWT_TOKEN]) onOpen() } catch (e: any) { disconnect() } @@ -145,12 +150,14 @@ function AccountProvider({ // handle change of account useEffect(() => { if (!connector) return - const handleLogin = () => login(connector) + const handleLogin = () => { + login(connector).then(onOpen).catch(onError) + } connector.on('change', handleLogin) return () => { connector.off('change', handleLogin) } - }, [connector, login]) + }, [connector, login, onError, onOpen]) const client = useMemo( // The client needs to be reset when the jwtToken changes but only on the client as the server will @@ -166,7 +173,12 @@ function AccountProvider({ [jwtToken, onError, LITEFLOW_API_KEY, BASE_URL], ) - return {children} + return ( + <> + {children} + + + ) } export type MyAppProps = { diff --git a/pages/checkout/[id].gql b/pages/checkout/[id].gql index ee561164..da974753 100644 --- a/pages/checkout/[id].gql +++ b/pages/checkout/[id].gql @@ -1,4 +1,4 @@ -query Checkout($id: UUID!, $address: Address, $now: Datetime!) { +query Checkout($id: UUID!, $address: Address) { offer(id: $id) { id type @@ -7,6 +7,7 @@ query Checkout($id: UUID!, $address: Address, $now: Datetime!) { collectionAddress tokenId id + deletedAt collection { chainId address @@ -28,42 +29,33 @@ query Checkout($id: UUID!, $address: Address, $now: Datetime!) { quantity } quantity - bestBid: bids( - orderBy: [UNIT_PRICE_IN_REF_DESC, CREATED_AT_ASC] - filter: { expiredAt: { greaterThan: $now } } - first: 1 - ) { - nodes { - unitPrice - amount - currency { - image - name - id - decimals - symbol - } + bestBid { + unitPrice + amount + currency { + image + name + id + decimals + symbol } } - firstSale: sales( - first: 1 - orderBy: [UNIT_PRICE_IN_REF_ASC, CREATED_AT_ASC] - filter: { expiredAt: { greaterThan: $now } } - ) { - totalCount - totalCurrencyDistinctCount - nodes { + firstSale { + id + unitPrice + currency { + image + name id - unitPrice - currency { - image - name - id - decimals - symbol - } + decimals + symbol + } + maker { + address } } + totalSalesCount + totalSalesCurrencyDistinctCount } maker { address @@ -80,6 +72,7 @@ query Checkout($id: UUID!, $address: Address, $now: Datetime!) { id decimals symbol + deletedAt } } } diff --git a/pages/checkout/[id].tsx b/pages/checkout/[id].tsx index fdd135fc..a58efd00 100644 --- a/pages/checkout/[id].tsx +++ b/pages/checkout/[id].tsx @@ -28,11 +28,7 @@ import useRequiredQueryParamSingle from '../../hooks/useRequiredQueryParamSingle import SmallLayout from '../../layouts/small' import Error from '../_error' -type Props = { - now: string -} - -const CheckoutPage: NextPage = ({ now }) => { +const CheckoutPage: NextPage = () => { const { t } = useTranslation('templates') const { back, push } = useRouter() const toast = useToast() @@ -40,12 +36,10 @@ const CheckoutPage: NextPage = ({ now }) => { const { address } = useAccount() - const date = useMemo(() => new Date(now), [now]) const { data: offerData } = useCheckoutQuery({ variables: { id: offerId, address: address || '', - now: date, }, }) const offer = offerData?.offer @@ -69,7 +63,12 @@ const CheckoutPage: NextPage = ({ now }) => { await push(`/tokens/${asset.id}`) }, [asset, toast, t, push]) - if (offer === null || asset === null) { + if ( + offer === null || + asset === null || + asset?.deletedAt || + offer?.currency.deletedAt + ) { return } return ( diff --git a/pages/collection/[chainId]/[id]/index.gql b/pages/collection/[chainId]/[id]/index.gql index 02374262..7d3cd77c 100644 --- a/pages/collection/[chainId]/[id]/index.gql +++ b/pages/collection/[chainId]/[id]/index.gql @@ -2,6 +2,7 @@ query FetchCollectionDetails($collectionAddress: Address!, $chainId: Int!) { collection(address: $collectionAddress, chainId: $chainId) { address chainId + deletedAt cover image name @@ -49,7 +50,6 @@ query FetchCollectionMetrics($collectionAddress: Address!, $chainId: Int!) { query FetchCollectionAssets( $currentAccount: Address! - $now: Datetime! $offset: Int! $limit: Int! $chainId: Int! @@ -102,41 +102,33 @@ query FetchCollectionAssets( quantity } quantity - bestBid: bids( - orderBy: [UNIT_PRICE_IN_REF_DESC, CREATED_AT_ASC] - filter: { expiredAt: { greaterThan: $now } } - first: 1 - ) { - nodes { - unitPrice - amount - currency { - image - name - id - decimals - symbol - } + bestBid { + unitPrice + amount + currency { + image + name + id + decimals + symbol } } - firstSale: sales( - first: 1 - orderBy: [UNIT_PRICE_IN_REF_ASC, CREATED_AT_ASC] - filter: { expiredAt: { greaterThan: $now } } - ) { - totalCount - totalCurrencyDistinctCount - nodes { + firstSale { + id + unitPrice + currency { + image + name id - unitPrice - currency { - image - id - decimals - symbol - } + decimals + symbol + } + maker { + address } } + totalSalesCount + totalSalesCurrencyDistinctCount } } } diff --git a/pages/collection/[chainId]/[id]/index.tsx b/pages/collection/[chainId]/[id]/index.tsx index 573be771..24531346 100644 --- a/pages/collection/[chainId]/[id]/index.tsx +++ b/pages/collection/[chainId]/[id]/index.tsx @@ -91,7 +91,6 @@ const CollectionPage: FC = ({ now }) => { const { data: assetData } = useFetchCollectionAssetsQuery({ variables: { collectionAddress, - now: date, currentAccount: address || '', limit, offset, @@ -149,7 +148,8 @@ const CollectionPage: FC = ({ now }) => { const { changeLimit } = usePaginate() - if (collection === null) return + if (collection === null || collection?.deletedAt) + return return ( { [push, t, toast], ) - if (collection === null) return + if (collection === null || collection?.deletedAt) + return return ( { asset={{ ...asset, creator, - bestBid: { nodes: [] }, - firstSale: undefined, + bestBid: null, + firstSale: null, + totalSalesCount: 0, + totalSalesCurrencyDistinctCount: 0, }} /> )} diff --git a/pages/explore/explore.gql b/pages/explore/explore.gql index 891322c7..dc40412b 100644 --- a/pages/explore/explore.gql +++ b/pages/explore/explore.gql @@ -1,5 +1,4 @@ query FetchAllERC721And1155( - $now: Datetime! $address: Address $limit: Int! $offset: Int! @@ -34,42 +33,33 @@ query FetchAllERC721And1155( quantity } quantity - bestBid: bids( - orderBy: [UNIT_PRICE_IN_REF_DESC, CREATED_AT_ASC] - filter: { expiredAt: { greaterThan: $now } } - first: 1 - ) { - nodes { - unitPrice - amount - currency { - image - name - id - decimals - symbol - } + bestBid { + unitPrice + amount + currency { + image + name + id + decimals + symbol } } - firstSale: sales( - first: 1 - orderBy: [UNIT_PRICE_IN_REF_ASC, CREATED_AT_ASC] - filter: { expiredAt: { greaterThan: $now } } - ) { - totalCount - totalCurrencyDistinctCount - nodes { + firstSale { + id + unitPrice + currency { + image + name id - unitPrice - currency { - image - name - id - decimals - symbol - } + decimals + symbol + } + maker { + address } } + totalSalesCount + totalSalesCurrencyDistinctCount creator { address name diff --git a/pages/explore/explore.tsx b/pages/explore/explore.tsx index 2c8664bc..b3d2ad00 100644 --- a/pages/explore/explore.tsx +++ b/pages/explore/explore.tsx @@ -59,7 +59,6 @@ const ExplorePage: NextPage = ({ now }) => { const { page, limit, offset } = usePaginateQuery() const { data: assetsData, refetch } = useFetchAllErc721And1155Query({ variables: { - now: date, address: address || '', limit, offset, diff --git a/pages/explore/users.gql b/pages/explore/users.gql index 23b00099..bf8e09f1 100644 --- a/pages/explore/users.gql +++ b/pages/explore/users.gql @@ -8,7 +8,7 @@ query FetchExploreUsers( first: $limit offset: $offset orderBy: $orderBy - filter: { isImported: { equalTo: false }, and: $filter } + filter: { and: $filter } ) { pageInfo { hasNextPage diff --git a/pages/index.tsx b/pages/index.tsx index 91bbd094..c7f0f446 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,5 +1,6 @@ import { Stack } from '@chakra-ui/react' import AssetsHomeSection from 'components/HomeSection/Assets' +import BannerHomeSection from 'components/HomeSection/Banner' import CollectionsHomeSection from 'components/HomeSection/Collections' import FeaturedHomeSection from 'components/HomeSection/Featured' import ResourcesHomeSection from 'components/HomeSection/Resources' @@ -21,6 +22,7 @@ const HomePage: NextPage = ({ now }) => { + diff --git a/pages/notification.gql b/pages/notification.gql index 05f7b6d5..861add0d 100644 --- a/pages/notification.gql +++ b/pages/notification.gql @@ -1,4 +1,4 @@ -query GetNotifications($address: Address!, $cursor: Cursor) { +query GetNotifications($address: Address!, $limit: Int!, $offset: Int!) { notifications( filter: { accountAddress: { equalTo: $address } @@ -14,13 +14,14 @@ query GetNotifications($address: Address!, $cursor: Cursor) { ] } } - first: 12 + first: $limit orderBy: CREATED_AT_DESC - after: $cursor + offset: $offset + includeWhenCollectionDeleted: YES + includeWhenTradeDeleted: YES ) { pageInfo { hasNextPage - endCursor } nodes { id diff --git a/pages/notification.tsx b/pages/notification.tsx index bc8bae2c..fc1982d2 100644 --- a/pages/notification.tsx +++ b/pages/notification.tsx @@ -12,7 +12,7 @@ import { import { FaBell } from '@react-icons/all-files/fa/FaBell' import { NextPage } from 'next' import useTranslation from 'next-translate/useTranslation' -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useCookies } from 'react-cookie' import Empty from '../components/Empty/Empty' import Head from '../components/Head' @@ -25,12 +25,15 @@ import useLoginRedirect from '../hooks/useLoginRedirect' import SmallLayout from '../layouts/small' import { formatError } from '../utils' +const PAGINATION_LIMIT = 12 + const NotificationPage: NextPage = ({}) => { const { t } = useTranslation('templates') const toast = useToast() const { address } = useAccount() useLoginRedirect() const [_, setCookies] = useCookies() + const [offset, setOffset] = useState(0) const { data: notificationData, @@ -38,7 +41,8 @@ const NotificationPage: NextPage = ({}) => { networkStatus, } = useGetNotificationsQuery({ variables: { - cursor: null, + offset: 0, // the offset change must be done when calling the fetchMore function to concat queries' results + limit: PAGINATION_LIMIT, address: address || '', }, notifyOnNetworkStatusChange: true, @@ -47,23 +51,22 @@ const NotificationPage: NextPage = ({}) => { const notifications = notificationData?.notifications?.nodes const hasNextPage = notificationData?.notifications?.pageInfo.hasNextPage - const endCursor = notificationData?.notifications?.pageInfo.endCursor const loadMore = useCallback(async () => { + const newOffset = offset + PAGINATION_LIMIT try { await fetchMore({ - variables: { - cursor: endCursor, - }, + variables: { offset: newOffset }, updateQuery: concatToQuery('notifications'), }) + setOffset(newOffset) } catch (e) { toast({ title: formatError(e), status: 'error', }) } - }, [endCursor, fetchMore, toast]) + }, [fetchMore, offset, toast]) useEffect(() => { if (!address) return diff --git a/pages/tokens/[id]/bid.gql b/pages/tokens/[id]/bid.gql index 7c57559f..1ac1e149 100644 --- a/pages/tokens/[id]/bid.gql +++ b/pages/tokens/[id]/bid.gql @@ -3,7 +3,6 @@ query BidOnAsset( $collectionAddress: Address! $tokenId: String! $address: Address - $now: Datetime! ) { asset( chainId: $chainId @@ -11,6 +10,7 @@ query BidOnAsset( tokenId: $tokenId ) { id + deletedAt chainId collectionAddress tokenId @@ -35,47 +35,33 @@ query BidOnAsset( owned: ownership(ownerAddress: $address) { quantity } - bestBid: bids( - orderBy: [UNIT_PRICE_IN_REF_DESC, CREATED_AT_ASC] - filter: { expiredAt: { greaterThan: $now } } - first: 1 - ) { - nodes { - unitPrice - amount - currency { - image - name - id - decimals - symbol - } + bestBid { + unitPrice + amount + currency { + image + name + id + decimals + symbol } } - firstSale: sales( - first: 1 - orderBy: [UNIT_PRICE_IN_REF_ASC, CREATED_AT_ASC] - filter: { expiredAt: { greaterThan: $now } } - ) { - totalCount - totalCurrencyDistinctCount - nodes { + firstSale { + id + unitPrice + currency { + image + name id - unitPrice - currency { - image - name - id - decimals - symbol - } + decimals + symbol } - } - ownerships(first: 1) { - nodes { - ownerAddress + maker { + address } } + totalSalesCount + totalSalesCurrencyDistinctCount } currencies( orderBy: CREATED_AT_ASC diff --git a/pages/tokens/[id]/bid.tsx b/pages/tokens/[id]/bid.tsx index 40ca3d0b..d339589b 100644 --- a/pages/tokens/[id]/bid.tsx +++ b/pages/tokens/[id]/bid.tsx @@ -16,11 +16,7 @@ import useRequiredQueryParamSingle from '../../../hooks/useRequiredQueryParamSin import SmallLayout from '../../../layouts/small' import Error from '../../_error' -type Props = { - now: string -} - -const BidPage: NextPage = ({ now }) => { +const BidPage: NextPage = () => { const { t } = useTranslation('templates') const { back, push } = useRouter() const toast = useToast() @@ -32,13 +28,11 @@ const BidPage: NextPage = ({ now }) => { ) invariant(chainId && collectionAddress && tokenId, 'Invalid asset id') - const date = useMemo(() => new Date(now), [now]) const { data } = useBidOnAssetQuery({ variables: { chainId: parseInt(chainId, 10), collectionAddress: collectionAddress, tokenId: tokenId, - now: date, address: address || '', }, }) @@ -52,7 +46,7 @@ const BidPage: NextPage = ({ now }) => { await push(`/tokens/${assetId}`) }, [toast, t, push, assetId]) - if (asset === null) return + if (asset === null || asset?.deletedAt) return return ( = ({ now: nowProp }) => { }, }) const asset = data?.asset + const sales = data?.sales + const bids = data?.bids + const ownerships = data?.ownerships const media = useDetectAssetMedia(asset) @@ -140,7 +143,7 @@ const DetailPage: NextPage = ({ now: nowProp }) => { [refresh, refreshAsset, toast], ) - if (asset === null) return + if (asset === null || asset?.deletedAt) return return ( = ({ now: nowProp }) => { )} - {!asset ? ( + {!asset || !sales || !ownerships ? ( ) : ( - + )} - {!asset || !data?.currencies?.nodes ? ( + {!asset || !sales || !data?.currencies?.nodes ? ( <> @@ -238,6 +245,7 @@ const DetailPage: NextPage = ({ now: nowProp }) => { ) : ( = ({ now: nowProp }) => { )} - {asset && ( + {asset && bids && ( <> {asset.description && ( @@ -383,7 +391,7 @@ const DetailPage: NextPage = ({ now: nowProp }) => { {(!query.filter || query.filter === AssetTabs.bids) && ( = ({ now }) => { +const OfferPage: NextPage = () => { const { t } = useTranslation('templates') const { back, push } = useRouter() const toast = useToast() @@ -41,13 +40,11 @@ const OfferPage: NextPage = ({ now }) => { ) invariant(chainId && collectionAddress && tokenId, 'Invalid asset id') - const date = useMemo(() => new Date(now), [now]) const { data } = useOfferForAssetQuery({ variables: { chainId: parseInt(chainId, 10), collectionAddress: collectionAddress, tokenId: tokenId, - now: date, address: address || '', }, }) @@ -63,7 +60,7 @@ const OfferPage: NextPage = ({ now }) => { await push(`/tokens/${assetId}`) }, [toast, t, push, assetId]) - if (asset === null) return + if (asset === null || asset?.deletedAt) return return ( { { ) : ( - + !item.asset.deletedAt && ( // only display new button if asset is not deleted + + ) )} )} diff --git a/pages/users/[id]/bids/received.gql b/pages/users/[id]/bids/received.gql index 79ce48a8..4bafd4a3 100644 --- a/pages/users/[id]/bids/received.gql +++ b/pages/users/[id]/bids/received.gql @@ -17,6 +17,8 @@ query FetchUserBidsReceived( makerAddress: { notEqualTo: $address } asset: { ownerships: { some: { ownerAddress: { equalTo: $address } } } } } + includeWhenAssetDeleted: YES + includeWhenCurrencyDeleted: YES ) { pageInfo { hasNextPage @@ -43,6 +45,7 @@ query FetchUserBidsReceived( id decimals symbol + deletedAt } asset { id @@ -52,6 +55,7 @@ query FetchUserBidsReceived( name image imageMimetype + deletedAt } } } diff --git a/pages/users/[id]/bids/received.tsx b/pages/users/[id]/bids/received.tsx index 191cbed0..8858f643 100644 --- a/pages/users/[id]/bids/received.tsx +++ b/pages/users/[id]/bids/received.tsx @@ -173,6 +173,7 @@ const BidReceivedPage: NextPage = ({ now }) => { = ({ now }) => { {dateFromNow(item.createdAt)} - {ownerLoggedIn && ( - - toast({ - status: 'error', - title: formatError(e), - }) - } - > - - {t('user.bid-received.actions.accept')} - - - )} + {ownerLoggedIn && + !item.asset.deletedAt && // only display accept button if asset and currency are not deleted + !item.currency.deletedAt && ( + + toast({ + status: 'error', + title: formatError(e), + }) + } + > + + {t('user.bid-received.actions.accept')} + + + )} ))} diff --git a/pages/users/[id]/created.gql b/pages/users/[id]/created.gql index 70b349a9..4da36c6f 100644 --- a/pages/users/[id]/created.gql +++ b/pages/users/[id]/created.gql @@ -1,7 +1,6 @@ query FetchCreatedAssets( $address: Address! $currentAddress: Address! - $now: Datetime! $limit: Int! $offset: Int! $orderBy: [AssetsOrderBy!] diff --git a/pages/users/[id]/created.tsx b/pages/users/[id]/created.tsx index 3d4380f3..94fe4b6a 100644 --- a/pages/users/[id]/created.tsx +++ b/pages/users/[id]/created.tsx @@ -13,11 +13,7 @@ import usePaginateQuery from '../../../hooks/usePaginateQuery' import useRequiredQueryParamSingle from '../../../hooks/useRequiredQueryParamSingle' import LargeLayout from '../../../layouts/large' -type Props = { - now: string -} - -const CreatedPage: NextPage = ({ now }) => { +const CreatedPage: NextPage = () => { const { PAGINATION_LIMIT, BASE_URL } = useEnvironment() const { t } = useTranslation('templates') const { pathname, replace, query } = useRouter() @@ -27,7 +23,6 @@ const CreatedPage: NextPage = ({ now }) => { const { address } = useAccount() const userAddress = useRequiredQueryParamSingle('id') - const date = useMemo(() => new Date(now), [now]) const { data } = useFetchCreatedAssetsQuery({ variables: { address: userAddress, @@ -35,7 +30,6 @@ const CreatedPage: NextPage = ({ now }) => { limit, offset, orderBy, - now: date, }, }) diff --git a/pages/users/[id]/offers.gql b/pages/users/[id]/offers.gql index 13035945..a2b06e01 100644 --- a/pages/users/[id]/offers.gql +++ b/pages/users/[id]/offers.gql @@ -13,6 +13,8 @@ query FetchUserFixedPrice( signature: { isNull: false } makerAddress: { equalTo: $address } } + includeWhenAssetDeleted: YES + includeWhenCurrencyDeleted: YES ) { pageInfo { hasNextPage @@ -43,6 +45,7 @@ query FetchUserFixedPrice( owned: ownership(ownerAddress: $address) { quantity } + deletedAt } currency { id @@ -50,6 +53,7 @@ query FetchUserFixedPrice( image decimals symbol + deletedAt } } } diff --git a/pages/users/[id]/offers.tsx b/pages/users/[id]/offers.tsx index c5edc11e..d0693905 100644 --- a/pages/users/[id]/offers.tsx +++ b/pages/users/[id]/offers.tsx @@ -135,6 +135,7 @@ const FixedPricePage: NextPage = () => { { ) : BigNumber.from( item.asset.owned?.quantity || 0, - ).gt(0) ? ( + ).gt(0) && + !item.asset.deletedAt && // only display new button if asset and currency are not deleted + !item.currency.deletedAt ? (