Commit a70ef432 authored by lynn's avatar lynn Committed by GitHub

feat: unlisted view my nft cards (#5129)

* init

* it's working with jack's card.tsx components

* add nft details on cards for view my nfts

* listed cards ready for review

* remove unnecessary code

* updated radius

* first round charlie comments

* respond all comments

* fix

* remove floor price when not on sell mode
parent 6edc7378
import graphql from 'babel-plugin-relay/macro' import graphql from 'babel-plugin-relay/macro'
import { parseEther } from 'ethers/lib/utils'
import { WalletAsset } from 'nft/types' import { WalletAsset } from 'nft/types'
import { useLazyLoadQuery, usePaginationFragment } from 'react-relay' import { useLazyLoadQuery, usePaginationFragment } from 'react-relay'
...@@ -136,10 +137,24 @@ export function useNftBalanceQuery( ...@@ -136,10 +137,24 @@ export function useNftBalanceQuery(
) )
const walletAssets: WalletAsset[] = data.nftBalances?.edges?.map((queryAsset: NftBalanceQueryAsset) => { const walletAssets: WalletAsset[] = data.nftBalances?.edges?.map((queryAsset: NftBalanceQueryAsset) => {
const asset = queryAsset.node.ownedAsset const asset = queryAsset.node.ownedAsset
const ethPrice = parseEther(
asset?.listings?.edges[0]?.node.price.value?.toLocaleString('fullwide', { useGrouping: false }) ?? '0'
).toString()
return { return {
id: asset?.id, id: asset?.id,
image_url: asset?.image?.url, imageUrl: asset?.image?.url,
image_preview_url: asset?.smallImage?.url, smallImageUrl: asset?.smallImage?.url,
notForSale: asset?.listings?.edges?.length === 0,
animationUrl: asset?.animationUrl,
susFlag: asset?.suspiciousFlag,
priceInfo: asset?.listings
? {
ETHPrice: ethPrice,
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: ethPrice,
}
: undefined,
name: asset?.name, name: asset?.name,
tokenId: asset?.tokenId, tokenId: asset?.tokenId,
asset_contract: { asset_contract: {
...@@ -149,6 +164,7 @@ export function useNftBalanceQuery( ...@@ -149,6 +164,7 @@ export function useNftBalanceQuery(
description: asset?.description, description: asset?.description,
image_url: asset?.collection?.image?.url, image_url: asset?.collection?.image?.url,
payout_address: queryAsset?.node?.listingFees?.[0]?.payoutAddress, payout_address: queryAsset?.node?.listingFees?.[0]?.payoutAddress,
tokenType: asset?.collection?.nftContracts?.[0].standard,
}, },
collection: asset?.collection, collection: asset?.collection,
collectionIsVerified: asset?.collection?.isVerified, collectionIsVerified: asset?.collection?.isVerified,
......
...@@ -27,7 +27,7 @@ const ProfileAssetRow = ({ asset }: { asset: WalletAsset }) => { ...@@ -27,7 +27,7 @@ const ProfileAssetRow = ({ asset }: { asset: WalletAsset }) => {
> >
<img className={styles.removeIcon} src={'/nft/svgs/minusCircle.svg'} alt="Remove item" /> <img className={styles.removeIcon} src={'/nft/svgs/minusCircle.svg'} alt="Remove item" />
</Box> </Box>
<img className={styles.tagAssetImage} src={asset.image_url} alt={asset.name} /> <img className={styles.tagAssetImage} src={asset.imageUrl} alt={asset.name} />
</div> </div>
<Column gap="4" overflow="hidden" flexWrap="nowrap"> <Column gap="4" overflow="hidden" flexWrap="nowrap">
<Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" className={subhead}> <Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" className={subhead}>
......
...@@ -142,7 +142,7 @@ export const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], Listin ...@@ -142,7 +142,7 @@ export const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], Listin
sellAssets.forEach((asset) => { sellAssets.forEach((asset) => {
asset.marketplaces?.forEach((marketplace: ListingMarket) => { asset.marketplaces?.forEach((marketplace: ListingMarket) => {
const newListing = { const newListing = {
images: [asset.image_preview_url, marketplace.icon], images: [asset.smallImageUrl, marketplace.icon],
name: asset.name || `#${asset.tokenId}`, name: asset.name || `#${asset.tokenId}`,
status: ListingStatus.DEFINED, status: ListingStatus.DEFINED,
asset, asset,
......
...@@ -25,6 +25,7 @@ export const notSelectedCard = style([ ...@@ -25,6 +25,7 @@ export const notSelectedCard = style([
sprinkles({ sprinkles({
backgroundColor: 'backgroundSurface', backgroundColor: 'backgroundSurface',
borderColor: 'backgroundOutline', borderColor: 'backgroundOutline',
borderRadius: '14',
}), }),
{ {
':hover': { ':hover': {
...@@ -45,6 +46,7 @@ export const selectedCard = style([ ...@@ -45,6 +46,7 @@ export const selectedCard = style([
borderWidth: '3px', borderWidth: '3px',
}), }),
{ {
borderRadius: '18px',
':hover': { ':hover': {
backgroundColor: themeVars.colors.stateOverlayHover, backgroundColor: themeVars.colors.stateOverlayHover,
}, },
......
This diff is collapsed.
import { BigNumber } from '@ethersproject/bignumber'
import { sendAnalyticsEvent } from 'analytics' import { sendAnalyticsEvent } from 'analytics'
import { EventName, PageName } from 'analytics/constants' import { EventName, PageName } from 'analytics/constants'
import { useTrace } from 'analytics/Trace' import { useTrace } from 'analytics/Trace'
import { useBag } from 'nft/hooks' import { useBag } from 'nft/hooks'
import { GenieAsset, Markets, UniformHeight } from 'nft/types' import { GenieAsset, Markets, UniformHeight } from 'nft/types'
import { formatWeiToDecimal, isAudio, isVideo, rarityProviderLogo } from 'nft/utils' import { formatWeiToDecimal, rarityProviderLogo } from 'nft/utils'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { useAssetMediaType, useNotForSale } from './Card'
import { AssetMediaType } from './Card'
import * as Card from './Card' import * as Card from './Card'
enum AssetMediaType {
Image,
Video,
Audio,
}
interface CollectionAssetProps { interface CollectionAssetProps {
asset: GenieAsset asset: GenieAsset
isMobile: boolean isMobile: boolean
...@@ -53,24 +48,8 @@ export const CollectionAsset = ({ ...@@ -53,24 +48,8 @@ export const CollectionAsset = ({
} }
}, [asset, itemsInBag]) }, [asset, itemsInBag])
const { notForSale, assetMediaType } = useMemo(() => { const notForSale = useNotForSale(asset)
let notForSale = true const assetMediaType = useAssetMediaType(asset)
let assetMediaType = AssetMediaType.Image
notForSale = asset.notForSale || BigNumber.from(asset.priceInfo ? asset.priceInfo.ETHPrice : 0).lt(0)
if (asset.animationUrl) {
if (isAudio(asset.animationUrl)) {
assetMediaType = AssetMediaType.Audio
} else if (isVideo(asset.animationUrl)) {
assetMediaType = AssetMediaType.Video
}
}
return {
notForSale,
assetMediaType,
}
}, [asset])
const { provider, rarityLogo } = useMemo(() => { const { provider, rarityLogo } = useMemo(() => {
return { return {
......
...@@ -173,7 +173,7 @@ export const VerifiedIcon = (props: SVGProps) => ( ...@@ -173,7 +173,7 @@ export const VerifiedIcon = (props: SVGProps) => (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path <path
d="M4.52795 13.8056C4.52719 14.4043 4.6712 14.8474 4.95997 15.135C5.24798 15.4233 5.68496 15.5651 6.27091 15.5605H7.57497C7.62945 15.5585 7.68379 15.5676 7.73463 15.5873C7.78547 15.607 7.83176 15.6369 7.87062 15.6752L8.79884 16.5928C9.22054 17.0142 9.63382 17.2237 10.0387 17.2214C10.4436 17.2191 10.8569 17.0096 11.2786 16.5928L12.1954 15.6752C12.2356 15.6365 12.2832 15.6063 12.3354 15.5866C12.3876 15.5669 12.4433 15.558 12.499 15.5605H13.7951C14.3871 15.5613 14.8283 15.4171 15.1186 15.1281C15.4089 14.839 15.5541 14.3959 15.5541 13.7987V12.5014C15.5511 12.389 15.5923 12.2799 15.6687 12.1974L16.5854 11.2798C17.0125 10.86 17.2245 10.4467 17.2214 10.0399C17.2184 9.63305 17.0064 9.21935 16.5854 8.79878L15.6687 7.88115C15.592 7.79886 15.5509 7.68965 15.5541 7.57719V6.2799C15.5533 5.68191 15.4093 5.23878 15.1221 4.95049C14.8348 4.66221 14.3925 4.51806 13.7951 4.51806H12.499C12.4433 4.52036 12.3877 4.51138 12.3355 4.49168C12.2834 4.47197 12.2357 4.44193 12.1954 4.40336L11.2786 3.48574C10.8569 3.06439 10.4436 2.85487 10.0387 2.85717C9.63382 2.85946 9.22054 3.06898 8.79884 3.48574L7.87062 4.40336C7.83164 4.44148 7.78536 4.4713 7.73454 4.49101C7.68373 4.51072 7.62943 4.51993 7.57497 4.51806H6.27091C5.67961 4.51883 5.23995 4.66182 4.95194 4.94705C4.66393 5.23228 4.51992 5.67656 4.51992 6.2799V7.58063C4.52314 7.69309 4.48197 7.80229 4.40533 7.88459L3.48859 8.80222C3.06765 9.22203 2.85718 9.63572 2.85718 10.0433C2.85718 10.4509 3.07033 10.8653 3.49662 11.2867L4.41336 12.2043C4.48979 12.2867 4.53092 12.3958 4.52795 12.5083V13.8056Z" d="M4.52795 13.8056C4.52719 14.4043 4.6712 14.8474 4.95997 15.135C5.24798 15.4233 5.68496 15.5651 6.27091 15.5605H7.57497C7.62945 15.5585 7.68379 15.5676 7.73463 15.5873C7.78547 15.607 7.83176 15.6369 7.87062 15.6752L8.79884 16.5928C9.22054 17.0142 9.63382 17.2237 10.0387 17.2214C10.4436 17.2191 10.8569 17.0096 11.2786 16.5928L12.1954 15.6752C12.2356 15.6365 12.2832 15.6063 12.3354 15.5866C12.3876 15.5669 12.4433 15.558 12.499 15.5605H13.7951C14.3871 15.5613 14.8283 15.4171 15.1186 15.1281C15.4089 14.839 15.5541 14.3959 15.5541 13.7987V12.5014C15.5511 12.389 15.5923 12.2799 15.6687 12.1974L16.5854 11.2798C17.0125 10.86 17.2245 10.4467 17.2214 10.0399C17.2184 9.63305 17.0064 9.21935 16.5854 8.79878L15.6687 7.88115C15.592 7.79886 15.5509 7.68965 15.5541 7.57719V6.2799C15.5533 5.68191 15.4093 5.23878 15.1221 4.95049C14.8348 4.66221 14.3925 4.51806 13.7951 4.51806H12.499C12.4433 4.52036 12.3877 4.51138 12.3355 4.49168C12.2834 4.47197 12.2357 4.44193 12.1954 4.40336L11.2786 3.48574C10.8569 3.06439 10.4436 2.85487 10.0387 2.85717C9.63382 2.85946 9.22054 3.06898 8.79884 3.48574L7.87062 4.40336C7.83164 4.44148 7.78536 4.4713 7.73454 4.49101C7.68373 4.51072 7.62943 4.51993 7.57497 4.51806H6.27091C5.67961 4.51883 5.23995 4.66182 4.95194 4.94705C4.66393 5.23228 4.51992 5.67656 4.51992 6.2799V7.58063C4.52314 7.69309 4.48197 7.80229 4.40533 7.88459L3.48859 8.80222C3.06765 9.22203 2.85718 9.63572 2.85718 10.0433C2.85718 10.4509 3.07033 10.8653 3.49662 11.2867L4.41336 12.2043C4.48979 12.2867 4.53092 12.3958 4.52795 12.5083V13.8056Z"
fill="#4673FA" fill={props.fill ?? '#4673FA'}
/> />
<path <path
d="M9.99737 12.4943C9.86205 12.7005 9.6623 12.8164 9.43032 12.8164C9.19191 12.8164 9.00504 12.7198 8.83106 12.4943L7.31036 10.6385C7.20082 10.5032 7.14282 10.3614 7.14282 10.2068C7.14282 9.88458 7.38768 9.63327 7.70342 9.63327C7.89673 9.63327 8.05138 9.70415 8.20603 9.90391L9.40455 11.4311L11.9498 7.34577C12.0851 7.12669 12.2591 7.02359 12.4524 7.02359C12.7553 7.02359 13.0388 7.23623 13.0388 7.55197C13.0388 7.70017 12.9615 7.85482 12.8777 7.99014L9.99737 12.4943Z" d="M9.99737 12.4943C9.86205 12.7005 9.6623 12.8164 9.43032 12.8164C9.19191 12.8164 9.00504 12.7198 8.83106 12.4943L7.31036 10.6385C7.20082 10.5032 7.14282 10.3614 7.14282 10.2068C7.14282 9.88458 7.38768 9.63327 7.70342 9.63327C7.89673 9.63327 8.05138 9.70415 8.20603 9.90391L9.40455 11.4311L11.9498 7.34577C12.0851 7.12669 12.2591 7.02359 12.4524 7.02359C12.7553 7.02359 13.0388 7.23623 13.0388 7.55197C13.0388 7.70017 12.9615 7.85482 12.8777 7.99014L9.99737 12.4943Z"
......
...@@ -796,7 +796,7 @@ const NFTListRow = ({ asset, globalPriceMethod, globalPrice, setGlobalPrice, sel ...@@ -796,7 +796,7 @@ const NFTListRow = ({ asset, globalPriceMethod, globalPrice, setGlobalPrice, sel
marginLeft={localMarkets.length > 1 ? '8' : '0'} marginLeft={localMarkets.length > 1 ? '8' : '0'}
marginRight="8" marginRight="8"
transition="500" transition="500"
src={asset.image_url || '/nft/svgs/image-placeholder.svg'} src={asset.imageUrl || '/nft/svgs/image-placeholder.svg'}
/> />
</Box> </Box>
<Column gap="4" minWidth="0"> <Column gap="4" minWidth="0">
......
...@@ -30,7 +30,7 @@ import shallow from 'zustand/shallow' ...@@ -30,7 +30,7 @@ import shallow from 'zustand/shallow'
import { EmptyWalletContent } from './EmptyWalletContent' import { EmptyWalletContent } from './EmptyWalletContent'
import * as styles from './ProfilePage.css' import * as styles from './ProfilePage.css'
import { WalletAssetDisplay } from './WalletAssetDisplay' import { ViewMyNftsAsset } from './ViewMyNftsAsset'
const SellModeButton = styled.button<{ active: boolean }>` const SellModeButton = styled.button<{ active: boolean }>`
display: flex; display: flex;
...@@ -79,6 +79,7 @@ export const ProfilePage = () => { ...@@ -79,6 +79,7 @@ export const ProfilePage = () => {
const setSellPageState = useProfilePageState((state) => state.setProfilePageState) const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded() const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
const isMobile = useIsMobile() const isMobile = useIsMobile()
const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState<string | undefined>()
const handleSellModeClick = useCallback(() => { const handleSellModeClick = useCallback(() => {
resetSellAssets() resetSellAssets()
...@@ -171,7 +172,14 @@ export const ProfilePage = () => { ...@@ -171,7 +172,14 @@ export const ProfilePage = () => {
<div className={assetList}> <div className={assetList}>
{ownerAssets?.length {ownerAssets?.length
? ownerAssets.map((asset, index) => ( ? ownerAssets.map((asset, index) => (
<WalletAssetDisplay asset={asset} isSellMode={isSellMode} key={index} /> <div key={index}>
<ViewMyNftsAsset
asset={asset}
isSellMode={isSellMode}
mediaShouldBePlaying={asset.tokenId === currentTokenPlayingMedia}
setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia}
/>
</div>
)) ))
: null} : null}
</div> </div>
......
import * as Card from 'nft/components/collection/Card'
import { AssetMediaType } from 'nft/components/collection/Card'
import { useBag, useIsMobile, useSellAsset } from 'nft/hooks'
import { WalletAsset } from 'nft/types'
import { useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
const NFT_DETAILS_HREF = (asset: WalletAsset) =>
`/nfts/asset/${asset.asset_contract.address}/${asset.tokenId}?origin=profile`
interface ViewMyNftsAssetProps {
asset: WalletAsset
isSellMode: boolean
mediaShouldBePlaying: boolean
setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
}
export const ViewMyNftsAsset = ({
asset,
isSellMode,
mediaShouldBePlaying,
setCurrentTokenPlayingMedia,
}: ViewMyNftsAssetProps) => {
const sellAssets = useSellAsset((state) => state.sellAssets)
const selectSellAsset = useSellAsset((state) => state.selectSellAsset)
const removeSellAsset = useSellAsset((state) => state.removeSellAsset)
const cartExpanded = useBag((state) => state.bagExpanded)
const toggleCart = useBag((state) => state.toggleBag)
const isMobile = useIsMobile()
const navigate = useNavigate()
const isSelected = useMemo(() => {
return sellAssets.some(
(item) => item.tokenId === asset.tokenId && item.asset_contract.address === asset.asset_contract.address
)
}, [asset, sellAssets])
const onCardClick = () => {
isSellMode ? handleSelect(isSelected) : navigate(NFT_DETAILS_HREF(asset))
}
const handleSelect = (removeAsset: boolean) => {
removeAsset ? removeSellAsset(asset) : selectSellAsset(asset)
if (
!cartExpanded &&
!sellAssets.find(
(x) => x.tokenId === asset.tokenId && x.asset_contract.address === asset.asset_contract.address
) &&
!isMobile
)
toggleCart()
}
const assetMediaType = Card.useAssetMediaType(asset)
return (
<Card.Container
asset={asset}
selected={isSelected}
addAssetToBag={() => handleSelect(false)}
removeAssetFromBag={() => handleSelect(true)}
onClick={onCardClick}
>
<Card.ImageContainer>
{assetMediaType === AssetMediaType.Image ? (
<Card.Image />
) : assetMediaType === AssetMediaType.Video ? (
<Card.Video shouldPlay={mediaShouldBePlaying} setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia} />
) : (
<Card.Audio shouldPlay={mediaShouldBePlaying} setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia} />
)}
</Card.ImageContainer>
<Card.DetailsContainer>
<Card.ProfileNftDetails asset={asset} isSellMode={isSellMode} />
</Card.DetailsContainer>
</Card.Container>
)
}
...@@ -71,7 +71,7 @@ export const WalletAssetDisplay = ({ asset, isSellMode }: { asset: WalletAsset; ...@@ -71,7 +71,7 @@ export const WalletAssetDisplay = ({ asset, isSellMode }: { asset: WalletAsset;
width="full" width="full"
borderTopLeftRadius="20" borderTopLeftRadius="20"
borderTopRightRadius="20" borderTopRightRadius="20"
src={asset.image_url ?? '/nft/svgs/image-placeholder.svg'} src={asset.imageUrl ?? '/nft/svgs/image-placeholder.svg'}
style={{ aspectRatio: '1' }} style={{ aspectRatio: '1' }}
/> />
<Column paddingTop="12" paddingX="12"> <Column paddingTop="12" paddingX="12">
......
import { NftMarketplace, OrderStatus, OrderType } from 'graphql/data/nft/__generated__/DetailsQuery.graphql' import { NftMarketplace, OrderStatus, OrderType } from 'graphql/data/nft/__generated__/DetailsQuery.graphql'
import { GenieCollection } from '../common' import { GenieCollection, PriceInfo, TokenType } from '../common'
export interface ListingMarket { export interface ListingMarket {
name: string name: string
...@@ -41,8 +41,12 @@ export interface Listing { ...@@ -41,8 +41,12 @@ export interface Listing {
export interface WalletAsset { export interface WalletAsset {
id?: string id?: string
image_url: string imageUrl: string
image_preview_url: string smallImageUrl: string
notForSale: boolean
animationUrl: string
susFlag: boolean
priceInfo: PriceInfo
name: string name: string
tokenId: string tokenId: string
asset_contract: { asset_contract: {
...@@ -52,6 +56,7 @@ export interface WalletAsset { ...@@ -52,6 +56,7 @@ export interface WalletAsset {
description: string description: string
image_url: string image_url: string
payout_address: string payout_address: string
tokenType: TokenType
} }
collection: GenieCollection collection: GenieCollection
collectionIsVerified: boolean collectionIsVerified: boolean
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment