diff --git a/ui/address/AddressDetails.tsx b/ui/address/AddressDetails.tsx index a95b1ea55b..c0e690b889 100644 --- a/ui/address/AddressDetails.tsx +++ b/ui/address/AddressDetails.tsx @@ -10,7 +10,6 @@ import useApiQuery from 'lib/api/useApiQuery'; import getQueryParamString from 'lib/router/getQueryParamString'; import { ADDRESS_COUNTERS } from 'stubs/address'; import AddressCounterItem from 'ui/address/details/AddressCounterItem'; -import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem'; @@ -83,62 +82,76 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { } return ( - - - - - { data.is_contract && data.creation_tx_hash && data.creator_address_hash && ( - + + { data.is_contract && data.creation_tx_hash && data.creator_address_hash && ( + + + at txn + + + ) } + { data.is_contract && data.implementation_address && ( + + - - at txn - - - ) } - { data.is_contract && data.implementation_address && ( - - - - ) } - - { data.has_tokens && ( - - { addressQuery.data ? : 0 } - - ) } + noIcon + /> + + ) } + + { data.has_tokens && ( + { addressQuery.data ? : 0 } + + ) } + + { addressQuery.data ? ( + + ) : + 0 } + + { data.has_token_transfers && ( + { addressQuery.data ? ( { ) : 0 } - { data.has_token_transfers && ( - - { addressQuery.data ? ( - - ) : - 0 } - - ) } + ) } + + { addressQuery.data ? ( + + ) : + 0 } + + { data.has_validated_blocks && ( { addressQuery.data ? ( { ) : 0 } - { data.has_validated_blocks && ( - - { addressQuery.data ? ( - - ) : - 0 } - - ) } - { data.block_number_balance_updated_at && ( - + - - - ) } - - - + /> + + ) } + + ); }; diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_contract-mobile-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_contract-mobile-1.png index 110cb3e209..532df5851c 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_contract-mobile-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_contract-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png index 06144209d2..28d70e3dfe 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png differ diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_validator-mobile-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_validator-mobile-1.png index 9f776fde0e..4a98354191 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_validator-mobile-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_validator-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_mobile_contract-mobile-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_mobile_contract-mobile-1.png index 9ed0454258..5f80ab8c8e 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_mobile_contract-mobile-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_mobile_contract-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_mobile_validator-mobile-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_mobile_validator-mobile-1.png index e00a04ecbb..65a58209b3 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_mobile_validator-mobile-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_mobile_validator-mobile-1.png differ diff --git a/ui/address/details/AddressFavoriteButton.tsx b/ui/address/details/AddressFavoriteButton.tsx index b9d0453389..b3c3c7cdad 100644 --- a/ui/address/details/AddressFavoriteButton.tsx +++ b/ui/address/details/AddressFavoriteButton.tsx @@ -3,6 +3,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; +import config from 'configs/app'; import starFilledIcon from 'icons/star_filled.svg'; import starOutlineIcon from 'icons/star_outline.svg'; import { getResourceKey } from 'lib/api/useApiQuery'; @@ -15,7 +16,7 @@ import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal'; interface Props { className?: string; hash: string; - watchListId: number | null; + watchListId: number | null | undefined; } const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { @@ -24,6 +25,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { const queryClient = useQueryClient(); const router = useRouter(); const isAccountActionAllowed = useIsAccountActionAllowed(); + const onFocusCapture = usePreventFocusAfterModalClosing(); const handleClick = React.useCallback(() => { if (!isAccountActionAllowed()) { @@ -54,6 +56,10 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { }; }, [ hash, watchListId ]); + if (!config.features.account.isEnabled) { + return null; + } + return ( <> @@ -68,7 +74,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { flexShrink={ 0 } onClick={ handleClick } icon={ } - onFocusCapture={ usePreventFocusAfterModalClosing() } + onFocusCapture={ onFocusCapture } /> { pr="6px" onClick={ onOpen } icon={ } + flexShrink={ 0 } /> diff --git a/ui/pages/Address.tsx b/ui/pages/Address.tsx index 7f1264373e..60ce914b4c 100644 --- a/ui/pages/Address.tsx +++ b/ui/pages/Address.tsx @@ -1,4 +1,4 @@ -import { Box, Icon } from '@chakra-ui/react'; +import { Box, Flex, Icon } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -10,7 +10,6 @@ import iconSuccess from 'icons/status/success.svg'; import useApiQuery from 'lib/api/useApiQuery'; import { useAppContext } from 'lib/contexts/app'; import useContractTabs from 'lib/hooks/useContractTabs'; -import useIsMobile from 'lib/hooks/useIsMobile'; import useIsSafeAddress from 'lib/hooks/useIsSafeAddress'; import getQueryParamString from 'lib/router/getQueryParamString'; import { ADDRESS_INFO, ADDRESS_TABS_COUNTERS } from 'stubs/address'; @@ -24,7 +23,12 @@ import AddressTokens from 'ui/address/AddressTokens'; import AddressTokenTransfers from 'ui/address/AddressTokenTransfers'; import AddressTxs from 'ui/address/AddressTxs'; import AddressWithdrawals from 'ui/address/AddressWithdrawals'; +import AddressFavoriteButton from 'ui/address/details/AddressFavoriteButton'; +import AddressQrCode from 'ui/address/details/AddressQrCode'; +import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu'; import TextAd from 'ui/shared/ad/TextAd'; +import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import EntityTags from 'ui/shared/EntityTags'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from 'ui/shared/Page/PageTitle'; @@ -41,7 +45,6 @@ const TOKEN_TABS = Object.values(tokenTabsByType); const AddressPageContent = () => { const router = useRouter(); - const isMobile = useIsMobile(); const appProps = useAppContext(); const tabsScrollRef = React.useRef(null); @@ -148,14 +151,11 @@ const AddressPageContent = () => { data={ addressQuery.data } isLoading={ addressQuery.isPlaceholderData } tagsBefore={ [ - addressQuery.data?.is_contract ? { label: 'contract', display_name: 'Contract' } : { label: 'eoa', display_name: 'EOA' }, + !addressQuery.data?.is_contract ? { label: 'eoa', display_name: 'EOA' } : undefined, addressQuery.data?.implementation_address ? { label: 'proxy', display_name: 'Proxy' } : undefined, addressQuery.data?.token ? { label: 'token', display_name: 'Token' } : undefined, isSafeAddress ? { label: 'safe', display_name: 'Multisig: Safe' } : undefined, ] } - contentAfter={ - - } /> ); @@ -174,6 +174,30 @@ const AddressPageContent = () => { }; }, [ appProps.referrer ]); + const isLoading = addressQuery.isPlaceholderData; + + const titleSecondRow = ( + + + { !isLoading && addressQuery.data?.is_contract && addressQuery.data.token && + } + { !isLoading && !addressQuery.data?.is_contract && config.features.account.isEnabled && ( + + ) } + + + + + ); + return ( <> @@ -181,7 +205,8 @@ const AddressPageContent = () => { title={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` } backLink={ backLink } contentAfter={ tags } - isLoading={ addressQuery.isPlaceholderData } + secondRow={ titleSecondRow } + isLoading={ isLoading } /> { /* should stay before tabs to scroll up with pagination */ } diff --git a/ui/pages/Block.tsx b/ui/pages/Block.tsx index ce509b5e17..c880c372c4 100644 --- a/ui/pages/Block.tsx +++ b/ui/pages/Block.tsx @@ -1,3 +1,4 @@ +import { chakra, Skeleton } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -16,6 +17,7 @@ import { WITHDRAWAL } from 'stubs/withdrawals'; import BlockDetails from 'ui/block/BlockDetails'; import BlockWithdrawals from 'ui/block/BlockWithdrawals'; import TextAd from 'ui/shared/ad/TextAd'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/pagination/Pagination'; @@ -112,6 +114,24 @@ const BlockPageContent = () => { }, [ appProps.referrer ]); const title = blockQuery.data?.type === 'reorg' ? `Reorged block #${ blockQuery.data?.height }` : `Block #${ blockQuery.data?.height }`; + const titleSecondRow = ( + <> + + + { config.chain.verificationType === 'validation' ? 'Validated by' : 'Mined by' } + + + + + + ); return ( <> @@ -119,7 +139,7 @@ const BlockPageContent = () => { } + secondRow={ titleSecondRow } isLoading={ blockQuery.isPlaceholderData } /> { blockQuery.isPlaceholderData ? : ( diff --git a/ui/pages/Token.tsx b/ui/pages/Token.tsx index 860eaa9cc6..a2e3dd8c58 100644 --- a/ui/pages/Token.tsx +++ b/ui/pages/Token.tsx @@ -1,4 +1,4 @@ -import { Box, Icon, Tooltip } from '@chakra-ui/react'; +import { Box, Flex, Icon, Tooltip } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React, { useEffect } from 'react'; @@ -23,8 +23,11 @@ import * as addressStubs from 'stubs/address'; import * as tokenStubs from 'stubs/token'; import { generateListStub } from 'stubs/utils'; import AddressContract from 'ui/address/AddressContract'; +import AddressQrCode from 'ui/address/details/AddressQrCode'; +import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu'; import TextAd from 'ui/shared/ad/TextAd'; -import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo'; +import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import EntityTags from 'ui/shared/EntityTags'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; @@ -261,41 +264,56 @@ const TokenPageContent = () => { [ { label: verifiedInfoQuery.data.projectSector, display_name: verifiedInfoQuery.data.projectSector } ] : undefined } - contentAfter={ - - } flexGrow={ 1 } /> ); + const isLoading = tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData; + + const titleSecondRow = ( + + + { !isLoading && tokenQuery.data && } + + + + + + + + ); + return ( <> ) : null } contentAfter={ titleContentAfter } + secondRow={ titleSecondRow } /> - - + + { /* should stay before tabs to scroll up with pagination */ } - { tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData ? + { isLoading ? : ( { throw Error('Token instance fetch failed', { cause: tokenInstanceQuery.error }); } - const nftShieldIcon = tokenInstanceQuery.isPlaceholderData ? - : - ; const tokenTag = { tokenInstanceQuery.data?.token.type }; + const address = { hash: hash || '', is_contract: true, @@ -130,6 +130,9 @@ const TokenInstanceContent = () => { watchlist_names: [], watchlist_address_id: null, }; + + const isLoading = tokenInstanceQuery.isPlaceholderData; + const appLink = (() => { if (!tokenInstanceQuery.data?.external_app_url) { return null; @@ -141,20 +144,15 @@ const TokenInstanceContent = () => { new URL('https://' + tokenInstanceQuery.data.external_app_url); return ( - - View in app - - { url.hostname || tokenInstanceQuery.data.external_app_url } - - + + { url.hostname || tokenInstanceQuery.data.external_app_url } + ); } catch (error) { return ( - - + View in app - - + ); } })(); @@ -167,27 +165,44 @@ const TokenInstanceContent = () => { pagination = holdersQuery.pagination; } + const titleSecondRow = ( + + + { !isLoading && tokenInstanceQuery.data && } + + + { appLink } + + ); + return ( <> - - - { appLink } - - + { /* should stay before tabs to scroll up with pagination */ } - { tokenInstanceQuery.isPlaceholderData ? : ( + { isLoading ? : ( { const router = useRouter(); const appProps = useAppContext(); - const isMobile = useIsMobile(); const hash = getQueryParamString(router.query.hash); @@ -57,9 +57,6 @@ const TransactionPageContent = () => { - } /> ); @@ -76,6 +73,14 @@ const TransactionPageContent = () => { }; }, [ appProps.referrer ]); + const titleSecondRow = ( + <> + + { !data?.tx_tag && } + + + ); + return ( <> @@ -83,6 +88,7 @@ const TransactionPageContent = () => { title="Transaction details" backLink={ backLink } contentAfter={ tags } + secondRow={ titleSecondRow } /> { isPlaceholderData ? ( <> diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_base-view-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_base-view-1.png index 82421444ee..9b891a087c 100644 Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_base-view-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_base-view-1.png differ diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png index c65a292d52..4de5603803 100644 Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png differ diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-base-view-1.png index 8022377d15..7df7185254 100644 Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png index f12a0c52de..04c40fa49b 100644 Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png differ diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png index 32e8e0c8d3..918eb61308 100644 Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png differ diff --git a/ui/privateTags/TransactionModal/TransactionForm.tsx b/ui/privateTags/TransactionModal/TransactionForm.tsx index c6ccf3625a..35097aeee3 100644 --- a/ui/privateTags/TransactionModal/TransactionForm.tsx +++ b/ui/privateTags/TransactionModal/TransactionForm.tsx @@ -21,7 +21,7 @@ import TransactionInput from 'ui/shared/TransactionInput'; const TAG_MAX_LENGTH = 35; type Props = { - data?: TransactionTag; + data?: Partial; onClose: () => void; onSuccess: () => Promise; setAlertVisible: (isAlertVisible: boolean) => void; diff --git a/ui/privateTags/TransactionModal/TransactionModal.tsx b/ui/privateTags/TransactionModal/TransactionModal.tsx index eefdefef2a..668119e88c 100644 --- a/ui/privateTags/TransactionModal/TransactionModal.tsx +++ b/ui/privateTags/TransactionModal/TransactionModal.tsx @@ -11,10 +11,11 @@ import TransactionForm from './TransactionForm'; type Props = { isOpen: boolean; onClose: () => void; - data?: TransactionTag; + onSuccess?: () => Promise; + data?: Partial; } -const AddressModal: React.FC = ({ isOpen, onClose, data }) => { +const AddressModal: React.FC = ({ isOpen, onClose, onSuccess, data }) => { const title = data ? 'Edit transaction tag' : 'New transaction tag'; const text = !data ? 'Label any transaction with a private transaction tag (up to 35 chars) to customize your explorer experience.' : ''; @@ -28,13 +29,14 @@ const AddressModal: React.FC = ({ isOpen, onClose, data }) => { }, [ data?.id, isOpen ]); const handleSuccess = React.useCallback(async() => { + onSuccess?.(); if (!data?.id) { mixpanel.logEvent( mixpanel.EventTypes.PRIVATE_TAG, { Action: 'Submit', 'Page type': PAGE_TYPE_DICT['/account/tag-address'], 'Tag type': 'Tx' }, ); } - }, [ data?.id ]); + }, [ data?.id, onSuccess ]); const renderForm = useCallback(() => { return ; diff --git a/ui/shared/AddressActions/Menu.tsx b/ui/shared/AccountActionsMenu/AccountActionsMenu.tsx similarity index 62% rename from ui/shared/AddressActions/Menu.tsx rename to ui/shared/AccountActionsMenu/AccountActionsMenu.tsx index a7b29d00f2..b8fdb8fac2 100644 --- a/ui/shared/AddressActions/Menu.tsx +++ b/ui/shared/AccountActionsMenu/AccountActionsMenu.tsx @@ -1,4 +1,4 @@ -import { Button, Menu, MenuButton, MenuList, Icon, Flex, Skeleton } from '@chakra-ui/react'; +import { Button, Menu, MenuButton, MenuList, Icon, Flex, Skeleton, chakra } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -8,28 +8,34 @@ import useIsAccountActionAllowed from 'lib/hooks/useIsAccountActionAllowed'; import * as mixpanel from 'lib/mixpanel/index'; import getQueryParamString from 'lib/router/getQueryParamString'; -import PrivateTagMenuItem from './PrivateTagMenuItem'; -import PublicTagMenuItem from './PublicTagMenuItem'; -import TokenInfoMenuItem from './TokenInfoMenuItem'; +import PrivateTagMenuItem from './items/PrivateTagMenuItem'; +import PublicTagMenuItem from './items/PublicTagMenuItem'; +import TokenInfoMenuItem from './items/TokenInfoMenuItem'; interface Props { isLoading?: boolean; + className?: string; } -const AddressActions = ({ isLoading }: Props) => { +const AccountActionsMenu = ({ isLoading, className }: Props) => { const router = useRouter(); const hash = getQueryParamString(router.query.hash); const isTokenPage = router.pathname === '/token/[hash]'; + const isTxPage = router.pathname === '/tx/[hash]'; const isAccountActionAllowed = useIsAccountActionAllowed(); const handleButtonClick = React.useCallback(() => { mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Address actions (more button)' }); }, []); + if (!config.features.account.isEnabled) { + return null; + } + return ( - + { { isTokenPage && config.features.addressVerification.isEnabled && } - - + + { !isTxPage && } ); }; -export default React.memo(AddressActions); +export default React.memo(chakra(AccountActionsMenu)); diff --git a/ui/shared/AddressActions/PrivateTagMenuItem.tsx b/ui/shared/AccountActionsMenu/items/PrivateTagMenuItem.tsx similarity index 56% rename from ui/shared/AddressActions/PrivateTagMenuItem.tsx rename to ui/shared/AccountActionsMenu/items/PrivateTagMenuItem.tsx index 95352dcf0a..4450dcd2b1 100644 --- a/ui/shared/AddressActions/PrivateTagMenuItem.tsx +++ b/ui/shared/AccountActionsMenu/items/PrivateTagMenuItem.tsx @@ -4,25 +4,28 @@ import { useRouter } from 'next/router'; import React from 'react'; import type { Address } from 'types/api/address'; +import type { Transaction } from 'types/api/transaction'; import iconPrivateTags from 'icons/privattags.svg'; import { getResourceKey } from 'lib/api/useApiQuery'; import getPageType from 'lib/mixpanel/getPageType'; -import PrivateTagModal from 'ui/privateTags/AddressModal/AddressModal'; +import AddressModal from 'ui/privateTags/AddressModal/AddressModal'; +import TransactionModal from 'ui/privateTags/TransactionModal/TransactionModal'; interface Props { className?: string; hash: string; onBeforeClick: () => boolean; + type?: 'address' | 'tx'; } -const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => { +const PrivateTagMenuItem = ({ className, hash, onBeforeClick, type = 'address' }: Props) => { const modal = useDisclosure(); const queryClient = useQueryClient(); const router = useRouter(); - const queryKey = getResourceKey('address', { pathParams: { hash } }); - const addressData = queryClient.getQueryData
(queryKey); + const queryKey = getResourceKey(type === 'tx' ? 'tx' : 'address', { pathParams: { hash } }); + const queryData = queryClient.getQueryData
(queryKey); const handleClick = React.useCallback(() => { if (!onBeforeClick()) { @@ -37,17 +40,23 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => { modal.onClose(); }, [ queryClient, queryKey, modal ]); - const formData = React.useMemo(() => { - return { - address_hash: hash, - }; - }, [ hash ]); - - if (addressData?.private_tags?.length) { + if ( + queryData && + ( + ('private_tags' in queryData && queryData.private_tags?.length) || + ('tx_tag' in queryData && queryData.tx_tag) + ) + ) { return null; } const pageType = getPageType(router.pathname); + const modalProps = { + isOpen: modal.isOpen, + onClose: modal.onClose, + onSuccess: handleAddPrivateTag, + pageType, + }; return ( <> @@ -55,13 +64,10 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => { Add private tag - + { type === 'tx' ? + : + + } ); }; diff --git a/ui/shared/AddressActions/PublicTagMenuItem.tsx b/ui/shared/AccountActionsMenu/items/PublicTagMenuItem.tsx similarity index 100% rename from ui/shared/AddressActions/PublicTagMenuItem.tsx rename to ui/shared/AccountActionsMenu/items/PublicTagMenuItem.tsx diff --git a/ui/shared/AddressActions/TokenInfoMenuItem.tsx b/ui/shared/AccountActionsMenu/items/TokenInfoMenuItem.tsx similarity index 100% rename from ui/shared/AddressActions/TokenInfoMenuItem.tsx rename to ui/shared/AccountActionsMenu/items/TokenInfoMenuItem.tsx diff --git a/ui/shared/AddressHeadingInfo.tsx b/ui/shared/AddressHeadingInfo.tsx deleted file mode 100644 index 9e79b49b28..0000000000 --- a/ui/shared/AddressHeadingInfo.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { Address } from 'types/api/address'; -import type { TokenInfo } from 'types/api/token'; - -import config from 'configs/app'; -import useIsSafeAddress from 'lib/hooks/useIsSafeAddress'; -import stripTrailingSlash from 'lib/stripTrailingSlash'; -import AddressFavoriteButton from 'ui/address/details/AddressFavoriteButton'; -import AddressQrCode from 'ui/address/details/AddressQrCode'; -import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; -import AddressActionsMenu from 'ui/shared/AddressActions/Menu'; -import AddressEntity from 'ui/shared/entities/address/AddressEntity'; -import LinkExternal from 'ui/shared/LinkExternal'; - -interface Props { - address?: Pick; - token?: TokenInfo | null; - isLinkDisabled?: boolean; - isLoading?: boolean; -} - -const AddressHeadingInfo = ({ address, token, isLinkDisabled, isLoading }: Props) => { - const isSafeAddress = useIsSafeAddress(!isLoading && address?.is_contract ? address.hash : undefined); - - if (!address) { - return null; - } - - const tokenOriginalLink = (() => { - const feature = config.features.bridgedTokens; - if (!token?.foreign_address || !token.origin_chain_id || !feature.isEnabled) { - return null; - } - - const chainBaseUrl = feature.chains.find(({ id }) => id === token.origin_chain_id)?.base_url; - - if (!chainBaseUrl) { - return null; - } - - try { - const url = new URL(stripTrailingSlash(chainBaseUrl) + '/' + token.foreign_address); - return ( - - Original token - - ); - } catch (error) { - return null; - } - })(); - - return ( - - - { !isLoading && address?.is_contract && token && } - { !isLoading && !address.is_contract && config.features.account.isEnabled && ( - - ) } - - { config.features.account.isEnabled && } - { tokenOriginalLink } - - ); -}; - -export default AddressHeadingInfo; diff --git a/ui/shared/LinkExternal.tsx b/ui/shared/LinkExternal.tsx index 329a66aed7..2ffd5f4d23 100644 --- a/ui/shared/LinkExternal.tsx +++ b/ui/shared/LinkExternal.tsx @@ -16,25 +16,42 @@ const LinkExternal = ({ href, children, className, isLoading, variant }: Props) const subtleLinkBg = useColorModeValue('gray.100', 'gray.700'); const styleProps: ChakraProps = (() => { + const commonProps = { + fontSize: 'sm', + lineHeight: 5, + display: 'inline-block', + alignItems: 'center', + }; + switch (variant) { case 'subtle': { return { + ...commonProps, px: '10px', - py: '5px', + py: '6px', bgColor: subtleLinkBg, borderRadius: 'base', }; } default:{ - return {}; + return commonProps; } } })(); if (isLoading) { + if (variant === 'subtle') { + return ( + + { children } + + + ); + } + return ( - + { children } @@ -42,7 +59,7 @@ const LinkExternal = ({ href, children, className, isLoading, variant }: Props) } return ( - + { children } diff --git a/ui/shared/NetworkExplorers.tsx b/ui/shared/NetworkExplorers.tsx index ea4a8a5b25..d187bd6203 100644 --- a/ui/shared/NetworkExplorers.tsx +++ b/ui/shared/NetworkExplorers.tsx @@ -12,10 +12,9 @@ interface Props { className?: string; type: keyof TNetworkExplorer['paths']; pathParam: string; - hideText?: boolean; } -const NetworkExplorers = ({ className, type, pathParam, hideText }: Props) => { +const NetworkExplorers = ({ className, type, pathParam }: Props) => { const { isOpen, onToggle, onClose } = useDisclosure(); const explorersLinks = config.UI.explorers.items @@ -41,11 +40,11 @@ const NetworkExplorers = ({ className, type, pathParam, hideText }: Props) => { aria-label="Verify in other explorers" fontWeight={ 500 } px={ 2 } - h="30px" + h="32px" + flexShrink={ 0 } > - - { !hideText && Explorers } - + + diff --git a/ui/shared/Page/PageTitle.tsx b/ui/shared/Page/PageTitle.tsx index 9d1879a6c5..9167e7e87e 100644 --- a/ui/shared/Page/PageTitle.tsx +++ b/ui/shared/Page/PageTitle.tsx @@ -16,6 +16,7 @@ type Props = { beforeTitle?: React.ReactNode; afterTitle?: React.ReactNode; contentAfter?: React.ReactNode; + secondRow?: React.ReactNode; isLoading?: boolean; withTextAd?: boolean; } @@ -31,7 +32,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => { return ; } - const icon = ; + const icon = ; if ('url' in props) { return ( @@ -52,7 +53,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => { ); }; -const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoading, afterTitle, beforeTitle }: Props) => { +const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoading, afterTitle, beforeTitle, secondRow }: Props) => { const tooltip = useDisclosure(); const isMobile = useIsMobile(); const [ isTextTruncated, setIsTextTruncated ] = React.useState(false); @@ -90,57 +91,62 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa }, [ updatedTruncateState ]); return ( - - - { backLink && } - { beforeTitle } - - + + + { backLink && } + { beforeTitle } + - - - { title } - - - - - { afterTitle } + + + { title } + + + + + { afterTitle } + + { contentAfter } + { withTextAd && } - { contentAfter } - { withTextAd && } + { secondRow && ( + + { secondRow } + + ) } ); }; diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_default-view-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_default-view-mobile-1.png index 37ff077127..8e5096497b 100644 Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_default-view-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_default-view-mobile-1.png differ diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-long-name-and-many-tags-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-long-name-and-many-tags-mobile-1.png index 0783f8bc45..d8878240f6 100644 Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-long-name-and-many-tags-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-long-name-and-many-tags-mobile-1.png differ diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-text-ad-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-text-ad-mobile-1.png index 3b9bd03c07..9a03708d13 100644 Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-text-ad-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-text-ad-mobile-1.png differ diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_default-view-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_default-view-mobile-1.png index 400f85c7bd..04be035dd4 100644 Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_default-view-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_default-view-mobile-1.png differ diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-long-name-and-many-tags-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-long-name-and-many-tags-mobile-1.png index da099aed19..11886cf45b 100644 Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-long-name-and-many-tags-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-long-name-and-many-tags-mobile-1.png differ diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-text-ad-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-text-ad-mobile-1.png index 6c8c7bd202..01d7d12f52 100644 Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-text-ad-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-text-ad-mobile-1.png differ diff --git a/ui/shared/Page/specs/DefaultView.tsx b/ui/shared/Page/specs/DefaultView.tsx index 4511f40aef..23db4033fd 100644 --- a/ui/shared/Page/specs/DefaultView.tsx +++ b/ui/shared/Page/specs/DefaultView.tsx @@ -4,7 +4,8 @@ import React from 'react'; import type { TokenInfo } from 'types/api/token'; import iconVerifiedToken from 'icons/verified_token.svg'; -import useIsMobile from 'lib/hooks/useIsMobile'; +import * as addressMock from 'mocks/address/address'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import EntityTags from 'ui/shared/EntityTags'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; @@ -12,8 +13,6 @@ import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from '../PageTitle'; const DefaultView = () => { - const isMobile = useIsMobile(); - const tokenData: TokenInfo = { address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29', circulating_market_cap: '117629601.61913824', @@ -39,12 +38,23 @@ const DefaultView = () => { tagsBefore={ [ { label: 'example', display_name: 'Example label' }, ] } - contentAfter={ } flexGrow={ 1 } /> ); + const secondRow = ( + <> + + + + ); + return ( { ) } backLink={ backLink } contentAfter={ contentAfter } + secondRow={ secondRow } /> ); }; diff --git a/ui/shared/Page/specs/LongNameAndManyTags.tsx b/ui/shared/Page/specs/LongNameAndManyTags.tsx index d9fca18d90..665d328e4a 100644 --- a/ui/shared/Page/specs/LongNameAndManyTags.tsx +++ b/ui/shared/Page/specs/LongNameAndManyTags.tsx @@ -5,7 +5,6 @@ import React from 'react'; import type { TokenInfo } from 'types/api/token'; import iconVerifiedToken from 'icons/verified_token.svg'; -import useIsMobile from 'lib/hooks/useIsMobile'; import { publicTag, privateTag, watchlistName } from 'mocks/address/tag'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import EntityTags from 'ui/shared/EntityTags'; @@ -14,8 +13,6 @@ import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from '../PageTitle'; const LongNameAndManyTags = () => { - const isMobile = useIsMobile(); - const tokenData: TokenInfo = { address: '0xa77A39CC9680B10C00af5D4ABFc92e1F07406c64', circulating_market_cap: null, @@ -45,7 +42,7 @@ const LongNameAndManyTags = () => { { label: 'after_1', display_name: 'Another tag' }, { label: 'after_2', display_name: 'And yet more' }, ] } - contentAfter={ } + contentAfter={ } flexGrow={ 1 } /> diff --git a/ui/shared/address/AddressAddToWallet.tsx b/ui/shared/address/AddressAddToWallet.tsx index e1eb02225d..a9c640b300 100644 --- a/ui/shared/address/AddressAddToWallet.tsx +++ b/ui/shared/address/AddressAddToWallet.tsx @@ -1,4 +1,4 @@ -import { Box, chakra, Icon, Skeleton, Tooltip } from '@chakra-ui/react'; +import { Box, chakra, Icon, IconButton, Skeleton, Tooltip } from '@chakra-ui/react'; import React from 'react'; import type { TokenInfo } from 'types/api/token'; @@ -16,10 +16,11 @@ interface Props { className?: string; token: TokenInfo; isLoading?: boolean; + variant?: 'icon' | 'button'; iconSize?: number; } -const AddressAddToWallet = ({ className, token, isLoading, iconSize = 6 }: Props) => { +const AddressAddToWallet = ({ className, token, isLoading, variant = 'icon', iconSize = 6 }: Props) => { const toast = useToast(); const { provider, wallet } = useProvider(); const addOrSwitchChain = useAddOrSwitchChain(); @@ -86,9 +87,26 @@ const AddressAddToWallet = ({ className, token, isLoading, iconSize = 6 }: Props return null; } + if (variant === 'button') { + return ( + + } + flexShrink={ 0 } + /> + + ); + } + return ( - + diff --git a/ui/token/TokenDetails.tsx b/ui/token/TokenDetails.tsx index 5acd950a51..388546a67b 100644 --- a/ui/token/TokenDetails.tsx +++ b/ui/token/TokenDetails.tsx @@ -86,7 +86,6 @@ const TokenDetails = ({ tokenQuery }: Props) => { return ( - - Project info + + Info ); diff --git a/ui/token/TokenVerifiedInfo.tsx b/ui/token/TokenVerifiedInfo.tsx index 5e15db2c6d..13765829dc 100644 --- a/ui/token/TokenVerifiedInfo.tsx +++ b/ui/token/TokenVerifiedInfo.tsx @@ -1,4 +1,4 @@ -import { Flex, Skeleton } from '@chakra-ui/react'; +import { Skeleton } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; @@ -25,9 +25,9 @@ const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => { if (isLoading) { return ( <> - - - + + + ); } @@ -40,7 +40,9 @@ const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => { try { const url = new URL(data.projectWebsite); return ( - { url.host } + + { url.host } + ); } catch (error) { return null; @@ -55,7 +57,7 @@ const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => { ); })(); - return { content }; + return content; }; export default React.memo(TokenVerifiedInfo); diff --git a/ui/tokenInstance/TokenInstanceDetails.tsx b/ui/tokenInstance/TokenInstanceDetails.tsx index 5837601ae8..aae2b1302c 100644 --- a/ui/tokenInstance/TokenInstanceDetails.tsx +++ b/ui/tokenInstance/TokenInstanceDetails.tsx @@ -8,7 +8,6 @@ import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider'; import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; -import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import NftMedia from 'ui/shared/nft/NftMedia'; import TokenNftMarketplaces from 'ui/token/TokenNftMarketplaces'; @@ -37,7 +36,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { return ( <> - + { templateColumns={{ base: 'minmax(0, 1fr)', lg: '200px minmax(0, 1fr)' }} overflow="hidden" > - - - { data.is_unique && data.owner && (