feat: [ListV2] Success modal + Twitter Share (#5924)
* added success screen with images * add listing proceeds * working return * working single tweet * working tweet multiple * update how we parse twitterName * add scrollbar styles * usestablecoinvalue * math.min * add collection name backup to tweet --------- Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
This commit is contained in:
parent
8f922b665a
commit
f26b09537d
@ -1004,7 +1004,7 @@ export type NftBalanceQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type NftBalanceQuery = { __typename?: 'Query', nftBalances?: { __typename?: 'NftBalanceConnection', edges: Array<{ __typename?: 'NftBalanceEdge', node: { __typename?: 'NftBalance', listedMarketplaces?: Array<NftMarketplace>, ownedAsset?: { __typename?: 'NftAsset', id: string, animationUrl?: string, description?: string, flaggedBy?: string, name?: string, ownerAddress?: string, suspiciousFlag?: boolean, tokenId: string, collection?: { __typename?: 'NftCollection', isVerified?: boolean, name?: string, image?: { __typename?: 'Image', url: string }, nftContracts?: Array<{ __typename?: 'NftContract', address: string, chain: Chain, name?: string, standard?: NftStandard, symbol?: string, totalSupply?: number }>, markets?: Array<{ __typename?: 'NftCollectionMarket', floorPrice?: { __typename?: 'TimestampedAmount', value: number } }> }, image?: { __typename?: 'Image', url: string }, originalImage?: { __typename?: 'Image', url: string }, smallImage?: { __typename?: 'Image', url: string }, thumbnail?: { __typename?: 'Image', url: string }, listings?: { __typename?: 'NftOrderConnection', edges: Array<{ __typename?: 'NftOrderEdge', node: { __typename?: 'NftOrder', createdAt: number, marketplace: NftMarketplace, endAt?: number, price: { __typename?: 'Amount', value: number, currency?: Currency } } }> } }, listingFees?: Array<{ __typename?: 'NftFee', payoutAddress: string, basisPoints: number }>, lastPrice?: { __typename?: 'TimestampedAmount', currency?: Currency, timestamp: number, value: number } } }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string, hasNextPage?: boolean, hasPreviousPage?: boolean, startCursor?: string } } };
|
||||
export type NftBalanceQuery = { __typename?: 'Query', nftBalances?: { __typename?: 'NftBalanceConnection', edges: Array<{ __typename?: 'NftBalanceEdge', node: { __typename?: 'NftBalance', listedMarketplaces?: Array<NftMarketplace>, ownedAsset?: { __typename?: 'NftAsset', id: string, animationUrl?: string, description?: string, flaggedBy?: string, name?: string, ownerAddress?: string, suspiciousFlag?: boolean, tokenId: string, collection?: { __typename?: 'NftCollection', isVerified?: boolean, name?: string, twitterName?: string, image?: { __typename?: 'Image', url: string }, nftContracts?: Array<{ __typename?: 'NftContract', address: string, chain: Chain, name?: string, standard?: NftStandard, symbol?: string, totalSupply?: number }>, markets?: Array<{ __typename?: 'NftCollectionMarket', floorPrice?: { __typename?: 'TimestampedAmount', value: number } }> }, image?: { __typename?: 'Image', url: string }, originalImage?: { __typename?: 'Image', url: string }, smallImage?: { __typename?: 'Image', url: string }, thumbnail?: { __typename?: 'Image', url: string }, listings?: { __typename?: 'NftOrderConnection', edges: Array<{ __typename?: 'NftOrderEdge', node: { __typename?: 'NftOrder', createdAt: number, marketplace: NftMarketplace, endAt?: number, price: { __typename?: 'Amount', value: number, currency?: Currency } } }> } }, listingFees?: Array<{ __typename?: 'NftFee', payoutAddress: string, basisPoints: number }>, lastPrice?: { __typename?: 'TimestampedAmount', currency?: Currency, timestamp: number, value: number } } }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string, hasNextPage?: boolean, hasPreviousPage?: boolean, startCursor?: string } } };
|
||||
|
||||
|
||||
export const TokenDocument = gql`
|
||||
@ -1622,6 +1622,7 @@ export const NftBalanceDocument = gql`
|
||||
url
|
||||
}
|
||||
name
|
||||
twitterName
|
||||
nftContracts {
|
||||
address
|
||||
chain
|
||||
|
@ -34,6 +34,7 @@ gql`
|
||||
url
|
||||
}
|
||||
name
|
||||
twitterName
|
||||
nftContracts {
|
||||
address
|
||||
chain
|
||||
@ -166,7 +167,12 @@ export function useNftBalance(
|
||||
image_url: asset?.collection?.image?.url,
|
||||
payout_address: queryAsset?.node?.listingFees?.[0]?.payoutAddress,
|
||||
},
|
||||
collection: asset?.collection as unknown as GenieCollection,
|
||||
collection: {
|
||||
name: asset.collection?.name,
|
||||
isVerified: asset.collection?.isVerified,
|
||||
imageUrl: asset.collection?.image?.url,
|
||||
twitterUrl: asset.collection?.twitterName ? `@${asset.collection?.twitterName}` : undefined,
|
||||
} as GenieCollection,
|
||||
collectionIsVerified: asset?.collection?.isVerified,
|
||||
lastPrice: queryAsset.node.lastPrice?.value,
|
||||
floorPrice: asset?.collection?.markets?.[0]?.floorPrice?.value,
|
||||
|
@ -2,7 +2,6 @@ import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, Trace, useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Row from 'components/Row'
|
||||
import { getTotalEthValue, signListingRow } from 'nft/components/bag/profile/utils'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { Overlay } from 'nft/components/modals/Overlay'
|
||||
@ -15,7 +14,9 @@ import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import { TitleRow } from '../shared'
|
||||
import { ListModalSection, Section } from './ListModalSection'
|
||||
import { SuccessScreen } from './SuccessScreen'
|
||||
|
||||
const ListModalWrapper = styled.div`
|
||||
position: fixed;
|
||||
@ -39,11 +40,6 @@ const ListModalWrapper = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const TitleRow = styled(Row)`
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||
const { provider } = useWeb3React()
|
||||
const signer = provider?.getSigner()
|
||||
@ -101,7 +97,7 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||
<Trace modal={InterfaceModalName.NFT_LISTING}>
|
||||
<ListModalWrapper>
|
||||
{listingStatus === ListingStatus.APPROVED ? (
|
||||
<>TODO Success Screen</>
|
||||
<SuccessScreen overlayClick={overlayClick} />
|
||||
) : (
|
||||
<>
|
||||
<TitleRow>
|
||||
|
129
src/nft/components/profile/list/Modal/SuccessScreen.tsx
Normal file
129
src/nft/components/profile/list/Modal/SuccessScreen.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
|
||||
import Column from 'components/Column'
|
||||
import { ScrollBarStyles } from 'components/Common'
|
||||
import Row from 'components/Row'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { getTotalEthValue } from 'nft/components/bag/profile/utils'
|
||||
import { useSellAsset } from 'nft/hooks'
|
||||
import { formatEth, generateTweetForList, pluralize } from 'nft/utils'
|
||||
import { useMemo } from 'react'
|
||||
import { Twitter, X } from 'react-feather'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
|
||||
import { TitleRow } from '../shared'
|
||||
|
||||
const SuccessImage = styled.img<{ numImages: number }>`
|
||||
width: calc(${({ numImages }) => (numImages > 1 ? (numImages > 2 ? '33%' : '50%') : '100%')} - 12px);
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
const SuccessImageWrapper = styled(Row)`
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
overflow-y: scroll;
|
||||
margin-bottom: 16px;
|
||||
${ScrollBarStyles}
|
||||
`
|
||||
|
||||
const ProceedsColumn = styled(Column)`
|
||||
text-align: right;
|
||||
`
|
||||
|
||||
const buttonStyle = css`
|
||||
width: 182px;
|
||||
cursor: pointer;
|
||||
padding: 12px 0px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const ReturnButton = styled.button`
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
${buttonStyle}
|
||||
`
|
||||
|
||||
const TweetButton = styled.a`
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
text-decoration: none;
|
||||
${buttonStyle}
|
||||
`
|
||||
|
||||
const TweetRow = styled(Row)`
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
`
|
||||
|
||||
export const SuccessScreen = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||
const theme = useTheme()
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
|
||||
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
|
||||
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
|
||||
const usdcValue = useStablecoinValue(parsedAmount)
|
||||
|
||||
return (
|
||||
<>
|
||||
<TitleRow>
|
||||
<ThemedText.HeadlineSmall lineHeight="28px">
|
||||
<Trans>Successfully listed</Trans> {sellAssets.length > 1 ? ` ${sellAssets.length} ` : ''}
|
||||
NFT{pluralize(sellAssets.length)}!
|
||||
</ThemedText.HeadlineSmall>
|
||||
<X size={24} cursor="pointer" onClick={overlayClick} />
|
||||
</TitleRow>
|
||||
<SuccessImageWrapper>
|
||||
{sellAssets.map((asset) => (
|
||||
<SuccessImage
|
||||
src={asset.imageUrl}
|
||||
key={asset?.asset_contract?.address ?? '' + asset?.tokenId}
|
||||
numImages={sellAssets.length}
|
||||
/>
|
||||
))}
|
||||
</SuccessImageWrapper>
|
||||
<Row justify="space-between" align="flex-start" marginBottom="16px">
|
||||
<ThemedText.SubHeader lineHeight="24px">
|
||||
<Trans>Proceeds if sold</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
<ProceedsColumn>
|
||||
<ThemedText.SubHeader lineHeight="24px">{formatEth(totalEthListingValue)} ETH</ThemedText.SubHeader>
|
||||
{usdcValue && (
|
||||
<ThemedText.BodySmall lineHeight="20px" color="textSecondary">
|
||||
{formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)}
|
||||
</ThemedText.BodySmall>
|
||||
)}
|
||||
</ProceedsColumn>
|
||||
</Row>
|
||||
<Row justify="space-between" flexWrap="wrap">
|
||||
<ReturnButton onClick={() => window.location.reload()}>
|
||||
<Trans>Return to My NFTs</Trans>
|
||||
</ReturnButton>
|
||||
<TweetButton href={generateTweetForList(sellAssets)} target="_blank" rel="noreferrer">
|
||||
<TweetRow>
|
||||
<Twitter height={20} width={20} color={theme.textPrimary} fill={theme.textPrimary} />
|
||||
<Trans>Share on Twitter</Trans>
|
||||
</TweetRow>
|
||||
</TweetButton>
|
||||
</Row>
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import Row from 'components/Row'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
export const RemoveIconWrap = styled.div<{ hovered: boolean }>`
|
||||
@ -8,3 +9,8 @@ export const RemoveIconWrap = styled.div<{ hovered: boolean }>`
|
||||
width: 32px;
|
||||
visibility: ${({ hovered }) => (hovered ? 'visible' : 'hidden')};
|
||||
`
|
||||
|
||||
export const TitleRow = styled(Row)`
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DetailsOrigin, GenieAsset, UpdatedGenieAsset, WalletAsset } from 'nft/types'
|
||||
import { DetailsOrigin, GenieAsset, Listing, UpdatedGenieAsset, WalletAsset } from 'nft/types'
|
||||
|
||||
export function getRarityStatus(
|
||||
rarityStatusCache: Map<string, boolean>,
|
||||
@ -44,3 +44,38 @@ export const generateTweetForPurchase = (assets: UpdatedGenieAsset[], txHashUrl:
|
||||
} with @Uniswap 🦄\n\nhttps://app.uniswap.org/#/nfts/collection/0x60bb1e2aa1c9acafb4d34f71585d7e959f387769\n${txHashUrl}`
|
||||
return `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}`
|
||||
}
|
||||
|
||||
function getMinListingPrice(listings: Listing[]): number {
|
||||
return Math.min(...listings.map((listing) => listing.price ?? 0)) ?? 0
|
||||
}
|
||||
|
||||
function mapAssetsToCollections(assets: WalletAsset[]): { collection: string; items: string[] }[] {
|
||||
const collections = assets.map((asset) => asset.collection?.twitterUrl ?? asset.collection?.name ?? '')
|
||||
const uniqueCollections = [...new Set(collections)]
|
||||
return uniqueCollections.map((collection) => {
|
||||
return {
|
||||
collection,
|
||||
items: assets
|
||||
.filter((asset) => asset.collection?.twitterUrl === collection || asset.collection?.name === collection)
|
||||
.map((asset) => asset.name ?? ''),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const generateTweetForList = (assets: WalletAsset[]): string => {
|
||||
const tweetText =
|
||||
assets.length == 1
|
||||
? `I just listed ${
|
||||
assets[0].collection?.twitterUrl
|
||||
? `${assets[0].collection?.twitterUrl} `
|
||||
: `${assets[0].collection?.name} ` ?? ''
|
||||
}${assets[0].name} for ${getMinListingPrice(assets[0].newListings ?? [])} ETH on ${assets[0].marketplaces
|
||||
?.map((market) => market.name)
|
||||
.join(', ')}. Buy it on @Uniswap at https://app.uniswap.org/#${getAssetHref(assets[0])}`
|
||||
: `I just listed ${
|
||||
assets.length
|
||||
} items on @Uniswap at https://app.uniswap.org/#/nfts/profile\n\nCollections: ${mapAssetsToCollections(assets)
|
||||
.map(({ collection, items }) => `${collection} ${items.map((item) => item).join(', ')}`)
|
||||
.join(', ')} \n\nMarketplaces: ${assets[0].marketplaces?.map((market) => market.name).join(', ')}`
|
||||
return `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user