Commit ac0ac2b1 authored by tom's avatar tom

refactor AddressDetails

parent 1f8e6709
......@@ -10,14 +10,14 @@ import fetchApi from 'nextjs/utils/fetchApi';
import config from 'configs/app';
import getQueryParamString from 'lib/router/getQueryParamString';
// import Address from 'ui/pages/Address';
import Address from 'ui/pages/Address';
const pathname: Route['pathname'] = '/address/[hash]';
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
return (
<PageNextJs pathname={ pathname } query={ props.query } apiData={ props.apiData }>
{ /* <Address/> */ }
<Address/>
</PageNextJs>
);
};
......
......@@ -26,7 +26,7 @@ export const recipe = defineSlotRecipe({
transition: 'translate 400ms, scale 400ms, opacity 200ms',
transitionTimingFunction: 'cubic-bezier(0.06, 0.71, 0.55, 1)',
},
bg: 'alert.bg.neutral',
bg: 'alert.bg.info',
color: 'alert.fg',
boxShadow: 'xl',
'--toast-trigger-bg': 'colors.bg.muted',
......
import { Box, Text, Grid } from '@chakra-ui/react';
import { Box, Text } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -94,11 +94,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
return (
<>
{ addressQuery.isDegradedData && <ServiceDegradationWarning isLoading={ addressQuery.isPlaceholderData } mb={ 6 }/> }
<Grid
columnGap={ 8 }
rowGap={{ base: 1, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
>
<DetailedInfo.Container templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} >
<AddressAlternativeFormat isLoading={ addressQuery.isPlaceholderData } addressHash={ addressHash }/>
{ data.filecoin?.id && (
......@@ -318,7 +314,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
) }
<DetailedInfoSponsoredItem isLoading={ addressQuery.isPlaceholderData }/>
</Grid>
</DetailedInfo.Container>
</>
);
};
......
......@@ -61,7 +61,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender =
const [ socketAlert, setSocketAlert ] = React.useState('');
const [ newItemsCount, setNewItemsCount ] = React.useState(0);
const [ sort, setSort ] = React.useState<TransactionsSortingValue | undefined>(getSortValueFromQuery<TransactionsSortingValue>(router.query, SORT_OPTIONS));
const [ sort, setSort ] = React.useState<TransactionsSortingValue>(getSortValueFromQuery<TransactionsSortingValue>(router.query, SORT_OPTIONS) || 'default');
const isMobile = useIsMobile();
const currentAddress = getQueryParamString(router.query.hash);
......@@ -165,14 +165,15 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender =
return null;
}
const filter = (
<AddressTxsFilter
defaultFilter={ filterValue }
onFilterChange={ handleFilterChange }
hasActiveFilter={ Boolean(filterValue) }
isLoading={ addressTxsQuery.pagination.isLoading }
/>
);
// const filter = (
// <AddressTxsFilter
// defaultFilter={ filterValue }
// onFilterChange={ handleFilterChange }
// hasActiveFilter={ Boolean(filterValue) }
// isLoading={ addressTxsQuery.pagination.isLoading }
// />
// );
const filter = null;
const csvExportLink = (
<AddressCsvExportLink
......
......@@ -74,34 +74,34 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder
return React.useMemo(() => {
return {
tabs: [
data?.hash && {
id: 'contract_code' as const,
title: 'Code',
component: <ContractDetails mainContractQuery={ contractQuery } channel={ channel } addressHash={ data.hash }/>,
subTabs: CONTRACT_DETAILS_TAB_IDS as unknown as Array<string>,
},
contractQuery.data?.abi && {
id: [ 'read_write_contract' as const, 'read_contract' as const, 'write_contract' as const ],
title: 'Read/Write contract',
component: <ContractMethodsRegular abi={ contractQuery.data.abi } isLoading={ contractQuery.isPlaceholderData }/>,
},
verifiedImplementations.length > 0 && {
id: [ 'read_write_proxy' as const, 'read_proxy' as const, 'write_proxy' as const ],
title: 'Read/Write proxy',
component: <ContractMethodsProxy implementations={ verifiedImplementations } isLoading={ contractQuery.isPlaceholderData }/>,
},
config.features.account.isEnabled && {
id: [ 'read_write_custom_methods' as const, 'read_custom_methods' as const, 'write_custom_methods' as const ],
title: 'Custom ABI',
component: <ContractMethodsCustom isLoading={ contractQuery.isPlaceholderData }/>,
},
hasMudTab && {
id: 'mud_system' as const,
title: 'MUD System',
component: mudSystemsQuery.isPlaceholderData ?
<ContentLoader/> :
<ContractMethodsMudSystem items={ mudSystemsQuery.data?.items ?? [] }/>,
},
// data?.hash && {
// id: 'contract_code' as const,
// title: 'Code',
// component: <ContractDetails mainContractQuery={ contractQuery } channel={ channel } addressHash={ data.hash }/>,
// subTabs: CONTRACT_DETAILS_TAB_IDS as unknown as Array<string>,
// },
// contractQuery.data?.abi && {
// id: [ 'read_write_contract' as const, 'read_contract' as const, 'write_contract' as const ],
// title: 'Read/Write contract',
// component: <ContractMethodsRegular abi={ contractQuery.data.abi } isLoading={ contractQuery.isPlaceholderData }/>,
// },
// verifiedImplementations.length > 0 && {
// id: [ 'read_write_proxy' as const, 'read_proxy' as const, 'write_proxy' as const ],
// title: 'Read/Write proxy',
// component: <ContractMethodsProxy implementations={ verifiedImplementations } isLoading={ contractQuery.isPlaceholderData }/>,
// },
// config.features.account.isEnabled && {
// id: [ 'read_write_custom_methods' as const, 'read_custom_methods' as const, 'write_custom_methods' as const ],
// title: 'Custom ABI',
// component: <ContractMethodsCustom isLoading={ contractQuery.isPlaceholderData }/>,
// },
// hasMudTab && {
// id: 'mud_system' as const,
// title: 'MUD System',
// component: mudSystemsQuery.isPlaceholderData ?
// <ContentLoader/> :
// <ContractMethodsMudSystem items={ mudSystemsQuery.data?.items ?? [] }/>,
// },
].filter(Boolean),
isLoading: contractQuery.isPlaceholderData,
};
......
......@@ -7,8 +7,8 @@ import type { AddressCounters } from 'types/api/address';
import { route } from 'nextjs-routes';
import type { ResourceError } from 'lib/api/resources';
import Skeleton from 'ui/shared/chakra/Skeleton';
import LinkInternal from 'ui/shared/links/LinkInternal';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
interface Props {
prop: keyof AddressCounters;
......@@ -27,7 +27,7 @@ const PROP_TO_TAB = {
const AddressCounterItem = ({ prop, query, address, onClick, isAddressQueryLoading, isDegradedData }: Props) => {
if (query.isPlaceholderData || isAddressQueryLoading) {
return <Skeleton h={ 5 } w="80px" borderRadius="full"/>;
return <Skeleton loading h={ 5 } w="80px" borderRadius="full"/>;
}
const data = query.data?.[prop];
......@@ -51,9 +51,9 @@ const AddressCounterItem = ({ prop, query, address, onClick, isAddressQueryLoadi
}
return (
<LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: address, tab: PROP_TO_TAB[prop] } }) } onClick={ onClick }>
<Link href={ route({ pathname: '/address/[hash]', query: { hash: address, tab: PROP_TO_TAB[prop] } }) } onClick={ onClick }>
{ Number(data).toLocaleString() }
</LinkInternal>
</Link>
);
}
}
......
......@@ -23,7 +23,7 @@ const AddressImplementations = ({ data, isLoading }: Props) => {
>
{ `Implementation${ hasManyItems ? 's' : '' }` }
</DetailedInfo.ItemLabel>
<DetailsInfoItem.ValueWithScroll
<DetailedInfo.ItemValueWithScroll
gradientHeight={ 48 }
onScrollVisibilityChange={ setHasScroll }
rowGap={ 2 }
......@@ -42,7 +42,7 @@ const AddressImplementations = ({ data, isLoading }: Props) => {
noIcon
/>
)) }
</DetailsInfoItem.ValueWithScroll>
</DetailedInfo.ItemValueWithScroll>
</>
);
};
......
import { Alert, Flex, chakra } from '@chakra-ui/react';
import { Flex, chakra } from '@chakra-ui/react';
import React from 'react';
import type { AddressMetadataTagFormatted } from 'types/client/addressMetadata';
import { Alert } from 'toolkit/chakra/alert';
interface Props {
tags: Array<AddressMetadataTagFormatted> | undefined;
className?: string;
......@@ -25,11 +27,11 @@ const AddressMetadataAlert = ({ tags, className }: Props) => {
color={ noteTag.meta?.alertTextColor }
whiteSpace="pre-wrap"
display="inline-block"
sx={{
css={{
'& a': {
color: 'link',
_hover: {
color: 'link_hovered',
color: 'link.primary.hover',
},
},
}}
......
import { Image, Tooltip } from '@chakra-ui/react';
import { capitalize } from 'es-toolkit';
import React from 'react';
......@@ -6,8 +5,9 @@ import type { MultichainProviderConfigParsed } from 'types/client/multichainProv
import { route } from 'nextjs-routes';
import LinkExternal from 'ui/shared/links/LinkExternal';
import LinkInternal from 'ui/shared/links/LinkInternal';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import { Tooltip } from 'toolkit/chakra/tooltip';
const TEMPLATE_ADDRESS = '{address}';
......@@ -28,39 +28,28 @@ const AddressMultichainButton = ({ item, addressHash, onClick, hasSingleProvider
{ capitalize(item.name) }
</>
) : (
<Tooltip label={ capitalize(item.name) }>{ buttonIcon }</Tooltip>
<Tooltip content={ capitalize(item.name) }>{ buttonIcon }</Tooltip>
);
const linkProps = {
variant: hasSingleProvider ? 'subtle' as const : undefined,
display: 'flex',
alignItems: 'center',
fontSize: 'sm',
lineHeight: 5,
fontWeight: 500,
onClick,
};
try {
const portfolioUrlString = item.urlTemplate.replace(TEMPLATE_ADDRESS, addressHash);
const portfolioUrl = new URL(portfolioUrlString);
portfolioUrl.searchParams.append('utm_source', 'blockscout');
portfolioUrl.searchParams.append('utm_medium', 'address');
const dappId = item.dappId;
return typeof dappId === 'string' ? (
<LinkInternal
href={ route({ pathname: '/apps/[id]', query: { id: dappId, url: portfolioUrl.toString() } }) }
{ ...linkProps }
>
{ buttonContent }
</LinkInternal>
) : (
<LinkExternal
href={ portfolioUrl.toString() }
{ ...linkProps }
const isExternal = typeof dappId !== 'string';
return (
<Link
external={ isExternal }
href={ isExternal ? portfolioUrl.toString() : route({ pathname: '/apps/[id]', query: { id: dappId, url: portfolioUrl.toString() } }) }
variant={ hasSingleProvider ? 'underlaid' : undefined }
textStyle="sm"
fontWeight="medium"
onClick={ onClick }
>
{ buttonContent }
</LinkExternal>
</Link>
);
} catch (error) {}
......
......@@ -2,7 +2,7 @@ import React from 'react';
import type { Address } from 'types/api/address';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
......@@ -43,7 +43,7 @@ const AddressNameInfo = ({ data, isLoading }: Props) => {
Contract name
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isLoading }>
<Skeleton loading={ isLoading }>
{ data.name }
</Skeleton>
</DetailedInfo.ItemValue>
......@@ -61,7 +61,7 @@ const AddressNameInfo = ({ data, isLoading }: Props) => {
Validator name
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isLoading }>
<Skeleton loading={ isLoading }>
{ data.name }
</Skeleton>
</DetailedInfo.ItemValue>
......
......@@ -6,7 +6,7 @@ import type { Address } from 'types/api/address';
import config from 'configs/app';
import getCurrencyValue from 'lib/getCurrencyValue';
import * as mixpanel from 'lib/mixpanel/index';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import TextSeparator from 'ui/shared/TextSeparator';
import { getTokensTotalInfo } from '../utils/tokenUtils';
......@@ -68,7 +68,7 @@ const AddressNetWorth = ({ addressData, isLoading, addressHash }: Props) => {
}
return (
<Skeleton display="flex" alignItems="center" flexWrap="wrap" isLoaded={ !isLoading && !(addressData?.has_tokens && isPending) } gap={ 2 }>
<Skeleton display="flex" alignItems="center" flexWrap="wrap" loading={ isLoading && !(addressData?.has_tokens && isPending) } gap={ 2 }>
<Text>
{ (isError || !addressData?.exchange_rate) ? 'N/A' : `${ prefix }$${ totalUsd.toFormat(2) }` }
</Text>
......
import { Image } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import * as v from 'valibot';
import config from 'configs/app';
import Skeleton from 'ui/shared/chakra/Skeleton';
import LinkExternal from 'ui/shared/links/LinkExternal';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import TextSeparator from 'ui/shared/TextSeparator';
const feature = config.features.saveOnGas;
......@@ -76,11 +76,11 @@ const AddressSaveOnGas = ({ gasUsed, address }: Props) => {
return (
<>
<TextSeparator color="border.divider"/>
<Skeleton isLoaded={ !query.isPlaceholderData } display="flex" alignItems="center" columnGap={ 2 }>
<Skeleton loading={ query.isPlaceholderData } display="flex" alignItems="center" columnGap={ 2 }>
<Image src="/static/gas_hawk_logo.svg" w="15px" h="20px" alt="GasHawk logo"/>
<LinkExternal href="https://www.gashawk.io?utm_source=blockscout&utm_medium=address" fontSize="sm">
<Link href="https://www.gashawk.io?utm_source=blockscout&utm_medium=address" fontSize="sm" external>
Save { percent.toLocaleString(undefined, { maximumFractionDigits: 0 }) }% with GasHawk
</LinkExternal>
</Link>
</Skeleton>
</>
);
......
import { Tag } from '@chakra-ui/react';
import React from 'react';
import type { FilecoinActorType } from 'types/api/addressParams';
import { Badge } from 'toolkit/chakra/badge';
const ACTOR_TYPES: Record<FilecoinActorType, string> = {
account: 'Account',
cron: 'Scheduled Tasks',
......@@ -33,7 +34,7 @@ const FilecoinActorTag = ({ actorType }: Props) => {
return null;
}
return <Tag colorScheme="gray">{ text }</Tag>;
return <Badge colorPalette="gray">{ text }</Badge>;
};
export default FilecoinActorTag;
import { Box, Flex, IconButton, Tooltip } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { useQueryClient, useIsFetching } from '@tanstack/react-query';
import { sumBy } from 'es-toolkit';
import NextLink from 'next/link';
......@@ -11,7 +11,9 @@ import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { IconButton } from 'toolkit/chakra/icon-button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tooltip } from 'toolkit/chakra/tooltip';
import IconSvg from 'ui/shared/IconSvg';
import useFetchTokens from '../utils/useFetchTokens';
......@@ -44,8 +46,8 @@ const TokenSelect = ({ onClick }: Props) => {
if (isPending) {
return (
<Flex columnGap={ 3 }>
<Skeleton h={ 8 } w="150px" borderRadius="base"/>
<Skeleton h={ 8 } w={ 9 } borderRadius="base"/>
<Skeleton loading h={ 8 } w="150px" borderRadius="base"/>
<Skeleton loading h={ 8 } w={ 9 } borderRadius="base"/>
</Flex>
);
}
......@@ -61,8 +63,9 @@ const TokenSelect = ({ onClick }: Props) => {
<TokenSelectMobile data={ data } isLoading={ tokensIsFetching === 1 }/> :
<TokenSelectDesktop data={ data } isLoading={ tokensIsFetching === 1 }/>
}
<Tooltip label="Show all tokens">
<Tooltip content="Show all tokens">
<Box>
{ /* TODO @tom2drum: replace with Link */ }
<NextLink href={{ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'tokens' } }} passHref legacyBehavior>
<IconButton
aria-label="Show all tokens"
......@@ -70,10 +73,11 @@ const TokenSelect = ({ onClick }: Props) => {
size="sm"
pl="6px"
pr="6px"
icon={ <IconSvg name="wallet" boxSize={ 5 }/> }
as="a"
onClick={ handleIconButtonClick }
/>
>
<IconSvg name="wallet" boxSize={ 5 }/>
</IconButton>
</NextLink>
</Box>
</Tooltip>
......
import { Box, Button, chakra, useColorModeValue } from '@chakra-ui/react';
import { Box, chakra } from '@chakra-ui/react';
import React from 'react';
import type { FormattedData } from './types';
import { space } from 'lib/html-entities';
import * as mixpanel from 'lib/mixpanel/index';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Button } from 'toolkit/chakra/button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import IconSvg from 'ui/shared/IconSvg';
import { getTokensTotalInfo } from '../utils/tokenUtils';
......@@ -13,13 +14,11 @@ import { getTokensTotalInfo } from '../utils/tokenUtils';
interface Props {
isOpen: boolean;
isLoading: boolean;
onClick: () => void;
data: FormattedData;
}
const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const TokenSelectButton = ({ isOpen, isLoading, data, ...rest }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const { usd, num, isOverflow } = getTokensTotalInfo(data);
const skeletonBgColor = useColorModeValue('white', 'black');
const prefix = isOverflow ? '>' : '';
......@@ -29,27 +28,27 @@ const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: Rea
}
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Tokens dropdown' });
onClick();
}, [ isLoading, isOpen, onClick ]);
}, [ isLoading, isOpen ]);
return (
<Box position="relative">
<Box position="relative" className="group">
<Button
ref={ ref }
size="sm"
variant="outline"
colorScheme="gray"
variant="dropdown"
onClick={ handleClick }
isActive={ isOpen }
gap={ 0 }
aria-label="Token select"
{ ...rest }
>
<IconSvg name="tokens" boxSize={ 4 } mr={ 2 }/>
<chakra.span fontWeight={ 600 }>{ prefix }{ num }</chakra.span>
<chakra.span
whiteSpace="pre"
color="text_secondary"
color={ isOpen ? 'inherit' : 'text.secondary' }
fontWeight={ 400 }
maxW={{ base: 'calc(100vw - 230px)', lg: '500px' }}
_groupHover={{ color: 'inherit' }}
overflow="hidden"
textOverflow="ellipsis"
>
......@@ -57,7 +56,18 @@ const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: Rea
</chakra.span>
<IconSvg name="arrows/east-mini" transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 3 }/>
</Button>
{ isLoading && !isOpen && <Skeleton h="100%" w="100%" position="absolute" top={ 0 } left={ 0 } bgColor={ skeletonBgColor } borderRadius="base"/> }
{ isLoading && !isOpen && (
<Skeleton
loading
h="100%"
w="100%"
position="absolute"
top={ 0 }
left={ 0 }
bgColor={{ _light: 'white', _dark: 'black' }}
borderRadius="base"
/>
) }
</Box>
);
};
......
import { PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react';
import React from 'react';
import type { FormattedData } from './types';
import Popover from 'ui/shared/chakra/Popover';
import { PopoverRoot, PopoverTrigger, PopoverContent, PopoverBody } from 'toolkit/chakra/popover';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import TokenSelectButton from './TokenSelectButton';
import TokenSelectMenu from './TokenSelectMenu';
......@@ -15,21 +15,21 @@ interface Props {
}
const TokenSelectDesktop = ({ data, isLoading }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const { open, onOpenChange } = useDisclosure();
const result = useTokenSelect(data);
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverRoot open={ open } onOpenChange={ onOpenChange }>
<PopoverTrigger>
<TokenSelectButton isOpen={ isOpen } onClick={ onToggle } data={ result.data } isLoading={ isLoading }/>
<TokenSelectButton data={ result.data } isLoading={ isLoading } isOpen={ open }/>
</PopoverTrigger>
<PopoverContent w="355px" maxH="450px" overflowY="scroll">
<PopoverBody px={ 4 } py={ 6 } boxShadow="2xl" >
<PopoverBody>
<TokenSelectMenu { ...result }/>
</PopoverBody>
</PopoverContent>
</Popover>
</PopoverRoot>
);
};
......
import { chakra, Flex, useColorModeValue } from '@chakra-ui/react';
import { chakra, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import { route } from 'nextjs-routes';
import getCurrencyValue from 'lib/getCurrencyValue';
import { Link } from 'toolkit/chakra/link';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import LinkInternal from 'ui/shared/links/LinkInternal';
import TruncatedValue from 'ui/shared/TruncatedValue';
import type { TokenEnhancedData } from '../utils/tokenUtils';
......@@ -71,7 +71,7 @@ const TokenSelectItem = ({ data }: Props) => {
const url = route({ pathname: '/token/[hash]', query: { hash: data.token.address } });
return (
<LinkInternal
<Link
px={ 1 }
py="10px"
display="flex"
......@@ -80,7 +80,7 @@ const TokenSelectItem = ({ data }: Props) => {
borderColor="border.divider"
borderBottomWidth="1px"
_hover={{
bgColor: useColorModeValue('blue.50', 'gray.800'),
bgColor: { _light: 'blue.50', _dark: 'gray.800' },
}}
color="unset"
fontSize="sm"
......@@ -102,7 +102,7 @@ const TokenSelectItem = ({ data }: Props) => {
<Flex alignItems="center" justifyContent="space-between" w="100%" whiteSpace="nowrap">
{ secondRow }
</Flex>
</LinkInternal>
</Link>
);
};
......
import { Text, Box, Input, InputGroup, InputLeftElement, useColorModeValue, Flex, Link } from '@chakra-ui/react';
import { Text, Box, Input, Flex } from '@chakra-ui/react';
import { sumBy } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react';
......@@ -7,6 +7,8 @@ import type { FormattedData } from './types';
import type { TokenType } from 'types/api/token';
import { getTokenTypeName } from 'lib/token/tokenTypes';
import { InputGroup } from 'toolkit/chakra/input-group';
import { Link } from 'toolkit/chakra/link';
import IconSvg from 'ui/shared/IconSvg';
import type { Sort } from '../utils/tokenUtils';
......@@ -24,23 +26,16 @@ interface Props {
}
const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onInputChange, onSortClick, searchTerm }: Props) => {
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const hasFilteredResult = sumBy(Object.values(filteredData), ({ items }) => items.length) > 0;
return (
<>
<InputGroup size="xs" mb={ 5 }>
<InputLeftElement >
<IconSvg name="search" boxSize={ 4 } color={ searchIconColor }/>
</InputLeftElement>
<Input
paddingInlineStart="38px"
placeholder="Search by token name"
ml="1px"
onChange={ onInputChange }
bgColor="dialog.bg"
/>
<InputGroup
startElement={ <IconSvg name="search" boxSize={ 4 } color={{ _light: 'blackAlpha.600', _dark: 'whiteAlpha.600' }}/> }
startOffset="38px"
mb={ 5 }
>
<Input placeholder="Search by token name" onChange={ onInputChange } size="sm" bgColor="dialog.bg"/>
</InputGroup>
<Flex flexDir="column" rowGap={ 6 }>
{ Object.entries(filteredData).sort(sortTokenGroups).map(([ tokenType, tokenInfo ]) => {
......
import { useDisclosure, Modal, ModalContent, ModalCloseButton } from '@chakra-ui/react';
import React from 'react';
import type { FormattedData } from './types';
import { DialogContent, DialogRoot } from 'toolkit/chakra/dialog';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import TokenSelectButton from './TokenSelectButton';
import TokenSelectMenu from './TokenSelectMenu';
import useTokenSelect from './useTokenSelect';
......@@ -13,18 +15,17 @@ interface Props {
}
const TokenSelectMobile = ({ data, isLoading }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const { open, onOpenChange } = useDisclosure();
const result = useTokenSelect(data);
return (
<>
<TokenSelectButton isOpen={ isOpen } onClick={ onToggle } data={ result.data } isLoading={ isLoading }/>
<Modal isOpen={ isOpen } onClose={ onClose } size="full">
<ModalContent>
<ModalCloseButton/>
<TokenSelectButton isOpen={ open } data={ result.data } isLoading={ isLoading }/>
<DialogRoot open={ open } onOpenChange={ onOpenChange } size="full">
<DialogContent>
<TokenSelectMenu { ...result }/>
</ModalContent>
</Modal>
</DialogContent>
</DialogRoot>
</>
);
};
......
import { Box, Flex, HStack, useColorModeValue } from '@chakra-ui/react';
import { Box, Flex, HStack } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -19,6 +19,7 @@ import useSocketMessage from 'lib/socket/useSocketMessage';
import useFetchXStarScore from 'lib/xStarScore/useFetchXStarScore';
import { ADDRESS_TABS_COUNTERS } from 'stubs/address';
import { USER_OPS_ACCOUNT } from 'stubs/userOps';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import AddressAccountHistory from 'ui/address/AddressAccountHistory';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
import AddressCoinBalance from 'ui/address/AddressCoinBalance';
......@@ -54,7 +55,6 @@ import sortEntityTags from 'ui/shared/EntityTags/sortEntityTags';
import IconSvg from 'ui/shared/IconSvg';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
const TOKEN_TABS = [ 'tokens_erc20', 'tokens_nfts', 'tokens_nfts_collection', 'tokens_nfts_list' ];
const PREDEFINED_TAG_PRIORITY = 100;
......@@ -142,7 +142,6 @@ const AddressPageContent = () => {
});
const isSafeAddress = useIsSafeAddress(!addressQuery.isPlaceholderData && addressQuery.data?.is_contract ? hash : undefined);
const safeIconColor = useColorModeValue('black', 'white');
const xStarQuery = useFetchXStarScore({ hash });
......@@ -154,111 +153,111 @@ const AddressPageContent = () => {
const tabs: Array<RoutedTab> = React.useMemo(() => {
return [
config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0 && {
id: 'mud',
title: 'MUD',
count: mudTablesCountQuery.data,
component: <AddressMud scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
},
// config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0 && {
// id: 'mud',
// title: 'MUD',
// count: mudTablesCountQuery.data,
// component: <AddressMud scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// },
{
id: 'txs',
title: 'Transactions',
count: addressTabsCountersQuery.data?.transactions_count,
component: <AddressTxs scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
},
txInterpretation.isEnabled && txInterpretation.provider === 'noves' ?
{
id: 'account_history',
title: 'Account history',
component: <AddressAccountHistory scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
config.features.userOps.isEnabled && Boolean(userOpsAccountQuery.data?.total_ops) ?
{
id: 'user_ops',
title: 'User operations',
count: userOpsAccountQuery.data?.total_ops,
component: <AddressUserOps shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
config.features.beaconChain.isEnabled && addressTabsCountersQuery.data?.withdrawals_count ?
{
id: 'withdrawals',
title: 'Withdrawals',
count: addressTabsCountersQuery.data?.withdrawals_count,
component: <AddressWithdrawals scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
{
id: 'token_transfers',
title: 'Token transfers',
count: addressTabsCountersQuery.data?.token_transfers_count,
component: <AddressTokenTransfers scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
},
{
id: 'tokens',
title: 'Tokens',
count: addressTabsCountersQuery.data?.token_balances_count,
component: <AddressTokens shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
subTabs: TOKEN_TABS,
},
{
id: 'internal_txns',
title: 'Internal txns',
count: addressTabsCountersQuery.data?.internal_transactions_count,
component: <AddressInternalTxs scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
},
addressTabsCountersQuery.data?.celo_election_rewards_count ? {
id: 'epoch_rewards',
title: 'Epoch rewards',
count: addressTabsCountersQuery.data?.celo_election_rewards_count,
component: <AddressEpochRewards scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} : undefined,
{
id: 'coin_balance_history',
title: 'Coin balance history',
component: <AddressCoinBalance shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
},
addressTabsCountersQuery.data?.validations_count ?
{
id: 'blocks_validated',
title: `Blocks ${ getNetworkValidationActionText() }`,
count: addressTabsCountersQuery.data?.validations_count,
component: <AddressBlocksValidated scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
addressTabsCountersQuery.data?.logs_count ?
{
id: 'logs',
title: 'Logs',
count: addressTabsCountersQuery.data?.logs_count,
component: <AddressLogs scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
addressQuery.data?.is_contract ? {
id: 'contract',
title: () => {
if (addressQuery.data.is_verified) {
return (
<>
<span>Contract</span>
<IconSvg name="status/success" boxSize="14px" color="green.500" ml={ 1 }/>
</>
);
}
return 'Contract';
},
component: (
<AddressContract
tabs={ contractTabs.tabs }
shouldRender={ !isTabsLoading }
isLoading={ contractTabs.isLoading }
/>
),
subTabs: CONTRACT_TAB_IDS,
} : undefined,
// txInterpretation.isEnabled && txInterpretation.provider === 'noves' ?
// {
// id: 'account_history',
// title: 'Account history',
// component: <AddressAccountHistory scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// } :
// undefined,
// config.features.userOps.isEnabled && Boolean(userOpsAccountQuery.data?.total_ops) ?
// {
// id: 'user_ops',
// title: 'User operations',
// count: userOpsAccountQuery.data?.total_ops,
// component: <AddressUserOps shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// } :
// undefined,
// config.features.beaconChain.isEnabled && addressTabsCountersQuery.data?.withdrawals_count ?
// {
// id: 'withdrawals',
// title: 'Withdrawals',
// count: addressTabsCountersQuery.data?.withdrawals_count,
// component: <AddressWithdrawals scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// } :
// undefined,
// {
// id: 'token_transfers',
// title: 'Token transfers',
// count: addressTabsCountersQuery.data?.token_transfers_count,
// component: <AddressTokenTransfers scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// },
// {
// id: 'tokens',
// title: 'Tokens',
// count: addressTabsCountersQuery.data?.token_balances_count,
// component: <AddressTokens shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// subTabs: TOKEN_TABS,
// },
// {
// id: 'internal_txns',
// title: 'Internal txns',
// count: addressTabsCountersQuery.data?.internal_transactions_count,
// component: <AddressInternalTxs scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// },
// addressTabsCountersQuery.data?.celo_election_rewards_count ? {
// id: 'epoch_rewards',
// title: 'Epoch rewards',
// count: addressTabsCountersQuery.data?.celo_election_rewards_count,
// component: <AddressEpochRewards scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// } : undefined,
// {
// id: 'coin_balance_history',
// title: 'Coin balance history',
// component: <AddressCoinBalance shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// },
// addressTabsCountersQuery.data?.validations_count ?
// {
// id: 'blocks_validated',
// title: `Blocks ${ getNetworkValidationActionText() }`,
// count: addressTabsCountersQuery.data?.validations_count,
// component: <AddressBlocksValidated scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// } :
// undefined,
// addressTabsCountersQuery.data?.logs_count ?
// {
// id: 'logs',
// title: 'Logs',
// count: addressTabsCountersQuery.data?.logs_count,
// component: <AddressLogs scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// } :
// undefined,
// addressQuery.data?.is_contract ? {
// id: 'contract',
// title: () => {
// if (addressQuery.data.is_verified) {
// return (
// <>
// <span>Contract</span>
// <IconSvg name="status/success" boxSize="14px" color="green.500" ml={ 1 }/>
// </>
// );
// }
// return 'Contract';
// },
// component: (
// <AddressContract
// tabs={ contractTabs.tabs }
// shouldRender={ !isTabsLoading }
// isLoading={ contractTabs.isLoading }
// />
// ),
// subTabs: CONTRACT_TAB_IDS,
// } : undefined,
].filter(Boolean);
}, [
addressQuery.data,
......@@ -343,7 +342,7 @@ const AddressPageContent = () => {
const content = (addressQuery.isError || addressQuery.isDegradedData) ?
null :
<RoutedTabs tabs={ tabs } tabListProps={{ mt: 6 }} isLoading={ isTabsLoading }/>;
<RoutedTabs tabs={ tabs } listProps={{ mt: 6 }} isLoading={ isTabsLoading }/>;
const backLink = React.useMemo(() => {
if (appProps.referrer && appProps.referrer.includes('/accounts')) {
......@@ -367,51 +366,51 @@ const AddressPageContent = () => {
// In this case it returns 404 with empty payload, so we calculate check-summed hash on the client
const checkSummedHash = React.useMemo(() => addressQuery.data?.hash ?? getCheckedSummedAddress(hash), [ hash, addressQuery.data?.hash ]);
const titleSecondRow = (
<Flex alignItems="center" w="100%" columnGap={ 2 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
{ addressQuery.data?.ens_domain_name && (
<EnsEntity
domain={ addressQuery.data?.ens_domain_name }
protocol={ !addressEnsDomainsQuery.isPending ? addressMainDomain?.protocol : null }
fontFamily="heading"
fontSize="lg"
fontWeight={ 500 }
mr={ 1 }
maxW="300px"
/>
) }
<AddressEntity
address={{
...addressQuery.data,
hash: checkSummedHash,
name: '',
ens_domain_name: '',
implementations: null,
}}
isLoading={ isLoading }
fontFamily="heading"
fontSize="lg"
fontWeight={ 500 }
noLink
isSafeAddress={ isSafeAddress }
icon={{ color: isSafeAddress ? safeIconColor : undefined }}
mr={ 4 }
/>
{ !isLoading && addressQuery.data?.is_contract && addressQuery.data.token &&
<AddressAddToWallet token={ addressQuery.data.token } variant="button"/> }
{ !isLoading && !addressQuery.data?.is_contract && config.features.account.isEnabled && (
<AddressFavoriteButton hash={ hash } watchListId={ addressQuery.data?.watchlist_address_id }/>
) }
<AddressQrCode address={{ hash: addressQuery.data?.filecoin?.robust ?? checkSummedHash }} isLoading={ isLoading }/>
<AccountActionsMenu isLoading={ isLoading }/>
<HStack ml="auto" gap={ 2 }/>
{ !isLoading && addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled &&
<SolidityscanReport hash={ hash }/> }
{ !isLoading && addressEnsDomainsQuery.data && config.features.nameService.isEnabled &&
<AddressEnsDomains query={ addressEnsDomainsQuery } addressHash={ hash } mainDomainName={ addressQuery.data?.ens_domain_name }/> }
<NetworkExplorers type="address" pathParam={ hash.toLowerCase() }/>
</Flex>
);
// const titleSecondRow = (
// <Flex alignItems="center" w="100%" columnGap={ 2 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
// { addressQuery.data?.ens_domain_name && (
// <EnsEntity
// domain={ addressQuery.data?.ens_domain_name }
// protocol={ !addressEnsDomainsQuery.isPending ? addressMainDomain?.protocol : null }
// fontFamily="heading"
// fontSize="lg"
// fontWeight={ 500 }
// mr={ 1 }
// maxW="300px"
// />
// ) }
// <AddressEntity
// address={{
// ...addressQuery.data,
// hash: checkSummedHash,
// name: '',
// ens_domain_name: '',
// implementations: null,
// }}
// isLoading={ isLoading }
// fontFamily="heading"
// fontSize="lg"
// fontWeight={ 500 }
// noLink
// isSafeAddress={ isSafeAddress }
// icon={{ color: isSafeAddress ? { _light: 'black', _dark: 'white' } : undefined }}
// mr={ 4 }
// />
// { !isLoading && addressQuery.data?.is_contract && addressQuery.data.token &&
// <AddressAddToWallet token={ addressQuery.data.token } variant="button"/> }
// { !isLoading && !addressQuery.data?.is_contract && config.features.account.isEnabled && (
// <AddressFavoriteButton hash={ hash } watchListId={ addressQuery.data?.watchlist_address_id }/>
// ) }
// <AddressQrCode address={{ hash: addressQuery.data?.filecoin?.robust ?? checkSummedHash }} isLoading={ isLoading }/>
// <AccountActionsMenu isLoading={ isLoading }/>
// <HStack ml="auto" gap={ 2 }/>
// { !isLoading && addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled &&
// <SolidityscanReport hash={ hash }/> }
// { !isLoading && addressEnsDomainsQuery.data && config.features.nameService.isEnabled &&
// <AddressEnsDomains query={ addressEnsDomainsQuery } addressHash={ hash } mainDomainName={ addressQuery.data?.ens_domain_name }/> }
// <NetworkExplorers type="address" pathParam={ hash.toLowerCase() }/>
// </Flex>
// );
return (
<>
......@@ -420,7 +419,7 @@ const AddressPageContent = () => {
title={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` }
backLink={ backLink }
contentAfter={ titleContentAfter }
secondRow={ titleSecondRow }
// secondRow={ titleSecondRow }
isLoading={ isLoading }
/>
{ !addressMetadataQuery.isPending &&
......
......@@ -12,7 +12,7 @@ const ServiceDegradationWarning = ({ isLoading, className }: Props) => {
return (
<Alert
loading={ isLoading }
status="neutral"
status="info"
className={ className }
startElement={ <Spinner size="sm" my="3px" flexShrink={ 0 }/> }
>
......
......@@ -172,7 +172,6 @@ const AddressEntry = (props: EntityProps) => {
onMouseLeave={ highlightContext?.onMouseLeave }
position="relative"
zIndex={ 0 }
fontWeight="medium"
>
<Icon { ...partsProps.icon }/>
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
......
......@@ -10,7 +10,7 @@ const MaintenanceAlert = () => {
}
return (
<Alert status="neutral" showIcon>
<Alert status="info" showIcon>
<Box
dangerouslySetInnerHTML={{ __html: config.UI.maintenanceAlert.message }}
css={{
......
......@@ -20,8 +20,8 @@ type Props = {
filterValue?: AddressFromToFilter;
enableTimeIncrement?: boolean;
top?: number;
sorting: TransactionsSortingValue | undefined;
setSort: (value?: TransactionsSortingValue) => void;
sorting: TransactionsSortingValue;
setSort: (value: TransactionsSortingValue) => void;
};
const TxsWithAPISorting = ({
......@@ -39,7 +39,7 @@ const TxsWithAPISorting = ({
setSort,
}: Props) => {
const handleSortChange = React.useCallback((value?: TransactionsSortingValue) => {
const handleSortChange = React.useCallback((value: TransactionsSortingValue) => {
setSort(value);
query.onSortingChange(getSortParamsFromValue(value));
}, [ setSort, query ]);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment