Repository: oceanprotocol/market (ts)

Issue:
Invalid nft tokenURI breaks UI
Our UI expects a json encoded tokenURI.  But if someone is not having that, our market fails.

Example:  https://market.oceanprotocol.com/asset/did:op:4e95bb06d1342e027fcf3dea2a3030034b66fff4fa77c64369aeb99621e6b2f3

![image](https://user-images.githubusercontent.com/7489447/200671718-42df70ec-8377-4cd4-8d3f-8a7734630da6.png)

Traceback:
- decoding their nft contract (https://mumbai.polygonscan.com/address/0x6010F7fb5180b89A172b4146dAd12a998222957D#readContract) we got:
```
[ tokenURI method Response ]
    string :  https://www.dataunion.app/wp-content/uploads/elementor/thumbs/logo2-pq2gnen6pz25kw859sdyuyyh23xml7r7wpzn9jqtq8.png
```


vs a market published nft (https://mumbai.polygonscan.com/address/0x2D2951DEFf34F869d7710e4fbAF7dc066B21F74C#readContract)
```
[ tokenURI method Response ]
    string :  data:application/json;base64,eyJuYW1lIjoiT2NlYW4gRGF0YSBORlQiLCJzeW1ib2wiOiJPQ0VBTi1ORlQiLCJkZXNjcmlwdGlvbiI6IlRoaXMgTkZUIHJlcHJlc2VudHMgYW4gYXNzZXQgaW4gdGhlIE9jZWFuIFByb3RvY29sIHY0IGVjb3N5c3RlbS5cblxuVmlldyBvbiBPY2VhbiBNYXJrZXQ6IGh0dHBzOi8vbWFya2V0Lm9jZWFucHJvdG9jb2wuY29tL2Fzc2V0L2RpZDpvcDozNjMyZTg1ODQ4MzdmMmVhYzA0ZDg1NDY2YzBjZWJkOGI4Y2IyNjczYjQ3MmE4MmEzMTAxNzVkYTk3MzAwNDJhIiwiZXh0ZXJuYWxfdXJsIjoiaHR0cHM6Ly9tYXJrZXQub2NlYW5wcm90b2NvbC5jb20vYXNzZXQvZGlkOm9wOjM2MzJlODU4NDgzN2YyZWFjMDRkODU0NjZjMGNlYmQ4YjhjYjI2NzNiNDcyYTgyYTMxMDE3NWRhOTczMDA0MmEiLCJiYWNrZ3JvdW5kX2NvbG9yIjoiMTQxNDE0IiwiaW1hZ2VfZGF0YSI6ImRhdGE6aW1hZ2Uvc3ZnK3htbCwlM0Nzdmcgdmlld0JveD0nMCAwIDk5IDk5JyBmaWxsPSd1bmRlZmluZWQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyclM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5Mjc3JyBkPSdNMCw5OUwwLDIwQzgsMTkgMTYsMTkgMjUsMTlDMzMsMTggNDAsMTcgNDksMTlDNTcsMjAgNjUsMjQgNzQsMjZDODIsMjcgOTAsMjcgOTksMjdMOTksOTlaJy8lM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5MmJiJyBkPSdNMCw5OUwwLDUwQzcsNTIgMTUsNTQgMjQsNTRDMzIsNTMgNDEsNTEgNTAsNTBDNTgsNDggNjUsNDggNzQsNDlDODIsNDkgOTAsNTIgOTksNTVMOTksOTlaJyUzRSUzQy9wYXRoJTNFJTNDcGF0aCBmaWxsPSclMjNmZjQwOTJmZicgZD0nTTAsOTlMMCw3NkM4LDc4IDE2LDgxIDI1LDgwQzMzLDc4IDQxLDczIDQ5LDcxQzU2LDY4IDY0LDY4IDczLDcwQzgxLDcxIDkwLDc0IDk5LDc3TDk5LDk5WiclM0UlM0MvcGF0aCUzRSUzQy9zdmclM0UifQ==
 ```
 
 which decodes to :
 ```
 {"name":"Ocean Data NFT","symbol":"OCEAN-NFT","description":"This NFT represents an asset in the Ocean Protocol v4 ecosystem.\n\nView on Ocean Market: https://market.oceanprotocol.com/asset/did:op:3632e8584837f2eac04d85466c0cebd8b8cb2673b472a82a310175da9730042a","external_url":"https://market.oceanprotocol.com/asset/did:op:3632e8584837f2eac04d85466c0cebd8b8cb2673b472a82a310175da9730042a","background_color":"141414","image_data":"data:image/svg+xml,%3Csvg viewBox='0 0 99 99' fill='undefined' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23ff409277' d='M0,99L0,20C8,19 16,19 25,19C33,18 40,17 49,19C57,20 65,24 74,26C82,27 90,27 99,27L99,99Z'/%3E%3Cpath fill='%23ff4092bb' d='M0,99L0,50C7,52 15,54 24,54C32,53 41,51 50,50C58,48 65,48 74,49C82,49 90,52 99,55L99,99Z'%3E%3C/path%3E%3Cpath fill='%23ff4092ff' d='M0,99L0,76C8,78 16,81 25,80C33,78 41,73 49,71C56,68 64,68 73,70C81,71 90,74 99,77L99,99Z'%3E%3C/path%3E%3C/svg%3E"}
 ```
 
 So, we should always asses if tokenURI is a json encoded string or not

Pull request:
NFT image & faulty metadata fixes
* fixes #1778
* safeguard against faulty metadata.tags in `Asset` & `RelatedAssets` component
* properly handle image strings in tokenURI

<img width="521" alt="Screenshot 2022-11-08 at 22 05 11" src="https://user-images.githubusercontent.com/90316/200685723-3f30c18d-4718-49d4-be16-4981363f91d9.png">

Click, click, test, test:

https://market-git-fix-1778-oceanprotocol.vercel.app/asset/did:op:4e95bb06d1342e027fcf3dea2a3030034b66fff4fa77c64369aeb99621e6b2f3

Patch:
diff --git a/src/@utils/nft.ts b/src/@utils/nft.ts
index 2bbc3015..a4343801 100644
--- a/src/@utils/nft.ts
+++ b/src/@utils/nft.ts
@@ -82,10 +82,13 @@ export function generateNftCreateData(
 
 export function decodeTokenURI(tokenURI: string): NftMetadata {
   if (!tokenURI) return undefined
+
   try {
-    const nftMeta = JSON.parse(
-      Buffer.from(tokenURI.replace(tokenUriPrefix, ''), 'base64').toString()
-    ) as NftMetadata
+    const nftMeta = tokenURI.includes('data:application/json')
+      ? (JSON.parse(
+          Buffer.from(tokenURI.replace(tokenUriPrefix, ''), 'base64').toString()
+        ) as NftMetadata)
+      : ({ image: tokenURI } as NftMetadata)
 
     return nftMeta
   } catch (error) {
diff --git a/src/components/@shared/AssetList/index.tsx b/src/components/@shared/AssetList/index.tsx
index f58a43c3..7624a61a 100644
--- a/src/components/@shared/AssetList/index.tsx
+++ b/src/components/@shared/AssetList/index.tsx
@@ -69,11 +69,13 @@ export default function AssetList({
 
   const styleClasses = `${styles.assetList} ${className || ''}`
 
-  return assetsWithPrices && !loading ? (
+  return loading ? (
+    <LoaderArea />
+  ) : (
     <>
       <div className={styleClasses}>
-        {assetsWithPrices.length > 0 ? (
-          assetsWithPrices.map((assetWithPrice) => (
+        {assetsWithPrices?.length > 0 ? (
+          assetsWithPrices?.map((assetWithPrice) => (
             <AssetTeaser
               asset={assetWithPrice}
               key={assetWithPrice.id}
@@ -95,7 +97,5 @@ export default function AssetList({
         />
       )}
     </>
-  ) : (
-    <LoaderArea />
   )
 }
diff --git a/src/components/@shared/atoms/Tags/index.tsx b/src/components/@shared/atoms/Tags/index.tsx
index 1f5c7094..2922417c 100644
--- a/src/components/@shared/atoms/Tags/index.tsx
+++ b/src/components/@shared/atoms/Tags/index.tsx
@@ -30,6 +30,9 @@ export default function Tags({
   className,
   noLinks
 }: TagsProps): ReactElement {
+  // safeguard against faults in the metadata
+  if (!(items instanceof Array)) return null
+
   max = max || items.length
   const remainder = items.length - max
   // filter out empty array items, and restrict to `max`
diff --git a/src/components/Asset/AssetContent/Nft/NftTooltip.module.css b/src/components/Asset/AssetContent/Nft/NftTooltip.module.css
index 3916afc2..1b7b73c3 100644
--- a/src/components/Asset/AssetContent/Nft/NftTooltip.module.css
+++ b/src/components/Asset/AssetContent/Nft/NftTooltip.module.css
@@ -7,7 +7,7 @@
 .wrapper img {
   margin: 0;
   width: 128px;
-  height: 128px;
+  height: auto;
 }
 
 .info {
diff --git a/src/components/Asset/AssetContent/Nft/NftTooltip.tsx b/src/components/Asset/AssetContent/Nft/NftTooltip.tsx
index 3a13868b..9290bd9f 100644
--- a/src/components/Asset/AssetContent/Nft/NftTooltip.tsx
+++ b/src/components/Asset/AssetContent/Nft/NftTooltip.tsx
@@ -14,11 +14,13 @@ const openSeaTestNetworks = [4]
 
 export default function NftTooltip({
   nft,
+  nftImage,
   address,
   chainId,
   isBlockscoutExplorer
 }: {
   nft: NftMetadata
+  nftImage: string
   address: string
   chainId: number
   isBlockscoutExplorer: boolean
@@ -39,7 +41,7 @@ export default function NftTooltip({
 
   return (
     <div className={styles.wrapper}>
-      {nft && <img src={nft.image_data || nft.image} alt={nft?.name} />}
+      {nftImage && <img src={nftImage} alt={nft?.name} />}
       <div className={styles.info}>
         {nft && <h5>{nft.name}</h5>}
         {address && (
diff --git a/src/components/Asset/AssetContent/Nft/index.module.css b/src/components/Asset/AssetContent/Nft/index.module.css
index 4c6e3850..eb54b9cd 100644
--- a/src/components/Asset/AssetContent/Nft/index.module.css
+++ b/src/components/Asset/AssetContent/Nft/index.module.css
@@ -4,14 +4,19 @@
   border-right: 1px solid var(--border-color);
   width: calc(var(--spacer) * 2);
   height: calc(var(--spacer) * 2);
+  display: flex;
+  align-items: center;
 }
 
-.nftImage img,
 .nftImage > svg:first-of-type {
   width: 100%;
   height: 100%;
 }
 
+.nftImage img {
+  height: auto;
+}
+
 .nftImage > svg:first-of-type {
   transform: scale(0.7);
 }
diff --git a/src/components/Asset/AssetContent/Nft/index.tsx b/src/components/Asset/AssetContent/Nft/index.tsx
index f741c6ea..7f063bd7 100644
--- a/src/components/Asset/AssetContent/Nft/index.tsx
+++ b/src/components/Asset/AssetContent/Nft/index.tsx
@@ -48,6 +48,7 @@ export default function Nft({
           content={
             <NftTooltip
               nft={nftMetadata}
+              nftImage={nftImage}
               address={asset?.nftAddress}
               chainId={asset?.chainId}
               isBlockscoutExplorer={isBlockscoutExplorer}
diff --git a/src/components/Asset/RelatedAssets/index.tsx b/src/components/Asset/RelatedAssets/index.tsx
index f6cba742..9476e323 100644
--- a/src/components/Asset/RelatedAssets/index.tsx
+++ b/src/components/Asset/RelatedAssets/index.tsx
@@ -31,11 +31,17 @@ export default function RelatedAssets(): ReactElement {
       setIsLoading(true)
 
       try {
-        const tagQuery = generateBaseQuery(
-          generateQuery(chainIds, asset.nftAddress, 4, asset.metadata.tags)
-        )
-        const tagResults = (await queryMetadata(tagQuery, newCancelToken()))
-          ?.results
+        let tagResults: Asset[] = []
+
+        // safeguard against faults in the metadata
+        if (asset.metadata.tags instanceof Array) {
+          const tagQuery = generateBaseQuery(
+            generateQuery(chainIds, asset.nftAddress, 4, asset.metadata.tags)
+          )
+
+          tagResults = (await queryMetadata(tagQuery, newCancelToken()))
+            ?.results
+        }
 
         if (tagResults.length === 4) {
           setRelatedAssets(tagResults)


Test patch:
diff --git a/src/components/@shared/atoms/Tags/index.test.tsx b/src/components/@shared/atoms/Tags/index.test.tsx
index 5867e96a..71b25331 100644
--- a/src/components/@shared/atoms/Tags/index.test.tsx
+++ b/src/components/@shared/atoms/Tags/index.test.tsx
@@ -24,4 +24,9 @@ describe('Tags', () => {
   it('renders WithoutLinks', () => {
     render(<Tags {...argsWithoutLinks} />)
   })
+
+  it('renders with faulty tags', () => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    render(<Tags items={'tags' as any} />)
+  })
 })