From 3a313261999a6b59b51b5c52207d3abcbcdbec12 Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Thu, 30 Jun 2022 10:46:38 -0500 Subject: [PATCH] Add fallback image/card for NFTs when image was not fetched correctly or does not exist (#15034) * add fallback image/card for collectibles when image was not fetched correctly or does not exist * UI and storybook updates (#15071) * UI and storybook updates * Adding break so token id is displayed * subtle border fix * Updating content * Removing unused image * Adding proptype descriptions * Lint fix Co-authored-by: George Marshall --- app/_locales/en/messages.json | 3 + ui/components/app/app-components.scss | 1 + .../collectible-default-image.js | 41 +++++++++++++ .../collectible-default-image.stories.js | 42 +++++++++++++ .../app/collectible-default-image/index.js | 1 + .../app/collectible-default-image/index.scss | 22 +++++++ .../collectible-details.js | 8 ++- .../collectible-details.stories.js | 38 +++++++----- .../collectibles-items/collectibles-items.js | 60 +++++++++++++------ .../app/collectibles-items/index.scss | 32 +++++----- 10 files changed, 201 insertions(+), 47 deletions(-) create mode 100644 ui/components/app/collectible-default-image/collectible-default-image.js create mode 100644 ui/components/app/collectible-default-image/collectible-default-image.stories.js create mode 100644 ui/components/app/collectible-default-image/index.js create mode 100644 ui/components/app/collectible-default-image/index.scss diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 0f6747b4b..1bc9b2944 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -3865,6 +3865,9 @@ "unknownCameraErrorTitle": { "message": "Ooops! Something went wrong...." }, + "unknownCollection": { + "message": "Unnamed collection" + }, "unknownNetwork": { "message": "Unknown Private Network" }, diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 3920e5372..6fd148adc 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -14,6 +14,7 @@ @import 'collectibles-items/index'; @import 'collectibles-tab/index'; @import 'collectible-details/index'; +@import 'collectible-default-image/index'; @import 'collectible-options/index'; @import 'collectibles-detection-notice/index'; @import 'connected-accounts-list/index'; diff --git a/ui/components/app/collectible-default-image/collectible-default-image.js b/ui/components/app/collectible-default-image/collectible-default-image.js new file mode 100644 index 000000000..c301c2c2c --- /dev/null +++ b/ui/components/app/collectible-default-image/collectible-default-image.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import Typography from '../../ui/typography'; +import { TYPOGRAPHY } from '../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../hooks/useI18nContext'; + +export default function CollectibleDefaultImage({ + name, + tokenId, + handleImageClick, +}) { + const t = useI18nContext(); + return ( +
+ + {name ?? t('unknownCollection')}
#{tokenId} +
+
+ ); +} + +CollectibleDefaultImage.propTypes = { + /** + * The name of the collectible collection if not supplied will default to "Unnamed collection" + */ + name: PropTypes.string, + /** + * The token id of the collectible + */ + tokenId: PropTypes.string, + /** + * The click handler for the collectible default image + */ + handleImageClick: PropTypes.func, +}; diff --git a/ui/components/app/collectible-default-image/collectible-default-image.stories.js b/ui/components/app/collectible-default-image/collectible-default-image.stories.js new file mode 100644 index 000000000..d4b7a2a69 --- /dev/null +++ b/ui/components/app/collectible-default-image/collectible-default-image.stories.js @@ -0,0 +1,42 @@ +import React from 'react'; +import CollectibleDefaultImage from '.'; + +export default { + title: 'Components/App/CollectibleDefaultImage', + id: __filename, + argTypes: { + name: { + control: 'text', + }, + tokenId: { + control: 'text', + }, + handleImageClick: { + action: 'handleImageClick', + }, + }, + args: { + name: null, + tokenId: '12345', + handleImageClick: null, + }, +}; + +export const DefaultStory = (args) => ( +
+ +
+); + +DefaultStory.storyName = 'Default'; + +export const handleImageClick = (args) => ( +
+ +
+); + +handleImageClick.args = { + // eslint-disable-next-line no-alert + handleImageClick: () => window.alert('CollectibleDefaultImage clicked!'), +}; diff --git a/ui/components/app/collectible-default-image/index.js b/ui/components/app/collectible-default-image/index.js new file mode 100644 index 000000000..76ab58746 --- /dev/null +++ b/ui/components/app/collectible-default-image/index.js @@ -0,0 +1 @@ +export { default } from './collectible-default-image'; diff --git a/ui/components/app/collectible-default-image/index.scss b/ui/components/app/collectible-default-image/index.scss new file mode 100644 index 000000000..ff2ba60e1 --- /dev/null +++ b/ui/components/app/collectible-default-image/index.scss @@ -0,0 +1,22 @@ +.collectible-default { + background-color: var(--color-background-alternative); + padding-top: 100%; // retains 1:1 aspect ratio + position: relative; + width: 100%; + + &__text { + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + position: absolute; + white-space: nowrap; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: calc(100% - 32px); + } + + &--clickable { + cursor: pointer; + } +} diff --git a/ui/components/app/collectible-details/collectible-details.js b/ui/components/app/collectible-details/collectible-details.js index 5c8bf8e3a..53078c457 100644 --- a/ui/components/app/collectible-details/collectible-details.js +++ b/ui/components/app/collectible-details/collectible-details.js @@ -52,6 +52,7 @@ import { usePrevious } from '../../../hooks/usePrevious'; import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard'; import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils'; import { ASSET_TYPES } from '../../../../shared/constants/transaction'; +import CollectibleDefaultImage from '../collectible-default-image'; export default function CollectibleDetails({ collectible }) { const { @@ -176,7 +177,11 @@ export default function CollectibleDetails({ collectible }) { justifyContent={JUSTIFY_CONTENT.CENTER} className="collectible-details__card" > - + {image ? ( + + ) : ( + + )} {description} diff --git a/ui/components/app/collectible-details/collectible-details.stories.js b/ui/components/app/collectible-details/collectible-details.stories.js index bbd18bac0..a40e81c02 100644 --- a/ui/components/app/collectible-details/collectible-details.stories.js +++ b/ui/components/app/collectible-details/collectible-details.stories.js @@ -1,16 +1,6 @@ import React from 'react'; import CollectibleDetails from './collectible-details'; -export default { - title: 'Components/App/CollectiblesDetail', - id: __filename, - argTypes: { - collectible: { - control: 'object', - }, - }, -}; - const collectible = { name: 'Catnip Spicywright', tokenId: '1124157', @@ -20,12 +10,32 @@ const collectible = { "Good day. My name is Catnip Spicywight, which got me teased a lot in high school. If I want to put low fat mayo all over my hamburgers, I shouldn't have to answer to anyone about it, am I right? One time I beat Arlene in an arm wrestle.", }; -export const DefaultStory = () => { - return ; +export default { + title: 'Components/App/CollectiblesDetail', + id: __filename, + argTypes: { + collectible: { + control: 'object', + }, + }, + args: { + collectible, + }, +}; + +export const DefaultStory = (args) => { + return ; }; DefaultStory.storyName = 'Default'; -DefaultStory.args = { - collectible, +export const NoImage = (args) => { + return ; +}; + +NoImage.args = { + collectible: { + ...collectible, + image: undefined, + }, }; diff --git a/ui/components/app/collectibles-items/collectibles-items.js b/ui/components/app/collectibles-items/collectibles-items.js index 15e45b434..07f5d2eae 100644 --- a/ui/components/app/collectibles-items/collectibles-items.js +++ b/ui/components/app/collectibles-items/collectibles-items.js @@ -28,6 +28,8 @@ import { getAssetImageURL } from '../../../helpers/utils/util'; import { updateCollectibleDropDownState } from '../../../store/actions'; import { usePrevious } from '../../../hooks/usePrevious'; import { getCollectiblesDropdownState } from '../../../ducks/metamask/metamask'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import CollectibleDefaultImage from '../collectible-default-image'; const width = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP @@ -46,6 +48,7 @@ export default function CollectiblesItems({ const previousCollectionKeys = usePrevious(collectionsKeys); const selectedAddress = useSelector(getSelectedAddress); const chainId = useSelector(getCurrentChainId); + const t = useI18nContext(); useEffect(() => { if ( @@ -101,7 +104,7 @@ export default function CollectiblesItems({ } return (
- {collectionName[0]} + {collectionName?.[0]?.toUpperCase() ?? null}
); }; @@ -164,7 +167,9 @@ export default function CollectiblesItems({ variant={TYPOGRAPHY.H5} margin={[0, 0, 0, 2]} > - {`${collectionName} (${collectibles.length})`} + {`${collectionName ?? t('unknownCollection')} (${ + collectibles.length + })`}
@@ -180,29 +185,48 @@ export default function CollectiblesItems({ {isExpanded ? ( {collectibles.map((collectible, i) => { - const { image, address, tokenId, backgroundColor } = collectible; + const { + image, + address, + tokenId, + backgroundColor, + name, + } = collectible; const collectibleImage = getAssetImageURL(image, ipfsGateway); + const handleImageClick = () => + history.push(`${ASSET_ROUTE}/${address}/${tokenId}`); + return ( - -
- - history.push(`${ASSET_ROUTE}/${address}/${tokenId}`) - } - className="collectibles-items__collection-item-image" - src={collectibleImage} + + {collectibleImage ? ( +
+ +
+ ) : ( + -
+ )}
); diff --git a/ui/components/app/collectibles-items/index.scss b/ui/components/app/collectibles-items/index.scss index 7087ac481..545528fde 100644 --- a/ui/components/app/collectibles-items/index.scss +++ b/ui/components/app/collectibles-items/index.scss @@ -27,29 +27,33 @@ color: var(--color-overlay-inverse); text-align: center; } + } - &-item-wrapper { - align-self: center; + &__item-wrapper { + align-self: center; + + &__card { + overflow: hidden; } + } - &-item { - border-radius: 4px; - width: 100%; - display: flex; - justify-content: center; - cursor: pointer; - align-self: center; - } + &__item { + border-radius: 4px; + width: 100%; + display: flex; + justify-content: center; + cursor: pointer; + align-self: center; - &-item-image { + &-image { border-radius: 4px; width: 100%; height: 100%; cursor: pointer; } + } - &__icon-chevron { - color: var(--color-icon-default); - } + &__icon-chevron { + color: var(--color-icon-default); } }