From 171587af9b1d1e20033df295218ea255b4988ca1 Mon Sep 17 00:00:00 2001 From: William Muli Date: Wed, 13 Apr 2022 19:04:46 +0300 Subject: [PATCH 01/39] Add NFT details screen --- browser/ui/BUILD.gn | 3 + browser/ui/webui/brave_wallet/nft/nft_ui.cc | 52 ++++++ browser/ui/webui/brave_wallet/nft/nft_ui.h | 37 +++++ .../ui/webui/brave_wallet/wallet_page_ui.cc | 3 + .../webui/chrome_untrusted_web_ui_configs.cc | 3 + components/brave_wallet_ui/BUILD.gn | 2 + .../brave_wallet_ui/common/async/handlers.ts | 11 +- .../brave_wallet_ui/common/hooks/assets.ts | 20 ++- .../edit-visible-assets-modal/index.tsx | 2 +- .../desktop/portfolio-asset-item/index.tsx | 11 +- .../desktop/portfolio-asset-item/style.ts | 10 +- .../components/nft-details/index.tsx | 155 ++++++++++-------- .../portfolio/components/nft-details/style.ts | 46 +++++- .../views/portfolio/portfolio-asset.tsx | 9 +- .../shared/create-network-icon/index.tsx | 4 +- .../shared/create-placeholder-icon/index.tsx | 9 +- .../components/shared/style.tsx | 5 + components/brave_wallet_ui/constants/types.ts | 6 +- components/brave_wallet_ui/nft/BUILD.gn | 25 +++ components/brave_wallet_ui/nft/nft.html | 20 +++ components/brave_wallet_ui/nft/nft.ts | 34 ++++ .../page/actions/wallet_page_actions.ts | 4 + .../page/async/wallet_page_async_handler.ts | 40 +++++ .../page/reducers/page_reducer.ts | 20 ++- .../panel/async/wallet_panel_async_handler.ts | 4 +- .../stories/mock-data/mock-nft-metadata.ts | 1 - .../stories/mock-data/mock-page-state.ts | 3 + .../utils/string-utils.test.ts | 12 +- .../brave_wallet_ui/utils/string-utils.ts | 18 +- components/constants/webui_url_constants.cc | 2 + components/constants/webui_url_constants.h | 2 + .../resources/brave_components_resources.grd | 1 + resources/resource_ids.spec | 5 + 33 files changed, 466 insertions(+), 113 deletions(-) create mode 100644 browser/ui/webui/brave_wallet/nft/nft_ui.cc create mode 100644 browser/ui/webui/brave_wallet/nft/nft_ui.h create mode 100644 components/brave_wallet_ui/nft/BUILD.gn create mode 100644 components/brave_wallet_ui/nft/nft.html create mode 100644 components/brave_wallet_ui/nft/nft.ts diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index f5890619481a..70959e8babbf 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -628,6 +628,8 @@ source_set("ui") { "wallet_bubble_manager_delegate_impl.h", "webui/brave_wallet/ledger/ledger_ui.cc", "webui/brave_wallet/ledger/ledger_ui.h", + "webui/brave_wallet/nft/nft_ui.cc", + "webui/brave_wallet/nft/nft_ui.h", "webui/brave_wallet/page_handler/wallet_page_handler.cc", "webui/brave_wallet/page_handler/wallet_page_handler.h", "webui/brave_wallet/trezor/trezor_ui.cc", @@ -648,6 +650,7 @@ source_set("ui") { "//brave/components/brave_wallet/common:mojom", "//brave/components/brave_wallet_ui:resources", "//brave/components/brave_wallet_ui/ledger:ledger_bridge_generated", + "//brave/components/brave_wallet_ui/nft:nft_display_generated", "//brave/components/brave_wallet_ui/page:brave_wallet_page_generated", "//brave/components/brave_wallet_ui/panel:brave_wallet_panel_generated", "//brave/components/brave_wallet_ui/trezor:trezor_bridge_generated", diff --git a/browser/ui/webui/brave_wallet/nft/nft_ui.cc b/browser/ui/webui/brave_wallet/nft/nft_ui.cc new file mode 100644 index 000000000000..45342b8542c9 --- /dev/null +++ b/browser/ui/webui/brave_wallet/nft/nft_ui.cc @@ -0,0 +1,52 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/webui/brave_wallet/nft/nft_ui.h" + +#include + +#include "brave/components/constants/webui_url_constants.h" +#include "brave/components/nft_display/resources/grit/nft_display_generated_map.h" +#include "components/grit/brave_components_resources.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui_data_source.h" +#include "ui/resources/grit/webui_generated_resources.h" + +namespace nft { + +UntrustedNftUI::UntrustedNftUI(content::WebUI* web_ui) + : ui::UntrustedWebUIController(web_ui) { + auto* untrusted_source = content::WebUIDataSource::Create(kUntrustedNftURL); + untrusted_source->SetDefaultResource(IDR_BRAVE_WALLET_NFT_DISPLAY_HTML); + untrusted_source->AddResourcePaths( + base::make_span(kNftDisplayGenerated, kNftDisplayGeneratedSize)); + untrusted_source->AddFrameAncestor(GURL(kBraveUIWalletPageURL)); + untrusted_source->AddFrameAncestor(GURL(kBraveUIWalletPanelURL)); + + untrusted_source->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::StyleSrc, + std::string("style-src 'self' 'unsafe-inline';")); + untrusted_source->AddResourcePath("load_time_data.js", + IDR_WEBUI_JS_LOAD_TIME_DATA_JS); + untrusted_source->UseStringsJs(); + untrusted_source->AddString("braveWalletNftBridgeUrl", kUntrustedNftURL); + untrusted_source->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::ImgSrc, + std::string("img-src 'self' https: data:;")); + auto* browser_context = web_ui->GetWebContents()->GetBrowserContext(); + content::WebUIDataSource::Add(browser_context, untrusted_source); +} + +UntrustedNftUI::~UntrustedNftUI() = default; + +std::unique_ptr +UntrustedNftUIConfig::CreateWebUIController(content::WebUI* web_ui) { + return std::make_unique(web_ui); +} + +UntrustedNftUIConfig::UntrustedNftUIConfig() + : WebUIConfig(content::kChromeUIUntrustedScheme, kUntrustedNftHost) {} + +} // namespace nft diff --git a/browser/ui/webui/brave_wallet/nft/nft_ui.h b/browser/ui/webui/brave_wallet/nft/nft_ui.h new file mode 100644 index 000000000000..21777843cebd --- /dev/null +++ b/browser/ui/webui/brave_wallet/nft/nft_ui.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_WEBUI_BRAVE_WALLET_NFT_NFT_UI_H_ +#define BRAVE_BROWSER_UI_WEBUI_BRAVE_WALLET_NFT_NFT_UI_H_ + +#include + +#include "content/public/browser/web_ui.h" +#include "content/public/browser/webui_config.h" +#include "content/public/common/url_constants.h" +#include "ui/webui/untrusted_web_ui_controller.h" + +namespace nft { + +class UntrustedNftUI : public ui::UntrustedWebUIController { + public: + explicit UntrustedNftUI(content::WebUI* web_ui); + UntrustedNftUI(const UntrustedNftUI&) = delete; + UntrustedNftUI& operator=(const UntrustedNftUI&) = delete; + ~UntrustedNftUI() override; +}; + +class UntrustedNftUIConfig : public content::WebUIConfig { + public: + UntrustedNftUIConfig(); + ~UntrustedNftUIConfig() override = default; + + std::unique_ptr CreateWebUIController( + content::WebUI* web_ui) override; +}; + +} // namespace nft + +#endif // BRAVE_BROWSER_UI_WEBUI_BRAVE_WALLET_NFT_NFT_UI_H_ diff --git a/browser/ui/webui/brave_wallet/wallet_page_ui.cc b/browser/ui/webui/brave_wallet/wallet_page_ui.cc index 751dddb71e0e..5e789aae3368 100644 --- a/browser/ui/webui/brave_wallet/wallet_page_ui.cc +++ b/browser/ui/webui/brave_wallet/wallet_page_ui.cc @@ -61,6 +61,9 @@ WalletPageUI::WalletPageUI(content::WebUI* web_ui) network::mojom::CSPDirectiveName::FrameSrc, std::string("frame-src ") + kUntrustedTrezorURL + " " + kUntrustedLedgerURL + ";"); + source->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::FrameSrc, + std::string("frame-src ") + kUntrustedNftURL + ";"); source->AddString("braveWalletTrezorBridgeUrl", kUntrustedTrezorURL); auto* profile = Profile::FromWebUI(web_ui); content::WebUIDataSource::Add(profile, source); diff --git a/chromium_src/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.cc b/chromium_src/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.cc index 8926979703e9..47bb94dc3823 100644 --- a/chromium_src/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.cc +++ b/chromium_src/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.cc @@ -6,6 +6,7 @@ #include "chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.h" #include "brave/browser/ui/webui/brave_wallet/ledger/ledger_ui.h" +#include "brave/browser/ui/webui/brave_wallet/nft/nft_ui.h" #include "brave/browser/ui/webui/brave_wallet/trezor/trezor_ui.h" #include "brave/components/brave_vpn/buildflags/buildflags.h" #include "build/build_config.h" @@ -30,6 +31,8 @@ void RegisterChromeUntrustedWebUIConfigs() { std::make_unique()); content::WebUIConfigMap::GetInstance().AddUntrustedWebUIConfig( std::make_unique()); + content::WebUIConfigMap::GetInstance().AddUntrustedWebUIConfig( + std::make_unique()); #if BUILDFLAG(ENABLE_BRAVE_VPN) if (brave_vpn::IsBraveVPNEnabled()) { content::WebUIConfigMap::GetInstance().AddUntrustedWebUIConfig( diff --git a/components/brave_wallet_ui/BUILD.gn b/components/brave_wallet_ui/BUILD.gn index 78b683284389..d9410c78d315 100644 --- a/components/brave_wallet_ui/BUILD.gn +++ b/components/brave_wallet_ui/BUILD.gn @@ -11,6 +11,7 @@ import("//tools/grit/repack.gni") repack("resources") { deps = [ "//brave/components/brave_wallet_ui/ledger:ledger_bridge_generated", + "//brave/components/brave_wallet_ui/nft:nft_display_generated", "//brave/components/brave_wallet_ui/page:brave_wallet_page_generated", "//brave/components/brave_wallet_ui/panel:brave_wallet_panel_generated", "//brave/components/brave_wallet_ui/trezor:trezor_bridge_generated", @@ -19,6 +20,7 @@ repack("resources") { "$root_gen_dir/brave/components/brave_wallet_page/resources/brave_wallet_page_generated.pak", "$root_gen_dir/brave/components/brave_wallet_panel/resources/brave_wallet_panel_generated.pak", "$root_gen_dir/brave/components/ledger_bridge/resources/ledger_bridge_generated.pak", + "$root_gen_dir/brave/components/nft_display/resources/nft_display_generated.pak", "$root_gen_dir/brave/components/trezor_bridge/resources/trezor_bridge_generated.pak", ] output = diff --git a/components/brave_wallet_ui/common/async/handlers.ts b/components/brave_wallet_ui/common/async/handlers.ts index 34e7666577d5..3573e9648f85 100644 --- a/components/brave_wallet_ui/common/async/handlers.ts +++ b/components/brave_wallet_ui/common/async/handlers.ts @@ -302,7 +302,16 @@ handler.on(WalletActions.getAllTokensList.getType(), async (store) => { }) handler.on(WalletActions.addUserAsset.getType(), async (store: Store, payload: BraveWallet.BlockchainToken) => { - const { braveWalletService } = getAPIProxy() + const { braveWalletService, jsonRpcService } = getAPIProxy() + if (payload.isErc721) { + // Get NFTMetadata + const result = await jsonRpcService.getERC721Metadata(payload.contractAddress, payload.tokenId, payload.chainId) + if (!result.error) { + const response = JSON.parse(result.response) + payload.logo = response.image || payload.logo + } + } + const result = await braveWalletService.addUserAsset(payload) store.dispatch(WalletActions.addUserAssetError(!result.success)) }) diff --git a/components/brave_wallet_ui/common/hooks/assets.ts b/components/brave_wallet_ui/common/hooks/assets.ts index de1bbf70a50a..974dab1f81c2 100644 --- a/components/brave_wallet_ui/common/hooks/assets.ts +++ b/components/brave_wallet_ui/common/hooks/assets.ts @@ -18,12 +18,24 @@ import usePricing from './pricing' import useBalance from './balance' import { useIsMounted } from './useIsMounted' import { useLib } from './useLib' +import { httpifyIpfsUrl } from '../../utils/string-utils' const assetsLogo = (assets: BraveWallet.BlockchainToken[]) => { - return assets.map(token => ({ - ...token, - logo: `chrome://erc-token-images/${token.logo}` - }) as BraveWallet.BlockchainToken) + return assets.map(token => { + let logo = token.logo + if (token.logo?.startsWith('ipfs://')) { + logo = httpifyIpfsUrl(token.logo) + } else if (token.logo?.startsWith('data:image/')) { + logo = token.logo + } else { + logo = `chrome://erc-token-images/${token.logo}` + } + + return { + ...token, + logo + } as BraveWallet.BlockchainToken + }) } export function useAssets () { diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx index e04a443b3539..55c0c717697e 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx @@ -570,7 +570,7 @@ const EditVisibleAssetsModal = ({ onClose }: Props) => { <> {filteredTokenList.slice(0, tokenDisplayAmount).map((token) => { const [assetNetworkSkeletonWidth, setAssetNetworkSkeletonWidth] = React.useState(0) const AssetIconWithPlaceholder = React.useMemo(() => { - return withPlaceholderIcon(AssetIcon, { size: 'big', marginLeft: 0, marginRight: 8 }) + return withPlaceholderIcon(token.isErc721 ? NFTAssetIcon : AssetIcon, { size: 'big', marginLeft: 0, marginRight: 8 }) }, []) const formattedAssetBalance = token.isErc721 @@ -78,8 +79,8 @@ const PortfolioAssetItem = (props: Props) => { }, [fiatBalance]) const isLoading = React.useMemo(() => { - return formattedAssetBalance === '' - }, [formattedAssetBalance]) + return formattedAssetBalance === '' && !token.isErc721 + }, [formattedAssetBalance, token]) const tokensNetwork = React.useMemo(() => { return getTokensNetwork(networks, token) @@ -111,7 +112,7 @@ const PortfolioAssetItem = (props: Props) => { {token.visible && // Selecting an erc721 token is temp disabled until UI is ready for viewing NFTs // or when showing loading skeleton - + {!token.logo diff --git a/components/brave_wallet_ui/components/desktop/portfolio-asset-item/style.ts b/components/brave_wallet_ui/components/desktop/portfolio-asset-item/style.ts index 5ed823510cfe..69ab75d1a91b 100644 --- a/components/brave_wallet_ui/components/desktop/portfolio-asset-item/style.ts +++ b/components/brave_wallet_ui/components/desktop/portfolio-asset-item/style.ts @@ -1,5 +1,5 @@ import styled from 'styled-components' -import { AssetIconProps, AssetIconFactory, WalletButton } from '../../shared/style' +import { AssetIconProps, AssetIconFactory, WalletButton, AssetIconIframe } from '../../shared/style' interface StyleProps { disabled: boolean @@ -60,9 +60,15 @@ export const AssetBalanceText = styled.span` // support with custom AssetIconFactory. // // Ref: https://styled-components.com/docs/advanced#style-objects -export const AssetIcon = AssetIconFactory({ +const assetIconProps = { width: '40px', height: 'auto' +} +export const AssetIcon = AssetIconFactory(assetIconProps) +export const NFTAssetIcon = AssetIconIframe({ + ...assetIconProps, + height: '40px', + border: 'transparent' }) export const IconsWrapper = styled.div` diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx index 46e10339424e..df38a9861f26 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx @@ -12,17 +12,15 @@ import { useExplorer } from '../../../../../../common/hooks' // Utils import Amount from '../../../../../../utils/amount' -import { CurrencySymbols } from '../../../../../../utils/currency-symbols' -import { getLocale } from '../../../../../../../common/locale' +import { getLocale } from '$web-common/locale' // Styled Components import { StyledWrapper, - NFTImage, + LoadIcon, + LoadingOverlay, DetailColumn, TokenName, - TokenFiatValue, - TokenCryptoValue, DetailSectionRow, DetailSectionColumn, DetailSectionTitle, @@ -30,7 +28,6 @@ import { ProjectDetailRow, ProjectDetailName, ProjectDetailDescription, - ProjectDetailImage, ProjectDetailButtonRow, ProjectDetailButton, ProjectDetailButtonSeperator, @@ -39,28 +36,32 @@ import { ProjectFacebookIcon, ProjectDetailIDRow, ExplorerIcon, - ExplorerButton + ExplorerButton, + NTFImageIframe, + nftImageDimension, + NftImageWrapper } from './style' export interface Props { + isLoading: boolean selectedAsset: BraveWallet.BlockchainToken - nftMetadata: NFTMetadataReturnType + nftMetadata: NFTMetadataReturnType | undefined defaultCurrencies: DefaultCurrencies - selectedNetwork: BraveWallet.EthereumChain + selectedNetwork: BraveWallet.NetworkInfo } const NFTDetails = (props: Props) => { const { + isLoading, selectedAsset, nftMetadata, - defaultCurrencies, selectedNetwork } = props const onClickViewOnBlockExplorer = useExplorer(selectedNetwork) const onClickWebsite = () => { - chrome.tabs.create({ url: nftMetadata.contractInformation.website }, () => { + chrome.tabs.create({ url: nftMetadata?.contractInformation?.website }, () => { if (chrome.runtime.lastError) { console.error('tabs.create failed: ' + chrome.runtime.lastError.message) } @@ -68,7 +69,7 @@ const NFTDetails = (props: Props) => { } const onClickTwitter = () => { - chrome.tabs.create({ url: nftMetadata.contractInformation.twitter }, () => { + chrome.tabs.create({ url: nftMetadata?.contractInformation?.twitter }, () => { if (chrome.runtime.lastError) { console.error('tabs.create failed: ' + chrome.runtime.lastError.message) } @@ -76,7 +77,7 @@ const NFTDetails = (props: Props) => { } const onClickFacebook = () => { - chrome.tabs.create({ url: nftMetadata.contractInformation.facebook }, () => { + chrome.tabs.create({ url: nftMetadata?.contractInformation?.facebook }, () => { if (chrome.runtime.lastError) { console.error('tabs.create failed: ' + chrome.runtime.lastError.message) } @@ -85,61 +86,79 @@ const NFTDetails = (props: Props) => { return ( - - - - {selectedAsset.name} { - selectedAsset.tokenId - ? '#' + new Amount(selectedAsset.tokenId).toNumber() - : '' - } - - {CurrencySymbols[defaultCurrencies.fiat]}{nftMetadata.floorFiatPrice} - {nftMetadata.floorCryptoPrice} {selectedNetwork.symbol} - - - {getLocale('braveWalletNFTDetailBlockchain')} - {nftMetadata.chainName} - - - {getLocale('braveWalletNFTDetailTokenStandard')} - {nftMetadata.tokenType} - - - {getLocale('braveWalletNFTDetailTokenID')} - - - { - selectedAsset.tokenId - ? '#' + new Amount(selectedAsset.tokenId).toNumber() - : '' - } - - - - - - - - - - {nftMetadata.contractInformation.name} - - - - - - - - - - - - - - - {nftMetadata.contractInformation.description} - + {isLoading && + + + + } + {nftMetadata && + <> + + + + + + {selectedAsset.name} { + selectedAsset.tokenId + ? '#' + new Amount(selectedAsset.tokenId).toNumber() + : '' + } + + {/* TODO: Add floorFiatPrice & floorCryptoPrice when data is available from backend: https://github.com/brave/brave-browser/issues/22627 */} + {/* {CurrencySymbols[defaultCurrencies.fiat]}{nftMetadata.floorFiatPrice} */} + {/* {nftMetadata.floorCryptoPrice} {selectedNetwork.symbol} */} + + + {getLocale('braveWalletNFTDetailBlockchain')} + {nftMetadata.chainName} + + + {getLocale('braveWalletNFTDetailTokenStandard')} + {nftMetadata.tokenType} + + + {getLocale('braveWalletNFTDetailTokenID')} + + + { + selectedAsset.tokenId + ? '#' + new Amount(selectedAsset.tokenId).toNumber() + : '' + } + + + + + + + + + {/* TODO: Add nft logo when data is available from backend: https://github.com/brave/brave-browser/issues/22627 */} + {/* */} + {nftMetadata.contractInformation.name} + {nftMetadata.contractInformation.website && nftMetadata.contractInformation.twitter && nftMetadata.contractInformation.facebook && + + + + + + + + + + + + + + } + + {nftMetadata.contractInformation.description} + + + } ) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts index 78b7e922c778..8f1bda7fb708 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts @@ -3,7 +3,13 @@ import { WalletButton } from '../../../../../shared/style' import WebsiteIcon from '../../../../../../assets/svg-icons/website-icon.svg' import TwitterIcon from '../../../../../../assets/svg-icons/twitter-icon.svg' import FacebookIcon from '../../../../../../assets/svg-icons/facebook-icon.svg' -import { OpenNewIcon } from 'brave-ui/components/icons' +import { LoaderIcon, OpenNewIcon } from 'brave-ui/components/icons' + +export interface StyleProps { + isLoading: boolean +} + +export const nftImageDimension = '440px' export const StyledWrapper = styled.div` display: flex; @@ -11,12 +17,23 @@ export const StyledWrapper = styled.div` align-items: flex-start; justify-content: flex-start; width: 100%; + min-height: ${nftImageDimension}; + margin: 16px 0 50px 0; ` -export const NFTImage = styled.img` - width: 440px; - height: 440px; - margin-right: 10px; +export const NftImageWrapper = styled.div` + display: flex; + width: ${nftImageDimension}; + height: ${nftImageDimension}; +` + +export const NTFImageIframe = styled.iframe` + display: block; + width: ${nftImageDimension}; + height: ${nftImageDimension}; + margin-right: 28px; + border-radius: 12px; + border: transparent; ` export const DetailColumn = styled.div` @@ -205,3 +222,22 @@ export const ExplorerIcon = styled(OpenNewIcon)` height: 14px; color: ${(p) => p.theme.color.interactive05}; ` + +export const LoadingOverlay = styled.div>` + display: ${(p) => p.isLoading ? 'flex' : 'none'}; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 440px; + position: absolute; + z-index: 10; + backdrop-filter: blur(5px); +` + +export const LoadIcon = styled(LoaderIcon)` + color: ${p => p.theme.color.interactive08}; + height: 70px; + width: 70px; + opacity: .4; +` diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index 65831ffac8a7..034a0285ee3d 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -63,6 +63,7 @@ import { NetworkDescription } from './style' import { Skeleton } from '../../../shared/loading-skeleton/styles' +import NFTDetails from './components/nft-details' const AssetIconWithPlaceholder = withPlaceholderIcon(AssetIcon, { size: 'big', marginLeft: 0, marginRight: 12 }) @@ -93,7 +94,9 @@ export const PortfolioAsset = () => { selectedAssetCryptoPrice, selectedAssetFiatPrice, selectedAssetPriceHistory, - selectedTimeline + selectedTimeline, + isFetchingNFTMetadata, + nftMetadata } = useSelector(({ page }: { page: PageState }) => page) // custom hooks @@ -377,7 +380,7 @@ export const PortfolioAsset = () => { /> } - {/* {selectedAsset?.isErc721 && + {selectedAsset?.isErc721 && { defaultCurrencies={defaultCurrencies} selectedNetwork={selectedNetwork} /> - } */} + } , config ) const tokenImageURL = stripERC20TokenImageURL(asset.logo) - const isRemoteURL = isRemoteImageURL(tokenImageURL) + const isRemoteURL = isFromDifferentOrigin(tokenImageURL) const isDataURL = asset.logo.startsWith('chrome://erc-token-images/') const isStorybook = asset.logo.startsWith('static/media/components/brave_wallet_ui/') const isValidIcon = React.useMemo(() => { if (isRemoteURL || isDataURL) { - const url = new URL(asset.logo) - return isValidIconExtension(url.pathname) + return tokenImageURL?.includes('data:image/') ? true : isValidIconExtension(new URL(asset.logo).pathname) } if (isStorybook) { return true @@ -76,7 +75,7 @@ function withPlaceholderIcon (WrappedComponent: React.ComponentType, config const remoteImage = React.useMemo(() => { if (isRemoteURL) { - return `chrome://image?${tokenImageURL}` + return httpifyIpfsUrl(tokenImageURL) } return '' }, [tokenImageURL]) diff --git a/components/brave_wallet_ui/components/shared/style.tsx b/components/brave_wallet_ui/components/shared/style.tsx index 349c0cb990ce..f9ed2d58c1c3 100644 --- a/components/brave_wallet_ui/components/shared/style.tsx +++ b/components/brave_wallet_ui/components/shared/style.tsx @@ -56,6 +56,11 @@ export const AssetIconFactory = styled.img.attrs(props => ({ loading: 'lazy' })) +export const AssetIconIframe = styled.iframe.attrs(props => ({ + src: `chrome-untrusted://nft-display?imageUrl=${encodeURIComponent(props.icon || '')}`, + sandbox: 'allow-scripts' +})) + export const WalletButton = styled.button<{ isDraggedOver?: boolean }>` diff --git a/components/brave_wallet_ui/constants/types.ts b/components/brave_wallet_ui/constants/types.ts index e828f52fe403..eae33667b7ba 100644 --- a/components/brave_wallet_ui/constants/types.ts +++ b/components/brave_wallet_ui/constants/types.ts @@ -265,6 +265,8 @@ export interface PageState { invalidMnemonic: boolean selectedTimeline: BraveWallet.AssetPriceTimeframe selectedAsset: BraveWallet.BlockchainToken | undefined + isFetchingNFTMetadata: boolean + nftMetadata: NFTMetadataReturnType | undefined selectedAssetFiatPrice: BraveWallet.AssetPrice | undefined selectedAssetCryptoPrice: BraveWallet.AssetPrice | undefined selectedAssetPriceHistory: GetPriceHistoryReturnInfo[] @@ -630,11 +632,7 @@ export interface CreateAccountOptionsType { icon: string } -// This is mostly speculative -// will likely change once we have an api for getting -// nft metadata export interface NFTMetadataReturnType { - chain: string chainName: string tokenType: string tokenID: string diff --git a/components/brave_wallet_ui/nft/BUILD.gn b/components/brave_wallet_ui/nft/BUILD.gn new file mode 100644 index 000000000000..2070fde29f71 --- /dev/null +++ b/components/brave_wallet_ui/nft/BUILD.gn @@ -0,0 +1,25 @@ +# Copyright (c) 2021 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# you can obtain one at http://mozilla.org/MPL/2.0/. + +import("//brave/components/common/typescript.gni") +import("//chrome/common/features.gni") +import("//mojo/public/tools/bindings/mojom.gni") +import("//tools/grit/preprocess_if_expr.gni") +import("//tools/grit/repack.gni") + +transpile_web_ui("nft_display_ui") { + entry_points = [ [ + "nft", + rebase_path("nft.ts"), + ] ] + webpack_aliases = [ "browser" ] + resource_name = "nft_display" +} + +pack_web_resources("nft_display_generated") { + resource_name = "nft_display" + output_dir = "$root_gen_dir/brave/components/nft_display/resources" + deps = [ ":nft_display_ui" ] +} diff --git a/components/brave_wallet_ui/nft/nft.html b/components/brave_wallet_ui/nft/nft.html new file mode 100644 index 000000000000..823f23b53cf8 --- /dev/null +++ b/components/brave_wallet_ui/nft/nft.html @@ -0,0 +1,20 @@ + + + + + + NFT iframe + + + + + + diff --git a/components/brave_wallet_ui/nft/nft.ts b/components/brave_wallet_ui/nft/nft.ts new file mode 100644 index 000000000000..2434821db45c --- /dev/null +++ b/components/brave_wallet_ui/nft/nft.ts @@ -0,0 +1,34 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +const params = new Proxy(new URLSearchParams(window.location.search), { + get: (searchParams, prop) => { + if (typeof prop === 'string') { + return searchParams.get(prop) + } + return null + } +}) +/* eslint-disable @typescript-eslint/dot-notation */ +const imageUrl = params['imageUrl'] +const imageWidth = params['imageWidth'] +const imageHeight = params['imageHeight'] +console.log({ imageWidth, imageHeight }) +/* eslint-enable @typescript-eslint/dot-notation */ +const imageEl = document.getElementById('image') + +if (imageEl) { + if (imageUrl) { + imageEl.src = imageUrl + } + + if (imageWidth) { + imageEl.style.width = imageWidth + } + + if (imageHeight) { + imageEl.style.height = imageHeight + } +} diff --git a/components/brave_wallet_ui/page/actions/wallet_page_actions.ts b/components/brave_wallet_ui/page/actions/wallet_page_actions.ts index 1b1d49d25e41..fb976c409647 100644 --- a/components/brave_wallet_ui/page/actions/wallet_page_actions.ts +++ b/components/brave_wallet_ui/page/actions/wallet_page_actions.ts @@ -23,6 +23,7 @@ import { } from '../constants/action_types' import { BraveWallet, + NFTMetadataReturnType, UpdateAccountNamePayloadType } from '../../constants/types' @@ -58,4 +59,7 @@ export const setMetaMaskInitialized = createAction('setMetaMaskInitiali export const importFromCryptoWallets = createAction('importFromCryptoWallets') export const importFromMetaMask = createAction('importFromMetaMask') export const openWalletSettings = createAction('openWalletSettings') +export const getNFTMetadata = createAction('getNFTMetadata') +export const setIsFetchingNFTMetadata = createAction('setIsFetchingNFTMetadata') +export const updateNFTMetadata = createAction('updateNFTMetadata') export const onOnboardingShown = createAction('onOnboardingShown') diff --git a/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts b/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts index ae7f30f1f72b..ec47c1053ee3 100644 --- a/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts +++ b/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts @@ -9,6 +9,7 @@ import * as WalletPageActions from '../actions/wallet_page_actions' import * as WalletActions from '../../common/actions/wallet_actions' import { BraveWallet, + NFTMetadataReturnType, UpdateAccountNamePayloadType, WalletState } from '../../constants/types' @@ -31,6 +32,9 @@ import { import { NewUnapprovedTxAdded } from '../../common/constants/action_types' import { Store } from '../../common/async/types' import { getTokenParam } from '../../utils/api-utils' +import getAPIProxy from '../../common/async/bridge' +import { getTokensNetwork } from '../../utils/network-utils' +import { httpifyIpfsUrl } from '../../utils/string-utils' const handler = new AsyncActionHandler() @@ -86,6 +90,10 @@ handler.on(WalletPageActions.selectAsset.getType(), async (store: Store, payload const defaultPrices = await assetRatioService.getPrice([getTokenParam(selectedAsset)], [defaultFiat, defaultCrypto], payload.timeFrame) const priceHistory = await assetRatioService.getPriceHistory(getTokenParam(selectedAsset), defaultFiat, payload.timeFrame) store.dispatch(WalletPageActions.updatePriceInfo({ priceHistory: priceHistory, defaultFiatPrice: defaultPrices.values[0], defaultCryptoPrice: defaultPrices.values[1], timeFrame: payload.timeFrame })) + + if (payload.asset.isErc721) { + store.dispatch(WalletPageActions.getNFTMetadata(payload.asset)) + } } else { store.dispatch(WalletPageActions.updatePriceInfo({ priceHistory: undefined, defaultFiatPrice: undefined, defaultCryptoPrice: undefined, timeFrame: payload.timeFrame })) } @@ -218,6 +226,38 @@ handler.on(WalletPageActions.openWalletSettings.getType(), async (store) => { }) }) +handler.on(WalletPageActions.getNFTMetadata.getType(), async (store, payload: BraveWallet.BlockchainToken) => { + store.dispatch(WalletPageActions.setIsFetchingNFTMetadata(true)) + const { jsonRpcService } = getAPIProxy() + const result = await jsonRpcService.getERC721Metadata(payload.contractAddress, payload.tokenId, payload.chainId) + + if (!result.error) { + const response = JSON.parse(result.response) + const tokenNetwork = getTokensNetwork(getWalletState(store).networkList, payload) + const nftMetadata: NFTMetadataReturnType = { + chainName: tokenNetwork.chainName, + tokenType: 'ERC721', // getNFTMetadata currently supports only ERC721 standard. When other standards are supported, this value should be dynamic + tokenID: payload.tokenId, + imageURL: response.image.startsWith('data:image/') ? response.image : httpifyIpfsUrl(response.image), + floorFiatPrice: '', + floorCryptoPrice: '', + contractInformation: { + address: payload.contractAddress, + name: response.name, + description: response.description, + website: '', + twitter: '', + facebook: '', + logo: '' + } + } + store.dispatch(WalletPageActions.updateNFTMetadata(nftMetadata)) + } else { + console.error(result.errorMessage) + } + store.dispatch(WalletPageActions.setIsFetchingNFTMetadata(false)) +}) + handler.on(WalletPageActions.onOnboardingShown.getType(), async (store: Store) => { const braveWalletService = getWalletPageApiProxy().braveWalletService await braveWalletService.onOnboardingShown() diff --git a/components/brave_wallet_ui/page/reducers/page_reducer.ts b/components/brave_wallet_ui/page/reducers/page_reducer.ts index 78862d2538d6..2d93cd4a8807 100644 --- a/components/brave_wallet_ui/page/reducers/page_reducer.ts +++ b/components/brave_wallet_ui/page/reducers/page_reducer.ts @@ -8,7 +8,8 @@ import * as Actions from '../actions/wallet_page_actions' import { BraveWallet, PageState, - ImportWalletError + ImportWalletError, + NFTMetadataReturnType } from '../../constants/types' import { WalletCreatedPayloadType, @@ -26,6 +27,8 @@ const defaultState: PageState = { importWalletError: { hasError: false }, selectedTimeline: BraveWallet.AssetPriceTimeframe.OneDay, selectedAsset: undefined, + isFetchingNFTMetadata: true, + nftMetadata: undefined, selectedAssetFiatPrice: undefined, selectedAssetCryptoPrice: undefined, selectedAssetPriceHistory: [], @@ -166,6 +169,21 @@ export const createPageReducer = (initialState: PageState) => { isMetaMaskInitialized: payload } }) + + reducer.on(Actions.updateNFTMetadata, (state: PageState, payload: NFTMetadataReturnType) => { + return { + ...state, + nftMetadata: payload + } + }) + + reducer.on(Actions.setIsFetchingNFTMetadata, (state: PageState, payload: boolean) => { + return { + ...state, + isFetchingNFTMetadata: payload + } + }) + return reducer } diff --git a/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts b/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts index 1691755d8288..6fe300782c63 100644 --- a/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts +++ b/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts @@ -47,7 +47,7 @@ import { import { Store } from '../../common/async/types' import { getLocale } from '../../../common/locale' import getWalletPanelApiProxy from '../wallet_panel_api_proxy' -import { isRemoteImageURL } from '../../utils/string-utils' +import { isFromDifferentOrigin } from '../../utils/string-utils' import { HardwareVendor } from 'components/brave_wallet_ui/common/api/hardware_keyrings' const handler = new AsyncActionHandler() @@ -146,7 +146,7 @@ async function getPendingAddSuggestTokenRequest () { (await braveWalletService.getPendingAddSuggestTokenRequests()).requests if (requests && requests.length) { const logo = requests[0].token.logo - if (logo !== '' && !isRemoteImageURL(logo)) { + if (logo !== '' && !isFromDifferentOrigin(logo)) { requests[0].token.logo = `chrome://erc-token-images/${logo}` } return requests[0] diff --git a/components/brave_wallet_ui/stories/mock-data/mock-nft-metadata.ts b/components/brave_wallet_ui/stories/mock-data/mock-nft-metadata.ts index 3849c20f592e..9c2f318e6043 100644 --- a/components/brave_wallet_ui/stories/mock-data/mock-nft-metadata.ts +++ b/components/brave_wallet_ui/stories/mock-data/mock-nft-metadata.ts @@ -4,7 +4,6 @@ import MooncatProjectIcon from '../../assets/png-icons/mooncat-project-icon.png' export const mockNFTMetadata: NFTMetadataReturnType[] = [ { - chain: '0x1', chainName: 'Ethereum', tokenType: 'ERC721', tokenID: '0x42a5', diff --git a/components/brave_wallet_ui/stories/mock-data/mock-page-state.ts b/components/brave_wallet_ui/stories/mock-data/mock-page-state.ts index 9140f9dd262e..099b59065c89 100644 --- a/components/brave_wallet_ui/stories/mock-data/mock-page-state.ts +++ b/components/brave_wallet_ui/stories/mock-data/mock-page-state.ts @@ -1,6 +1,9 @@ import { BraveWallet, PageState } from '../../constants/types' +import { mockNFTMetadata } from './mock-nft-metadata' export const mockPageState: PageState = { + isFetchingNFTMetadata: false, + nftMetadata: mockNFTMetadata[0], hasInitialized: false, importAccountError: false, importWalletError: { diff --git a/components/brave_wallet_ui/utils/string-utils.test.ts b/components/brave_wallet_ui/utils/string-utils.test.ts index 99f56ea5d61e..5e33250e96d1 100644 --- a/components/brave_wallet_ui/utils/string-utils.test.ts +++ b/components/brave_wallet_ui/utils/string-utils.test.ts @@ -1,24 +1,24 @@ -import { isRemoteImageURL, isValidIconExtension, formatAsDouble } from './string-utils' +import { isFromDifferentOrigin, isValidIconExtension, formatAsDouble } from './string-utils' describe('Checking URL is remote image or not', () => { test('HTTP URL should return true', () => { - expect(isRemoteImageURL('http://test.com/test.png')).toEqual(true) + expect(isFromDifferentOrigin('http://test.com/test.png')).toEqual(true) }) test('HTTPS URL should return true', () => { - expect(isRemoteImageURL('https://test.com/test.png')).toEqual(true) + expect(isFromDifferentOrigin('https://test.com/test.png')).toEqual(true) }) test('Data URL image should return true', () => { - expect(isRemoteImageURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC')).toEqual(true) + expect(isFromDifferentOrigin('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC')).toEqual(true) }) test('local path should return false', () => { - expect(isRemoteImageURL('bat.png')).toEqual(false) + expect(isFromDifferentOrigin('bat.png')).toEqual(false) }) test('undefined input should return undefined', () => { - expect(isRemoteImageURL(undefined)).toEqual(undefined) + expect(isFromDifferentOrigin(undefined)).toEqual(undefined) }) }) diff --git a/components/brave_wallet_ui/utils/string-utils.ts b/components/brave_wallet_ui/utils/string-utils.ts index c08432b0b025..cebd69cab8fb 100644 --- a/components/brave_wallet_ui/utils/string-utils.ts +++ b/components/brave_wallet_ui/utils/string-utils.ts @@ -7,11 +7,23 @@ export const toProperCase = (value: string) => value.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()) -export const isRemoteImageURL = (url?: string) => - url?.startsWith('http://') || url?.startsWith('https://') || url?.startsWith('data:image/') +export const isFromDifferentOrigin = (url?: string) => + url?.startsWith('http://') || url?.startsWith('https://') || url?.startsWith('data:image/') || url?.startsWith('ipfs://') export const isValidIconExtension = (url?: string) => - url?.endsWith('.jpg') || url?.endsWith('.jpeg') || url?.endsWith('.png') || url?.endsWith('.svg') + url?.endsWith('.jpg') || url?.endsWith('.jpeg') || url?.endsWith('.png') || url?.endsWith('.svg') || url?.endsWith('.gif') + +export const httpifyIpfsUrl = (url: string | undefined) => { + if (!url) { + return '' + } + + if (url.startsWith('data:image/')) { + return url + } + + return `chrome://image/?${url.includes('ipfs://') ? url.replace('ipfs://', 'https://ipfs.io/ipfs/') : url}` +} export const getRampNetworkPrefix = (chainId: string) => { switch (chainId) { diff --git a/components/constants/webui_url_constants.cc b/components/constants/webui_url_constants.cc index a58c0bddba4f..ac53b351f84d 100644 --- a/components/constants/webui_url_constants.cc +++ b/components/constants/webui_url_constants.cc @@ -39,6 +39,8 @@ const char kBraveSyncSetupPath[] = "braveSync/setup"; const char kTorInternalsHost[] = "tor-internals"; const char kUntrustedLedgerHost[] = "ledger-bridge"; const char kUntrustedLedgerURL[] = "chrome-untrusted://ledger-bridge/"; +const char kUntrustedNftHost[] = "nft-display"; +const char kUntrustedNftURL[] = "chrome-untrusted://nft-display/"; const char kUntrustedTrezorHost[] = "trezor-bridge"; const char kUntrustedTrezorURL[] = "chrome-untrusted://trezor-bridge/"; const char kShieldsPanelURL[] = "chrome://brave-shields.top-chrome"; diff --git a/components/constants/webui_url_constants.h b/components/constants/webui_url_constants.h index 773146734547..29e02836e282 100644 --- a/components/constants/webui_url_constants.h +++ b/components/constants/webui_url_constants.h @@ -40,6 +40,8 @@ extern const char kBraveSyncSetupPath[]; extern const char kTorInternalsHost[]; extern const char kUntrustedLedgerHost[]; extern const char kUntrustedLedgerURL[]; +extern const char kUntrustedNftHost[]; +extern const char kUntrustedNftURL[]; extern const char kUntrustedTrezorHost[]; extern const char kUntrustedTrezorURL[]; extern const char kShieldsPanelURL[]; diff --git a/components/resources/brave_components_resources.grd b/components/resources/brave_components_resources.grd index 3b09ad185d86..d1a23b4acb50 100644 --- a/components/resources/brave_components_resources.grd +++ b/components/resources/brave_components_resources.grd @@ -39,6 +39,7 @@ + diff --git a/resources/resource_ids.spec b/resources/resource_ids.spec index d41fbe2d1f37..b69a334d4528 100644 --- a/resources/resource_ids.spec +++ b/resources/resource_ids.spec @@ -200,4 +200,9 @@ "META": {"sizes": {"includes": [250]}}, "includes": [59270] }, + # This file is generated during the build. + "<(SHARED_INTERMEDIATE_DIR)/brave/web-ui-nft_display/nft_display.grd": { + "META": {"sizes": {"includes": [250]}}, + "includes": [59520] + }, } From 3307d5d3558a46d360f90a0bf25b095029695487 Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 7 Jul 2022 13:29:23 +0300 Subject: [PATCH 02/39] chore: remove console.log --- components/brave_wallet_ui/nft/nft.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/components/brave_wallet_ui/nft/nft.ts b/components/brave_wallet_ui/nft/nft.ts index 2434821db45c..7e18b5f5bb2c 100644 --- a/components/brave_wallet_ui/nft/nft.ts +++ b/components/brave_wallet_ui/nft/nft.ts @@ -15,7 +15,6 @@ const params = new Proxy(new URLSearchParams(window.location.search), { const imageUrl = params['imageUrl'] const imageWidth = params['imageWidth'] const imageHeight = params['imageHeight'] -console.log({ imageWidth, imageHeight }) /* eslint-enable @typescript-eslint/dot-notation */ const imageEl = document.getElementById('image') From 4d89ceff5985da47c08a14699cc47389511f77ce Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 8 Jul 2022 15:01:50 +0300 Subject: [PATCH 03/39] chore: remove chrome://image prefix --- components/brave_wallet_ui/utils/string-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/brave_wallet_ui/utils/string-utils.ts b/components/brave_wallet_ui/utils/string-utils.ts index cebd69cab8fb..7df1b45fd494 100644 --- a/components/brave_wallet_ui/utils/string-utils.ts +++ b/components/brave_wallet_ui/utils/string-utils.ts @@ -22,7 +22,7 @@ export const httpifyIpfsUrl = (url: string | undefined) => { return url } - return `chrome://image/?${url.includes('ipfs://') ? url.replace('ipfs://', 'https://ipfs.io/ipfs/') : url}` + return url.includes('ipfs://') ? url.replace('ipfs://', 'https://ipfs.io/ipfs/') : url } export const getRampNetworkPrefix = (chainId: string) => { From ae18fd09d6a194965bcdfa7c5e9c7f52980293b5 Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 8 Jul 2022 15:02:48 +0300 Subject: [PATCH 04/39] chore: fix loading of nft asset use url params --- .../components/desktop/views/portfolio/portfolio-asset.tsx | 5 ++--- .../desktop/views/portfolio/portfolio-overview.tsx | 5 ++++- components/brave_wallet_ui/constants/types.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index 034a0285ee3d..9f1425dfa7d6 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -70,7 +70,7 @@ const AssetIconWithPlaceholder = withPlaceholderIcon(AssetIcon, { size: 'big', m export const PortfolioAsset = () => { // routing const history = useHistory() - const { id: assetId } = useParams<{ id?: string }>() + const { id: assetId, tokenId } = useParams<{ id?: string, tokenId?: string }>() // redux const dispatch = useDispatch() @@ -98,7 +98,6 @@ export const PortfolioAsset = () => { isFetchingNFTMetadata, nftMetadata } = useSelector(({ page }: { page: PageState }) => page) - // custom hooks const getAccountBalance = useBalance(networkList) @@ -167,7 +166,7 @@ export const PortfolioAsset = () => { // If the id length is greater than 15 assumes it's a contractAddress return assetId.length > 15 - ? userVisibleTokensInfo.find((token) => token.contractAddress === assetId) + ? userVisibleTokensInfo.find((token) => tokenId ? token.contractAddress === assetId && token.tokenId === tokenId : token.contractAddress === assetId) : userVisibleTokensInfo.find((token) => token.symbol.toLowerCase() === assetId?.toLowerCase()) }, [assetId, userVisibleTokensInfo, selectedTimeline]) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx index d48a9fcf98be..6f332b32185a 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx @@ -173,8 +173,11 @@ export const PortfolioOverview = () => { if (asset.contractAddress === '') { history.push(`${WalletRoutes.Portfolio}/${asset.symbol}`) return + } else if (asset.isErc721) { + history.push(`${WalletRoutes.Portfolio}/${asset.contractAddress}/${asset.tokenId}`) + } else { + history.push(`${WalletRoutes.Portfolio}/${asset.contractAddress}`) } - history.push(`${WalletRoutes.Portfolio}/${asset.contractAddress}`) dispatch(WalletPageActions.selectAsset({ asset, timeFrame: selectedTimeline })) }, [selectedTimeline]) diff --git a/components/brave_wallet_ui/constants/types.ts b/components/brave_wallet_ui/constants/types.ts index eae33667b7ba..1904813cbf4f 100644 --- a/components/brave_wallet_ui/constants/types.ts +++ b/components/brave_wallet_ui/constants/types.ts @@ -611,7 +611,7 @@ export enum WalletRoutes { // portfolio Portfolio = '/crypto/portfolio', - PortfolioAsset = '/crypto/portfolio/:id', + PortfolioAsset = '/crypto/portfolio/:id/:tokenId?', // portfolio asset modals AddAssetModal = '/crypto/portfolio/add-asset', From 7afba2993cd1bf0dad286105d6ded6289eb18e36 Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 8 Jul 2022 15:03:20 +0300 Subject: [PATCH 05/39] chore: add skeleton loader for nft image --- .../portfolio/components/nft-details/index.tsx | 11 +++++++++-- .../portfolio/components/nft-details/style.ts | 16 ++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx index df38a9861f26..275e99689685 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx @@ -39,8 +39,10 @@ import { ExplorerButton, NTFImageIframe, nftImageDimension, - NftImageWrapper + NftImageWrapper, + NFTImageSkeletonWrapper } from './style' +import { LoadingSkeleton } from '../../../../../shared' export interface Props { isLoading: boolean @@ -57,6 +59,7 @@ const NFTDetails = (props: Props) => { nftMetadata, selectedNetwork } = props + const [isImageLoaded, setIsImageLoaded] = React.useState(false) const onClickViewOnBlockExplorer = useExplorer(selectedNetwork) @@ -93,12 +96,16 @@ const NFTDetails = (props: Props) => { } {nftMetadata && <> - + setIsImageLoaded(true)} /> + {!isImageLoaded && + + } {selectedAsset.name} { diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts index 8f1bda7fb708..7a9930efc4dd 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts @@ -21,19 +21,27 @@ export const StyledWrapper = styled.div` margin: 16px 0 50px 0; ` -export const NftImageWrapper = styled.div` +export const NftImageWrapper = styled.div` display: flex; - width: ${nftImageDimension}; - height: ${nftImageDimension}; + width: ${p => p.isLoading ? 0 : nftImageDimension}; + height: ${p => p.isLoading ? 0 : nftImageDimension}; + visibility: ${p => p.isLoading ? 'hidden' : 'visible'}; + margin-right: 28px; + border-radius: 12px; ` export const NTFImageIframe = styled.iframe` display: block; width: ${nftImageDimension}; height: ${nftImageDimension}; + border: transparent; +` + +export const NFTImageSkeletonWrapper = styled.div` + min-width: ${nftImageDimension}; + height: ${nftImageDimension}; margin-right: 28px; border-radius: 12px; - border: transparent; ` export const DetailColumn = styled.div` From 5cb0546d35f3b8d23234cce22e4be18c23153949 Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 8 Jul 2022 15:04:30 +0300 Subject: [PATCH 06/39] chore: check token id when deleting assets --- components/brave_wallet_ui/common/hooks/assets-management.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/brave_wallet_ui/common/hooks/assets-management.ts b/components/brave_wallet_ui/common/hooks/assets-management.ts index 75af5db3301e..36756c2040ed 100644 --- a/components/brave_wallet_ui/common/hooks/assets-management.ts +++ b/components/brave_wallet_ui/common/hooks/assets-management.ts @@ -17,7 +17,7 @@ const onlyInLeft = (left: BraveWallet.BlockchainToken[], right: BraveWallet.Bloc left.filter(leftValue => !right.some(rightValue => leftValue.contractAddress.toLowerCase() === rightValue.contractAddress.toLowerCase() && - leftValue.chainId === rightValue.chainId)) + leftValue.chainId === rightValue.chainId && leftValue.tokenId === rightValue.tokenId)) export default function useAssetManagement () { // redux From f7ee2267d7576acc6b1d4361558f72c77a6595a2 Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 8 Jul 2022 15:16:36 +0300 Subject: [PATCH 07/39] chore: remove redundant lines --- components/brave_wallet_ui/utils/string-utils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/brave_wallet_ui/utils/string-utils.ts b/components/brave_wallet_ui/utils/string-utils.ts index 7df1b45fd494..c3bf7d16e50d 100644 --- a/components/brave_wallet_ui/utils/string-utils.ts +++ b/components/brave_wallet_ui/utils/string-utils.ts @@ -18,10 +18,6 @@ export const httpifyIpfsUrl = (url: string | undefined) => { return '' } - if (url.startsWith('data:image/')) { - return url - } - return url.includes('ipfs://') ? url.replace('ipfs://', 'https://ipfs.io/ipfs/') : url } From 87d509823ae9d7ec4b6c00bbbefaf9e3292c21fc Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 8 Jul 2022 15:16:53 +0300 Subject: [PATCH 08/39] chore: add tokenId to dep list --- .../components/desktop/views/portfolio/portfolio-asset.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index 9f1425dfa7d6..5ec1fb22150c 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -168,7 +168,7 @@ export const PortfolioAsset = () => { return assetId.length > 15 ? userVisibleTokensInfo.find((token) => tokenId ? token.contractAddress === assetId && token.tokenId === tokenId : token.contractAddress === assetId) : userVisibleTokensInfo.find((token) => token.symbol.toLowerCase() === assetId?.toLowerCase()) - }, [assetId, userVisibleTokensInfo, selectedTimeline]) + }, [assetId, userVisibleTokensInfo, selectedTimeline, tokenId]) // This will scrape all of the user's accounts and combine the fiat value for every asset const fullPortfolioFiatBalance = React.useMemo(() => { From 94f8730c80f4d6b25f1739cc7647d820392fe98a Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 8 Jul 2022 16:00:25 +0300 Subject: [PATCH 09/39] feat: display visible asset modal asset icon in chrome-untrusted --- .../components/desktop/asset-watchlist-item/index.tsx | 5 +++-- .../components/desktop/portfolio-asset-item/index.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx b/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx index 06eb58cfb873..fd29a63cc30c 100644 --- a/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx +++ b/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx @@ -24,6 +24,7 @@ import { NameAndSymbol, AssetSymbol } from './style' +import { NFTAssetIcon } from '../portfolio-asset-item/style' export interface Props { onSelectAsset: (key: string, selected: boolean, token: BraveWallet.BlockchainToken, isCustom: boolean) => void @@ -57,8 +58,8 @@ const AssetWatchlistItem = (props: Props) => { } const AssetIconWithPlaceholder = React.useMemo(() => { - return withPlaceholderIcon(AssetIcon, { size: 'big', marginLeft: 0, marginRight: 8 }) - }, []) + return withPlaceholderIcon(token.isErc721 ? NFTAssetIcon : AssetIcon, { size: 'big', marginLeft: 0, marginRight: 8 }) + }, [token]) const tokensNetwork = React.useMemo(() => { if (!token) { diff --git a/components/brave_wallet_ui/components/desktop/portfolio-asset-item/index.tsx b/components/brave_wallet_ui/components/desktop/portfolio-asset-item/index.tsx index 3ea06e94d729..1933d0ab52c7 100644 --- a/components/brave_wallet_ui/components/desktop/portfolio-asset-item/index.tsx +++ b/components/brave_wallet_ui/components/desktop/portfolio-asset-item/index.tsx @@ -59,7 +59,7 @@ const PortfolioAssetItem = (props: Props) => { const AssetIconWithPlaceholder = React.useMemo(() => { return withPlaceholderIcon(token.isErc721 ? NFTAssetIcon : AssetIcon, { size: 'big', marginLeft: 0, marginRight: 8 }) - }, []) + }, [token]) const formattedAssetBalance = token.isErc721 ? new Amount(assetBalance) From 08d38a09b7a3a45f67ce16da95010967af0122ae Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 8 Jul 2022 16:39:17 +0300 Subject: [PATCH 10/39] chore: trim url --- components/brave_wallet_ui/utils/string-utils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/components/brave_wallet_ui/utils/string-utils.ts b/components/brave_wallet_ui/utils/string-utils.ts index c3bf7d16e50d..02dab6465ede 100644 --- a/components/brave_wallet_ui/utils/string-utils.ts +++ b/components/brave_wallet_ui/utils/string-utils.ts @@ -14,11 +14,8 @@ export const isValidIconExtension = (url?: string) => url?.endsWith('.jpg') || url?.endsWith('.jpeg') || url?.endsWith('.png') || url?.endsWith('.svg') || url?.endsWith('.gif') export const httpifyIpfsUrl = (url: string | undefined) => { - if (!url) { - return '' - } - - return url.includes('ipfs://') ? url.replace('ipfs://', 'https://ipfs.io/ipfs/') : url + const trimmedUrl = url ? url.trim() : '' + return trimmedUrl.startsWith('ipfs://') ? trimmedUrl.replace('ipfs://', 'https://ipfs.io/ipfs/') : trimmedUrl } export const getRampNetworkPrefix = (chainId: string) => { From 022337fcd671dc46e7983222409616475381780a Mon Sep 17 00:00:00 2001 From: William Muli Date: Wed, 13 Jul 2022 08:54:35 +0300 Subject: [PATCH 11/39] chore: add tokenId check when removing asset from visible assets list --- .../components/desktop/asset-watchlist-item/index.tsx | 4 ++-- .../popup-modals/edit-visible-assets-modal/index.tsx | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx b/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx index fd29a63cc30c..a63ade631cbf 100644 --- a/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx +++ b/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx @@ -95,8 +95,8 @@ const AssetWatchlistItem = (props: Props) => { } - -
+ +
diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx index 55c0c717697e..83c0e253a9a5 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx @@ -292,7 +292,8 @@ const EditVisibleAssetsModal = ({ onClose }: Props) => { return updatedTokensList.find((t) => t.symbol.toLowerCase() === token.symbol.toLowerCase() && t.contractAddress.toLowerCase() === token.contractAddress.toLowerCase() && - t.chainId === token.chainId) + t.chainId === token.chainId && + t.tokenId === token.tokenId) } const isUserToken = (token: BraveWallet.BlockchainToken) => { @@ -333,7 +334,8 @@ const EditVisibleAssetsModal = ({ onClose }: Props) => { const tokenIndex = updatedTokensList.findIndex((t) => t.contractAddress.toLowerCase() === token.contractAddress.toLowerCase() && t.symbol.toLowerCase() === token.symbol.toLowerCase() && - t.chainId === token.chainId) + t.chainId === token.chainId && + t.tokenId === token.tokenId) let newList = [...updatedTokensList] newList.splice(tokenIndex, 1, updatedToken) setUpdatedTokensList(newList) @@ -366,9 +368,9 @@ const EditVisibleAssetsModal = ({ onClose }: Props) => { } const onRemoveAsset = (token: BraveWallet.BlockchainToken) => { - const newUserList = updatedTokensList.filter((t) => t.contractAddress.toLowerCase() !== token.contractAddress.toLowerCase()) + const newUserList = updatedTokensList.filter((t) => t.contractAddress.toLowerCase() !== token.contractAddress.toLowerCase() && t.tokenId === token.tokenId) setUpdatedTokensList(newUserList) - const newFilteredTokenList = filteredTokenList.filter((t) => t.contractAddress.toLowerCase() !== token.contractAddress.toLowerCase()) + const newFilteredTokenList = filteredTokenList.filter((t) => t.contractAddress.toLowerCase() !== token.contractAddress.toLowerCase() && t.tokenId === token.tokenId) setFilteredTokenList(newFilteredTokenList) } From 36397a07a1052c1d62eeec367b0e2f6da3a0ad01 Mon Sep 17 00:00:00 2001 From: William Muli Date: Wed, 13 Jul 2022 08:55:35 +0300 Subject: [PATCH 12/39] chore: fix tokenBalanceRegistry keys to avoid duplicates for erc721 tokens --- components/brave_wallet_ui/common/hooks/balance.ts | 4 +++- .../brave_wallet_ui/common/reducers/wallet_reducer.ts | 6 ++++-- components/brave_wallet_ui/utils/account-utils.ts | 6 +++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/components/brave_wallet_ui/common/hooks/balance.ts b/components/brave_wallet_ui/common/hooks/balance.ts index f5ff6a928ca1..37cd4aa292c7 100644 --- a/components/brave_wallet_ui/common/hooks/balance.ts +++ b/components/brave_wallet_ui/common/hooks/balance.ts @@ -7,6 +7,7 @@ import * as React from 'react' import { BraveWallet, WalletAccountType } from '../../constants/types' import { getTokensCoinType } from '../../utils/network-utils' +import { createTokenBalanceRegistryKey } from '../../utils/account-utils' export default function useBalance (networks: BraveWallet.NetworkInfo[]) { const getBalance = React.useCallback((account?: WalletAccountType, token?: BraveWallet.BlockchainToken) => { @@ -31,7 +32,8 @@ export default function useBalance (networks: BraveWallet.NetworkInfo[]) { return (account.nativeBalanceRegistry || {})[token.chainId || ''] || '' } - return (account.tokenBalanceRegistry || {})[token.contractAddress.toLowerCase()] || '' + const registryKey = createTokenBalanceRegistryKey(token) + return (account.tokenBalanceRegistry || {})[registryKey] || '' }, [networks]) return getBalance diff --git a/components/brave_wallet_ui/common/reducers/wallet_reducer.ts b/components/brave_wallet_ui/common/reducers/wallet_reducer.ts index a2e46017dc89..f3d71bca7bf8 100644 --- a/components/brave_wallet_ui/common/reducers/wallet_reducer.ts +++ b/components/brave_wallet_ui/common/reducers/wallet_reducer.ts @@ -35,6 +35,7 @@ import { mojoTimeDeltaToJSDate } from '../../../common/mojomUtils' import { sortTransactionByDate } from '../../utils/tx-utils' import Amount from '../../utils/amount' import { AllNetworksOption } from '../../options/network-filter-options' +import { createTokenBalanceRegistryKey } from '../../utils/account-utils' const defaultState: WalletState = { hasInitialized: false, @@ -219,8 +220,9 @@ export const createWalletReducer = (initialState: WalletState) => { accounts.forEach((account, accountIndex) => { payload.balances[accountIndex]?.forEach((info, tokenIndex) => { if (info.error === BraveWallet.ProviderError.kSuccess) { - const contractAddress = visibleTokens[tokenIndex].contractAddress.toLowerCase() - accounts[accountIndex].tokenBalanceRegistry[contractAddress] = Amount.normalize(info.balance) + const token = visibleTokens[tokenIndex] + const registryKey = createTokenBalanceRegistryKey(token) + accounts[accountIndex].tokenBalanceRegistry[registryKey] = Amount.normalize(info.balance) } }) }) diff --git a/components/brave_wallet_ui/utils/account-utils.ts b/components/brave_wallet_ui/utils/account-utils.ts index ffbd7c207cfc..9a0bac874e7b 100644 --- a/components/brave_wallet_ui/utils/account-utils.ts +++ b/components/brave_wallet_ui/utils/account-utils.ts @@ -3,7 +3,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // you can obtain one at http://mozilla.org/MPL/2.0/. -import { WalletAccountType } from '../constants/types' +import { BraveWallet, WalletAccountType } from '../constants/types' export const sortAccountsByName = (accounts: WalletAccountType[]) => { return [...accounts].sort(function (a: WalletAccountType, b: WalletAccountType) { @@ -29,3 +29,7 @@ export const groupAccountsById = (accounts: WalletAccountType[], key: string) => export const findAccountName = (accounts: WalletAccountType[], address: string) => { return accounts.find((account) => account.address.toLowerCase() === address.toLowerCase())?.name } + +export const createTokenBalanceRegistryKey = (token: BraveWallet.BlockchainToken) => { + return token.isErc721 ? `${token.contractAddress.toLowerCase()}#${token.tokenId}` : token.contractAddress.toLowerCase() +} From 59c1f24aa63e2deac95016dab91557821b6d87de Mon Sep 17 00:00:00 2001 From: William Muli Date: Wed, 13 Jul 2022 08:57:03 +0300 Subject: [PATCH 13/39] chore: refactor nft details screen --- .../brave_wallet_ui/common/hooks/explorer.ts | 2 +- .../components/nft-details/index.tsx | 29 ++++++++++--------- .../portfolio/components/nft-details/style.ts | 2 +- .../views/portfolio/portfolio-asset.tsx | 8 +---- .../views/portfolio/portfolio-overview.tsx | 6 +++- 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/components/brave_wallet_ui/common/hooks/explorer.ts b/components/brave_wallet_ui/common/hooks/explorer.ts index c9bca58d83ed..b8f34ec26d03 100644 --- a/components/brave_wallet_ui/common/hooks/explorer.ts +++ b/components/brave_wallet_ui/common/hooks/explorer.ts @@ -15,7 +15,7 @@ export function buildExplorerUrl ( const explorerURL = network.blockExplorerUrls[0] if (type === 'contract') { - return `${explorerURL}/${value}?a=${new Amount(id ?? '').format()}` + return id ? `${explorerURL}/${value}?a=${new Amount(id).format()}` : `${explorerURL}/${value}}` } const isFileCoinNet = diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx index 275e99689685..9ba2acb624e9 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx @@ -1,10 +1,11 @@ import * as React from 'react' +import { useSelector } from 'react-redux' // Types import { BraveWallet, - NFTMetadataReturnType, - DefaultCurrencies + WalletState, + PageState } from '../../../../../../constants/types' // Hooks @@ -43,25 +44,25 @@ import { NFTImageSkeletonWrapper } from './style' import { LoadingSkeleton } from '../../../../../shared' +import { getTokensNetwork } from '../../../../../../utils/network-utils' export interface Props { - isLoading: boolean selectedAsset: BraveWallet.BlockchainToken - nftMetadata: NFTMetadataReturnType | undefined - defaultCurrencies: DefaultCurrencies - selectedNetwork: BraveWallet.NetworkInfo } -const NFTDetails = (props: Props) => { - const { - isLoading, - selectedAsset, - nftMetadata, - selectedNetwork - } = props +const NFTDetails = ({ selectedAsset }: Props) => { const [isImageLoaded, setIsImageLoaded] = React.useState(false) - const onClickViewOnBlockExplorer = useExplorer(selectedNetwork) + // redux + const { networkList } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) + const { + isFetchingNFTMetadata: isLoading, + nftMetadata + } = useSelector(({ page }: { page: PageState }) => page) + const tokenNetwork = getTokensNetwork(networkList, selectedAsset) + + // hooks + const onClickViewOnBlockExplorer = useExplorer(tokenNetwork) const onClickWebsite = () => { chrome.tabs.create({ url: nftMetadata?.contractInformation?.website }, () => { diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts index 7a9930efc4dd..3acc56bb5363 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts @@ -26,7 +26,7 @@ export const NftImageWrapper = styled.div` width: ${p => p.isLoading ? 0 : nftImageDimension}; height: ${p => p.isLoading ? 0 : nftImageDimension}; visibility: ${p => p.isLoading ? 'hidden' : 'visible'}; - margin-right: 28px; + margin-right: ${p => p.isLoading ? 0 : '28px'};; border-radius: 12px; ` diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index 5ec1fb22150c..eaeea7b668dc 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -94,9 +94,7 @@ export const PortfolioAsset = () => { selectedAssetCryptoPrice, selectedAssetFiatPrice, selectedAssetPriceHistory, - selectedTimeline, - isFetchingNFTMetadata, - nftMetadata + selectedTimeline } = useSelector(({ page }: { page: PageState }) => page) // custom hooks const getAccountBalance = useBalance(networkList) @@ -381,11 +379,7 @@ export const PortfolioAsset = () => { {selectedAsset?.isErc721 && } diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx index 6f332b32185a..76ff6012000d 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx @@ -74,7 +74,7 @@ export const PortfolioOverview = () => { selectedNetworkFilter } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) - const selectedTimeline = useSelector(({ page }: { page: PageState }) => page.selectedTimeline) + const { selectedTimeline, nftMetadata } = useSelector(({ page }: { page: PageState }) => page) // custom hooks const getAccountAssetBalance = useBalance(networkList) @@ -180,6 +180,10 @@ export const PortfolioOverview = () => { } dispatch(WalletPageActions.selectAsset({ asset, timeFrame: selectedTimeline })) + if (asset.isErc721 && nftMetadata) { + // reset nft metadata + dispatch(WalletPageActions.updateNFTMetadata(undefined)) + } }, [selectedTimeline]) const onUpdateBalance = React.useCallback((value: number | undefined) => { From 9356802e01e567bac28acd473f7d7974b45e35cc Mon Sep 17 00:00:00 2001 From: William Muli Date: Wed, 13 Jul 2022 08:57:22 +0300 Subject: [PATCH 14/39] chore: add border-radius to nft image --- components/brave_wallet_ui/nft/nft.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/brave_wallet_ui/nft/nft.html b/components/brave_wallet_ui/nft/nft.html index 823f23b53cf8..9bf54394c2f8 100644 --- a/components/brave_wallet_ui/nft/nft.html +++ b/components/brave_wallet_ui/nft/nft.html @@ -12,6 +12,10 @@ margin: 0; overflow: hidden; } + + #image { + border-radius: 4px; + } From 413f2e64e9f0d98cac274379bad197fc6f15543f Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 14:58:27 +0300 Subject: [PATCH 15/39] feat: create react app for rendering nft details in a chrome-untrusted tab --- components/brave_wallet_ui/nft/nft.html | 19 +---- components/brave_wallet_ui/nft/nft.ts | 33 -------- components/brave_wallet_ui/nft/nft.tsx | 102 ++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 49 deletions(-) delete mode 100644 components/brave_wallet_ui/nft/nft.ts create mode 100644 components/brave_wallet_ui/nft/nft.tsx diff --git a/components/brave_wallet_ui/nft/nft.html b/components/brave_wallet_ui/nft/nft.html index 9bf54394c2f8..7f36eb5453b0 100644 --- a/components/brave_wallet_ui/nft/nft.html +++ b/components/brave_wallet_ui/nft/nft.html @@ -1,24 +1,11 @@ - - NFT iframe - + + - +
diff --git a/components/brave_wallet_ui/nft/nft.ts b/components/brave_wallet_ui/nft/nft.ts deleted file mode 100644 index 7e18b5f5bb2c..000000000000 --- a/components/brave_wallet_ui/nft/nft.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2022 The Brave Authors. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// you can obtain one at http://mozilla.org/MPL/2.0/. - -const params = new Proxy(new URLSearchParams(window.location.search), { - get: (searchParams, prop) => { - if (typeof prop === 'string') { - return searchParams.get(prop) - } - return null - } -}) -/* eslint-disable @typescript-eslint/dot-notation */ -const imageUrl = params['imageUrl'] -const imageWidth = params['imageWidth'] -const imageHeight = params['imageHeight'] -/* eslint-enable @typescript-eslint/dot-notation */ -const imageEl = document.getElementById('image') - -if (imageEl) { - if (imageUrl) { - imageEl.src = imageUrl - } - - if (imageWidth) { - imageEl.style.width = imageWidth - } - - if (imageHeight) { - imageEl.style.height = imageHeight - } -} diff --git a/components/brave_wallet_ui/nft/nft.tsx b/components/brave_wallet_ui/nft/nft.tsx new file mode 100644 index 000000000000..ad31dc6c1d2b --- /dev/null +++ b/components/brave_wallet_ui/nft/nft.tsx @@ -0,0 +1,102 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { render } from 'react-dom' +import { BrowserRouter } from 'react-router-dom' +import { initLocale } from 'brave-ui' +import { loadTimeData } from '../../common/loadTimeData' + +// css +import 'emptykit.css' +import '../../../ui/webui/resources/fonts/poppins.css' +import './css/nft-global.css' + +// theme setup +import BraveCoreThemeProvider from '../../common/BraveCoreThemeProvider' +import walletDarkTheme from '../theme/wallet-dark' +import walletLightTheme from '../theme/wallet-light' + +// utils +import { + braveWalletOrigin, + CommandMessage, + NftUiCommand, + UpdateLoadingMessage, + UpdateNFtMetadataMessage, + UpdateSelectedAssetMessage, + UpdateTokenNetworkMessage +} from './nft-ui-messages' +import { BraveWallet, NFTMetadataReturnType } from '../constants/types' + +// components +import { NftContent } from './components/nft-content/nft-content' + +const App = () => { + const [loadingNftMetadata, setLoadingNftMetadata] = React.useState(true) + const [selectedAsset, setSelectedAsset] = React.useState() + const [nftMetadata, setNftMetadata] = React.useState() + const [tokenNetwork, setTokenNetwork] = React.useState() + + const onMessageEventListener = React.useCallback((event: MessageEvent) => { + // validate message origin + if (event.origin !== braveWalletOrigin) return + + const message = event.data + switch (message.command) { + case NftUiCommand.UpdateLoading: { + const { payload } = message as UpdateLoadingMessage + setLoadingNftMetadata(payload) + break + } + + case NftUiCommand.UpdateSelectedAsset: { + const { payload } = message as UpdateSelectedAssetMessage + setSelectedAsset(payload) + break + } + + case NftUiCommand.UpdateNFTMetadata: { + const { payload } = message as UpdateNFtMetadataMessage + setNftMetadata(payload) + break + } + + case NftUiCommand.UpdateTokenNetwork: { + const { payload } = message as UpdateTokenNetworkMessage + setTokenNetwork(payload) + break + } + } + }, []) + + React.useEffect(() => { + window.addEventListener('message', onMessageEventListener) + return () => window.removeEventListener('message', onMessageEventListener) + }, []) + + return ( + + + + + + ) +} + +function initialize () { + initLocale(loadTimeData.data_) + render(, document.getElementById('root')) +} + +document.addEventListener('DOMContentLoaded', initialize) From 0a2cddf808ac79d5a67e07f52d240258db7be6a4 Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 15:00:24 +0300 Subject: [PATCH 16/39] Add string to access via loadTimeData object --- browser/ui/webui/brave_wallet/wallet_page_ui.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/browser/ui/webui/brave_wallet/wallet_page_ui.cc b/browser/ui/webui/brave_wallet/wallet_page_ui.cc index 5e789aae3368..5a4393d631fc 100644 --- a/browser/ui/webui/brave_wallet/wallet_page_ui.cc +++ b/browser/ui/webui/brave_wallet/wallet_page_ui.cc @@ -65,6 +65,8 @@ WalletPageUI::WalletPageUI(content::WebUI* web_ui) network::mojom::CSPDirectiveName::FrameSrc, std::string("frame-src ") + kUntrustedNftURL + ";"); source->AddString("braveWalletTrezorBridgeUrl", kUntrustedTrezorURL); + source->AddString("braveWalletNftBridgeUrl", kUntrustedNftURL); + source->AddString("braveWalletBridgeUrl", kBraveUIWalletURL); auto* profile = Profile::FromWebUI(web_ui); content::WebUIDataSource::Add(profile, source); content::URLDataSource::Add(profile, From d20d0eb1192c17eb6e51492d8d15bf9c3e92a7c8 Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 15:01:10 +0300 Subject: [PATCH 17/39] Add configurations nft ui untrusted tab --- browser/ui/webui/brave_wallet/nft/nft_ui.cc | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/browser/ui/webui/brave_wallet/nft/nft_ui.cc b/browser/ui/webui/brave_wallet/nft/nft_ui.cc index 45342b8542c9..ccc878e3e0ba 100644 --- a/browser/ui/webui/brave_wallet/nft/nft_ui.cc +++ b/browser/ui/webui/brave_wallet/nft/nft_ui.cc @@ -7,8 +7,13 @@ #include +#include "brave/components/brave_wallet/browser/brave_wallet_constants.h" #include "brave/components/constants/webui_url_constants.h" +#include "brave/components/l10n/common/locale_util.h" #include "brave/components/nft_display/resources/grit/nft_display_generated_map.h" +#include "chrome/browser/ui/webui/webui_util.h" +#include "chrome/grit/browser_resources.h" +#include "chrome/grit/generated_resources.h" #include "components/grit/brave_components_resources.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui_data_source.h" @@ -19,19 +24,39 @@ namespace nft { UntrustedNftUI::UntrustedNftUI(content::WebUI* web_ui) : ui::UntrustedWebUIController(web_ui) { auto* untrusted_source = content::WebUIDataSource::Create(kUntrustedNftURL); + + for (const auto& str : brave_wallet::kLocalizedStrings) { + std::u16string l10n_str = + brave_l10n::GetLocalizedResourceUTF16String(str.id); + untrusted_source->AddString(str.name, l10n_str); + } + untrusted_source->SetDefaultResource(IDR_BRAVE_WALLET_NFT_DISPLAY_HTML); untrusted_source->AddResourcePaths( base::make_span(kNftDisplayGenerated, kNftDisplayGeneratedSize)); untrusted_source->AddFrameAncestor(GURL(kBraveUIWalletPageURL)); untrusted_source->AddFrameAncestor(GURL(kBraveUIWalletPanelURL)); - + webui::SetupWebUIDataSource( + untrusted_source, + base::make_span(kNftDisplayGenerated, kNftDisplayGeneratedSize), + IDR_BRAVE_WALLET_NFT_DISPLAY_HTML); + untrusted_source->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::ScriptSrc, + std::string("script-src 'self' chrome-untrusted://resources " + "chrome-untrusted://brave-resources;")); untrusted_source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::StyleSrc, std::string("style-src 'self' 'unsafe-inline';")); + untrusted_source->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::FontSrc, + std::string("font-src 'self' data:;")); untrusted_source->AddResourcePath("load_time_data.js", IDR_WEBUI_JS_LOAD_TIME_DATA_JS); untrusted_source->UseStringsJs(); untrusted_source->AddString("braveWalletNftBridgeUrl", kUntrustedNftURL); + untrusted_source->AddString("braveWalletTrezorBridgeUrl", + kUntrustedTrezorURL); + untrusted_source->AddString("braveWalletBridgeUrl", kBraveUIWalletURL); untrusted_source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ImgSrc, std::string("img-src 'self' https: data:;")); From 0d545d23ae298318b652e6847f067beeb52ba19b Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 15:01:52 +0300 Subject: [PATCH 18/39] Update build requirements for nft ui untrusted tab --- components/brave_wallet_ui/nft/BUILD.gn | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/brave_wallet_ui/nft/BUILD.gn b/components/brave_wallet_ui/nft/BUILD.gn index 2070fde29f71..13c2723ba1b8 100644 --- a/components/brave_wallet_ui/nft/BUILD.gn +++ b/components/brave_wallet_ui/nft/BUILD.gn @@ -12,10 +12,14 @@ import("//tools/grit/repack.gni") transpile_web_ui("nft_display_ui") { entry_points = [ [ "nft", - rebase_path("nft.ts"), + rebase_path("nft.tsx"), ] ] webpack_aliases = [ "browser" ] resource_name = "nft_display" + deps = [ + "//brave/components/brave_wallet/common:mojom_js", + "//brave/components/brave_wallet/common:preprocess_mojo", + ] } pack_web_resources("nft_display_generated") { From 03b1c13a9cda01fff69b52ca0d5fc93659248325 Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 15:02:35 +0300 Subject: [PATCH 19/39] Send message to nft iframe to update state --- .../views/portfolio/portfolio-asset.tsx | 99 +++++++++++++------ 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index eaeea7b668dc..5817f6e91c43 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -9,20 +9,26 @@ import { Redirect, useHistory, useParams } from 'react-router' // types import { - BraveWallet, - UserAssetInfoType, AddAccountNavTypes, - WalletState, + BraveWallet, PageState, SupportedTestNetworks, - WalletRoutes + UserAssetInfoType, + WalletRoutes, + WalletState } from '../../../../constants/types' // Utils import Amount from '../../../../utils/amount' import { mojoTimeDeltaToJSDate } from '../../../../../common/mojomUtils' import { sortTransactionByDate } from '../../../../utils/tx-utils' -import { getTokensNetwork, getTokensCoinType } from '../../../../utils/network-utils' +import { getTokensCoinType, getTokensNetwork } from '../../../../utils/network-utils' +import { + NftUiCommand, + sendMessageToNftUiFrame, + UpdateLoadingMessage, UpdateNFtMetadataMessage, + UpdateSelectedAssetMessage, UpdateTokenNetworkMessage +} from '../../../../nft/nft-ui-messages' // actions import { WalletPageActions } from '../../../../page/actions' @@ -32,12 +38,9 @@ import { ChartTimelineOptions } from '../../../../options/chart-timeline-options import { AllNetworksOption } from '../../../../options/network-filter-options' // Components -import { BackButton, withPlaceholderIcon } from '../../../shared' -import { - ChartControlBar, - LineChart -} from '../../' -// import NFTDetails from './components/nft-details' +import { BackButton } from '../../../shared' +import withPlaceholderIcon from '../../../shared/create-placeholder-icon' +import { ChartControlBar, LineChart } from '../../' import AccountsAndTransactionsList from './components/accounts-and-transctions-list' // Hooks @@ -45,25 +48,25 @@ import { useBalance, usePricing, useTransactionParser } from '../../../../common // Styled Components import { - StyledWrapper, - TopRow, - AssetIcon, - AssetRow, + ArrowIcon, AssetColumn, - PriceRow, + AssetIcon, AssetNameText, + AssetRow, + BalanceRow, DetailText, InfoColumn, - PriceText, + NetworkDescription, + NftDetails, PercentBubble, PercentText, - ArrowIcon, - BalanceRow, + PriceRow, + PriceText, ShowBalanceButton, - NetworkDescription + StyledWrapper, + TopRow } from './style' import { Skeleton } from '../../../shared/loading-skeleton/styles' -import NFTDetails from './components/nft-details' const AssetIconWithPlaceholder = withPlaceholderIcon(AssetIcon, { size: 'big', marginLeft: 0, marginRight: 12 }) @@ -71,7 +74,7 @@ export const PortfolioAsset = () => { // routing const history = useHistory() const { id: assetId, tokenId } = useParams<{ id?: string, tokenId?: string }>() - + const nftDetailsRef = React.useRef(null) // redux const dispatch = useDispatch() const { @@ -94,7 +97,9 @@ export const PortfolioAsset = () => { selectedAssetCryptoPrice, selectedAssetFiatPrice, selectedAssetPriceHistory, - selectedTimeline + selectedTimeline, + isFetchingNFTMetadata, + nftMetadata } = useSelector(({ page }: { page: PageState }) => page) // custom hooks const getAccountBalance = useBalance(networkList) @@ -299,6 +304,43 @@ export const PortfolioAsset = () => { } }, [selectedAssetFromParams]) + React.useEffect(() => { + if (nftDetailsRef && nftDetailsRef.current) { + const command: UpdateLoadingMessage = { + command: NftUiCommand.UpdateLoading, + payload: isFetchingNFTMetadata + } + sendMessageToNftUiFrame(nftDetailsRef.current.contentWindow, command) + } + }, [nftDetailsRef, isFetchingNFTMetadata]) + + React.useEffect(() => { + if (selectedAsset && nftDetailsRef && nftDetailsRef.current) { + const command: UpdateSelectedAssetMessage = { + command: NftUiCommand.UpdateSelectedAsset, + payload: selectedAsset + } + sendMessageToNftUiFrame(nftDetailsRef.current.contentWindow, command) + } + + if (selectedAsset && networkList && nftDetailsRef.current) { + const tokenNetwork = getTokensNetwork(networkList, selectedAsset) + const command: UpdateTokenNetworkMessage = { + command: NftUiCommand.UpdateTokenNetwork, + payload: tokenNetwork + } + sendMessageToNftUiFrame(nftDetailsRef.current.contentWindow, command) + } + + if (nftMetadata && nftDetailsRef && nftDetailsRef.current) { + const command: UpdateNFtMetadataMessage = { + command: NftUiCommand.UpdateNFTMetadata, + payload: nftMetadata + } + sendMessageToNftUiFrame(nftDetailsRef.current.contentWindow, command) + } + }, [nftDetailsRef, selectedAsset, nftMetadata, networkList]) + // token list needs to load before we can find an asset to select from the url params if (userVisibleTokensInfo.length === 0) { return @@ -377,11 +419,12 @@ export const PortfolioAsset = () => { /> } - {selectedAsset?.isErc721 && - - } + Date: Thu, 21 Jul 2022 15:03:02 +0300 Subject: [PATCH 20/39] Added iframe component --- .../components/desktop/views/portfolio/style.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/style.ts b/components/brave_wallet_ui/components/desktop/views/portfolio/style.ts index bd11d26328dc..bad120897eca 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/style.ts +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/style.ts @@ -5,8 +5,10 @@ import EyeOffIcon from '../../../../assets/svg-icons/eye-off-icon.svg' import { AssetIconProps, AssetIconFactory, WalletButton } from '../../../shared/style' interface StyleProps { - isDown: boolean - hideBalances: boolean + isDown?: boolean + hideBalances?: boolean + visible?: boolean + isLoading?: boolean } export const StyledWrapper = styled.div` @@ -253,3 +255,10 @@ export const FilterTokenRow = styled.div` flex-direction: row; width: 100%; ` + +export const NftDetails = styled.iframe` + width: 100%; + min-height: ${p => p.visible ? '490px' : 'hidden'}; + border: none; + visibility: ${p => p.visible ? 'visible' : 'hidden'}; +` From 77d3bfefe38e99c07b60cd109fd47d4bc2cb5bff Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 15:03:49 +0300 Subject: [PATCH 21/39] Create components for diplaying nft content in chrome untrusted tab --- .../nft-content/nft-content-styles.ts | 39 +++ .../components/nft-content/nft-content.tsx | 57 +++++ .../nft-details/nft-details-styles.ts | 233 ++++++++++++++++++ .../components/nft-details/nft-details.tsx | 151 ++++++++++++ .../brave_wallet_ui/nft/css/nft-global.css | 7 + 5 files changed, 487 insertions(+) create mode 100644 components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts create mode 100644 components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx create mode 100644 components/brave_wallet_ui/nft/components/nft-details/nft-details-styles.ts create mode 100644 components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx create mode 100644 components/brave_wallet_ui/nft/css/nft-global.css diff --git a/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts b/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts new file mode 100644 index 000000000000..abf04228eccb --- /dev/null +++ b/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts @@ -0,0 +1,39 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import styled from 'styled-components' +import { LoaderIcon } from 'brave-ui/components/icons' +import { StyleProps } from '../nft-details/nft-details-styles' + +interface Props { + customWidth?: string + customHeight?: string +} + +export const Image = styled.img` + border-radius: 4px; + border-color: ${p => p.theme.color.interactive08}; + width: ${p => p.customWidth ? p.customWidth : '100%'}; + height: ${p => p.customHeight ? p.customHeight : 'auto'}; +` + +export const LoadingOverlay = styled.div>` + display: ${(p) => p.isLoading ? 'flex' : 'none'}; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 440px; + position: absolute; + z-index: 10; + backdrop-filter: blur(5px); +` + +export const LoadIcon = styled(LoaderIcon)` + color: ${p => p.theme.color.interactive08}; + height: 70px; + width: 70px; + opacity: .4; +` diff --git a/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx b/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx new file mode 100644 index 000000000000..7869f9704911 --- /dev/null +++ b/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx @@ -0,0 +1,57 @@ +import * as React from 'react' +import { useLocation } from 'react-router-dom' + +// components +import { Image, LoadingOverlay, LoadIcon } from './nft-content-styles' +import { NftDetails } from '../nft-details/nft-details' + +// utils +import { BraveWallet, NFTMetadataReturnType } from '../../../constants/types' + +interface Props { + isLoading?: boolean + selectedAsset?: BraveWallet.BlockchainToken + nftMetadata?: NFTMetadataReturnType + tokenNetwork?: BraveWallet.NetworkInfo +} + +export const NftContent = (props: Props) => { + const { isLoading, selectedAsset } = props + + // hooks + const { search } = useLocation() + + // memos + const params = React.useMemo(() => { + return new URLSearchParams(search) + }, [search]) + + const imageUrl = params.get('imageUrl') + const imageWidth = params.get('imageWidth') + const imageHeight = params.get('imageHeight') + + return ( + <> + {imageUrl + ? + : <> + {isLoading && + + + + } + {selectedAsset && + + } + + } + + ) +} diff --git a/components/brave_wallet_ui/nft/components/nft-details/nft-details-styles.ts b/components/brave_wallet_ui/nft/components/nft-details/nft-details-styles.ts new file mode 100644 index 000000000000..e455d843aeb9 --- /dev/null +++ b/components/brave_wallet_ui/nft/components/nft-details/nft-details-styles.ts @@ -0,0 +1,233 @@ +import styled from 'styled-components' +import { WalletButton } from '../../../components/shared/style' +import WebsiteIcon from '../../../assets/svg-icons/website-icon.svg' +import TwitterIcon from '../../../assets/svg-icons/twitter-icon.svg' +import FacebookIcon from '../../../assets/svg-icons/facebook-icon.svg' +import { OpenNewIcon } from 'brave-ui/components/icons' + +export interface StyleProps { + isLoading: boolean +} + +export const nftImageDimension = '440px' + +export const StyledWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: flex-start; + width: 100%; + min-height: ${nftImageDimension}; + margin: 16px 0 50px 0; +` + +export const NftImageWrapper = styled.div` + display: flex; + width: ${p => p.isLoading ? 0 : nftImageDimension}; + height: ${p => p.isLoading ? 0 : nftImageDimension}; + visibility: ${p => p.isLoading ? 'hidden' : 'visible'}; + margin-right: ${p => p.isLoading ? 0 : '28px'};; + border-radius: 12px; +` + +export const NTFImage = styled.img` + display: block; + width: ${nftImageDimension}; + height: ${nftImageDimension}; + border: transparent; + border-radius: 4px; +` + +export const NFTImageSkeletonWrapper = styled.div` + min-width: ${nftImageDimension}; + height: ${nftImageDimension}; + margin-right: 28px; + border-radius: 12px; +` + +export const DetailColumn = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + width: 100%; +` + +export const TokenName = styled.span` + font-family: Poppins; + font-size: 20px; + line-height: 30px; + font-weight: 600; + letter-spacing: 0.02em; + color: ${(p) => p.theme.color.text01}; + margin-bottom: 15px; +` + +export const TokenFiatValue = styled.span` + font-family: Poppins; + font-size: 24px; + line-height: 36px; + font-weight: 600; + letter-spacing: 0.02em; + color: ${(p) => p.theme.color.text01}; +` + +export const TokenCryptoValue = styled.span` + font-family: Poppins; + font-size: 12px; + line-height: 20px; + letter-spacing: 0.01em; + color: ${(p) => p.theme.color.text03}; + margin-bottom: 20px; +` + +export const DetailSectionRow = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + min-width: 70%; + margin-bottom: 30px; +` + +export const DetailSectionColumn = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; +` + +export const DetailSectionTitle = styled.span` + font-family: Poppins; + font-size: 12px; + line-height: 18px; + letter-spacing: 0.01em; + color: ${(p) => p.theme.color.text02}; + margin-bottom: 10px; +` + +export const DetailSectionValue = styled.span` + font-family: Poppins; + font-size: 15px; + line-height: 20px; + font-weight: 600; + letter-spacing: 0.04em; + color: ${(p) => p.theme.color.text01}; + margin-right: 8px; +` + +export const ProjectDetailIDRow = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +` + +export const ProjectDetailRow = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + margin-bottom: 20px; +` + +export const ProjectDetailName = styled.span` + font-family: Poppins; + font-size: 14px; + line-height: 24px; + font-weight: 500; + color: ${(p) => p.theme.color.text01}; + margin-right: 12px; +` + +export const ProjectDetailDescription = styled.span` + font-family: Poppins; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.01em; + color: ${(p) => p.theme.color.text02}; + margin-right: 12px; + max-width: 80%; +` + +export const ProjectDetailImage = styled.img` + width: 24px; + height: 24px; + margin-right: 5px; + border-radius: 100%; +` + +export const ProjectDetailButtonRow = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + border: 1px solid #E5E8EB; + border-radius: 6.5px; +` + +export const ProjectDetailButtonSeperator = styled.div` + display: flex; + width: 1px; + height: 32px; + background-color: #E5E8EB; +` + +export const ProjectDetailButton = styled(WalletButton)` + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + outline: none; + border: none; + background: none; + padding: 0px; + margin: 0px; +` + +export const ProjectWebsiteIcon = styled.div` + width: 14px; + height: 14px; + background-color: #8A939B; + -webkit-mask-image: url(${WebsiteIcon}); + mask-image: url(${WebsiteIcon}); + mask-size: contain; +` + +export const ProjectTwitterIcon = styled.div` + width: 14px; + height: 14px; + background-color: #8A939B; + -webkit-mask-image: url(${TwitterIcon}); + mask-image: url(${TwitterIcon}); + mask-size: contain; +` + +export const ProjectFacebookIcon = styled.div` + width: 14px; + height: 14px; + background-color: #8A939B; + -webkit-mask-image: url(${FacebookIcon}); + mask-image: url(${FacebookIcon}); + mask-size: contain; +` + +export const ExplorerButton = styled(WalletButton)` + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + outline: none; + border: none; + background: none; + padding: 0px; + margin: 0px; +` + +export const ExplorerIcon = styled(OpenNewIcon)` + width: 14px; + height: 14px; + color: ${(p) => p.theme.color.interactive05}; +` diff --git a/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx b/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx new file mode 100644 index 000000000000..7bd4948452cb --- /dev/null +++ b/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx @@ -0,0 +1,151 @@ +import * as React from 'react' + +// Types +import { + BraveWallet, + NFTMetadataReturnType +} from '../../../constants/types' + +// Hooks +import { useExplorer } from '../../../common/hooks' + +// Utils +import Amount from '../../../utils/amount' +import { getLocale } from '$web-common/locale' + +// Styled Components +import { + StyledWrapper, + DetailColumn, + TokenName, + DetailSectionRow, + DetailSectionColumn, + DetailSectionTitle, + DetailSectionValue, + ProjectDetailRow, + ProjectDetailName, + ProjectDetailDescription, + ProjectDetailButtonRow, + ProjectDetailButton, + ProjectDetailButtonSeperator, + ProjectWebsiteIcon, + ProjectTwitterIcon, + ProjectFacebookIcon, + ProjectDetailIDRow, + ExplorerIcon, + ExplorerButton, + NTFImage, + NftImageWrapper, + NFTImageSkeletonWrapper +} from './nft-details-styles' +import { LoadingSkeleton } from '../../../components/shared' + +interface Props { + isLoading?: boolean + selectedAsset: BraveWallet.BlockchainToken + nftMetadata?: NFTMetadataReturnType + tokenNetwork?: BraveWallet.NetworkInfo +} + +export const NftDetails = ({ selectedAsset, nftMetadata, tokenNetwork }: Props) => { + const [isImageLoaded, setIsImageLoaded] = React.useState() + const onClickViewOnBlockExplorer = useExplorer(tokenNetwork || new BraveWallet.NetworkInfo()) + + const onClickWebsite = () => { + chrome.tabs.create({ url: nftMetadata?.contractInformation?.website }, () => { + if (chrome.runtime.lastError) { + console.error('tabs.create failed: ' + chrome.runtime.lastError.message) + } + }) + } + + const onClickTwitter = () => { + chrome.tabs.create({ url: nftMetadata?.contractInformation?.twitter }, () => { + if (chrome.runtime.lastError) { + console.error('tabs.create failed: ' + chrome.runtime.lastError.message) + } + }) + } + + const onClickFacebook = () => { + chrome.tabs.create({ url: nftMetadata?.contractInformation?.facebook }, () => { + if (chrome.runtime.lastError) { + console.error('tabs.create failed: ' + chrome.runtime.lastError.message) + } + }) + } + + return ( + + {nftMetadata && + <> + + setIsImageLoaded(true)} /> + + {!isImageLoaded && + + } + + + {selectedAsset.name} { + selectedAsset.tokenId + ? '#' + new Amount(selectedAsset.tokenId).toNumber() + : '' + } + + {/* TODO: Add floorFiatPrice & floorCryptoPrice when data is available from backend: https://github.com/brave/brave-browser/issues/22627 */} + {/* {CurrencySymbols[defaultCurrencies.fiat]}{nftMetadata.floorFiatPrice} */} + {/* {nftMetadata.floorCryptoPrice} {selectedNetwork.symbol} */} + + + {getLocale('braveWalletNFTDetailBlockchain')} + {nftMetadata.chainName} + + + {getLocale('braveWalletNFTDetailTokenStandard')} + {nftMetadata.tokenType} + + + {getLocale('braveWalletNFTDetailTokenID')} + + + { + selectedAsset.tokenId + ? '#' + new Amount(selectedAsset.tokenId).toNumber() + : '' + } + + + + + + + + + {/* TODO: Add nft logo when data is available from backend: https://github.com/brave/brave-browser/issues/22627 */} + {/* */} + {nftMetadata.contractInformation.name} + {nftMetadata.contractInformation.website && nftMetadata.contractInformation.twitter && nftMetadata.contractInformation.facebook && + + + + + + + + + + + + + + } + + {nftMetadata.contractInformation.description} + + + } + + + ) +} diff --git a/components/brave_wallet_ui/nft/css/nft-global.css b/components/brave_wallet_ui/nft/css/nft-global.css new file mode 100644 index 000000000000..72ac477c2f17 --- /dev/null +++ b/components/brave_wallet_ui/nft/css/nft-global.css @@ -0,0 +1,7 @@ +html, body { + width: 100%; + height: 100%; + padding: 0; + margin: 0; + overflow: hidden; +} From fe01c959884636fd42938d3166eb533acf87ff15 Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 15:04:28 +0300 Subject: [PATCH 22/39] Added utilities for communicating with nft chrome untrusted tab using postmessage --- .../brave_wallet_ui/nft/nft-ui-messages.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 components/brave_wallet_ui/nft/nft-ui-messages.ts diff --git a/components/brave_wallet_ui/nft/nft-ui-messages.ts b/components/brave_wallet_ui/nft/nft-ui-messages.ts new file mode 100644 index 000000000000..d20b1366d52a --- /dev/null +++ b/components/brave_wallet_ui/nft/nft-ui-messages.ts @@ -0,0 +1,52 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import { loadTimeData } from '../../common/loadTimeData' +import { BraveWallet, NFTMetadataReturnType } from '../constants/types' + +const walletOrigin = loadTimeData.getString('braveWalletBridgeUrl') +// remove trailing / +export const braveWalletOrigin = walletOrigin.endsWith('/') ? walletOrigin.slice(0, -1) : walletOrigin + +export const enum NftUiCommand { + UpdateLoading = 'update-loading', + UpdateSelectedAsset = 'update-selected-asset', + UpdateNFTMetadata = 'update-nft-metadata', + UpdateTokenNetwork = 'update-token-network' +} + +export type CommandMessage = { + command: NftUiCommand +} + +export type UpdateLoadingMessage = CommandMessage & { + payload: boolean +} + +export type UpdateSelectedAssetMessage = CommandMessage & { + payload: BraveWallet.BlockchainToken +} + +export type UpdateNFtMetadataMessage = CommandMessage & { + payload: NFTMetadataReturnType +} + +export type UpdateTokenNetworkMessage = CommandMessage & { + payload: BraveWallet.NetworkInfo +} + +export const sendMessageToNftUiFrame = (targetWindow: Window | null, message: CommandMessage) => { + if (targetWindow) { + // Using targetOrigin '*' is not recommended for security reasons + // The challenge with the current use case is that brave wallet's origin is chrome://wallet + // and the iframe's origin is chrome-untrusted://nft-display + // the two origins do not share the same scheme as required by window.postMessage + // https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage + // the solution I have implemented is to validate the origin of the message before + // proceeding to with the logic + // if alternatives exist, we can refactor. + targetWindow.postMessage(message, '*') + } +} From c06ec88d7a6b1c57e2760520d6d700650b4b5cad Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 15:53:06 +0300 Subject: [PATCH 23/39] chore: remove unused component --- .../components/nft-details/index.tsx | 175 ------------ .../portfolio/components/nft-details/style.ts | 251 ------------------ 2 files changed, 426 deletions(-) delete mode 100644 components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx delete mode 100644 components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx deleted file mode 100644 index 9ba2acb624e9..000000000000 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/index.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import * as React from 'react' -import { useSelector } from 'react-redux' - -// Types -import { - BraveWallet, - WalletState, - PageState -} from '../../../../../../constants/types' - -// Hooks -import { useExplorer } from '../../../../../../common/hooks' - -// Utils -import Amount from '../../../../../../utils/amount' -import { getLocale } from '$web-common/locale' - -// Styled Components -import { - StyledWrapper, - LoadIcon, - LoadingOverlay, - DetailColumn, - TokenName, - DetailSectionRow, - DetailSectionColumn, - DetailSectionTitle, - DetailSectionValue, - ProjectDetailRow, - ProjectDetailName, - ProjectDetailDescription, - ProjectDetailButtonRow, - ProjectDetailButton, - ProjectDetailButtonSeperator, - ProjectWebsiteIcon, - ProjectTwitterIcon, - ProjectFacebookIcon, - ProjectDetailIDRow, - ExplorerIcon, - ExplorerButton, - NTFImageIframe, - nftImageDimension, - NftImageWrapper, - NFTImageSkeletonWrapper -} from './style' -import { LoadingSkeleton } from '../../../../../shared' -import { getTokensNetwork } from '../../../../../../utils/network-utils' - -export interface Props { - selectedAsset: BraveWallet.BlockchainToken -} - -const NFTDetails = ({ selectedAsset }: Props) => { - const [isImageLoaded, setIsImageLoaded] = React.useState(false) - - // redux - const { networkList } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) - const { - isFetchingNFTMetadata: isLoading, - nftMetadata - } = useSelector(({ page }: { page: PageState }) => page) - const tokenNetwork = getTokensNetwork(networkList, selectedAsset) - - // hooks - const onClickViewOnBlockExplorer = useExplorer(tokenNetwork) - - const onClickWebsite = () => { - chrome.tabs.create({ url: nftMetadata?.contractInformation?.website }, () => { - if (chrome.runtime.lastError) { - console.error('tabs.create failed: ' + chrome.runtime.lastError.message) - } - }) - } - - const onClickTwitter = () => { - chrome.tabs.create({ url: nftMetadata?.contractInformation?.twitter }, () => { - if (chrome.runtime.lastError) { - console.error('tabs.create failed: ' + chrome.runtime.lastError.message) - } - }) - } - - const onClickFacebook = () => { - chrome.tabs.create({ url: nftMetadata?.contractInformation?.facebook }, () => { - if (chrome.runtime.lastError) { - console.error('tabs.create failed: ' + chrome.runtime.lastError.message) - } - }) - } - - return ( - - {isLoading && - - - - } - {nftMetadata && - <> - - setIsImageLoaded(true)} - /> - - {!isImageLoaded && - - } - - - {selectedAsset.name} { - selectedAsset.tokenId - ? '#' + new Amount(selectedAsset.tokenId).toNumber() - : '' - } - - {/* TODO: Add floorFiatPrice & floorCryptoPrice when data is available from backend: https://github.com/brave/brave-browser/issues/22627 */} - {/* {CurrencySymbols[defaultCurrencies.fiat]}{nftMetadata.floorFiatPrice} */} - {/* {nftMetadata.floorCryptoPrice} {selectedNetwork.symbol} */} - - - {getLocale('braveWalletNFTDetailBlockchain')} - {nftMetadata.chainName} - - - {getLocale('braveWalletNFTDetailTokenStandard')} - {nftMetadata.tokenType} - - - {getLocale('braveWalletNFTDetailTokenID')} - - - { - selectedAsset.tokenId - ? '#' + new Amount(selectedAsset.tokenId).toNumber() - : '' - } - - - - - - - - - {/* TODO: Add nft logo when data is available from backend: https://github.com/brave/brave-browser/issues/22627 */} - {/* */} - {nftMetadata.contractInformation.name} - {nftMetadata.contractInformation.website && nftMetadata.contractInformation.twitter && nftMetadata.contractInformation.facebook && - - - - - - - - - - - - - - } - - {nftMetadata.contractInformation.description} - - - } - - - ) -} - -export default NFTDetails diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts b/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts deleted file mode 100644 index 3acc56bb5363..000000000000 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/components/nft-details/style.ts +++ /dev/null @@ -1,251 +0,0 @@ -import styled from 'styled-components' -import { WalletButton } from '../../../../../shared/style' -import WebsiteIcon from '../../../../../../assets/svg-icons/website-icon.svg' -import TwitterIcon from '../../../../../../assets/svg-icons/twitter-icon.svg' -import FacebookIcon from '../../../../../../assets/svg-icons/facebook-icon.svg' -import { LoaderIcon, OpenNewIcon } from 'brave-ui/components/icons' - -export interface StyleProps { - isLoading: boolean -} - -export const nftImageDimension = '440px' - -export const StyledWrapper = styled.div` - display: flex; - flex-direction: row; - align-items: flex-start; - justify-content: flex-start; - width: 100%; - min-height: ${nftImageDimension}; - margin: 16px 0 50px 0; -` - -export const NftImageWrapper = styled.div` - display: flex; - width: ${p => p.isLoading ? 0 : nftImageDimension}; - height: ${p => p.isLoading ? 0 : nftImageDimension}; - visibility: ${p => p.isLoading ? 'hidden' : 'visible'}; - margin-right: ${p => p.isLoading ? 0 : '28px'};; - border-radius: 12px; -` - -export const NTFImageIframe = styled.iframe` - display: block; - width: ${nftImageDimension}; - height: ${nftImageDimension}; - border: transparent; -` - -export const NFTImageSkeletonWrapper = styled.div` - min-width: ${nftImageDimension}; - height: ${nftImageDimension}; - margin-right: 28px; - border-radius: 12px; -` - -export const DetailColumn = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: flex-start; - width: 100%; -` - -export const TokenName = styled.span` - font-family: Poppins; - font-size: 20px; - line-height: 30px; - font-weight: 600; - letter-spacing: 0.02em; - color: ${(p) => p.theme.color.text01}; - margin-bottom: 15px; -` - -export const TokenFiatValue = styled.span` - font-family: Poppins; - font-size: 24px; - line-height: 36px; - font-weight: 600; - letter-spacing: 0.02em; - color: ${(p) => p.theme.color.text01}; -` - -export const TokenCryptoValue = styled.span` - font-family: Poppins; - font-size: 12px; - line-height: 20px; - letter-spacing: 0.01em; - color: ${(p) => p.theme.color.text03}; - margin-bottom: 20px; -` - -export const DetailSectionRow = styled.div` - display: flex; - flex-direction: row; - align-items: flex-start; - justify-content: space-between; - min-width: 70%; - margin-bottom: 30px; -` - -export const DetailSectionColumn = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: flex-start; -` - -export const DetailSectionTitle = styled.span` - font-family: Poppins; - font-size: 12px; - line-height: 18px; - letter-spacing: 0.01em; - color: ${(p) => p.theme.color.text02}; - margin-bottom: 10px; -` - -export const DetailSectionValue = styled.span` - font-family: Poppins; - font-size: 15px; - line-height: 20px; - font-weight: 600; - letter-spacing: 0.04em; - color: ${(p) => p.theme.color.text01}; - margin-right: 8px; -` - -export const ProjectDetailIDRow = styled.div` - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -` - -export const ProjectDetailRow = styled.div` - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - margin-bottom: 20px; -` - -export const ProjectDetailName = styled.span` - font-family: Poppins; - font-size: 14px; - line-height: 24px; - font-weight: 500; - color: ${(p) => p.theme.color.text01}; - margin-right: 12px; -` - -export const ProjectDetailDescription = styled.span` - font-family: Poppins; - font-size: 14px; - line-height: 20px; - letter-spacing: 0.01em; - color: ${(p) => p.theme.color.text02}; - margin-right: 12px; - max-width: 80%; -` - -export const ProjectDetailImage = styled.img` - width: 24px; - height: 24px; - margin-right: 5px; - border-radius: 100%; -` - -export const ProjectDetailButtonRow = styled.div` - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - border: 1px solid #E5E8EB; - border-radius: 6.5px; -` - -export const ProjectDetailButtonSeperator = styled.div` - display: flex; - width: 1px; - height: 32px; - background-color: #E5E8EB; -` - -export const ProjectDetailButton = styled(WalletButton)` - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - outline: none; - border: none; - background: none; - padding: 0px; - margin: 0px; -` - -export const ProjectWebsiteIcon = styled.div` - width: 14px; - height: 14px; - background-color: #8A939B; - -webkit-mask-image: url(${WebsiteIcon}); - mask-image: url(${WebsiteIcon}); - mask-size: contain; -` - -export const ProjectTwitterIcon = styled.div` - width: 14px; - height: 14px; - background-color: #8A939B; - -webkit-mask-image: url(${TwitterIcon}); - mask-image: url(${TwitterIcon}); - mask-size: contain; -` - -export const ProjectFacebookIcon = styled.div` - width: 14px; - height: 14px; - background-color: #8A939B; - -webkit-mask-image: url(${FacebookIcon}); - mask-image: url(${FacebookIcon}); - mask-size: contain; -` - -export const ExplorerButton = styled(WalletButton)` - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - outline: none; - border: none; - background: none; - padding: 0px; - margin: 0px; -` - -export const ExplorerIcon = styled(OpenNewIcon)` - width: 14px; - height: 14px; - color: ${(p) => p.theme.color.interactive05}; -` - -export const LoadingOverlay = styled.div>` - display: ${(p) => p.isLoading ? 'flex' : 'none'}; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - height: 440px; - position: absolute; - z-index: 10; - backdrop-filter: blur(5px); -` - -export const LoadIcon = styled(LoaderIcon)` - color: ${p => p.theme.color.interactive08}; - height: 70px; - width: 70px; - opacity: .4; -` From f1da32422bcc6776bad711cf89afb47b356871d0 Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 16:31:45 +0300 Subject: [PATCH 24/39] chore: validate url to avoid xss attack --- .../components/nft-details/nft-details.tsx | 29 +++++++++---------- .../brave_wallet_ui/utils/string-utils.ts | 5 ++++ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx b/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx index 7bd4948452cb..ffe6536bfcdb 100644 --- a/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx +++ b/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx @@ -39,6 +39,7 @@ import { NFTImageSkeletonWrapper } from './nft-details-styles' import { LoadingSkeleton } from '../../../components/shared' +import { isValidateUrl } from '../../../utils/string-utils' interface Props { isLoading?: boolean @@ -51,28 +52,26 @@ export const NftDetails = ({ selectedAsset, nftMetadata, tokenNetwork }: Props) const [isImageLoaded, setIsImageLoaded] = React.useState() const onClickViewOnBlockExplorer = useExplorer(tokenNetwork || new BraveWallet.NetworkInfo()) + const onClickLink = React.useCallback((url?: string) => { + if (url && isValidateUrl(url)) { + chrome.tabs.create({ url }, () => { + if (chrome.runtime.lastError) { + console.error('tabs.create failed: ' + chrome.runtime.lastError.message) + } + }) + } + }, []) + const onClickWebsite = () => { - chrome.tabs.create({ url: nftMetadata?.contractInformation?.website }, () => { - if (chrome.runtime.lastError) { - console.error('tabs.create failed: ' + chrome.runtime.lastError.message) - } - }) + onClickLink(nftMetadata?.contractInformation?.website) } const onClickTwitter = () => { - chrome.tabs.create({ url: nftMetadata?.contractInformation?.twitter }, () => { - if (chrome.runtime.lastError) { - console.error('tabs.create failed: ' + chrome.runtime.lastError.message) - } - }) + onClickLink(nftMetadata?.contractInformation?.twitter) } const onClickFacebook = () => { - chrome.tabs.create({ url: nftMetadata?.contractInformation?.facebook }, () => { - if (chrome.runtime.lastError) { - console.error('tabs.create failed: ' + chrome.runtime.lastError.message) - } - }) + onClickLink(nftMetadata?.contractInformation?.facebook) } return ( diff --git a/components/brave_wallet_ui/utils/string-utils.ts b/components/brave_wallet_ui/utils/string-utils.ts index 02dab6465ede..89656a05ce74 100644 --- a/components/brave_wallet_ui/utils/string-utils.ts +++ b/components/brave_wallet_ui/utils/string-utils.ts @@ -42,3 +42,8 @@ export const getRampNetworkPrefix = (chainId: string) => { export const formatAsDouble = (value: string): string => // Removes all characters except numbers, commas and decimals value.replace(/[^0-9.,]+/g, '') + +export const isValidateUrl = (url: string) => { + const re = /^\s*https?:\/\// + return re.test(url) +} From 756264a88b3d02d6967a5c8677cbf0423b2ee28b Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 17:33:41 +0300 Subject: [PATCH 25/39] feat: change nft icon display from query parameter(imageUrl) to postmessage api --- .../desktop/asset-watchlist-item/index.tsx | 4 +-- .../desktop/portfolio-asset-item/index.tsx | 6 ++-- .../desktop/portfolio-asset-item/style.ts | 7 +--- .../shared/nft-icon/nft-icon-styles.tsx | 7 ++++ .../components/shared/nft-icon/nft-icon.tsx | 34 +++++++++++++++++++ .../components/shared/style.tsx | 5 --- .../nft-content/nft-content-styles.ts | 5 ++- .../components/nft-content/nft-content.tsx | 18 ++-------- .../brave_wallet_ui/nft/nft-ui-messages.ts | 7 +++- components/brave_wallet_ui/nft/nft.tsx | 10 +++++- 10 files changed, 66 insertions(+), 37 deletions(-) create mode 100644 components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.tsx create mode 100644 components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx diff --git a/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx b/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx index a63ade631cbf..099902498332 100644 --- a/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx +++ b/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx @@ -24,7 +24,7 @@ import { NameAndSymbol, AssetSymbol } from './style' -import { NFTAssetIcon } from '../portfolio-asset-item/style' +import { NftIcon } from '../../shared/nft-icon/nft-icon' export interface Props { onSelectAsset: (key: string, selected: boolean, token: BraveWallet.BlockchainToken, isCustom: boolean) => void @@ -58,7 +58,7 @@ const AssetWatchlistItem = (props: Props) => { } const AssetIconWithPlaceholder = React.useMemo(() => { - return withPlaceholderIcon(token.isErc721 ? NFTAssetIcon : AssetIcon, { size: 'big', marginLeft: 0, marginRight: 8 }) + return withPlaceholderIcon(token.isErc721 ? NftIcon : AssetIcon, { size: 'big', marginLeft: 0, marginRight: 8 }) }, [token]) const tokensNetwork = React.useMemo(() => { diff --git a/components/brave_wallet_ui/components/desktop/portfolio-asset-item/index.tsx b/components/brave_wallet_ui/components/desktop/portfolio-asset-item/index.tsx index 1933d0ab52c7..93d8ddedcd8a 100644 --- a/components/brave_wallet_ui/components/desktop/portfolio-asset-item/index.tsx +++ b/components/brave_wallet_ui/components/desktop/portfolio-asset-item/index.tsx @@ -20,8 +20,7 @@ import { NetworkIconWrapper, NameColumn, Spacer, - NetworkDescriptionText, - NFTAssetIcon + NetworkDescriptionText } from './style' import { withPlaceholderIcon, CreateNetworkIcon, LoadingSkeleton } from '../../shared' import { WithHideBalancePlaceholder } from '../' @@ -31,6 +30,7 @@ import { getTokensNetwork } from '../../../utils/network-utils' // Hooks import { usePricing } from '../../../common/hooks' import { unbiasedRandom } from '../../../utils/random-utils' +import { NftIcon } from '../../shared/nft-icon/nft-icon' interface Props { spotPrices: BraveWallet.AssetPrice[] @@ -58,7 +58,7 @@ const PortfolioAssetItem = (props: Props) => { const [assetNetworkSkeletonWidth, setAssetNetworkSkeletonWidth] = React.useState(0) const AssetIconWithPlaceholder = React.useMemo(() => { - return withPlaceholderIcon(token.isErc721 ? NFTAssetIcon : AssetIcon, { size: 'big', marginLeft: 0, marginRight: 8 }) + return withPlaceholderIcon(token.isErc721 ? NftIcon : AssetIcon, { size: 'big', marginLeft: 0, marginRight: 8 }) }, [token]) const formattedAssetBalance = token.isErc721 diff --git a/components/brave_wallet_ui/components/desktop/portfolio-asset-item/style.ts b/components/brave_wallet_ui/components/desktop/portfolio-asset-item/style.ts index 69ab75d1a91b..88b82d8fab91 100644 --- a/components/brave_wallet_ui/components/desktop/portfolio-asset-item/style.ts +++ b/components/brave_wallet_ui/components/desktop/portfolio-asset-item/style.ts @@ -1,5 +1,5 @@ import styled from 'styled-components' -import { AssetIconProps, AssetIconFactory, WalletButton, AssetIconIframe } from '../../shared/style' +import { AssetIconProps, AssetIconFactory, WalletButton } from '../../shared/style' interface StyleProps { disabled: boolean @@ -65,11 +65,6 @@ const assetIconProps = { height: 'auto' } export const AssetIcon = AssetIconFactory(assetIconProps) -export const NFTAssetIcon = AssetIconIframe({ - ...assetIconProps, - height: '40px', - border: 'transparent' -}) export const IconsWrapper = styled.div` display: flex; diff --git a/components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.tsx b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.tsx new file mode 100644 index 000000000000..04f2e6178148 --- /dev/null +++ b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.tsx @@ -0,0 +1,7 @@ +import styled from 'styled-components' + +export const NftImageIframe = styled.iframe` + border: none; + width: ${p => p.width ? p.width : '40px'}; + height: ${p => p.height ? p.height : '40px'}; +` diff --git a/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx new file mode 100644 index 000000000000..7974bdf1115b --- /dev/null +++ b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx @@ -0,0 +1,34 @@ +import * as React from 'react' + +import { AssetIconProps } from '../style' +import { + NftUiCommand, + sendMessageToNftUiFrame, + UpdateNftImageUrl +} from '../../../nft/nft-ui-messages' + +import { NftImageIframe } from './nft-icon-styles' + +export const NftIcon = ({ icon }: AssetIconProps) => { + const [loaded, setLoaded] = React.useState() + const nftImageIframeRef = React.useRef(null) + + React.useEffect(() => { + if (loaded && icon && nftImageIframeRef && nftImageIframeRef.current) { + const command: UpdateNftImageUrl = { + command: NftUiCommand.UpdateNFTImageUrl, + payload: icon + } + sendMessageToNftUiFrame(nftImageIframeRef.current.contentWindow, command) + } + }, [loaded, icon, nftImageIframeRef]) + + return ( + setLoaded(true)} + ref={nftImageIframeRef} + src="chrome-untrusted://nft-display" + sandbox="allow-scripts" + /> + ) +} diff --git a/components/brave_wallet_ui/components/shared/style.tsx b/components/brave_wallet_ui/components/shared/style.tsx index f9ed2d58c1c3..349c0cb990ce 100644 --- a/components/brave_wallet_ui/components/shared/style.tsx +++ b/components/brave_wallet_ui/components/shared/style.tsx @@ -56,11 +56,6 @@ export const AssetIconFactory = styled.img.attrs(props => ({ loading: 'lazy' })) -export const AssetIconIframe = styled.iframe.attrs(props => ({ - src: `chrome-untrusted://nft-display?imageUrl=${encodeURIComponent(props.icon || '')}`, - sandbox: 'allow-scripts' -})) - export const WalletButton = styled.button<{ isDraggedOver?: boolean }>` diff --git a/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts b/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts index abf04228eccb..2d5b0db90b4c 100644 --- a/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts +++ b/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts @@ -14,9 +14,8 @@ interface Props { export const Image = styled.img` border-radius: 4px; - border-color: ${p => p.theme.color.interactive08}; - width: ${p => p.customWidth ? p.customWidth : '100%'}; - height: ${p => p.customHeight ? p.customHeight : 'auto'}; + width: 100%; + height: auto ` export const LoadingOverlay = styled.div>` diff --git a/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx b/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx index 7869f9704911..8eccfac0d095 100644 --- a/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx +++ b/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx @@ -1,5 +1,4 @@ import * as React from 'react' -import { useLocation } from 'react-router-dom' // components import { Image, LoadingOverlay, LoadIcon } from './nft-content-styles' @@ -13,29 +12,16 @@ interface Props { selectedAsset?: BraveWallet.BlockchainToken nftMetadata?: NFTMetadataReturnType tokenNetwork?: BraveWallet.NetworkInfo + imageUrl?: string } export const NftContent = (props: Props) => { - const { isLoading, selectedAsset } = props - - // hooks - const { search } = useLocation() - - // memos - const params = React.useMemo(() => { - return new URLSearchParams(search) - }, [search]) - - const imageUrl = params.get('imageUrl') - const imageWidth = params.get('imageWidth') - const imageHeight = params.get('imageHeight') + const { isLoading, selectedAsset, imageUrl } = props return ( <> {imageUrl ? : <> diff --git a/components/brave_wallet_ui/nft/nft-ui-messages.ts b/components/brave_wallet_ui/nft/nft-ui-messages.ts index d20b1366d52a..25ac202e09d3 100644 --- a/components/brave_wallet_ui/nft/nft-ui-messages.ts +++ b/components/brave_wallet_ui/nft/nft-ui-messages.ts @@ -14,7 +14,8 @@ export const enum NftUiCommand { UpdateLoading = 'update-loading', UpdateSelectedAsset = 'update-selected-asset', UpdateNFTMetadata = 'update-nft-metadata', - UpdateTokenNetwork = 'update-token-network' + UpdateTokenNetwork = 'update-token-network', + UpdateNFTImageUrl = 'update-nft-image-url' } export type CommandMessage = { @@ -37,6 +38,10 @@ export type UpdateTokenNetworkMessage = CommandMessage & { payload: BraveWallet.NetworkInfo } +export type UpdateNftImageUrl = CommandMessage & { + payload: string +} + export const sendMessageToNftUiFrame = (targetWindow: Window | null, message: CommandMessage) => { if (targetWindow) { // Using targetOrigin '*' is not recommended for security reasons diff --git a/components/brave_wallet_ui/nft/nft.tsx b/components/brave_wallet_ui/nft/nft.tsx index ad31dc6c1d2b..3fe71278cc07 100644 --- a/components/brave_wallet_ui/nft/nft.tsx +++ b/components/brave_wallet_ui/nft/nft.tsx @@ -24,7 +24,7 @@ import { braveWalletOrigin, CommandMessage, NftUiCommand, - UpdateLoadingMessage, + UpdateLoadingMessage, UpdateNftImageUrl, UpdateNFtMetadataMessage, UpdateSelectedAssetMessage, UpdateTokenNetworkMessage @@ -39,6 +39,7 @@ const App = () => { const [selectedAsset, setSelectedAsset] = React.useState() const [nftMetadata, setNftMetadata] = React.useState() const [tokenNetwork, setTokenNetwork] = React.useState() + const [imageUrl, setImageUrl] = React.useState() const onMessageEventListener = React.useCallback((event: MessageEvent) => { // validate message origin @@ -69,6 +70,12 @@ const App = () => { setTokenNetwork(payload) break } + + case NftUiCommand.UpdateNFTImageUrl: { + const { payload } = message as UpdateNftImageUrl + setImageUrl(payload) + break + } } }, []) @@ -88,6 +95,7 @@ const App = () => { selectedAsset={selectedAsset} nftMetadata={nftMetadata} tokenNetwork={tokenNetwork} + imageUrl={imageUrl} /> From 2128ceacf35238c73c3ca1c196060d7b235deae2 Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 18:23:29 +0300 Subject: [PATCH 26/39] use specific targetOrigin in postMessage --- .../components/shared/nft-icon/nft-icon.tsx | 2 +- components/brave_wallet_ui/nft/nft-ui-messages.ts | 12 +++--------- components/brave_wallet_ui/nft/nft.tsx | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx index 7974bdf1115b..02fa021bc19e 100644 --- a/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx +++ b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx @@ -28,7 +28,7 @@ export const NftIcon = ({ icon }: AssetIconProps) => { onLoad={() => setLoaded(true)} ref={nftImageIframeRef} src="chrome-untrusted://nft-display" - sandbox="allow-scripts" + sandbox="allow-scripts allow-same-origin" /> ) } diff --git a/components/brave_wallet_ui/nft/nft-ui-messages.ts b/components/brave_wallet_ui/nft/nft-ui-messages.ts index 25ac202e09d3..ea8d07050537 100644 --- a/components/brave_wallet_ui/nft/nft-ui-messages.ts +++ b/components/brave_wallet_ui/nft/nft-ui-messages.ts @@ -7,8 +7,10 @@ import { loadTimeData } from '../../common/loadTimeData' import { BraveWallet, NFTMetadataReturnType } from '../constants/types' const walletOrigin = loadTimeData.getString('braveWalletBridgeUrl') +const nftDisplayOrigin = loadTimeData.getString('braveWalletNftBridgeUrl') // remove trailing / export const braveWalletOrigin = walletOrigin.endsWith('/') ? walletOrigin.slice(0, -1) : walletOrigin +export const braveNftDisplayOrigin = nftDisplayOrigin.endsWith('/') ? nftDisplayOrigin.slice(0, -1) : nftDisplayOrigin export const enum NftUiCommand { UpdateLoading = 'update-loading', @@ -44,14 +46,6 @@ export type UpdateNftImageUrl = CommandMessage & { export const sendMessageToNftUiFrame = (targetWindow: Window | null, message: CommandMessage) => { if (targetWindow) { - // Using targetOrigin '*' is not recommended for security reasons - // The challenge with the current use case is that brave wallet's origin is chrome://wallet - // and the iframe's origin is chrome-untrusted://nft-display - // the two origins do not share the same scheme as required by window.postMessage - // https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage - // the solution I have implemented is to validate the origin of the message before - // proceeding to with the logic - // if alternatives exist, we can refactor. - targetWindow.postMessage(message, '*') + targetWindow.postMessage(message, braveNftDisplayOrigin) } } diff --git a/components/brave_wallet_ui/nft/nft.tsx b/components/brave_wallet_ui/nft/nft.tsx index 3fe71278cc07..02bcc19f1288 100644 --- a/components/brave_wallet_ui/nft/nft.tsx +++ b/components/brave_wallet_ui/nft/nft.tsx @@ -40,7 +40,7 @@ const App = () => { const [nftMetadata, setNftMetadata] = React.useState() const [tokenNetwork, setTokenNetwork] = React.useState() const [imageUrl, setImageUrl] = React.useState() - + console.log(imageUrl) const onMessageEventListener = React.useCallback((event: MessageEvent) => { // validate message origin if (event.origin !== braveWalletOrigin) return From 7c3d68a4dd7fc5e6aa2856c76000f869e7a7238a Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 21 Jul 2022 18:25:06 +0300 Subject: [PATCH 27/39] chore: remove console.log --- components/brave_wallet_ui/nft/nft.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/brave_wallet_ui/nft/nft.tsx b/components/brave_wallet_ui/nft/nft.tsx index 02bcc19f1288..3fe71278cc07 100644 --- a/components/brave_wallet_ui/nft/nft.tsx +++ b/components/brave_wallet_ui/nft/nft.tsx @@ -40,7 +40,7 @@ const App = () => { const [nftMetadata, setNftMetadata] = React.useState() const [tokenNetwork, setTokenNetwork] = React.useState() const [imageUrl, setImageUrl] = React.useState() - console.log(imageUrl) + const onMessageEventListener = React.useCallback((event: MessageEvent) => { // validate message origin if (event.origin !== braveWalletOrigin) return From 200efb9e5741ea093978358eeebb3108a39b1bfa Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 22 Jul 2022 09:57:44 +0300 Subject: [PATCH 28/39] chore: fix feedback --- .../desktop/views/portfolio/portfolio-asset.tsx | 6 +++--- .../components/desktop/views/portfolio/style.ts | 15 ++++----------- .../components/shared/nft-icon/nft-icon-styles.ts | 12 ++++++++++++ .../shared/nft-icon/nft-icon-styles.tsx | 7 ------- .../components/shared/nft-icon/nft-icon.tsx | 7 ++++++- .../components/nft-content/nft-content-styles.ts | 9 +++------ .../nft/components/nft-content/nft-content.tsx | 11 ++++++++++- .../components/nft-details/nft-details-styles.ts | 11 ++++++----- .../nft/components/nft-details/nft-details.tsx | 5 +++++ 9 files changed, 49 insertions(+), 34 deletions(-) create mode 100644 components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.ts delete mode 100644 components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.tsx diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index 5817f6e91c43..d72de089da10 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -305,7 +305,7 @@ export const PortfolioAsset = () => { }, [selectedAssetFromParams]) React.useEffect(() => { - if (nftDetailsRef && nftDetailsRef.current) { + if (nftDetailsRef?.current) { const command: UpdateLoadingMessage = { command: NftUiCommand.UpdateLoading, payload: isFetchingNFTMetadata @@ -315,7 +315,7 @@ export const PortfolioAsset = () => { }, [nftDetailsRef, isFetchingNFTMetadata]) React.useEffect(() => { - if (selectedAsset && nftDetailsRef && nftDetailsRef.current) { + if (selectedAsset && nftDetailsRef.current) { const command: UpdateSelectedAssetMessage = { command: NftUiCommand.UpdateSelectedAsset, payload: selectedAsset @@ -332,7 +332,7 @@ export const PortfolioAsset = () => { sendMessageToNftUiFrame(nftDetailsRef.current.contentWindow, command) } - if (nftMetadata && nftDetailsRef && nftDetailsRef.current) { + if (nftMetadata && nftDetailsRef?.current) { const command: UpdateNFtMetadataMessage = { command: NftUiCommand.UpdateNFTMetadata, payload: nftMetadata diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/style.ts b/components/brave_wallet_ui/components/desktop/views/portfolio/style.ts index bad120897eca..fd31ce9b8624 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/style.ts +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/style.ts @@ -4,13 +4,6 @@ import EyeOnIcon from '../../../../assets/svg-icons/eye-on-icon.svg' import EyeOffIcon from '../../../../assets/svg-icons/eye-off-icon.svg' import { AssetIconProps, AssetIconFactory, WalletButton } from '../../../shared/style' -interface StyleProps { - isDown?: boolean - hideBalances?: boolean - visible?: boolean - isLoading?: boolean -} - export const StyledWrapper = styled.div` display: flex; flex-direction: column; @@ -152,7 +145,7 @@ export const DividerText = styled.span` color: ${(p) => p.theme.color.text03}; ` -export const PercentBubble = styled.div>` +export const PercentBubble = styled.div<{ isDown?: boolean}>` display: flex; align-items: center; justify-conent: center; @@ -170,7 +163,7 @@ export const PercentText = styled.span` color: ${(p) => p.theme.palette.white}; ` -export const ArrowIcon = styled(ArrowUpIcon) >` +export const ArrowIcon = styled(ArrowUpIcon) <{ isDown?: boolean }>` width: 12px; height: 12px; margin-right: 2px; @@ -229,7 +222,7 @@ export const CoinGeckoText = styled.span` margin: 15px 0px; ` -export const ShowBalanceButton = styled(WalletButton) >` +export const ShowBalanceButton = styled(WalletButton) <{ hideBalances?: boolean}>` display: flex; align-items: center; justify-content: center; @@ -256,7 +249,7 @@ export const FilterTokenRow = styled.div` width: 100%; ` -export const NftDetails = styled.iframe` +export const NftDetails = styled.iframe<{ visible?: boolean }>` width: 100%; min-height: ${p => p.visible ? '490px' : 'hidden'}; border: none; diff --git a/components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.ts b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.ts new file mode 100644 index 000000000000..0796c005e638 --- /dev/null +++ b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import styled from 'styled-components' + +export const NftImageIframe = styled.iframe` + border: none; + width: ${p => p.width ? p.width : '40px'}; + height: ${p => p.height ? p.height : '40px'}; +` diff --git a/components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.tsx b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.tsx deleted file mode 100644 index 04f2e6178148..000000000000 --- a/components/brave_wallet_ui/components/shared/nft-icon/nft-icon-styles.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import styled from 'styled-components' - -export const NftImageIframe = styled.iframe` - border: none; - width: ${p => p.width ? p.width : '40px'}; - height: ${p => p.height ? p.height : '40px'}; -` diff --git a/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx index 02fa021bc19e..29e02697015b 100644 --- a/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx +++ b/components/brave_wallet_ui/components/shared/nft-icon/nft-icon.tsx @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' import { AssetIconProps } from '../style' @@ -14,7 +19,7 @@ export const NftIcon = ({ icon }: AssetIconProps) => { const nftImageIframeRef = React.useRef(null) React.useEffect(() => { - if (loaded && icon && nftImageIframeRef && nftImageIframeRef.current) { + if (loaded && icon && nftImageIframeRef?.current) { const command: UpdateNftImageUrl = { command: NftUiCommand.UpdateNFTImageUrl, payload: icon diff --git a/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts b/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts index 2d5b0db90b4c..75c6a3bde21d 100644 --- a/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts +++ b/components/brave_wallet_ui/nft/components/nft-content/nft-content-styles.ts @@ -5,20 +5,17 @@ import styled from 'styled-components' import { LoaderIcon } from 'brave-ui/components/icons' -import { StyleProps } from '../nft-details/nft-details-styles' -interface Props { +export const Image = styled.img<{ customWidth?: string customHeight?: string -} - -export const Image = styled.img` +}>` border-radius: 4px; width: 100%; height: auto ` -export const LoadingOverlay = styled.div>` +export const LoadingOverlay = styled.div<{isLoading: boolean}>` display: ${(p) => p.isLoading ? 'flex' : 'none'}; flex-direction: column; align-items: center; diff --git a/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx b/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx index 8eccfac0d095..e3908b88b937 100644 --- a/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx +++ b/components/brave_wallet_ui/nft/components/nft-content/nft-content.tsx @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' // components @@ -16,7 +21,11 @@ interface Props { } export const NftContent = (props: Props) => { - const { isLoading, selectedAsset, imageUrl } = props + const { + isLoading, + selectedAsset, + imageUrl + } = props return ( <> diff --git a/components/brave_wallet_ui/nft/components/nft-details/nft-details-styles.ts b/components/brave_wallet_ui/nft/components/nft-details/nft-details-styles.ts index e455d843aeb9..226545a40ef8 100644 --- a/components/brave_wallet_ui/nft/components/nft-details/nft-details-styles.ts +++ b/components/brave_wallet_ui/nft/components/nft-details/nft-details-styles.ts @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import styled from 'styled-components' import { WalletButton } from '../../../components/shared/style' import WebsiteIcon from '../../../assets/svg-icons/website-icon.svg' @@ -5,10 +10,6 @@ import TwitterIcon from '../../../assets/svg-icons/twitter-icon.svg' import FacebookIcon from '../../../assets/svg-icons/facebook-icon.svg' import { OpenNewIcon } from 'brave-ui/components/icons' -export interface StyleProps { - isLoading: boolean -} - export const nftImageDimension = '440px' export const StyledWrapper = styled.div` @@ -21,7 +22,7 @@ export const StyledWrapper = styled.div` margin: 16px 0 50px 0; ` -export const NftImageWrapper = styled.div` +export const NftImageWrapper = styled.div<{ isLoading: boolean }>` display: flex; width: ${p => p.isLoading ? 0 : nftImageDimension}; height: ${p => p.isLoading ? 0 : nftImageDimension}; diff --git a/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx b/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx index ffe6536bfcdb..a8398f31995d 100644 --- a/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx +++ b/components/brave_wallet_ui/nft/components/nft-details/nft-details.tsx @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' // Types From e2ce2faa077383399d607fd21829c409e987e3ce Mon Sep 17 00:00:00 2001 From: William Muli Date: Fri, 22 Jul 2022 10:53:47 +0300 Subject: [PATCH 29/39] chore: feedback --- .../components/desktop/views/portfolio/portfolio-asset.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index d72de089da10..461a0b685626 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -315,7 +315,7 @@ export const PortfolioAsset = () => { }, [nftDetailsRef, isFetchingNFTMetadata]) React.useEffect(() => { - if (selectedAsset && nftDetailsRef.current) { + if (selectedAsset && nftDetailsRef?.current) { const command: UpdateSelectedAssetMessage = { command: NftUiCommand.UpdateSelectedAsset, payload: selectedAsset @@ -323,7 +323,7 @@ export const PortfolioAsset = () => { sendMessageToNftUiFrame(nftDetailsRef.current.contentWindow, command) } - if (selectedAsset && networkList && nftDetailsRef.current) { + if (selectedAsset && networkList && nftDetailsRef?.current) { const tokenNetwork = getTokensNetwork(networkList, selectedAsset) const command: UpdateTokenNetworkMessage = { command: NftUiCommand.UpdateTokenNetwork, From 995aa0bab99eaf7de05e8768136b2ffd32cf4287 Mon Sep 17 00:00:00 2001 From: William Muli Date: Sat, 23 Jul 2022 01:33:30 +0300 Subject: [PATCH 30/39] chore: add license header and documentation --- components/brave_wallet_ui/nft/css/nft-global.css | 5 +++++ components/brave_wallet_ui/nft/nft.html | 5 +++++ components/brave_wallet_ui/nft/nft.tsx | 3 +++ 3 files changed, 13 insertions(+) diff --git a/components/brave_wallet_ui/nft/css/nft-global.css b/components/brave_wallet_ui/nft/css/nft-global.css index 72ac477c2f17..f6f779910a56 100644 --- a/components/brave_wallet_ui/nft/css/nft-global.css +++ b/components/brave_wallet_ui/nft/css/nft-global.css @@ -1,3 +1,8 @@ +/*Copyright (c) 2022 The Brave Authors. All rights reserved.*/ +/*This Source Code Form is subject to the terms of the Mozilla Public*/ +/*License, v. 2.0. If a copy of the MPL was not distributed with this file,*/ +/*you can obtain one at http://mozilla.org/MPL/2.0/.*/ + html, body { width: 100%; height: 100%; diff --git a/components/brave_wallet_ui/nft/nft.html b/components/brave_wallet_ui/nft/nft.html index 7f36eb5453b0..e63501df6790 100644 --- a/components/brave_wallet_ui/nft/nft.html +++ b/components/brave_wallet_ui/nft/nft.html @@ -1,3 +1,8 @@ + + + + + diff --git a/components/brave_wallet_ui/nft/nft.tsx b/components/brave_wallet_ui/nft/nft.tsx index 3fe71278cc07..8ce82df9be21 100644 --- a/components/brave_wallet_ui/nft/nft.tsx +++ b/components/brave_wallet_ui/nft/nft.tsx @@ -41,6 +41,8 @@ const App = () => { const [tokenNetwork, setTokenNetwork] = React.useState() const [imageUrl, setImageUrl] = React.useState() + // handle postMessage from wallet ui by setting component state + // each message has a payload parameter containing the event data const onMessageEventListener = React.useCallback((event: MessageEvent) => { // validate message origin if (event.origin !== braveWalletOrigin) return @@ -80,6 +82,7 @@ const App = () => { }, []) React.useEffect(() => { + // add event listener for postMessage from wallet ui window.addEventListener('message', onMessageEventListener) return () => window.removeEventListener('message', onMessageEventListener) }, []) From 73b9bed32c2fd4837e2ba73e0ad9c5a977550b28 Mon Sep 17 00:00:00 2001 From: William Muli Date: Mon, 25 Jul 2022 11:55:02 +0300 Subject: [PATCH 31/39] Revert "chore: add tokenId check when removing asset from visible assets list" This reverts commit 440134d27366c9e08a483b00b7878c8649496595. This will be fixed in https://github.com/brave/brave-browser/issues/24238 --- .../components/desktop/asset-watchlist-item/index.tsx | 4 ++-- .../popup-modals/edit-visible-assets-modal/index.tsx | 10 ++++------ .../desktop/views/portfolio/portfolio-asset.tsx | 6 +++++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx b/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx index 099902498332..85fa24186396 100644 --- a/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx +++ b/components/brave_wallet_ui/components/desktop/asset-watchlist-item/index.tsx @@ -95,8 +95,8 @@ const AssetWatchlistItem = (props: Props) => { } - -
+ +
diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx index 83c0e253a9a5..55c0c717697e 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx @@ -292,8 +292,7 @@ const EditVisibleAssetsModal = ({ onClose }: Props) => { return updatedTokensList.find((t) => t.symbol.toLowerCase() === token.symbol.toLowerCase() && t.contractAddress.toLowerCase() === token.contractAddress.toLowerCase() && - t.chainId === token.chainId && - t.tokenId === token.tokenId) + t.chainId === token.chainId) } const isUserToken = (token: BraveWallet.BlockchainToken) => { @@ -334,8 +333,7 @@ const EditVisibleAssetsModal = ({ onClose }: Props) => { const tokenIndex = updatedTokensList.findIndex((t) => t.contractAddress.toLowerCase() === token.contractAddress.toLowerCase() && t.symbol.toLowerCase() === token.symbol.toLowerCase() && - t.chainId === token.chainId && - t.tokenId === token.tokenId) + t.chainId === token.chainId) let newList = [...updatedTokensList] newList.splice(tokenIndex, 1, updatedToken) setUpdatedTokensList(newList) @@ -368,9 +366,9 @@ const EditVisibleAssetsModal = ({ onClose }: Props) => { } const onRemoveAsset = (token: BraveWallet.BlockchainToken) => { - const newUserList = updatedTokensList.filter((t) => t.contractAddress.toLowerCase() !== token.contractAddress.toLowerCase() && t.tokenId === token.tokenId) + const newUserList = updatedTokensList.filter((t) => t.contractAddress.toLowerCase() !== token.contractAddress.toLowerCase()) setUpdatedTokensList(newUserList) - const newFilteredTokenList = filteredTokenList.filter((t) => t.contractAddress.toLowerCase() !== token.contractAddress.toLowerCase() && t.tokenId === token.tokenId) + const newFilteredTokenList = filteredTokenList.filter((t) => t.contractAddress.toLowerCase() !== token.contractAddress.toLowerCase()) setFilteredTokenList(newFilteredTokenList) } diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index 461a0b685626..aa50c7e04af5 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -75,6 +75,7 @@ export const PortfolioAsset = () => { const history = useHistory() const { id: assetId, tokenId } = useParams<{ id?: string, tokenId?: string }>() const nftDetailsRef = React.useRef(null) + const [nftIframeLoaded, setNftIframeLoaded] = React.useState(false) // redux const dispatch = useDispatch() const { @@ -315,6 +316,8 @@ export const PortfolioAsset = () => { }, [nftDetailsRef, isFetchingNFTMetadata]) React.useEffect(() => { + if (!nftIframeLoaded) return + if (selectedAsset && nftDetailsRef?.current) { const command: UpdateSelectedAssetMessage = { command: NftUiCommand.UpdateSelectedAsset, @@ -339,7 +342,7 @@ export const PortfolioAsset = () => { } sendMessageToNftUiFrame(nftDetailsRef.current.contentWindow, command) } - }, [nftDetailsRef, selectedAsset, nftMetadata, networkList]) + }, [nftIframeLoaded, nftDetailsRef, selectedAsset, nftMetadata, networkList]) // token list needs to load before we can find an asset to select from the url params if (userVisibleTokensInfo.length === 0) { @@ -420,6 +423,7 @@ export const PortfolioAsset = () => { } setNftIframeLoaded(true)} visible={selectedAsset?.isErc721} ref={nftDetailsRef} sandbox="allow-scripts allow-popups allow-same-origin" From cc371f175fee15cf812287d1cd8f83b6f802c885 Mon Sep 17 00:00:00 2001 From: William Muli Date: Mon, 25 Jul 2022 14:46:19 +0300 Subject: [PATCH 32/39] chore: fix unstable test behaviour --- components/brave_wallet_ui/utils/copy-to-clipboard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/brave_wallet_ui/utils/copy-to-clipboard.ts b/components/brave_wallet_ui/utils/copy-to-clipboard.ts index 2d09d70a54d8..c21ae08ae905 100644 --- a/components/brave_wallet_ui/utils/copy-to-clipboard.ts +++ b/components/brave_wallet_ui/utils/copy-to-clipboard.ts @@ -1,6 +1,6 @@ export const copyToClipboard = async (data: string) => { try { - await navigator.clipboard.writeText(data) + await navigator.clipboard?.writeText(data) } catch (e) { console.log(`Could not copy address ${e.toString()}`) } From 001785c911648eef0508ac5564d869d975f1dd35 Mon Sep 17 00:00:00 2001 From: William Muli Date: Tue, 26 Jul 2022 10:32:42 +0300 Subject: [PATCH 33/39] chore: fix tests --- browser/ui/webui/brave_wallet/nft/nft_ui.cc | 1 - browser/ui/webui/brave_wallet/wallet_page_ui.cc | 1 - browser/ui/webui/brave_wallet/wallet_panel_ui.cc | 1 + components/brave_wallet_ui/nft/nft-ui-messages.ts | 3 +-- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/browser/ui/webui/brave_wallet/nft/nft_ui.cc b/browser/ui/webui/brave_wallet/nft/nft_ui.cc index ccc878e3e0ba..8c4d0d5f9cd1 100644 --- a/browser/ui/webui/brave_wallet/nft/nft_ui.cc +++ b/browser/ui/webui/brave_wallet/nft/nft_ui.cc @@ -56,7 +56,6 @@ UntrustedNftUI::UntrustedNftUI(content::WebUI* web_ui) untrusted_source->AddString("braveWalletNftBridgeUrl", kUntrustedNftURL); untrusted_source->AddString("braveWalletTrezorBridgeUrl", kUntrustedTrezorURL); - untrusted_source->AddString("braveWalletBridgeUrl", kBraveUIWalletURL); untrusted_source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ImgSrc, std::string("img-src 'self' https: data:;")); diff --git a/browser/ui/webui/brave_wallet/wallet_page_ui.cc b/browser/ui/webui/brave_wallet/wallet_page_ui.cc index 5a4393d631fc..0b6f1159d19d 100644 --- a/browser/ui/webui/brave_wallet/wallet_page_ui.cc +++ b/browser/ui/webui/brave_wallet/wallet_page_ui.cc @@ -66,7 +66,6 @@ WalletPageUI::WalletPageUI(content::WebUI* web_ui) std::string("frame-src ") + kUntrustedNftURL + ";"); source->AddString("braveWalletTrezorBridgeUrl", kUntrustedTrezorURL); source->AddString("braveWalletNftBridgeUrl", kUntrustedNftURL); - source->AddString("braveWalletBridgeUrl", kBraveUIWalletURL); auto* profile = Profile::FromWebUI(web_ui); content::WebUIDataSource::Add(profile, source); content::URLDataSource::Add(profile, diff --git a/browser/ui/webui/brave_wallet/wallet_panel_ui.cc b/browser/ui/webui/brave_wallet/wallet_panel_ui.cc index 2335c1839040..0a4c92584289 100644 --- a/browser/ui/webui/brave_wallet/wallet_panel_ui.cc +++ b/browser/ui/webui/brave_wallet/wallet_panel_ui.cc @@ -64,6 +64,7 @@ WalletPanelUI::WalletPanelUI(content::WebUI* web_ui) std::string("frame-src ") + kUntrustedTrezorURL + " " + kUntrustedLedgerURL + ";"); source->AddString("braveWalletTrezorBridgeUrl", kUntrustedTrezorURL); + source->AddString("braveWalletNftBridgeUrl", kUntrustedNftURL); if (ShouldDisableCSPForTesting()) { source->DisableContentSecurityPolicy(); } diff --git a/components/brave_wallet_ui/nft/nft-ui-messages.ts b/components/brave_wallet_ui/nft/nft-ui-messages.ts index ea8d07050537..d20571439951 100644 --- a/components/brave_wallet_ui/nft/nft-ui-messages.ts +++ b/components/brave_wallet_ui/nft/nft-ui-messages.ts @@ -6,10 +6,9 @@ import { loadTimeData } from '../../common/loadTimeData' import { BraveWallet, NFTMetadataReturnType } from '../constants/types' -const walletOrigin = loadTimeData.getString('braveWalletBridgeUrl') const nftDisplayOrigin = loadTimeData.getString('braveWalletNftBridgeUrl') // remove trailing / -export const braveWalletOrigin = walletOrigin.endsWith('/') ? walletOrigin.slice(0, -1) : walletOrigin +export const braveWalletOrigin = 'chrome://wallet' export const braveNftDisplayOrigin = nftDisplayOrigin.endsWith('/') ? nftDisplayOrigin.slice(0, -1) : nftDisplayOrigin export const enum NftUiCommand { From 7e279e67e1684cc874636f3ee8497480f6e93768 Mon Sep 17 00:00:00 2001 From: William Muli Date: Tue, 26 Jul 2022 11:33:57 +0300 Subject: [PATCH 34/39] chore: rename function --- .../components/shared/create-network-icon/index.tsx | 4 ++-- .../shared/create-placeholder-icon/index.tsx | 4 ++-- .../panel/async/wallet_panel_async_handler.ts | 4 ++-- .../brave_wallet_ui/utils/string-utils.test.ts | 12 ++++++------ components/brave_wallet_ui/utils/string-utils.ts | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/components/brave_wallet_ui/components/shared/create-network-icon/index.tsx b/components/brave_wallet_ui/components/shared/create-network-icon/index.tsx index 2a9fe8086b5e..744e74ae86c3 100644 --- a/components/brave_wallet_ui/components/shared/create-network-icon/index.tsx +++ b/components/brave_wallet_ui/components/shared/create-network-icon/index.tsx @@ -10,7 +10,7 @@ import { create } from 'ethereum-blockies' import { BraveWallet, SupportedTestNetworks } from '../../../constants/types' // Utils -import { stripERC20TokenImageURL, isFromDifferentOrigin, isValidIconExtension } from '../../../utils/string-utils' +import { stripERC20TokenImageURL, isRemoteImageURL, isValidIconExtension } from '../../../utils/string-utils' // Styled components import { IconWrapper, Placeholder, NetworkIcon } from './style' @@ -36,7 +36,7 @@ function CreateNetworkIcon (props: Props) { ) const networkImageURL = stripERC20TokenImageURL(network?.iconUrls[0]) - const isRemoteURL = isFromDifferentOrigin(networkImageURL) + const isRemoteURL = isRemoteImageURL(networkImageURL) const isDataURL = network.iconUrls[0]?.startsWith('chrome://erc-token-images/') const isStorybook = network.iconUrls[0]?.startsWith('static/media/components/brave_wallet_ui/') diff --git a/components/brave_wallet_ui/components/shared/create-placeholder-icon/index.tsx b/components/brave_wallet_ui/components/shared/create-placeholder-icon/index.tsx index 5754fcfc60aa..51634228fe50 100644 --- a/components/brave_wallet_ui/components/shared/create-placeholder-icon/index.tsx +++ b/components/brave_wallet_ui/components/shared/create-placeholder-icon/index.tsx @@ -5,7 +5,7 @@ import { background } from 'ethereum-blockies' import { BraveWallet } from '../../../constants/types' // Utils -import { stripERC20TokenImageURL, isFromDifferentOrigin, isValidIconExtension, httpifyIpfsUrl } from '../../../utils/string-utils' +import { stripERC20TokenImageURL, isRemoteImageURL, isValidIconExtension, httpifyIpfsUrl } from '../../../utils/string-utils' // Styled components import { IconWrapper, PlaceholderText } from './style' @@ -49,7 +49,7 @@ function withPlaceholderIcon (WrappedComponent: React.ComponentType, config ) const tokenImageURL = stripERC20TokenImageURL(asset.logo) - const isRemoteURL = isFromDifferentOrigin(tokenImageURL) + const isRemoteURL = isRemoteImageURL(tokenImageURL) const isDataURL = asset.logo.startsWith('chrome://erc-token-images/') const isStorybook = asset.logo.startsWith('static/media/components/brave_wallet_ui/') diff --git a/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts b/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts index 6fe300782c63..1691755d8288 100644 --- a/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts +++ b/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts @@ -47,7 +47,7 @@ import { import { Store } from '../../common/async/types' import { getLocale } from '../../../common/locale' import getWalletPanelApiProxy from '../wallet_panel_api_proxy' -import { isFromDifferentOrigin } from '../../utils/string-utils' +import { isRemoteImageURL } from '../../utils/string-utils' import { HardwareVendor } from 'components/brave_wallet_ui/common/api/hardware_keyrings' const handler = new AsyncActionHandler() @@ -146,7 +146,7 @@ async function getPendingAddSuggestTokenRequest () { (await braveWalletService.getPendingAddSuggestTokenRequests()).requests if (requests && requests.length) { const logo = requests[0].token.logo - if (logo !== '' && !isFromDifferentOrigin(logo)) { + if (logo !== '' && !isRemoteImageURL(logo)) { requests[0].token.logo = `chrome://erc-token-images/${logo}` } return requests[0] diff --git a/components/brave_wallet_ui/utils/string-utils.test.ts b/components/brave_wallet_ui/utils/string-utils.test.ts index 5e33250e96d1..99f56ea5d61e 100644 --- a/components/brave_wallet_ui/utils/string-utils.test.ts +++ b/components/brave_wallet_ui/utils/string-utils.test.ts @@ -1,24 +1,24 @@ -import { isFromDifferentOrigin, isValidIconExtension, formatAsDouble } from './string-utils' +import { isRemoteImageURL, isValidIconExtension, formatAsDouble } from './string-utils' describe('Checking URL is remote image or not', () => { test('HTTP URL should return true', () => { - expect(isFromDifferentOrigin('http://test.com/test.png')).toEqual(true) + expect(isRemoteImageURL('http://test.com/test.png')).toEqual(true) }) test('HTTPS URL should return true', () => { - expect(isFromDifferentOrigin('https://test.com/test.png')).toEqual(true) + expect(isRemoteImageURL('https://test.com/test.png')).toEqual(true) }) test('Data URL image should return true', () => { - expect(isFromDifferentOrigin('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC')).toEqual(true) + expect(isRemoteImageURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC')).toEqual(true) }) test('local path should return false', () => { - expect(isFromDifferentOrigin('bat.png')).toEqual(false) + expect(isRemoteImageURL('bat.png')).toEqual(false) }) test('undefined input should return undefined', () => { - expect(isFromDifferentOrigin(undefined)).toEqual(undefined) + expect(isRemoteImageURL(undefined)).toEqual(undefined) }) }) diff --git a/components/brave_wallet_ui/utils/string-utils.ts b/components/brave_wallet_ui/utils/string-utils.ts index 89656a05ce74..b8fa5696aba4 100644 --- a/components/brave_wallet_ui/utils/string-utils.ts +++ b/components/brave_wallet_ui/utils/string-utils.ts @@ -7,7 +7,7 @@ export const toProperCase = (value: string) => value.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()) -export const isFromDifferentOrigin = (url?: string) => +export const isRemoteImageURL = (url?: string) => url?.startsWith('http://') || url?.startsWith('https://') || url?.startsWith('data:image/') || url?.startsWith('ipfs://') export const isValidIconExtension = (url?: string) => From 70b43bb8cd73c988e06549c7c0d1c2dd5252ab26 Mon Sep 17 00:00:00 2001 From: William Muli Date: Tue, 26 Jul 2022 11:34:44 +0300 Subject: [PATCH 35/39] chore: refactor to avoid adding unnecessary string --- .../components/desktop/views/portfolio/portfolio-asset.tsx | 4 +++- components/brave_wallet_ui/nft/nft-ui-messages.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index aa50c7e04af5..684c51be6a61 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -306,6 +306,8 @@ export const PortfolioAsset = () => { }, [selectedAssetFromParams]) React.useEffect(() => { + if (!nftIframeLoaded) return + if (nftDetailsRef?.current) { const command: UpdateLoadingMessage = { command: NftUiCommand.UpdateLoading, @@ -313,7 +315,7 @@ export const PortfolioAsset = () => { } sendMessageToNftUiFrame(nftDetailsRef.current.contentWindow, command) } - }, [nftDetailsRef, isFetchingNFTMetadata]) + }, [nftIframeLoaded, nftDetailsRef, isFetchingNFTMetadata]) React.useEffect(() => { if (!nftIframeLoaded) return diff --git a/components/brave_wallet_ui/nft/nft-ui-messages.ts b/components/brave_wallet_ui/nft/nft-ui-messages.ts index d20571439951..6f89b7b1d7e7 100644 --- a/components/brave_wallet_ui/nft/nft-ui-messages.ts +++ b/components/brave_wallet_ui/nft/nft-ui-messages.ts @@ -8,9 +8,10 @@ import { BraveWallet, NFTMetadataReturnType } from '../constants/types' const nftDisplayOrigin = loadTimeData.getString('braveWalletNftBridgeUrl') // remove trailing / -export const braveWalletOrigin = 'chrome://wallet' export const braveNftDisplayOrigin = nftDisplayOrigin.endsWith('/') ? nftDisplayOrigin.slice(0, -1) : nftDisplayOrigin +export const braveWalletOrigin = 'chrome://wallet' + export const enum NftUiCommand { UpdateLoading = 'update-loading', UpdateSelectedAsset = 'update-selected-asset', From f3e8d2b70b9f9660f30abd20a40c5c5bc37cc3ae Mon Sep 17 00:00:00 2001 From: William Muli Date: Tue, 26 Jul 2022 11:37:17 +0300 Subject: [PATCH 36/39] chore: change copyright year --- components/brave_wallet_ui/nft/BUILD.gn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/brave_wallet_ui/nft/BUILD.gn b/components/brave_wallet_ui/nft/BUILD.gn index 13c2723ba1b8..5ff181c8adb4 100644 --- a/components/brave_wallet_ui/nft/BUILD.gn +++ b/components/brave_wallet_ui/nft/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2021 The Brave Authors. All rights reserved. +# Copyright (c) 2022 The Brave Authors. All rights reserved. # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # you can obtain one at http://mozilla.org/MPL/2.0/. From 7eb7b0552228c691a45f5615ec3524d9b6f39673 Mon Sep 17 00:00:00 2001 From: William Muli Date: Tue, 26 Jul 2022 12:46:47 +0300 Subject: [PATCH 37/39] chore: add braveWalletLedgerBridgeUrl string --- browser/ui/webui/brave_wallet/nft/nft_ui.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/browser/ui/webui/brave_wallet/nft/nft_ui.cc b/browser/ui/webui/brave_wallet/nft/nft_ui.cc index 8c4d0d5f9cd1..8adc2cfbf2d9 100644 --- a/browser/ui/webui/brave_wallet/nft/nft_ui.cc +++ b/browser/ui/webui/brave_wallet/nft/nft_ui.cc @@ -56,6 +56,8 @@ UntrustedNftUI::UntrustedNftUI(content::WebUI* web_ui) untrusted_source->AddString("braveWalletNftBridgeUrl", kUntrustedNftURL); untrusted_source->AddString("braveWalletTrezorBridgeUrl", kUntrustedTrezorURL); + untrusted_source->AddString("braveWalletLedgerBridgeUrl", + kUntrustedLedgerURL); untrusted_source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ImgSrc, std::string("img-src 'self' https: data:;")); From 8549236e27d15e8b1eee8125237b32d3d671474f Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 28 Jul 2022 13:33:07 +0300 Subject: [PATCH 38/39] chore: use getWalletPageProxy --- .../brave_wallet_ui/page/async/wallet_page_async_handler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts b/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts index ec47c1053ee3..1f6cb532f2d2 100644 --- a/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts +++ b/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts @@ -32,7 +32,6 @@ import { import { NewUnapprovedTxAdded } from '../../common/constants/action_types' import { Store } from '../../common/async/types' import { getTokenParam } from '../../utils/api-utils' -import getAPIProxy from '../../common/async/bridge' import { getTokensNetwork } from '../../utils/network-utils' import { httpifyIpfsUrl } from '../../utils/string-utils' @@ -228,7 +227,7 @@ handler.on(WalletPageActions.openWalletSettings.getType(), async (store) => { handler.on(WalletPageActions.getNFTMetadata.getType(), async (store, payload: BraveWallet.BlockchainToken) => { store.dispatch(WalletPageActions.setIsFetchingNFTMetadata(true)) - const { jsonRpcService } = getAPIProxy() + const jsonRpcService = getWalletPageApiProxy().jsonRpcService const result = await jsonRpcService.getERC721Metadata(payload.contractAddress, payload.tokenId, payload.chainId) if (!result.error) { From 0a570f483688dbdc7467ea8cfa3ed7e35439a478 Mon Sep 17 00:00:00 2001 From: William Muli Date: Thu, 28 Jul 2022 13:34:00 +0300 Subject: [PATCH 39/39] chore: useCallback --- .../components/desktop/views/portfolio/portfolio-asset.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index 684c51be6a61..6152bb52b9cd 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -293,6 +293,8 @@ export const PortfolioAsset = () => { setHideBalances(prevHideBalances => !prevHideBalances) }, []) + const onNftDetailsLoad = React.useCallback(() => setNftIframeLoaded(true), []) + // effects React.useEffect(() => { setfilteredAssetList(userAssetList) @@ -425,7 +427,7 @@ export const PortfolioAsset = () => { } setNftIframeLoaded(true)} + onLoad={onNftDetailsLoad} visible={selectedAsset?.isErc721} ref={nftDetailsRef} sandbox="allow-scripts allow-popups allow-same-origin"