Skip to content

Commit

Permalink
fet-1613: Wrong NFTs being listed for Avatar selection
Browse files Browse the repository at this point in the history
  • Loading branch information
Stanislav Lysak committed Sep 10, 2024
1 parent acc4be3 commit e76c87c
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 55 deletions.
4 changes: 4 additions & 0 deletions public/locales/en/transactionFlow.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"unknown": "Unknown NFT",
"loadError": "NFT cannot be loaded",
"noNFTs": "No NFTs found for this address.",
"address": {
"owned": "Owned",
"other": "Other"
},
"selected": {
"title": "Selected NFT",
"subtitle": "Are you sure you want to use this NFT?"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { useAccount, useClient } from 'wagmi'

import * as ThorinComponents from '@ensdomains/thorin'

import { AvatarNFT } from './AvatarNFT'
import { makeMockIntersectionObserver } from '../../../../../test/mock/makeMockIntersectionObserver'
import { AvatarNFT } from './AvatarNFT'

vi.mock('wagmi')

Expand All @@ -24,6 +24,7 @@ const mockHandleCancel = vi.fn()
makeMockIntersectionObserver()

const props = {
name: 'test',
handleSubmit: mockHandleSubmit,
handleCancel: mockHandleCancel,
}
Expand Down
175 changes: 121 additions & 54 deletions src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {

import { SpinnerRow } from '@app/components/@molecules/ScrollBoxWithSpinner'
import { useChainName } from '@app/hooks/chain/useChainName'
import { useNameDetails } from '@app/hooks/useNameDetails'
import { getSupportedChainContractAddress } from '@app/utils/getSupportedChainContractAddress'
import { useInfiniteQuery } from '@app/utils/query/useInfiniteQuery'

Expand Down Expand Up @@ -61,8 +62,65 @@ type NFTResponse = {
totalCount: number
}

const makeBaseURL = (network: string) =>
`https://ens-nft-worker.ens-cf.workers.dev/v1/${network}/getNfts/`
async function getNfts({
network,
owner,
pageKey,
}: {
network: string
owner: string
pageKey: string
}) {
const baseURL = `https://ens-nft-worker.ens-cf.workers.dev/v1/${network}/getNfts/`

const urlParams = new URLSearchParams()

urlParams.append('owner', owner)
urlParams.append('filters[]', 'SPAM')

if (pageKey) {
urlParams.append('pageKey', pageKey)
}

const res = await fetch(`${baseURL}?${urlParams.toString()}`, {
method: 'GET',
redirect: 'follow',
})

return (await res.json()) as NFTResponse
}

function useNtfs(chain: string, address: string) {
const client = useClient()

return useInfiniteQuery({
queryKey: [chain, address, 'NFTs'],
queryFn: async ({ pageParam }) => {
const response = await getNfts({ network: chain, owner: address, pageKey: pageParam })

return {
...response,
ownedNfts: response.ownedNfts.filter(
(nft) =>
(nft.media?.[0]?.thumbnail || nft.media?.[0]?.gateway) &&
nft.contract.address !==
getSupportedChainContractAddress({
client,
contract: 'ensBaseRegistrarImplementation',
}) &&
nft.contract.address !==
getSupportedChainContractAddress({
client,
contract: 'ensNameWrapper',
}),
),
}
},
initialPageParam: '',
placeholderData: keepPreviousData,
getNextPageParam: (lastPage) => lastPage.pageKey,
})
}

const ScrollBoxContent = styled.div(
({ theme }) => css`
Expand Down Expand Up @@ -174,6 +232,28 @@ const SelectedNFTImage = styled.img(
`,
)

const FilterContainer = styled.div(
({ theme }) => css`
width: 100%;
display: flex;
align-items: center;
gap: ${theme.space['4']};
margin-bottom: ${theme.space['4']};
${mq.sm.min(css`
margin-bottom: ${theme.space['6']};
`)}
& > button {
flex-basis: 100px;
margin-bottom: -${theme.space['4']};
${mq.sm.min(css`
margin-bottom: -${theme.space['6']};
`)}
}
`,
)

const LoadingContainer = styled.div(({ theme }) => [
css`
width: ${theme.space.full};
Expand Down Expand Up @@ -260,64 +340,31 @@ const NftItem = ({
}

export const AvatarNFT = ({
name,
handleCancel,
handleSubmit,
}: {
name: string
handleCancel: () => void
handleSubmit: (type: 'nft', uri: string, display: string) => void
}) => {
const chain = useChainName()
const { t } = useTranslation('transactionFlow')

const chain = useChainName()
const { address: _address } = useAccount()
const address = _address!

const client = useClient()
const { profile } = useNameDetails({ name })

const {
data: NFTPages,
fetchNextPage,
isLoading,
} = useInfiniteQuery({
queryKey: [chain, address, 'NFTs'],
queryFn: async ({ pageParam }) => {
const urlParams = new URLSearchParams()
urlParams.append('owner', address)
urlParams.append('filters[]', 'SPAM')
if (pageParam) {
urlParams.append('pageKey', pageParam)
}
const response = (await fetch(`${makeBaseURL(chain)}?${urlParams.toString()}`, {
method: 'GET',
redirect: 'follow',
}).then((res) => res.json())) as NFTResponse

return {
...response,
ownedNfts: response.ownedNfts.filter(
(nft) =>
(nft.media?.[0]?.thumbnail || nft.media?.[0]?.gateway) &&
nft.contract.address !==
getSupportedChainContractAddress({
client,
contract: 'ensBaseRegistrarImplementation',
}) &&
nft.contract.address !==
getSupportedChainContractAddress({
client,
contract: 'ensNameWrapper',
}),
),
}
},
initialPageParam: '',
placeholderData: keepPreviousData,
getNextPageParam: (lastPage) => lastPage.pageKey,
})
const addresses = (profile?.coins ?? []).filter((x) => ['eth'].includes(x.name.toLowerCase()))
const ethAddress = addresses[0]?.value

const [searchedInput, setSearchedInput] = useState('')
const [selectedAddress, setSelectedAddress] = useState<string>(address)
const [selectedNFT, setSelectedNFT] = useState<number | null>(null)

const { data: NFTPages, fetchNextPage, isLoading } = useNtfs(chain, selectedAddress)

const NFTs = NFTPages?.pages
.reduce((prev, curr) => [...prev, ...curr.ownedNfts], [] as OwnedNFT[])
.filter((nft) => nft.title.toLowerCase().includes(searchedInput))
Expand All @@ -326,6 +373,12 @@ export const AvatarNFT = ({
const hasNextPage = !!NFTPages?.pages[NFTPages.pages.length - 1].pageKey
const fetchPage = useCallback(() => fetchNextPage(), [fetchNextPage])

const handleSelectAddress = () => {
if (!ethAddress) return

setSelectedAddress((prev) => (prev === address ? ethAddress : address))
}

if (selectedNFT !== null) {
const nftReference = NFTs?.[selectedNFT]!

Expand Down Expand Up @@ -369,6 +422,16 @@ export const AvatarNFT = ({

let innerContent: ReactNode

const toggleButton = address !== ethAddress && (
<Button onClick={handleSelectAddress}>
{t(
`input.profileEditor.tabs.avatar.nft.address.${
selectedAddress === address ? 'other' : 'owned'
}`,
)}
</Button>
)

if (isLoading) {
innerContent = (
<Dialog.Content>
Expand All @@ -381,16 +444,19 @@ export const AvatarNFT = ({
} else if (hasNFTs) {
innerContent = (
<>
<DialogInput
icon={<MagnifyingGlassSVG />}
hideLabel
label="search"
value={searchedInput}
onChange={(e) => setSearchedInput(e.target.value)}
placeholder={t('input.profileEditor.tabs.avatar.nft.searchPlaceholder')}
data-testid="avatar-search-input"
clearable
/>
<FilterContainer>
{toggleButton}
<DialogInput
icon={<MagnifyingGlassSVG />}
hideLabel
label="search"
value={searchedInput}
onChange={(e) => setSearchedInput(e.target.value)}
placeholder={t('input.profileEditor.tabs.avatar.nft.searchPlaceholder')}
data-testid="avatar-search-input"
clearable
/>
</FilterContainer>
{NFTs.length > 0 ? (
<Dialog.Content
data-testid="nft-scroll-box"
Expand Down Expand Up @@ -422,6 +488,7 @@ export const AvatarNFT = ({
} else {
innerContent = (
<Dialog.Content>
<FilterContainer>{toggleButton}</FilterContainer>
<LoadingContainer>
<Heading>{t('input.profileEditor.tabs.avatar.nft.noNFTs')}</Heading>
</LoadingContainer>
Expand Down

0 comments on commit e76c87c

Please sign in to comment.