add some loading and empty states to nft market
This commit is contained in:
parent
6ba3ca9b55
commit
28f46d6be5
|
@ -24,7 +24,8 @@ import {
|
|||
import { ImgWithLoader } from '@components/ImgWithLoader'
|
||||
import NftMarketButton from './NftMarketButton'
|
||||
import { abbreviateAddress } from 'utils/formatting'
|
||||
import { NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import EmptyState from './EmptyState'
|
||||
import { formatNumericValue } from 'utils/numbers'
|
||||
|
||||
const AllBidsView = () => {
|
||||
const { publicKey } = useWallet()
|
||||
|
@ -124,9 +125,11 @@ const AllBidsView = () => {
|
|||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{toUiDecimals(
|
||||
x.price.basisPoints.toNumber(),
|
||||
MANGO_MINT_DECIMALS,
|
||||
{formatNumericValue(
|
||||
toUiDecimals(
|
||||
x.price.basisPoints.toNumber(),
|
||||
MANGO_MINT_DECIMALS,
|
||||
),
|
||||
)}
|
||||
<span className="font-body">{' MNGO'}</span>
|
||||
</p>
|
||||
|
@ -136,9 +139,11 @@ const AllBidsView = () => {
|
|||
<p className="text-right">
|
||||
{listing ? (
|
||||
<>
|
||||
{toUiDecimals(
|
||||
listing.price.basisPoints.toNumber(),
|
||||
MANGO_MINT_DECIMALS,
|
||||
{formatNumericValue(
|
||||
toUiDecimals(
|
||||
listing.price.basisPoints.toNumber(),
|
||||
MANGO_MINT_DECIMALS,
|
||||
),
|
||||
)}
|
||||
<span className="font-body">{' MNGO'}</span>
|
||||
</>
|
||||
|
@ -207,10 +212,7 @@ const AllBidsView = () => {
|
|||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="mt-4 flex flex-col items-center rounded-md border border-th-bkg-3 p-4">
|
||||
<NoSymbolIcon className="mb-1 h-7 w-7 text-th-fgd-4" />
|
||||
<p>No offers to show...</p>
|
||||
</div>
|
||||
<EmptyState text="No offers to display..." />
|
||||
)}
|
||||
</div>
|
||||
{showBidModal ? (
|
||||
|
|
|
@ -11,6 +11,7 @@ import { useAuctionHouse, useBids } from 'hooks/market/useAuctionHouse'
|
|||
import { ImgWithLoader } from '@components/ImgWithLoader'
|
||||
// import { useTranslation } from 'next-i18next'
|
||||
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
|
||||
import Loading from '@components/shared/Loading'
|
||||
|
||||
type ListingModalProps = {
|
||||
listing?: Listing
|
||||
|
@ -25,17 +26,25 @@ const BidNftModal = ({ isOpen, onClose, listing }: ListingModalProps) => {
|
|||
|
||||
const [bidPrice, setBidPrice] = useState('')
|
||||
const [assetMint, setAssetMint] = useState('')
|
||||
const [submittingOffer, setSubmittingOffer] = useState(false)
|
||||
|
||||
const bid = useCallback(async () => {
|
||||
await metaplex!.auctionHouse().bid({
|
||||
auctionHouse: auctionHouse!,
|
||||
price: token(bidPrice, MANGO_MINT_DECIMALS),
|
||||
mintAccount: noneListedAssetMode
|
||||
? new PublicKey(assetMint)
|
||||
: listing!.asset.mint.address,
|
||||
})
|
||||
onClose()
|
||||
refetch()
|
||||
setSubmittingOffer(true)
|
||||
try {
|
||||
await metaplex!.auctionHouse().bid({
|
||||
auctionHouse: auctionHouse!,
|
||||
price: token(bidPrice, MANGO_MINT_DECIMALS),
|
||||
mintAccount: noneListedAssetMode
|
||||
? new PublicKey(assetMint)
|
||||
: listing!.asset.mint.address,
|
||||
})
|
||||
onClose()
|
||||
refetch()
|
||||
} catch (e) {
|
||||
console.log('error making offer', e)
|
||||
} finally {
|
||||
setSubmittingOffer(false)
|
||||
}
|
||||
}, [
|
||||
metaplex,
|
||||
auctionHouse,
|
||||
|
@ -45,6 +54,7 @@ const BidNftModal = ({ isOpen, onClose, listing }: ListingModalProps) => {
|
|||
listing,
|
||||
onClose,
|
||||
refetch,
|
||||
setSubmittingOffer,
|
||||
])
|
||||
|
||||
return (
|
||||
|
@ -96,12 +106,12 @@ const BidNftModal = ({ isOpen, onClose, listing }: ListingModalProps) => {
|
|||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="ml-2 whitespace-nowrap"
|
||||
className="ml-2 flex items-center justify-center whitespace-nowrap"
|
||||
onClick={bid}
|
||||
disabled={!bidPrice}
|
||||
size="large"
|
||||
>
|
||||
Make Offer
|
||||
{submittingOffer ? <Loading /> : 'Make Offer'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
|
||||
const EmptyState = ({ text }: { text: string }) => {
|
||||
return (
|
||||
<div className="mt-4 flex flex-col items-center rounded-md border border-th-bkg-3 p-4">
|
||||
<NoSymbolIcon className="mb-1 h-7 w-7 text-th-fgd-4" />
|
||||
<p>{text}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmptyState
|
|
@ -17,6 +17,10 @@ import { MANGO_MINT_DECIMALS } from 'utils/governance/constants'
|
|||
// import { useTranslation } from 'next-i18next'
|
||||
import { ImgWithLoader } from '@components/ImgWithLoader'
|
||||
import NftMarketButton from './NftMarketButton'
|
||||
import { formatNumericValue } from 'utils/numbers'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import EmptyState from './EmptyState'
|
||||
|
||||
const filter = [ALL_FILTER, 'My Listings']
|
||||
|
||||
|
@ -26,32 +30,49 @@ const ListingsView = () => {
|
|||
// const { t } = useTranslation(['nft-market'])
|
||||
const [currentFilter, setCurrentFilter] = useState(ALL_FILTER)
|
||||
const { data: bids } = useBids()
|
||||
|
||||
// const [page, setPage] = useState(1)
|
||||
const [bidListing, setBidListing] = useState<null | Listing>(null)
|
||||
const [assetBidsListing, setAssetBidsListing] = useState<null | Listing>(null)
|
||||
const { data: auctionHouse } = useAuctionHouse()
|
||||
const [asssetBidsModal, setAssetBidsModal] = useState(false)
|
||||
const [bidNftModal, setBidNftModal] = useState(false)
|
||||
const [cancellingListing, setCancellingListing] = useState('')
|
||||
const [buying, setBuying] = useState('')
|
||||
|
||||
const { refetch } = useLazyListings()
|
||||
// const { data: listings } = useListings(currentFilter, page)
|
||||
const { data: listings } = useListings()
|
||||
const {
|
||||
data: listings,
|
||||
isLoading: loadingListings,
|
||||
isFetching: fetchingListings,
|
||||
} = useListings()
|
||||
|
||||
const cancelListing = async (listing: Listing) => {
|
||||
await metaplex!.auctionHouse().cancelListing({
|
||||
auctionHouse: auctionHouse!,
|
||||
listing: listing,
|
||||
})
|
||||
refetch()
|
||||
setCancellingListing(listing.asset.mint.address.toString())
|
||||
try {
|
||||
await metaplex!.auctionHouse().cancelListing({
|
||||
auctionHouse: auctionHouse!,
|
||||
listing: listing,
|
||||
})
|
||||
refetch()
|
||||
} catch (e) {
|
||||
console.log('error cancelling listing', e)
|
||||
} finally {
|
||||
setCancellingListing('')
|
||||
}
|
||||
}
|
||||
|
||||
const buyAsset = async (listing: Listing) => {
|
||||
await metaplex!.auctionHouse().buy({
|
||||
auctionHouse: auctionHouse!,
|
||||
listing,
|
||||
})
|
||||
refetch()
|
||||
setBuying(listing.asset.mint.address.toString())
|
||||
try {
|
||||
await metaplex!.auctionHouse().buy({
|
||||
auctionHouse: auctionHouse!,
|
||||
listing,
|
||||
})
|
||||
refetch()
|
||||
} catch (e) {
|
||||
console.log('error buying nft', e)
|
||||
} finally {
|
||||
setBuying('')
|
||||
}
|
||||
}
|
||||
|
||||
const openBidModal = (listing: Listing) => {
|
||||
|
@ -74,6 +95,9 @@ const ListingsView = () => {
|
|||
// setPage(page)
|
||||
// }
|
||||
|
||||
const loading = loadingListings || fetchingListings
|
||||
console.log(listings?.results)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="mb-4 mt-2 flex items-center justify-between rounded-md bg-th-bkg-2 p-2 pl-4">
|
||||
|
@ -91,110 +115,140 @@ const ListingsView = () => {
|
|||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
{asssetBidsModal && assetBidsListing && (
|
||||
{asssetBidsModal && assetBidsListing ? (
|
||||
<AssetBidsModal
|
||||
listing={assetBidsListing}
|
||||
isOpen={asssetBidsModal}
|
||||
onClose={closeBidsModal}
|
||||
></AssetBidsModal>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
<div className="grid auto-cols-max grid-flow-row auto-rows-max gap-4">
|
||||
{listings?.results?.map((x, idx) => {
|
||||
const imgSource = x.asset.json?.image
|
||||
const nftBids = bids?.filter((bid) =>
|
||||
bid.metadataAddress.equals(x.asset.metadataAddress),
|
||||
)
|
||||
const bestBid = nftBids
|
||||
? nftBids.reduce((a, c) => {
|
||||
const price = toUiDecimals(
|
||||
c.price.basisPoints.toNumber(),
|
||||
MANGO_MINT_DECIMALS,
|
||||
)
|
||||
if (price > a) {
|
||||
a = price
|
||||
}
|
||||
return a
|
||||
}, 0)
|
||||
: 0
|
||||
return (
|
||||
<div className="w-60 rounded-lg border border-th-bkg-3" key={idx}>
|
||||
{imgSource ? (
|
||||
<div className="flex h-60 w-full items-start overflow-hidden rounded-t-lg">
|
||||
<ImgWithLoader
|
||||
alt="nft"
|
||||
className="h-auto w-60 flex-shrink-0"
|
||||
src={imgSource}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<p className="text-xs">Buy Now</p>
|
||||
<div className="flex items-center">
|
||||
{/* <img
|
||||
<div className="grid grid-flow-row auto-rows-max grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5">
|
||||
{listings?.results ? (
|
||||
listings.results.map((x, idx) => {
|
||||
const imgSource = x.asset.json?.image
|
||||
const nftBids = bids?.filter((bid) =>
|
||||
bid.metadataAddress.equals(x.asset.metadataAddress),
|
||||
)
|
||||
const bestBid = nftBids
|
||||
? nftBids.reduce((a, c) => {
|
||||
const price = toUiDecimals(
|
||||
c.price.basisPoints.toNumber(),
|
||||
MANGO_MINT_DECIMALS,
|
||||
)
|
||||
if (price > a) {
|
||||
a = price
|
||||
}
|
||||
return a
|
||||
}, 0)
|
||||
: 0
|
||||
return (
|
||||
<div
|
||||
className="col-span-1 rounded-lg border border-th-bkg-3"
|
||||
key={idx}
|
||||
>
|
||||
{imgSource ? (
|
||||
<div className="flex h-60 w-full items-center overflow-hidden rounded-t-lg">
|
||||
<ImgWithLoader
|
||||
alt="nft"
|
||||
className="h-auto w-full flex-shrink-0"
|
||||
src={imgSource}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<p className="text-xs">Buy Now</p>
|
||||
<div className="flex items-center">
|
||||
{/* <img
|
||||
className="mr-1 h-3.5 w-auto"
|
||||
src="/icons/mngo.svg"
|
||||
/> */}
|
||||
<span className="font-display text-base">
|
||||
{toUiDecimals(
|
||||
x.price.basisPoints.toNumber(),
|
||||
MANGO_MINT_DECIMALS,
|
||||
)}{' '}
|
||||
<span className="font-body font-bold">MNGO</span>
|
||||
</span>
|
||||
<span className="font-display text-base">
|
||||
{formatNumericValue(
|
||||
toUiDecimals(
|
||||
x.price.basisPoints.toNumber(),
|
||||
MANGO_MINT_DECIMALS,
|
||||
),
|
||||
)}{' '}
|
||||
<span className="font-body font-bold">MNGO</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mt-2 text-xs">
|
||||
{bestBid ? `Best Offer: ${bestBid} MNGO` : 'No offers'}
|
||||
</p>
|
||||
</div>
|
||||
{publicKey && !x.sellerAddress.equals(publicKey) && (
|
||||
<div className="mt-3 flex space-x-2 border-t border-th-bkg-3 pt-4">
|
||||
<NftMarketButton
|
||||
className="w-1/2"
|
||||
text="Buy Now"
|
||||
colorClass="success"
|
||||
onClick={() => buyAsset(x)}
|
||||
/>
|
||||
<NftMarketButton
|
||||
className="w-1/2"
|
||||
text="Make Offer"
|
||||
colorClass="fgd-3"
|
||||
onClick={() => openBidModal(x)}
|
||||
/>
|
||||
{bidNftModal && bidListing && (
|
||||
<BidNftModal
|
||||
listing={bidListing}
|
||||
isOpen={bidNftModal}
|
||||
onClose={closeBidModal}
|
||||
></BidNftModal>
|
||||
)}
|
||||
<div>
|
||||
<p className="mt-2 text-xs">
|
||||
{bestBid ? `Best Offer: ${bestBid} MNGO` : 'No offers'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{publicKey && x.sellerAddress.equals(publicKey) && (
|
||||
<div className="mt-3 flex space-x-2 border-t border-th-bkg-3 pt-4">
|
||||
<NftMarketButton
|
||||
className="w-1/2"
|
||||
text="Delist"
|
||||
colorClass="error"
|
||||
onClick={() => cancelListing(x)}
|
||||
/>
|
||||
<NftMarketButton
|
||||
className="w-1/2"
|
||||
text={`Offers (${nftBids?.length})`}
|
||||
colorClass="fgd-3"
|
||||
onClick={() => openBidsModal(x)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{publicKey && !x.sellerAddress.equals(publicKey) && (
|
||||
<div className="mt-3 space-y-2 border-t border-th-bkg-3 pt-4">
|
||||
<NftMarketButton
|
||||
className="w-full"
|
||||
text={
|
||||
buying === x.asset.mint.address.toString() ? (
|
||||
<Loading />
|
||||
) : (
|
||||
'Buy Now'
|
||||
)
|
||||
}
|
||||
colorClass="success"
|
||||
onClick={() => buyAsset(x)}
|
||||
/>
|
||||
<NftMarketButton
|
||||
className="w-full"
|
||||
text="Make Offer"
|
||||
colorClass="fgd-3"
|
||||
onClick={() => openBidModal(x)}
|
||||
/>
|
||||
{bidNftModal && bidListing && (
|
||||
<BidNftModal
|
||||
listing={bidListing}
|
||||
isOpen={bidNftModal}
|
||||
onClose={closeBidModal}
|
||||
></BidNftModal>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{publicKey && x.sellerAddress.equals(publicKey) && (
|
||||
<div className="mt-3 space-y-2 border-t border-th-bkg-3 pt-4">
|
||||
<NftMarketButton
|
||||
className="w-full"
|
||||
text={
|
||||
cancellingListing ===
|
||||
x.asset.mint.address.toString() ? (
|
||||
<Loading />
|
||||
) : (
|
||||
'Delist'
|
||||
)
|
||||
}
|
||||
colorClass="error"
|
||||
onClick={() => cancelListing(x)}
|
||||
/>
|
||||
<NftMarketButton
|
||||
className="w-full"
|
||||
text={`Offers (${nftBids?.length})`}
|
||||
colorClass="fgd-3"
|
||||
onClick={() => openBidsModal(x)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
)
|
||||
})
|
||||
) : loading ? (
|
||||
[...Array(4)].map((x, i) => (
|
||||
<SheenLoader className="flex flex-1" key={i}>
|
||||
<div className="col-span-1 h-64 w-full bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
))
|
||||
) : (
|
||||
<div className="col-span-5">
|
||||
<EmptyState text="No listings to display..." />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* <div>
|
||||
<ResponsivePagination
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { ReactNode } from 'react'
|
||||
|
||||
const NftMarketButton = ({
|
||||
className,
|
||||
colorClass,
|
||||
|
@ -6,7 +8,7 @@ const NftMarketButton = ({
|
|||
}: {
|
||||
className?: string
|
||||
colorClass: string
|
||||
text: string
|
||||
text: string | ReactNode
|
||||
onClick: () => void
|
||||
}) => {
|
||||
return (
|
||||
|
|
|
@ -14,6 +14,7 @@ import { PublicKey } from '@solana/web3.js'
|
|||
import { token } from '@metaplex-foundation/js'
|
||||
import metaplexStore from '@store/metaplexStore'
|
||||
import { useAuctionHouse, useLazyListings } from 'hooks/market/useAuctionHouse'
|
||||
import Loading from '@components/shared/Loading'
|
||||
|
||||
const SellNftModal = ({ isOpen, onClose }: ModalProps) => {
|
||||
const { publicKey } = useWallet()
|
||||
|
@ -25,7 +26,7 @@ const SellNftModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
const nfts = mangoStore((s) => s.wallet.nfts.data)
|
||||
const isLoadingNfts = mangoStore((s) => s.wallet.nfts.loading)
|
||||
const fetchNfts = mangoStore((s) => s.actions.fetchNfts)
|
||||
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [minPrice, setMinPrice] = useState('')
|
||||
const [selectedNft, setSelectedNft] = useState<NFT | null>(null)
|
||||
|
||||
|
@ -36,23 +37,30 @@ const SellNftModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
}, [publicKey])
|
||||
|
||||
const listAsset = async (mint: string, price: number) => {
|
||||
const currentListings = await metaplex?.auctionHouse().findListings({
|
||||
auctionHouse: auctionHouse!,
|
||||
seller: publicKey!,
|
||||
mint: new PublicKey(mint),
|
||||
})
|
||||
const isCurrentlyListed = currentListings?.filter((x) => !x.canceledAt)
|
||||
.length
|
||||
if (isCurrentlyListed) {
|
||||
throw 'Item is currently listed by you'
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const currentListings = await metaplex?.auctionHouse().findListings({
|
||||
auctionHouse: auctionHouse!,
|
||||
seller: publicKey!,
|
||||
mint: new PublicKey(mint),
|
||||
})
|
||||
const isCurrentlyListed = currentListings?.filter((x) => !x.canceledAt)
|
||||
.length
|
||||
if (isCurrentlyListed) {
|
||||
throw 'Item is currently listed by you'
|
||||
}
|
||||
await metaplex!.auctionHouse().list({
|
||||
auctionHouse: auctionHouse!, // A model of the Auction House related to this listing
|
||||
mintAccount: new PublicKey(mint), // The mint account to create a listing for, used to find the metadata
|
||||
price: token(price, MANGO_MINT_DECIMALS), // The listing price
|
||||
})
|
||||
refetch()
|
||||
onClose()
|
||||
} catch (e) {
|
||||
console.log('error listing nft', e)
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
await metaplex!.auctionHouse().list({
|
||||
auctionHouse: auctionHouse!, // A model of the Auction House related to this listing
|
||||
mintAccount: new PublicKey(mint), // The mint account to create a listing for, used to find the metadata
|
||||
price: token(price, MANGO_MINT_DECIMALS), // The listing price
|
||||
})
|
||||
refetch()
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -107,12 +115,12 @@ const SellNftModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
></Input>
|
||||
</div>
|
||||
<Button
|
||||
className="ml-2"
|
||||
className="ml-2 flex items-center justify-center"
|
||||
disabled={!selectedNft || !minPrice}
|
||||
onClick={() => listAsset(selectedNft!.mint, Number(minPrice))}
|
||||
size="large"
|
||||
>
|
||||
{t('nftMarket:list')}
|
||||
{submitting ? <Loading /> : 'List'}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -65,7 +65,7 @@ const Market: NextPage = () => {
|
|||
|
||||
return isWhiteListed ? (
|
||||
<>
|
||||
<div className="mx-auto flex max-w-[1140px] flex-col px-6">
|
||||
<div className="mx-auto flex max-w-[1140px] flex-col px-6 pb-16">
|
||||
<div className="flex items-center justify-between pb-6 pt-8">
|
||||
<h1>NFT Market</h1>
|
||||
<div className="flex space-x-2">
|
||||
|
|
Loading…
Reference in New Issue