Commit ac93ad8b authored by Nick Zenchik's avatar Nick Zenchik

Merging main to resolve conflicts

parents b0627573 cc425ddb
...@@ -199,7 +199,7 @@ module.exports = { ...@@ -199,7 +199,7 @@ module.exports = {
groups: [ groups: [
'module', 'module',
'/types/', '/types/',
[ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^lib/', '/^mocks/', '/^pages/', '/^playwright/', '/^theme/', '/^ui/' ], [ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^lib/', '/^mocks/', '/^pages/', '/^playwright/', '/^stubs/', '/^theme/', '/^ui/' ],
[ 'parent', 'sibling', 'index' ], [ 'parent', 'sibling', 'index' ],
], ],
alphabetize: { order: 'asc', ignoreCase: true }, alphabetize: { order: 'asc', ignoreCase: true },
......
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 20"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.645 19.375h-10A1.875 1.875 0 0 1 3.77 17.5V5.625a.625.625 0 0 1 1.25 0V17.5a.625.625 0 0 0 .625.625h10a.625.625 0 0 0 .625-.625V5.625a.625.625 0 0 1 1.25 0V17.5a1.875 1.875 0 0 1-1.875 1.875Zm2.5-15h-15a.625.625 0 0 1 0-1.25h15a.625.625 0 1 1 0 1.25Z"/> <path d="M15 19.375H5A1.875 1.875 0 0 1 3.125 17.5V5.625a.625.625 0 0 1 1.25 0V17.5a.625.625 0 0 0 .625.625h10a.624.624 0 0 0 .625-.625V5.625a.625.625 0 1 1 1.25 0V17.5A1.875 1.875 0 0 1 15 19.375Zm2.5-15h-15a.625.625 0 0 1 0-1.25h15a.625.625 0 1 1 0 1.25Z" fill="currentColor"/>
<path d="M13.145 4.375a.625.625 0 0 1-.625-.625V1.875H8.77V3.75a.625.625 0 0 1-1.25 0v-2.5a.625.625 0 0 1 .625-.625h5a.625.625 0 0 1 .625.625v2.5a.625.625 0 0 1-.625.625Zm-2.5 11.875a.625.625 0 0 1-.625-.625v-8.75a.625.625 0 0 1 1.25 0v8.75a.625.625 0 0 1-.625.625ZM13.77 15a.625.625 0 0 1-.625-.625v-6.25a.625.625 0 0 1 1.25 0v6.25a.625.625 0 0 1-.625.625Zm-6.25 0a.625.625 0 0 1-.625-.625v-6.25a.625.625 0 0 1 1.25 0v6.25A.625.625 0 0 1 7.52 15Z"/> <path d="M12.5 4.375a.625.625 0 0 1-.625-.625V1.875h-3.75V3.75a.625.625 0 0 1-1.25 0v-2.5A.625.625 0 0 1 7.5.625h5a.625.625 0 0 1 .625.625v2.5a.625.625 0 0 1-.625.625ZM10 16.25a.625.625 0 0 1-.625-.625v-8.75a.625.625 0 0 1 1.25 0v8.75a.624.624 0 0 1-.625.625ZM13.125 15a.624.624 0 0 1-.625-.625v-6.25a.625.625 0 1 1 1.25 0v6.25a.624.624 0 0 1-.625.625Zm-6.25 0a.625.625 0 0 1-.625-.625v-6.25a.625.625 0 0 1 1.25 0v6.25a.625.625 0 0 1-.625.625Z" fill="currentColor"/>
</svg> </svg>
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.433 3.6 16.399.569A1.926 1.926 0 0 0 15.03 0c-.518 0-1.005.202-1.37.568L.961 13.264a.779.779 0 0 0-.221.446l-.734 5.406a.78.78 0 0 0 .877.877l5.406-.734a.78.78 0 0 0 .446-.221L19.433 6.342c.366-.366.567-.853.567-1.37 0-.518-.201-1.005-.567-1.371ZM5.82 17.75l-4.131.561.56-4.131 8.997-8.997 3.571 3.57L5.82 17.75ZM18.33 5.24l-2.41 2.412-3.57-3.57 2.411-2.413a.379.379 0 0 1 .538 0l3.033 3.033a.379.379 0 0 1 0 .538Z"/> <path d="M19.432 3.6 16.4.569A1.925 1.925 0 0 0 15.03 0c-.518 0-1.005.202-1.371.568L.962 13.264a.779.779 0 0 0-.221.446l-.734 5.406a.779.779 0 0 0 .877.877l5.406-.734a.778.778 0 0 0 .446-.221L19.432 6.342c.366-.366.568-.853.568-1.37 0-.518-.202-1.005-.568-1.371ZM5.82 17.75l-4.132.561.561-4.131 8.997-8.997 3.57 3.57L5.82 17.75ZM18.33 5.24l-2.41 2.412-3.571-3.57L14.76 1.67a.379.379 0 0 1 .537 0l3.034 3.032a.378.378 0 0 1 0 .538Z" fill="currentColor"/>
</svg> </svg>
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import type { PageParams } from 'lib/next/token/types'; import type { PageParams } from 'lib/next/token/types';
import getSeo from 'lib/next/token/getSeo'; import getSeo from 'lib/next/token/getSeo';
import Token from 'ui/pages/Token'; import Page from 'ui/shared/Page/Page';
const Token = dynamic(() => import('ui/pages/Token'), { ssr: false });
const TokenPage: NextPage<PageParams> = ({ hash }: PageParams) => { const TokenPage: NextPage<PageParams> = ({ hash }: PageParams) => {
const { title, description } = getSeo({ hash }); const { title, description } = getSeo({ hash });
...@@ -16,7 +19,9 @@ const TokenPage: NextPage<PageParams> = ({ hash }: PageParams) => { ...@@ -16,7 +19,9 @@ const TokenPage: NextPage<PageParams> = ({ hash }: PageParams) => {
<title>{ title }</title> <title>{ title }</title>
<meta name="description" content={ description }/> <meta name="description" content={ description }/>
</Head> </Head>
<Token/> <Page>
<Token/>
</Page>
</> </>
); );
}; };
......
import { ADDRESS_PARAMS, ADDRESS_HASH } from './addressParams';
export const PRIVATE_TAG_ADDRESS = {
address: ADDRESS_PARAMS,
address_hash: ADDRESS_HASH,
id: '4',
name: 'placeholder',
};
import type { Address } from 'types/api/address';
import { ADDRESS_HASH } from './addressParams';
import { TOKEN_INFO_ERC_20 } from './token';
export const ADDRESS_INFO: Address = {
block_number_balance_updated_at: 8774377,
coin_balance: '0',
creation_tx_hash: null,
creator_address_hash: null,
exchange_rate: null,
has_custom_methods_read: false,
has_custom_methods_write: false,
has_decompiled_code: false,
has_logs: true,
has_methods_read: false,
has_methods_read_proxy: false,
has_methods_write: false,
has_methods_write_proxy: false,
has_token_transfers: false,
has_tokens: false,
has_validated_blocks: false,
hash: ADDRESS_HASH,
implementation_address: null,
implementation_name: null,
is_contract: false,
is_verified: false,
name: 'ChainLink Token (goerli)',
token: TOKEN_INFO_ERC_20,
private_tags: [],
public_tags: [],
watchlist_names: [],
watchlist_address_id: null,
};
import type { AddressParam } from 'types/api/addressParams';
export const ADDRESS_HASH = '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a';
export const ADDRESS_PARAMS: AddressParam = {
hash: ADDRESS_HASH,
implementation_name: null,
is_contract: false,
is_verified: null,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
};
export const BLOCK_HASH = '0x8fa7b9e5e5e79deeb62d608db22ba9a5cb45388c7ebb9223ae77331c6080dc70';
import type { SmartContract } from 'types/api/contract';
export const CONTRACT_CODE_UNVERIFIED = {
creation_bytecode: '0x60806040526e',
deployed_bytecode: '0x608060405233',
is_self_destructed: false,
} as SmartContract;
export const CONTRACT_CODE_VERIFIED = {
abi: [],
additional_sources: [],
can_be_visualized_via_sol2uml: true,
compiler_settings: {
compilationTarget: {
'contracts/StubContract.sol': 'StubContract',
},
evmVersion: 'london',
libraries: {},
metadata: {
bytecodeHash: 'ipfs',
},
optimizer: {
enabled: false,
runs: 200,
},
remappings: [],
},
compiler_version: 'v0.8.7+commit.e28d00a7',
creation_bytecode: '0x6080604052348',
deployed_bytecode: '0x60806040',
evm_version: 'london',
external_libraries: [],
file_path: 'contracts/StubContract.sol',
is_verified: true,
name: 'StubContract',
optimization_enabled: false,
optimization_runs: 200,
source_code: 'source_code',
verified_at: '2023-02-21T14:39:16.906760Z',
} as unknown as SmartContract;
import type { TokenCounters, TokenHolder, TokenHolders, TokenInfo, TokenInstance, TokenInventoryResponse, TokenType } from 'types/api/token';
import type { TokenTransfer, TokenTransferResponse } from 'types/api/tokenTransfer';
import { ADDRESS_PARAMS, ADDRESS_HASH } from './addressParams';
import { BLOCK_HASH } from './block';
import { TX_HASH } from './tx';
export const TOKEN_INFO_ERC_20: TokenInfo<'ERC-20'> = {
address: ADDRESS_HASH,
decimals: '18',
exchange_rate: null,
holders: '16026',
name: 'Stub Token (goerli)',
symbol: 'STUB',
total_supply: '6000000000000000000',
type: 'ERC-20',
};
export const TOKEN_INFO_ERC_721: TokenInfo<'ERC-721'> = {
...TOKEN_INFO_ERC_20,
type: 'ERC-721',
};
export const TOKEN_INFO_ERC_1155: TokenInfo<'ERC-1155'> = {
...TOKEN_INFO_ERC_20,
type: 'ERC-1155',
};
export const TOKEN_COUNTERS: TokenCounters = {
token_holders_count: '123456',
transfers_count: '123456',
};
export const TOKEN_HOLDER: TokenHolder = {
address: ADDRESS_PARAMS,
value: '1021378038331138520',
};
export const TOKEN_HOLDERS: TokenHolders = { items: Array(50).fill(TOKEN_HOLDER), next_page_params: null };
export const TOKEN_TRANSFER_ERC_20: TokenTransfer = {
block_hash: BLOCK_HASH,
from: ADDRESS_PARAMS,
log_index: '4',
method: 'addLiquidity',
timestamp: '2022-06-24T10:22:11.000000Z',
to: ADDRESS_PARAMS,
token: TOKEN_INFO_ERC_20,
total: {
decimals: '18',
value: '9851351626684503',
},
tx_hash: TX_HASH,
type: 'token_minting',
};
export const TOKEN_TRANSFER_ERC_721: TokenTransfer = {
...TOKEN_TRANSFER_ERC_20,
total: {
token_id: '35870',
},
token: TOKEN_INFO_ERC_721,
};
export const TOKEN_TRANSFER_ERC_1155: TokenTransfer = {
...TOKEN_TRANSFER_ERC_20,
total: {
token_id: '35870',
value: '123',
decimals: '18',
},
token: TOKEN_INFO_ERC_1155,
};
export const getTokenTransfersStub = (type?: TokenType): TokenTransferResponse => {
switch (type) {
case 'ERC-721':
return { items: Array(50).fill(TOKEN_TRANSFER_ERC_721), next_page_params: null };
case 'ERC-1155':
return { items: Array(50).fill(TOKEN_TRANSFER_ERC_1155), next_page_params: null };
default:
return { items: Array(50).fill(TOKEN_TRANSFER_ERC_20), next_page_params: null };
}
};
export const TOKEN_INSTANCE: TokenInstance = {
animation_url: null,
external_app_url: 'https://vipsland.com/nft/collections/genesis/188882',
id: '188882',
image_url: 'https://ipfs.vipsland.com/nft/collections/genesis/188882.gif',
is_unique: true,
metadata: {
attributes: Array(3).fill({ trait_type: 'skin', value: '6' }),
description: '**GENESIS #188882**, **8a77ca1bcaa4036f** :: *844th* generation of *#57806 and #57809* :: **eGenetic Hash Code (eDNA)** = *2822355e953a462d*',
external_url: 'https://vipsland.com/nft/collections/genesis/188882',
image: 'https://ipfs.vipsland.com/nft/collections/genesis/188882.gif',
name: 'GENESIS #188882, 8a77ca1bcaa4036f. Blockchain pixel PFP NFT + "on music video" trait inspired by God',
},
owner: ADDRESS_PARAMS,
token: TOKEN_INFO_ERC_1155,
holder_address_hash: ADDRESS_HASH,
};
export const TOKEN_INSTANCES: TokenInventoryResponse = {
items: Array(50).fill(TOKEN_INSTANCE),
next_page_params: null,
};
export const TX_HASH = '0x3ed9d81e7c1001bdda1caa1dc62c0acbbe3d2c671cdc20dc1e65efdaa4186967';
...@@ -26,7 +26,7 @@ const baseStyle = defineStyle((props) => { ...@@ -26,7 +26,7 @@ const baseStyle = defineStyle((props) => {
return { return {
opacity: 1, opacity: 1,
borderRadius: 'base', borderRadius: 'md',
borderColor: start, borderColor: start,
background: `linear-gradient(90deg, ${ start } 8%, ${ end } 18%, ${ start } 33%)`, background: `linear-gradient(90deg, ${ start } 8%, ${ end } 18%, ${ start } 33%)`,
backgroundSize: '200% 100%', backgroundSize: '200% 100%',
......
...@@ -20,7 +20,7 @@ export interface TokenCounters { ...@@ -20,7 +20,7 @@ export interface TokenCounters {
export interface TokenHolders { export interface TokenHolders {
items: Array<TokenHolder>; items: Array<TokenHolder>;
next_page_params: TokenHoldersPagination; next_page_params: TokenHoldersPagination | null;
} }
export type TokenHolder = { export type TokenHolder = {
...@@ -51,7 +51,7 @@ export interface TokenInstanceTransfersCount { ...@@ -51,7 +51,7 @@ export interface TokenInstanceTransfersCount {
export interface TokenInventoryResponse { export interface TokenInventoryResponse {
items: Array<TokenInstance>; items: Array<TokenInstance>;
next_page_params: TokenInventoryPagination; next_page_params: TokenInventoryPagination | null;
} }
export type TokenInventoryPagination = { export type TokenInventoryPagination = {
......
This diff is collapsed.
import { Flex, Text, Tooltip } from '@chakra-ui/react'; import { Flex, Skeleton, Text, Tooltip } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
...@@ -17,14 +17,15 @@ interface Props { ...@@ -17,14 +17,15 @@ interface Props {
filePath?: string; filePath?: string;
additionalSource?: SmartContract['additional_sources']; additionalSource?: SmartContract['additional_sources'];
remappings?: Array<string>; remappings?: Array<string>;
isLoading?: boolean;
} }
const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, additionalSource, remappings }: Props) => { const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, additionalSource, remappings, isLoading }: Props) => {
const heading = ( const heading = (
<Text fontWeight={ 500 }> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>
<span>Contract source code</span> <span>Contract source code</span>
<Text whiteSpace="pre" as="span" variant="secondary"> ({ isViper ? 'Vyper' : 'Solidity' })</Text> <Text whiteSpace="pre" as="span" variant="secondary"> ({ isViper ? 'Vyper' : 'Solidity' })</Text>
</Text> </Skeleton>
); );
const diagramLink = hasSol2Yml && address ? ( const diagramLink = hasSol2Yml && address ? (
...@@ -32,9 +33,10 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi ...@@ -32,9 +33,10 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
<LinkInternal <LinkInternal
href={ route({ pathname: '/visualize/sol2uml', query: { address } }) } href={ route({ pathname: '/visualize/sol2uml', query: { address } }) }
ml="auto" ml="auto"
mr={ 3 }
> >
View UML diagram <Skeleton isLoaded={ !isLoading }>
View UML diagram
</Skeleton>
</LinkInternal> </LinkInternal>
</Tooltip> </Tooltip>
) : null; ) : null;
...@@ -47,7 +49,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi ...@@ -47,7 +49,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
}, [ additionalSource, data, filePath, isViper ]); }, [ additionalSource, data, filePath, isViper ]);
const copyToClipboard = editorData.length === 1 ? const copyToClipboard = editorData.length === 1 ?
<CopyToClipboard text={ editorData[0].source_code }/> : <CopyToClipboard text={ editorData[0].source_code } isLoading={ isLoading } ml={ 3 }/> :
null; null;
return ( return (
...@@ -57,7 +59,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi ...@@ -57,7 +59,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
{ diagramLink } { diagramLink }
{ copyToClipboard } { copyToClipboard }
</Flex> </Flex>
<CodeEditor data={ editorData } remappings={ remappings }/> { isLoading ? <Skeleton h="557px" w="100%"/> : <CodeEditor data={ editorData } remappings={ remappings }/> }
</section> </section>
); );
}; };
......
import { chakra, Alert, Icon, Modal, ModalBody, ModalContent, ModalCloseButton, ModalOverlay, Box, useDisclosure, Tooltip, IconButton } from '@chakra-ui/react'; import {
chakra,
Alert,
Icon,
Modal,
ModalBody,
ModalContent,
ModalCloseButton,
ModalOverlay,
Box,
useDisclosure,
Tooltip,
IconButton,
Skeleton,
} from '@chakra-ui/react';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
import React from 'react'; import React from 'react';
...@@ -13,9 +27,10 @@ const SVG_OPTIONS = { ...@@ -13,9 +27,10 @@ const SVG_OPTIONS = {
interface Props { interface Props {
className?: string; className?: string;
hash: string; hash: string;
isLoading?: boolean;
} }
const AddressQrCode = ({ hash, className }: Props) => { const AddressQrCode = ({ hash, className, isLoading }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [ qr, setQr ] = React.useState(''); const [ qr, setQr ] = React.useState('');
...@@ -36,6 +51,10 @@ const AddressQrCode = ({ hash, className }: Props) => { ...@@ -36,6 +51,10 @@ const AddressQrCode = ({ hash, className }: Props) => {
} }
}, [ hash, isOpen, onClose ]); }, [ hash, isOpen, onClose ]);
if (isLoading) {
return <Skeleton className={ className } w="36px" h="32px" borderRadius="base"/>;
}
return ( return (
<> <>
<Tooltip label="Click to view QR code"> <Tooltip label="Click to view QR code">
......
...@@ -11,6 +11,7 @@ import dayjs from 'lib/date/dayjs'; ...@@ -11,6 +11,7 @@ import dayjs from 'lib/date/dayjs';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import InOutTag from 'ui/shared/InOutTag'; import InOutTag from 'ui/shared/InOutTag';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
...@@ -56,6 +57,7 @@ const TxInternalsListItem = ({ ...@@ -56,6 +57,7 @@ const TxInternalsListItem = ({
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
<AddressIcon address={ from }/> <AddressIcon address={ from }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ isOut }/> <AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ isOut }/>
{ isIn && <CopyToClipboard text={ from.hash }/> }
</Address> </Address>
{ (isIn || isOut) ? { (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut }/> : <InOutTag isIn={ isIn } isOut={ isOut }/> :
...@@ -65,6 +67,7 @@ const TxInternalsListItem = ({ ...@@ -65,6 +67,7 @@ const TxInternalsListItem = ({
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
<AddressIcon address={ toData }/> <AddressIcon address={ toData }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ toData.hash } isDisabled={ isIn }/> <AddressLink type="address" ml={ 2 } fontWeight="500" hash={ toData.hash } isDisabled={ isIn }/>
{ isOut && <CopyToClipboard text={ toData.hash }/> }
</Address> </Address>
) } ) }
</Box> </Box>
......
...@@ -11,6 +11,7 @@ import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; ...@@ -11,6 +11,7 @@ import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import InOutTag from 'ui/shared/InOutTag'; import InOutTag from 'ui/shared/InOutTag';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
import TxStatus from 'ui/shared/TxStatus'; import TxStatus from 'ui/shared/TxStatus';
...@@ -64,6 +65,7 @@ const AddressIntTxsTableItem = ({ ...@@ -64,6 +65,7 @@ const AddressIntTxsTableItem = ({
<Address display="inline-flex" maxW="100%"> <Address display="inline-flex" maxW="100%">
<AddressIcon address={ from }/> <AddressIcon address={ from }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 } isDisabled={ isOut }/> <AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 } isDisabled={ isOut }/>
{ isIn && <CopyToClipboard text={ from.hash }/> }
</Address> </Address>
</Td> </Td>
<Td px={ 0 } verticalAlign="middle"> <Td px={ 0 } verticalAlign="middle">
...@@ -77,6 +79,7 @@ const AddressIntTxsTableItem = ({ ...@@ -77,6 +79,7 @@ const AddressIntTxsTableItem = ({
<Address display="inline-flex" maxW="100%"> <Address display="inline-flex" maxW="100%">
<AddressIcon address={ toData }/> <AddressIcon address={ toData }/>
<AddressLink type="address" hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 } isDisabled={ isIn }/> <AddressLink type="address" hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 } isDisabled={ isIn }/>
{ isOut && <CopyToClipboard text={ toData.hash }/> }
</Address> </Address>
) } ) }
</Td> </Td>
......
...@@ -49,7 +49,7 @@ const Home = () => { ...@@ -49,7 +49,7 @@ const Home = () => {
</Box> </Box>
<Stats/> <Stats/>
<ChainIndicators/> <ChainIndicators/>
<AdBanner mt={{ base: 6, lg: 8 }} justifyContent="center"/> <AdBanner mt={{ base: 6, lg: 8 }} mx="auto" display="flex" justifyContent="center"/>
<Flex mt={ 8 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 8 }> <Flex mt={ 8 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 8 }>
<LatestBlocks/> <LatestBlocks/>
<Box flexGrow={ 1 }> <Box flexGrow={ 1 }>
......
...@@ -67,7 +67,7 @@ test('base view', async({ mount, page, createSocket }) => { ...@@ -67,7 +67,7 @@ test('base view', async({ mount, page, createSocket }) => {
await insertAdPlaceholder(page); await insertAdPlaceholder(page);
await expect(component.locator('main')).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test.describe('mobile', () => { test.describe('mobile', () => {
...@@ -86,6 +86,6 @@ test.describe('mobile', () => { ...@@ -86,6 +86,6 @@ test.describe('mobile', () => {
await insertAdPlaceholder(page); await insertAdPlaceholder(page);
await expect(component.locator('main')).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
}); });
import { Skeleton, Box, Flex, SkeletonCircle, Icon, Tag } from '@chakra-ui/react'; import { Box, Icon } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
...@@ -16,9 +16,11 @@ import useQueryWithPages from 'lib/hooks/useQueryWithPages'; ...@@ -16,9 +16,11 @@ import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
import trimTokenSymbol from 'lib/token/trimTokenSymbol'; import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import * as addressStubs from 'stubs/address';
import * as tokenStubs from 'stubs/token';
import AddressContract from 'ui/address/AddressContract'; import AddressContract from 'ui/address/AddressContract';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page'; import Tag from 'ui/shared/chakra/Tag';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import type { Props as PaginationProps } from 'ui/shared/Pagination'; import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
...@@ -51,7 +53,18 @@ const TokenPageContent = () => { ...@@ -51,7 +53,18 @@ const TokenPageContent = () => {
const tokenQuery = useApiQuery('token', { const tokenQuery = useApiQuery('token', {
pathParams: { hash: hashString }, pathParams: { hash: hashString },
queryOptions: { enabled: isSocketOpen && Boolean(router.query.hash) }, queryOptions: {
enabled: Boolean(router.query.hash),
placeholderData: tokenStubs.TOKEN_INFO_ERC_20,
},
});
const contractQuery = useApiQuery('address', {
pathParams: { hash: hashString },
queryOptions: {
enabled: isSocketOpen && Boolean(router.query.hash),
placeholderData: addressStubs.ADDRESS_INFO,
},
}); });
React.useEffect(() => { React.useEffect(() => {
...@@ -88,7 +101,7 @@ const TokenPageContent = () => { ...@@ -88,7 +101,7 @@ const TokenPageContent = () => {
}); });
useEffect(() => { useEffect(() => {
if (tokenQuery.data) { if (tokenQuery.data && !tokenQuery.isPlaceholderData) {
const tokenSymbol = tokenQuery.data.symbol ? ` (${ tokenQuery.data.symbol })` : ''; const tokenSymbol = tokenQuery.data.symbol ? ` (${ tokenQuery.data.symbol })` : '';
const tokenName = `${ tokenQuery.data.name || 'Unnamed' }${ tokenSymbol }`; const tokenName = `${ tokenQuery.data.name || 'Unnamed' }${ tokenSymbol }`;
const title = document.getElementsByTagName('title')[0]; const title = document.getElementsByTagName('title')[0];
...@@ -100,14 +113,17 @@ const TokenPageContent = () => { ...@@ -100,14 +113,17 @@ const TokenPageContent = () => {
description.content = description.content.replace(tokenQuery.data.address, tokenName) || description.content; description.content = description.content.replace(tokenQuery.data.address, tokenName) || description.content;
} }
} }
}, [ tokenQuery.data ]); }, [ tokenQuery.data, tokenQuery.isPlaceholderData ]);
const hasData = (tokenQuery.data && !tokenQuery.isPlaceholderData) && (contractQuery.data && !contractQuery.isPlaceholderData);
const transfersQuery = useQueryWithPages({ const transfersQuery = useQueryWithPages({
resourceName: 'token_transfers', resourceName: 'token_transfers',
pathParams: { hash: hashString }, pathParams: { hash: hashString },
scrollRef, scrollRef,
options: { options: {
enabled: Boolean(router.query.hash && (!router.query.tab || router.query.tab === 'token_transfers') && tokenQuery.data), enabled: Boolean(hashString && (!router.query.tab || router.query.tab === 'token_transfers') && hasData),
placeholderData: tokenStubs.getTokenTransfersStub(tokenQuery.data?.type),
}, },
}); });
...@@ -116,7 +132,8 @@ const TokenPageContent = () => { ...@@ -116,7 +132,8 @@ const TokenPageContent = () => {
pathParams: { hash: hashString }, pathParams: { hash: hashString },
scrollRef, scrollRef,
options: { options: {
enabled: Boolean(router.query.hash && router.query.tab === 'holders' && tokenQuery.data), enabled: Boolean(router.query.hash && router.query.tab === 'holders' && hasData),
placeholderData: tokenStubs.TOKEN_HOLDERS,
}, },
}); });
...@@ -125,19 +142,15 @@ const TokenPageContent = () => { ...@@ -125,19 +142,15 @@ const TokenPageContent = () => {
pathParams: { hash: hashString }, pathParams: { hash: hashString },
scrollRef, scrollRef,
options: { options: {
enabled: Boolean(router.query.hash && router.query.tab === 'inventory' && tokenQuery.data), enabled: Boolean(router.query.hash && router.query.tab === 'inventory' && hasData),
placeholderData: tokenStubs.TOKEN_INSTANCES,
}, },
}); });
const contractQuery = useApiQuery('address', {
pathParams: { hash: hashString },
queryOptions: { enabled: Boolean(router.query.hash) },
});
const contractTabs = useContractTabs(contractQuery.data); const contractTabs = useContractTabs(contractQuery.data);
const tabs: Array<RoutedTab> = [ const tabs: Array<RoutedTab> = [
{ id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery }/> }, { id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery } token={ tokenQuery.data }/> },
{ id: 'holders', title: 'Holders', component: <TokenHolders token={ tokenQuery.data } holdersQuery={ holdersQuery }/> }, { id: 'holders', title: 'Holders', component: <TokenHolders token={ tokenQuery.data } holdersQuery={ holdersQuery }/> },
(tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') ? (tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') ?
{ id: 'inventory', title: 'Inventory', component: <TokenInventory inventoryQuery={ inventoryQuery }/> } : { id: 'inventory', title: 'Inventory', component: <TokenInventory inventoryQuery={ inventoryQuery }/> } :
...@@ -145,7 +158,7 @@ const TokenPageContent = () => { ...@@ -145,7 +158,7 @@ const TokenPageContent = () => {
contractQuery.data?.is_contract ? { contractQuery.data?.is_contract ? {
id: 'contract', id: 'contract',
title: () => { title: () => {
if (contractQuery.data.is_verified) { if (contractQuery.data?.is_verified) {
return ( return (
<> <>
<span>Contract</span> <span>Contract</span>
...@@ -195,45 +208,36 @@ const TokenPageContent = () => { ...@@ -195,45 +208,36 @@ const TokenPageContent = () => {
}, [ isMobile ]); }, [ isMobile ]);
return ( return (
<Page> <>
{ tokenQuery.isLoading ? ( <TextAd mb={ 6 }/>
<> <PageTitle
<Skeleton h={{ base: 12, lg: 6 }} mb={ 6 } w="100%" maxW="680px"/> isLoading={ tokenQuery.isPlaceholderData }
<Flex alignItems="center" mb={ 6 }> text={ `${ tokenQuery.data?.name || 'Unnamed' }${ tokenSymbolText } token` }
<SkeletonCircle w={ 6 } h={ 6 } mr={ 3 }/> backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
<Skeleton w="500px" h={ 10 }/> backLinkLabel="Back to tokens list"
</Flex> additionalsLeft={ (
</> <TokenLogo hash={ tokenQuery.data?.address } name={ tokenQuery.data?.name } boxSize={ 6 } isLoading={ tokenQuery.isPlaceholderData }/>
) : ( ) }
<> additionalsRight={ <Tag isLoading={ tokenQuery.isPlaceholderData }>{ tokenQuery.data?.type }</Tag> }
<TextAd mb={ 6 }/> />
<PageTitle <TokenContractInfo tokenQuery={ tokenQuery } contractQuery={ contractQuery }/>
text={ `${ tokenQuery.data?.name || 'Unnamed' }${ tokenSymbolText } token` }
backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
backLinkLabel="Back to tokens list"
additionalsLeft={ (
<TokenLogo hash={ tokenQuery.data?.address } name={ tokenQuery.data?.name } boxSize={ 6 }/>
) }
additionalsRight={ <Tag>{ tokenQuery.data?.type }</Tag> }
/>
</>
) }
<TokenContractInfo tokenQuery={ tokenQuery }/>
<TokenDetails tokenQuery={ tokenQuery }/> <TokenDetails tokenQuery={ tokenQuery }/>
{ /* should stay before tabs to scroll up with pagination */ } { /* should stay before tabs to scroll up with pagination */ }
<Box ref={ scrollRef }></Box> <Box ref={ scrollRef }></Box>
{ tokenQuery.isLoading || contractQuery.isLoading ? <SkeletonTabs/> : ( { tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData ?
<RoutedTabs <SkeletonTabs tabs={ tabs }/> :
tabs={ tabs } (
tabListProps={ tabListProps } <RoutedTabs
rightSlot={ !isMobile && hasPagination && pagination ? <Pagination { ...pagination }/> : null } tabs={ tabs }
stickyEnabled={ !isMobile } tabListProps={ tabListProps }
/> rightSlot={ !isMobile && hasPagination && pagination ? <Pagination { ...pagination }/> : null }
) } stickyEnabled={ !isMobile }
/>
) }
{ !tokenQuery.isLoading && !tokenQuery.isError && <Box h={{ base: 0, lg: '40vh' }}/> } { !tokenQuery.isLoading && !tokenQuery.isError && <Box h={{ base: 0, lg: '40vh' }}/> }
</Page> </>
); );
}; };
......
import { Tag, Flex, HStack, Text } from '@chakra-ui/react'; import { Tag, Flex, HStack, Text, Skeleton } from '@chakra-ui/react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { AddressTag } from 'types/api/account'; import type { AddressTag } from 'types/api/account';
...@@ -11,9 +11,10 @@ interface Props { ...@@ -11,9 +11,10 @@ interface Props {
item: AddressTag; item: AddressTag;
onEditClick: (data: AddressTag) => void; onEditClick: (data: AddressTag) => void;
onDeleteClick: (data: AddressTag) => void; onDeleteClick: (data: AddressTag) => void;
isLoading?: boolean;
} }
const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => { const AddressTagListItem = ({ item, onEditClick, onDeleteClick, isLoading }: Props) => {
const onItemEditClick = useCallback(() => { const onItemEditClick = useCallback(() => {
return onEditClick(item); return onEditClick(item);
}, [ item, onEditClick ]); }, [ item, onEditClick ]);
...@@ -25,15 +26,17 @@ const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -25,15 +26,17 @@ const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return ( return (
<ListItemMobile> <ListItemMobile>
<Flex alignItems="flex-start" flexDirection="column" maxW="100%"> <Flex alignItems="flex-start" flexDirection="column" maxW="100%">
<AddressSnippet address={ item.address }/> <AddressSnippet address={ item.address } isLoading={ isLoading }/>
<HStack spacing={ 3 } mt={ 4 }> <HStack spacing={ 3 } mt={ 4 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text> <Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Tag> <Skeleton isLoaded={ !isLoading } display="inline-block" borderRadius="sm">
{ item.name } <Tag>
</Tag> { item.name }
</Tag>
</Skeleton>
</HStack> </HStack>
</Flex> </Flex>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/> <TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
</ListItemMobile> </ListItemMobile>
); );
}; };
......
...@@ -12,28 +12,30 @@ import type { AddressTags, AddressTag } from 'types/api/account'; ...@@ -12,28 +12,30 @@ import type { AddressTags, AddressTag } from 'types/api/account';
import AddressTagTableItem from './AddressTagTableItem'; import AddressTagTableItem from './AddressTagTableItem';
interface Props { interface Props {
data: AddressTags; data?: AddressTags;
onEditClick: (data: AddressTag) => void; onEditClick: (data: AddressTag) => void;
onDeleteClick: (data: AddressTag) => void; onDeleteClick: (data: AddressTag) => void;
isLoading: boolean;
} }
const AddressTagTable = ({ data, onDeleteClick, onEditClick }: Props) => { const AddressTagTable = ({ data, onDeleteClick, onEditClick, isLoading }: Props) => {
return ( return (
<Table variant="simple" minWidth="600px"> <Table variant="simple" minWidth="600px">
<Thead> <Thead>
<Tr> <Tr>
<Th width="60%">Address</Th> <Th width="60%">Address</Th>
<Th width="40%">Private tag</Th> <Th width="40%">Private tag</Th>
<Th width="108px"></Th> <Th width="116px"></Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ data.map((item: AddressTag) => ( { data?.map((item: AddressTag, index: number) => (
<AddressTagTableItem <AddressTagTableItem
item={ item } item={ item }
key={ item.id } key={ item.id + (isLoading ? index : '') }
onDeleteClick={ onDeleteClick } onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick } onEditClick={ onEditClick }
isLoading={ isLoading }
/> />
)) } )) }
</Tbody> </Tbody>
......
import { import {
Tag,
Tr, Tr,
Td, Td,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
...@@ -8,16 +7,17 @@ import React, { useCallback } from 'react'; ...@@ -8,16 +7,17 @@ import React, { useCallback } from 'react';
import type { AddressTag } from 'types/api/account'; import type { AddressTag } from 'types/api/account';
import AddressSnippet from 'ui/shared/AddressSnippet'; import AddressSnippet from 'ui/shared/AddressSnippet';
import Tag from 'ui/shared/chakra/Tag';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons'; import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
interface Props { interface Props {
item: AddressTag; item: AddressTag;
onEditClick: (data: AddressTag) => void; onEditClick: (data: AddressTag) => void;
onDeleteClick: (data: AddressTag) => void; onDeleteClick: (data: AddressTag) => void;
isLoading: boolean;
} }
const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { const AddressTagTableItem = ({ item, onEditClick, onDeleteClick, isLoading }: Props) => {
const onItemEditClick = useCallback(() => { const onItemEditClick = useCallback(() => {
return onEditClick(item); return onEditClick(item);
}, [ item, onEditClick ]); }, [ item, onEditClick ]);
...@@ -29,17 +29,13 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -29,17 +29,13 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return ( return (
<Tr alignItems="top" key={ item.id }> <Tr alignItems="top" key={ item.id }>
<Td> <Td>
<AddressSnippet address={ item.address }/> <AddressSnippet address={ item.address } isLoading={ isLoading }/>
</Td> </Td>
<Td whiteSpace="nowrap"> <Td whiteSpace="nowrap">
<TruncatedTextTooltip label={ item.name }> <Tag isLoading={ isLoading } isTruncated>{ item.name }</Tag>
<Tag>
{ item.name }
</Tag>
</TruncatedTextTooltip>
</Td> </Td>
<Td> <Td>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/> <TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
</Td> </Td>
</Tr> </Tr>
); );
......
...@@ -5,10 +5,9 @@ import type { AddressTag } from 'types/api/account'; ...@@ -5,10 +5,9 @@ import type { AddressTag } from 'types/api/account';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { PRIVATE_TAG_ADDRESS } from 'stubs/account';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import SkeletonListAccount from 'ui/shared/skeletons/SkeletonListAccount';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import AddressModal from './AddressModal/AddressModal'; import AddressModal from './AddressModal/AddressModal';
import AddressTagListItem from './AddressTagTable/AddressTagListItem'; import AddressTagListItem from './AddressTagTable/AddressTagListItem';
...@@ -16,7 +15,12 @@ import AddressTagTable from './AddressTagTable/AddressTagTable'; ...@@ -16,7 +15,12 @@ import AddressTagTable from './AddressTagTable/AddressTagTable';
import DeletePrivateTagModal from './DeletePrivateTagModal'; import DeletePrivateTagModal from './DeletePrivateTagModal';
const PrivateAddressTags = () => { const PrivateAddressTags = () => {
const { data: addressTagsData, isLoading, isError } = useApiQuery('private_tags_address', { queryOptions: { refetchOnMount: false } }); const { data: addressTagsData, isError, isPlaceholderData } = useApiQuery('private_tags_address', {
queryOptions: {
refetchOnMount: false,
placeholderData: Array(3).fill(PRIVATE_TAG_ADDRESS),
},
});
const addressModalProps = useDisclosure(); const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
...@@ -45,46 +49,25 @@ const PrivateAddressTags = () => { ...@@ -45,46 +49,25 @@ const PrivateAddressTags = () => {
deleteModalProps.onClose(); deleteModalProps.onClose();
}, [ deleteModalProps ]); }, [ deleteModalProps ]);
const description = (
<AccountPageDescription>
Use private address tags to track any addresses of interest.
Private tags are saved in your account and are only visible when you are logged in.
</AccountPageDescription>
);
if (isLoading && !addressTagsData) {
const loader = isMobile ? <SkeletonListAccount/> : (
<>
<SkeletonTable columns={ [ '60%', '40%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</>
);
return (
<>
{ description }
{ loader }
</>
);
}
if (isError) { if (isError) {
return <DataFetchAlert/>; return <DataFetchAlert/>;
} }
const list = isMobile ? ( const list = isMobile ? (
<Box> <Box>
{ addressTagsData.map((item: AddressTag) => ( { addressTagsData?.map((item: AddressTag, index: number) => (
<AddressTagListItem <AddressTagListItem
item={ item } item={ item }
key={ item.id } key={ item.id + (isPlaceholderData ? index : '') }
onDeleteClick={ onDeleteClick } onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick } onEditClick={ onEditClick }
isLoading={ isPlaceholderData }
/> />
)) } )) }
</Box> </Box>
) : ( ) : (
<AddressTagTable <AddressTagTable
isLoading={ isPlaceholderData }
data={ addressTagsData } data={ addressTagsData }
onDeleteClick={ onDeleteClick } onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick } onEditClick={ onEditClick }
...@@ -93,15 +76,20 @@ const PrivateAddressTags = () => { ...@@ -93,15 +76,20 @@ const PrivateAddressTags = () => {
return ( return (
<> <>
{ description } <AccountPageDescription>
Use private address tags to track any addresses of interest.
Private tags are saved in your account and are only visible when you are logged in.
</AccountPageDescription>
{ Boolean(addressTagsData?.length) && list } { Boolean(addressTagsData?.length) && list }
<Box marginTop={ 8 }> <Box marginTop={ 8 }>
<Button <Skeleton isLoaded={ !isPlaceholderData } display="inline-block">
size="lg" <Button
onClick={ addressModalProps.onOpen } size="lg"
> onClick={ addressModalProps.onOpen }
Add address tag >
</Button> Add address tag
</Button>
</Skeleton>
</Box> </Box>
<AddressModal { ...addressModalProps } onClose={ onAddressModalClose } data={ addressModalData }/> <AddressModal { ...addressModalProps } onClose={ onAddressModalClose } data={ addressModalData }/>
{ deleteModalData && ( { deleteModalData && (
......
...@@ -17,14 +17,15 @@ interface Props { ...@@ -17,14 +17,15 @@ interface Props {
address: Pick<Address, 'hash' | 'is_contract' | 'implementation_name' | 'watchlist_names' | 'watchlist_address_id'>; address: Pick<Address, 'hash' | 'is_contract' | 'implementation_name' | 'watchlist_names' | 'watchlist_address_id'>;
token?: TokenInfo | null; token?: TokenInfo | null;
isLinkDisabled?: boolean; isLinkDisabled?: boolean;
isLoading?: boolean;
} }
const AddressHeadingInfo = ({ address, token, isLinkDisabled }: Props) => { const AddressHeadingInfo = ({ address, token, isLinkDisabled, isLoading }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
return ( return (
<Flex alignItems="center"> <Flex alignItems="center">
<AddressIcon address={ address }/> <AddressIcon address={ address } isLoading={ isLoading }/>
<AddressLink <AddressLink
type="address" type="address"
hash={ address.hash } hash={ address.hash }
...@@ -33,13 +34,14 @@ const AddressHeadingInfo = ({ address, token, isLinkDisabled }: Props) => { ...@@ -33,13 +34,14 @@ const AddressHeadingInfo = ({ address, token, isLinkDisabled }: Props) => {
fontWeight={ 500 } fontWeight={ 500 }
truncation={ isMobile ? 'constant' : 'none' } truncation={ isMobile ? 'constant' : 'none' }
isDisabled={ isLinkDisabled } isDisabled={ isLinkDisabled }
isLoading={ isLoading }
/> />
<CopyToClipboard text={ address.hash }/> <CopyToClipboard text={ address.hash } isLoading={ isLoading }/>
{ address.is_contract && token && <AddressAddToWallet ml={ 2 } token={ token }/> } { !isLoading && address.is_contract && token && <AddressAddToWallet ml={ 2 } token={ token }/> }
{ !address.is_contract && config.isAccountSupported && ( { !isLoading && !address.is_contract && config.isAccountSupported && (
<AddressFavoriteButton hash={ address.hash } watchListId={ address.watchlist_address_id } ml={ 3 }/> <AddressFavoriteButton hash={ address.hash } watchListId={ address.watchlist_address_id } ml={ 3 }/>
) } ) }
<AddressQrCode hash={ address.hash } ml={ 2 }/> <AddressQrCode hash={ address.hash } ml={ 2 } isLoading={ isLoading }/>
</Flex> </Flex>
); );
}; };
......
...@@ -11,15 +11,16 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard'; ...@@ -11,15 +11,16 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard';
interface Props { interface Props {
address: AddressParam; address: AddressParam;
subtitle?: string; subtitle?: string;
isLoading?: boolean;
} }
const AddressSnippet = ({ address, subtitle }: Props) => { const AddressSnippet = ({ address, subtitle, isLoading }: Props) => {
return ( return (
<Box maxW="100%"> <Box maxW="100%">
<Address> <Address>
<AddressIcon address={ address }/> <AddressIcon address={ address } isLoading={ isLoading }/>
<AddressLink type="address" hash={ address.hash } fontWeight="600" ml={ 2 }/> <AddressLink type="address" hash={ address.hash } fontWeight="600" ml={ 2 } isLoading={ isLoading }/>
<CopyToClipboard text={ address.hash } ml={ 1 }/> <CopyToClipboard text={ address.hash } ml={ 1 } isLoading={ isLoading }/>
</Address> </Address>
{ subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 } ml={ 8 }>{ subtitle }</Text> } { subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 } ml={ 8 }>{ subtitle }</Text> }
</Box> </Box>
......
import { IconButton, Tooltip, useClipboard, chakra, useDisclosure } from '@chakra-ui/react'; import { IconButton, Tooltip, useClipboard, chakra, useDisclosure, Skeleton } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import CopyIcon from 'icons/copy.svg'; import CopyIcon from 'icons/copy.svg';
const CopyToClipboard = ({ text, className }: {text: string; className?: string}) => { interface Props {
text: string;
className?: string;
isLoading?: boolean;
}
const CopyToClipboard = ({ text, className, isLoading }: Props) => {
const { hasCopied, onCopy } = useClipboard(text, 1000); const { hasCopied, onCopy } = useClipboard(text, 1000);
const [ copied, setCopied ] = useState(false); const [ copied, setCopied ] = useState(false);
// have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107 // have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107
...@@ -17,6 +23,10 @@ const CopyToClipboard = ({ text, className }: {text: string; className?: string} ...@@ -17,6 +23,10 @@ const CopyToClipboard = ({ text, className }: {text: string; className?: string}
} }
}, [ hasCopied ]); }, [ hasCopied ]);
if (isLoading) {
return <Skeleton boxSize={ 5 } className={ className } borderRadius="sm" flexShrink={ 0 }/>;
}
return ( return (
<Tooltip label={ copied ? 'Copied' : 'Copy to clipboard' } isOpen={ isOpen || copied }> <Tooltip label={ copied ? 'Copied' : 'Copy to clipboard' } isOpen={ isOpen || copied }>
<IconButton <IconButton
...@@ -32,6 +42,7 @@ const CopyToClipboard = ({ text, className }: {text: string; className?: string} ...@@ -32,6 +42,7 @@ const CopyToClipboard = ({ text, className }: {text: string; className?: string}
className={ className } className={ className }
onMouseEnter={ onOpen } onMouseEnter={ onOpen }
onMouseLeave={ onClose } onMouseLeave={ onClose }
ml={ 1 }
/> />
</Tooltip> </Tooltip>
); );
......
import { GridItem, Flex, Text } from '@chakra-ui/react'; import { GridItem, Flex, Text, Skeleton } from '@chakra-ui/react';
import type { HTMLChakraProps } from '@chakra-ui/system'; import type { HTMLChakraProps } from '@chakra-ui/system';
import React from 'react'; import React from 'react';
...@@ -9,18 +9,21 @@ interface Props extends Omit<HTMLChakraProps<'div'>, 'title'> { ...@@ -9,18 +9,21 @@ interface Props extends Omit<HTMLChakraProps<'div'>, 'title'> {
hint: string; hint: string;
children: React.ReactNode; children: React.ReactNode;
note?: string; note?: string;
isLoading?: boolean;
} }
const DetailsInfoItem = ({ title, hint, note, children, id, ...styles }: Props) => { const DetailsInfoItem = ({ title, hint, note, children, id, isLoading, ...styles }: Props) => {
return ( return (
<> <>
<GridItem py={{ base: 1, lg: 2 }} id={ id } lineHeight={ 5 } { ...styles } whiteSpace="nowrap" _notFirst={{ mt: { base: 3, lg: 0 } }}> <GridItem py={{ base: 1, lg: 2 }} id={ id } lineHeight={ 5 } { ...styles } whiteSpace="nowrap" _notFirst={{ mt: { base: 3, lg: 0 } }}>
<Flex columnGap={ 2 } alignItems="flex-start"> <Flex columnGap={ 2 } alignItems="flex-start">
<Hint label={ hint }/> <Hint label={ hint } isLoading={ isLoading }/>
<Text fontWeight={{ base: 700, lg: 500 }}> <Skeleton isLoaded={ !isLoading }>
{ title } <Text fontWeight={{ base: 700, lg: 500 }}>
{ note && <Text fontWeight={ 500 } variant="secondary" fontSize="xs" className="note" align="right">{ note }</Text> } { title }
</Text> { note && <Text fontWeight={ 500 } variant="secondary" fontSize="xs" className="note" align="right">{ note }</Text> }
</Text>
</Skeleton>
</Flex> </Flex>
</GridItem> </GridItem>
<GridItem <GridItem
......
...@@ -7,18 +7,22 @@ import isSelfHosted from 'lib/isSelfHosted'; ...@@ -7,18 +7,22 @@ import isSelfHosted from 'lib/isSelfHosted';
import AdBanner from 'ui/shared/ad/AdBanner'; import AdBanner from 'ui/shared/ad/AdBanner';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
const DetailsSponsoredItem = () => { interface Props {
isLoading?: boolean;
}
const DetailsSponsoredItem = ({ isLoading }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED); const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED);
if (hasAdblockCookie || !isSelfHosted()) { if (!isSelfHosted() || hasAdblockCookie) {
return null; return null;
} }
if (isMobile) { if (isMobile) {
return ( return (
<GridItem mt={ 5 }> <GridItem mt={ 5 }>
<AdBanner justifyContent="center"/> <AdBanner mx="auto" isLoading={ isLoading } display="flex" justifyContent="center"/>
</GridItem> </GridItem>
); );
} }
...@@ -27,8 +31,9 @@ const DetailsSponsoredItem = () => { ...@@ -27,8 +31,9 @@ const DetailsSponsoredItem = () => {
<DetailsInfoItem <DetailsInfoItem
title="Sponsored" title="Sponsored"
hint="Sponsored banner advertisement" hint="Sponsored banner advertisement"
isLoading={ isLoading }
> >
<AdBanner/> <AdBanner isLoading={ isLoading }/>
</DetailsInfoItem> </DetailsInfoItem>
); );
}; };
......
import type { TooltipProps } from '@chakra-ui/react'; import type { TooltipProps } from '@chakra-ui/react';
import { chakra, IconButton, Tooltip, useDisclosure } from '@chakra-ui/react'; import { chakra, IconButton, Tooltip, useDisclosure, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import InfoIcon from 'icons/info.svg'; import InfoIcon from 'icons/info.svg';
...@@ -8,9 +8,10 @@ interface Props { ...@@ -8,9 +8,10 @@ interface Props {
label: string | React.ReactNode; label: string | React.ReactNode;
className?: string; className?: string;
tooltipProps?: Partial<TooltipProps>; tooltipProps?: Partial<TooltipProps>;
isLoading?: boolean;
} }
const Hint = ({ label, className, tooltipProps }: Props) => { const Hint = ({ label, className, tooltipProps, isLoading }: Props) => {
// have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107 // have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107
const { isOpen, onOpen, onToggle, onClose } = useDisclosure(); const { isOpen, onOpen, onToggle, onClose } = useDisclosure();
...@@ -19,6 +20,10 @@ const Hint = ({ label, className, tooltipProps }: Props) => { ...@@ -19,6 +20,10 @@ const Hint = ({ label, className, tooltipProps }: Props) => {
onToggle(); onToggle();
}, [ onToggle ]); }, [ onToggle ]);
if (isLoading) {
return <Skeleton boxSize={ 5 } borderRadius="sm"/>;
}
return ( return (
<Tooltip <Tooltip
label={ label } label={ label }
......
...@@ -26,6 +26,8 @@ const ListItemMobile = ({ children, className, isAnimated }: Props) => { ...@@ -26,6 +26,8 @@ const ListItemMobile = ({ children, className, isAnimated }: Props) => {
borderBottomWidth: '1px', borderBottomWidth: '1px',
}} }}
className={ className } className={ className }
fontSize="16px"
lineHeight="20px"
> >
{ children } { children }
</Flex> </Flex>
......
import { Heading, Flex, Grid, Tooltip, Icon, chakra } from '@chakra-ui/react'; import { Heading, Flex, Grid, Tooltip, Icon, chakra, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import eastArrowIcon from 'icons/arrows/east.svg'; import eastArrowIcon from 'icons/arrows/east.svg';
...@@ -13,18 +13,21 @@ type Props = { ...@@ -13,18 +13,21 @@ type Props = {
className?: string; className?: string;
backLinkLabel?: string; backLinkLabel?: string;
backLinkUrl?: string; backLinkUrl?: string;
isLoading?: boolean;
} }
const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLinkUrl, backLinkLabel, className }: Props) => { const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLinkUrl, backLinkLabel, className, isLoading }: Props) => {
const title = ( const title = (
<Heading <Skeleton isLoaded={ !isLoading }>
as="h1" <Heading
size="lg" as="h1"
flex="none" size="lg"
wordBreak="break-word" flex="none"
> wordBreak="break-word"
{ text } >
</Heading> { text }
</Heading>
</Skeleton>
); );
return ( return (
......
import { Box, Flex, Text, chakra, useColorModeValue } from '@chakra-ui/react'; import { Box, Flex, chakra, useColorModeValue, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import CopyToClipboard from './CopyToClipboard'; import CopyToClipboard from './CopyToClipboard';
...@@ -11,33 +11,36 @@ interface Props { ...@@ -11,33 +11,36 @@ interface Props {
beforeSlot?: React.ReactNode; beforeSlot?: React.ReactNode;
textareaMaxHeight?: string; textareaMaxHeight?: string;
showCopy?: boolean; showCopy?: boolean;
isLoading?: boolean;
} }
const RawDataSnippet = ({ data, className, title, rightSlot, beforeSlot, textareaMaxHeight, showCopy = true }: Props) => { const RawDataSnippet = ({ data, className, title, rightSlot, beforeSlot, textareaMaxHeight, showCopy = true, isLoading }: Props) => {
// see issue in theme/components/Textarea.ts // see issue in theme/components/Textarea.ts
const bgColor = useColorModeValue('#f5f5f6', '#1a1b1b'); const bgColor = useColorModeValue('#f5f5f6', '#1a1b1b');
return ( return (
<Box className={ className } as="section" title={ title }> <Box className={ className } as="section" title={ title }>
{ (title || rightSlot || showCopy) && ( { (title || rightSlot || showCopy) && (
<Flex justifyContent={ title ? 'space-between' : 'flex-end' } alignItems="center" mb={ 3 }> <Flex justifyContent={ title ? 'space-between' : 'flex-end' } alignItems="center" mb={ 3 }>
{ title && <Text fontWeight={ 500 }>{ title }</Text> } { title && <Skeleton fontWeight={ 500 } isLoaded={ !isLoading }>{ title }</Skeleton> }
{ rightSlot } { rightSlot }
{ typeof data === 'string' && showCopy && <CopyToClipboard text={ data }/> } { typeof data === 'string' && showCopy && <CopyToClipboard text={ data } isLoading={ isLoading }/> }
</Flex> </Flex>
) } ) }
{ beforeSlot } { beforeSlot }
<Box <Skeleton
p={ 4 } p={ 4 }
bgColor={ bgColor } bgColor={ isLoading ? 'inherit' : bgColor }
maxH={ textareaMaxHeight || '400px' } maxH={ textareaMaxHeight || '400px' }
minH={ isLoading ? '200px' : undefined }
fontSize="sm" fontSize="sm"
borderRadius="md" borderRadius="md"
wordBreak="break-all" wordBreak="break-all"
whiteSpace="pre-wrap" whiteSpace="pre-wrap"
overflowY="auto" overflowY="auto"
isLoaded={ !isLoading }
> >
{ data } { data }
</Box> </Skeleton>
</Box> </Box>
); );
}; };
......
...@@ -175,6 +175,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, . ...@@ -175,6 +175,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, .
} }
onItemClick={ handleTabChange } onItemClick={ handleTabChange }
buttonRef={ tabsRefs[index] } buttonRef={ tabsRefs[index] }
size={ themeProps.size || 'md' }
/> />
); );
} }
......
import type {
ButtonProps } from '@chakra-ui/react';
import { Popover, import { Popover,
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
...@@ -20,9 +22,10 @@ interface Props { ...@@ -20,9 +22,10 @@ interface Props {
styles?: StyleProps; styles?: StyleProps;
onItemClick: (index: number) => void; onItemClick: (index: number) => void;
buttonRef: React.RefObject<HTMLButtonElement>; buttonRef: React.RefObject<HTMLButtonElement>;
size: ButtonProps['size'];
} }
const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRef, activeTab }: Props) => { const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRef, activeTab, size }: Props) => {
const { isOpen, onClose, onOpen } = useDisclosure(); const { isOpen, onClose, onOpen } = useDisclosure();
const handleItemClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => { const handleItemClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
...@@ -40,6 +43,7 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe ...@@ -40,6 +43,7 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe
variant="ghost" variant="ghost"
isActive={ isOpen || isActive } isActive={ isOpen || isActive }
ref={ buttonRef } ref={ buttonRef }
size={ size }
{ ...styles } { ...styles }
> >
{ menuButton.title } { menuButton.title }
......
import { Alert, Link, Text, chakra, useTheme, useColorModeValue } from '@chakra-ui/react'; import { Alert, Link, Text, chakra, useTheme, useColorModeValue, Skeleton, Tr, Td } from '@chakra-ui/react';
import { transparentize } from '@chakra-ui/theme-tools'; import { transparentize } from '@chakra-ui/theme-tools';
import React from 'react'; import React from 'react';
...@@ -13,9 +13,10 @@ interface Props { ...@@ -13,9 +13,10 @@ interface Props {
url: string; url: string;
alert?: string; alert?: string;
num?: number; num?: number;
isLoading?: boolean;
} }
const SocketNewItemsNotice = ({ children, className, url, num, alert, type = 'transaction' }: Props) => { const SocketNewItemsNotice = chakra(({ children, className, url, num, alert, type = 'transaction', isLoading }: Props) => {
const theme = useTheme(); const theme = useTheme();
const alertContent = (() => { const alertContent = (() => {
...@@ -49,7 +50,10 @@ const SocketNewItemsNotice = ({ children, className, url, num, alert, type = 'tr ...@@ -49,7 +50,10 @@ const SocketNewItemsNotice = ({ children, className, url, num, alert, type = 'tr
); );
})(); })();
const content = ( const color = useColorModeValue('blackAlpha.800', 'whiteAlpha.800');
const bgColor = useColorModeValue('orange.50', transparentize('orange.200', 0.16)(theme));
const content = !isLoading ? (
<Alert <Alert
className={ className } className={ className }
status="warning" status="warning"
...@@ -57,14 +61,39 @@ const SocketNewItemsNotice = ({ children, className, url, num, alert, type = 'tr ...@@ -57,14 +61,39 @@ const SocketNewItemsNotice = ({ children, className, url, num, alert, type = 'tr
py="6px" py="6px"
fontWeight={ 400 } fontWeight={ 400 }
fontSize="sm" fontSize="sm"
bgColor={ useColorModeValue('orange.50', transparentize('orange.200', 0.16)(theme)) } bgColor={ bgColor }
color={ useColorModeValue('blackAlpha.800', 'whiteAlpha.800') } color={ color }
> >
{ alertContent } { alertContent }
</Alert> </Alert>
); ) : <Skeleton className={ className } h="33px"/>;
return children ? children({ content }) : content; return children ? children({ content }) : content;
});
export default SocketNewItemsNotice;
export const Desktop = ({ ...props }: Props) => {
return (
<SocketNewItemsNotice
borderRadius={ props.isLoading ? 'sm' : 0 }
h={ props.isLoading ? 4 : 'auto' }
maxW={ props.isLoading ? '215px' : undefined }
w="100%"
mx={ props.isLoading ? 4 : 0 }
my={ props.isLoading ? '6px' : 0 }
{ ...props }
>
{ ({ content }) => <Tr><Td colSpan={ 100 } p={ 0 }>{ content }</Td></Tr> }
</SocketNewItemsNotice>
);
}; };
export default chakra(SocketNewItemsNotice); export const Mobile = ({ ...props }: Props) => {
return (
<SocketNewItemsNotice
borderBottomRadius={ 0 }
{ ...props }
/>
);
};
import { Tooltip, IconButton, Icon, HStack } from '@chakra-ui/react'; import { Tooltip, IconButton, HStack, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import DeleteIcon from 'icons/delete.svg'; import DeleteIcon from 'icons/delete.svg';
...@@ -8,33 +8,47 @@ import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModa ...@@ -8,33 +8,47 @@ import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModa
type Props = { type Props = {
onEditClick: () => void; onEditClick: () => void;
onDeleteClick: () => void; onDeleteClick: () => void;
isLoading?: boolean;
} }
const TableItemActionButtons = ({ onEditClick, onDeleteClick }: Props) => { const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props) => {
const onFocusCapture = usePreventFocusAfterModalClosing(); const onFocusCapture = usePreventFocusAfterModalClosing();
if (isLoading) {
return (
<HStack spacing={ 6 } alignSelf="flex-end">
<Skeleton boxSize={ 5 } flexShrink={ 0 } borderRadius="base"/>
<Skeleton boxSize={ 5 } flexShrink={ 0 } borderRadius="base"/>
</HStack>
);
}
return ( return (
<HStack spacing={ 6 } alignSelf="flex-end"> <HStack spacing={ 6 } alignSelf="flex-end">
<Tooltip label="Edit"> <Tooltip label="Edit">
<IconButton <IconButton
aria-label="edit" aria-label="edit"
variant="simple" variant="simple"
w="30px" boxSize={ 5 }
h="30px"
onClick={ onEditClick } onClick={ onEditClick }
icon={ <Icon as={ EditIcon } w="20px" h="20px"/> } icon={ <EditIcon/> }
onFocusCapture={ onFocusCapture } onFocusCapture={ onFocusCapture }
display="inline-block"
flexShrink={ 0 }
borderRadius="none"
/> />
</Tooltip> </Tooltip>
<Tooltip label="Delete"> <Tooltip label="Delete">
<IconButton <IconButton
aria-label="delete" aria-label="delete"
variant="simple" variant="simple"
w="30px" boxSize={ 5 }
h="30px"
onClick={ onDeleteClick } onClick={ onDeleteClick }
icon={ <Icon as={ DeleteIcon } w="20px" h="20px"/> } icon={ <DeleteIcon/> }
onFocusCapture={ onFocusCapture } onFocusCapture={ onFocusCapture }
display="inline-block"
flexShrink={ 0 }
borderRadius="none"
/> />
</Tooltip> </Tooltip>
</HStack> </HStack>
......
import { Image, chakra, useColorModeValue, Icon } from '@chakra-ui/react'; import { Image, chakra, useColorModeValue, Icon, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
...@@ -27,9 +27,15 @@ interface Props { ...@@ -27,9 +27,15 @@ interface Props {
hash?: string; hash?: string;
name?: string | null; name?: string | null;
className?: string; className?: string;
isLoading?: boolean;
} }
const TokenLogo = ({ hash, name, className }: Props) => { const TokenLogo = ({ hash, name, className, isLoading }: Props) => {
if (isLoading) {
return <Skeleton className={ className } borderRadius="base"/>;
}
const logoSrc = appConfig.network.assetsPathname && hash ? [ const logoSrc = appConfig.network.assetsPathname && hash ? [
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/', 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/',
appConfig.network.assetsPathname, appConfig.network.assetsPathname,
......
...@@ -17,6 +17,8 @@ import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers'; ...@@ -17,6 +17,8 @@ import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft'; import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo'; import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import CopyToClipboard from '../CopyToClipboard';
type Props = TokenTransfer & { type Props = TokenTransfer & {
baseAddress?: string; baseAddress?: string;
showTxInfo?: boolean; showTxInfo?: boolean;
...@@ -84,6 +86,7 @@ const TokenTransferListItem = ({ ...@@ -84,6 +86,7 @@ const TokenTransferListItem = ({
<Address width={ addressWidth }> <Address width={ addressWidth }>
<AddressIcon address={ from }/> <AddressIcon address={ from }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ baseAddress === from.hash }/> <AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ baseAddress === from.hash }/>
{ baseAddress !== from.hash && <CopyToClipboard text={ from.hash }/> }
</Address> </Address>
{ baseAddress ? { baseAddress ?
<InOutTag isIn={ baseAddress === to.hash } isOut={ baseAddress === from.hash } w="50px" textAlign="center"/> : <InOutTag isIn={ baseAddress === to.hash } isOut={ baseAddress === from.hash } w="50px" textAlign="center"/> :
...@@ -92,6 +95,7 @@ const TokenTransferListItem = ({ ...@@ -92,6 +95,7 @@ const TokenTransferListItem = ({
<Address width={ addressWidth }> <Address width={ addressWidth }>
<AddressIcon address={ to }/> <AddressIcon address={ to }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ to.hash } isDisabled={ baseAddress === to.hash }/> <AddressLink type="address" ml={ 2 } fontWeight="500" hash={ to.hash } isDisabled={ baseAddress === to.hash }/>
{ baseAddress !== to.hash && <CopyToClipboard text={ to.hash }/> }
</Address> </Address>
</Flex> </Flex>
{ value && ( { value && (
......
import { Box, Icon, chakra } from '@chakra-ui/react'; import { Box, Icon, chakra, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
...@@ -13,10 +13,11 @@ interface Props { ...@@ -13,10 +13,11 @@ interface Props {
className?: string; className?: string;
isDisabled?: boolean; isDisabled?: boolean;
truncation?: 'dynamic' | 'constant'; truncation?: 'dynamic' | 'constant';
isLoading?: boolean;
} }
const TokenTransferNft = ({ hash, id, className, isDisabled, truncation = 'dynamic' }: Props) => { const TokenTransferNft = ({ hash, id, className, isDisabled, isLoading, truncation = 'dynamic' }: Props) => {
const Component = isDisabled ? Box : LinkInternal; const Component = isDisabled || isLoading ? Box : LinkInternal;
return ( return (
<Component <Component
...@@ -28,10 +29,12 @@ const TokenTransferNft = ({ hash, id, className, isDisabled, truncation = 'dynam ...@@ -28,10 +29,12 @@ const TokenTransferNft = ({ hash, id, className, isDisabled, truncation = 'dynam
w="100%" w="100%"
className={ className } className={ className }
> >
<Icon as={ nftPlaceholder } boxSize="30px" mr={ 1 } color="inherit"/> <Skeleton isLoaded={ !isLoading } boxSize="30px" mr={ 1 } borderRadius="base">
<Box maxW="calc(100% - 34px)"> <Icon as={ nftPlaceholder } boxSize="30px" color="inherit"/>
</Skeleton>
<Skeleton isLoaded={ !isLoading } maxW="calc(100% - 34px)">
{ truncation === 'constant' ? <HashStringShorten hash={ id }/> : <HashStringShortenDynamic hash={ id } fontWeight={ 500 }/> } { truncation === 'constant' ? <HashStringShorten hash={ id }/> : <HashStringShortenDynamic hash={ id } fontWeight={ 500 }/> }
</Box> </Skeleton>
</Component> </Component>
); );
}; };
......
...@@ -14,6 +14,8 @@ import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers'; ...@@ -14,6 +14,8 @@ import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft'; import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo'; import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import CopyToClipboard from '../CopyToClipboard';
type Props = TokenTransfer & { type Props = TokenTransfer & {
baseAddress?: string; baseAddress?: string;
showTxInfo?: boolean; showTxInfo?: boolean;
...@@ -63,6 +65,7 @@ const TokenTransferTableItem = ({ ...@@ -63,6 +65,7 @@ const TokenTransferTableItem = ({
<Address display="inline-flex" maxW="100%" lineHeight="30px"> <Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ from }/> <AddressIcon address={ from }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 } isDisabled={ baseAddress === from.hash }/> <AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 } isDisabled={ baseAddress === from.hash }/>
{ baseAddress !== from.hash && <CopyToClipboard text={ from.hash }/> }
</Address> </Address>
</Td> </Td>
{ baseAddress && ( { baseAddress && (
...@@ -74,6 +77,7 @@ const TokenTransferTableItem = ({ ...@@ -74,6 +77,7 @@ const TokenTransferTableItem = ({
<Address display="inline-flex" maxW="100%" lineHeight="30px"> <Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ to }/> <AddressIcon address={ to }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ to.hash } alias={ to.name } flexGrow={ 1 } isDisabled={ baseAddress === to.hash }/> <AddressLink type="address" ml={ 2 } fontWeight="500" hash={ to.hash } alias={ to.name } flexGrow={ 1 } isDisabled={ baseAddress === to.hash }/>
{ baseAddress !== to.hash && <CopyToClipboard text={ to.hash }/> }
</Address> </Address>
</Td> </Td>
<Td isNumeric verticalAlign="top" lineHeight="30px"> <Td isNumeric verticalAlign="top" lineHeight="30px">
......
import { Box, Flex, Text, chakra, useColorModeValue } from '@chakra-ui/react'; import { Box, Flex, chakra, useColorModeValue, Skeleton } from '@chakra-ui/react';
import clamp from 'lodash/clamp'; import clamp from 'lodash/clamp';
import React from 'react'; import React from 'react';
...@@ -6,21 +6,28 @@ interface Props { ...@@ -6,21 +6,28 @@ interface Props {
className?: string; className?: string;
value: number; value: number;
colorScheme?: 'green' | 'gray'; colorScheme?: 'green' | 'gray';
isLoading?: boolean;
} }
const WIDTH = 50; const WIDTH = 50;
const Utilization = ({ className, value, colorScheme = 'green' }: Props) => { const Utilization = ({ className, value, colorScheme = 'green', isLoading }: Props) => {
const valueString = (clamp(value * 100 || 0, 0, 100)).toLocaleString(undefined, { maximumFractionDigits: 2 }) + '%'; const valueString = (clamp(value * 100 || 0, 0, 100)).toLocaleString(undefined, { maximumFractionDigits: 2 }) + '%';
const colorGrayScheme = useColorModeValue('gray.500', 'gray.400'); const colorGrayScheme = useColorModeValue('gray.500', 'gray.400');
const color = colorScheme === 'gray' ? colorGrayScheme : 'green.500'; const color = colorScheme === 'gray' ? colorGrayScheme : 'green.500';
return ( return (
<Flex className={ className } alignItems="center"> <Flex className={ className } alignItems="center" columnGap="10px">
<Box bg={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } w={ `${ WIDTH }px` } h="4px" borderRadius="full" overflow="hidden"> <Skeleton isLoaded={ !isLoading } w={ `${ WIDTH }px` } h="4px" borderRadius="full" overflow="hidden">
<Box bg={ color } w={ valueString } h="100%"/> <Box bg={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } h="100%">
</Box> <Box bg={ color } w={ valueString } h="100%"/>
<Text color={ color } ml="10px" fontWeight="bold">{ valueString }</Text> </Box>
</Skeleton>
<Skeleton isLoaded={ !isLoading } color={ color } fontWeight="bold">
<span>
{ valueString }
</span>
</Skeleton>
</Flex> </Flex>
); );
}; };
......
import { chakra } from '@chakra-ui/react'; import { chakra, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
...@@ -9,18 +9,26 @@ import isSelfHosted from 'lib/isSelfHosted'; ...@@ -9,18 +9,26 @@ import isSelfHosted from 'lib/isSelfHosted';
import AdbutlerBanner from './AdbutlerBanner'; import AdbutlerBanner from './AdbutlerBanner';
import CoinzillaBanner from './CoinzillaBanner'; import CoinzillaBanner from './CoinzillaBanner';
const AdBanner = ({ className }: { className?: string }) => { const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: boolean }) => {
const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies); const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies);
if (!isSelfHosted() || hasAdblockCookie) { if (!isSelfHosted() || hasAdblockCookie) {
return null; return null;
} }
if (appConfig.ad.adButlerOn) { const content = appConfig.ad.adButlerOn ? <AdbutlerBanner/> : <CoinzillaBanner/>;
return <AdbutlerBanner className={ className }/>;
}
return <CoinzillaBanner className={ className }/>; return (
<Skeleton
className={ className }
isLoaded={ !isLoading }
borderRadius="none"
maxW={ appConfig.ad.adButlerOn ? '760px' : '728px' }
w="100%"
>
{ content }
</Skeleton>
);
}; };
export default chakra(AdBanner); export default chakra(AdBanner);
import { Box, Image, Link, Text, chakra } from '@chakra-ui/react'; import { Box, Image, Link, Text, chakra, Skeleton } from '@chakra-ui/react';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
...@@ -55,7 +55,7 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => { ...@@ -55,7 +55,7 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => {
} }
if (isLoading) { if (isLoading) {
return <Box className={ className } h={{ base: 12, lg: 6 }}/>; return <Skeleton className={ className } h={{ base: 12, lg: 6 }} maxW="1000px"/>;
} }
if (!adData) { if (!adData) {
......
import { Box, chakra, Tooltip } from '@chakra-ui/react'; import { Box, chakra, Skeleton, Tooltip } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon'; import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
...@@ -9,9 +9,14 @@ import AddressContractIcon from 'ui/shared/address/AddressContractIcon'; ...@@ -9,9 +9,14 @@ import AddressContractIcon from 'ui/shared/address/AddressContractIcon';
type Props = { type Props = {
address: Pick<AddressParam, 'hash' | 'is_contract' | 'implementation_name'>; address: Pick<AddressParam, 'hash' | 'is_contract' | 'implementation_name'>;
className?: string; className?: string;
isLoading?: boolean;
} }
const AddressIcon = ({ address, className }: Props) => { const AddressIcon = ({ address, className, isLoading }: Props) => {
if (isLoading) {
return <Skeleton boxSize={ 6 } className={ className } borderRadius="full" flexShrink={ 0 }/>;
}
if (address.is_contract) { if (address.is_contract) {
return ( return (
<AddressContractIcon className={ className }/> <AddressContractIcon className={ className }/>
...@@ -20,7 +25,7 @@ const AddressIcon = ({ address, className }: Props) => { ...@@ -20,7 +25,7 @@ const AddressIcon = ({ address, className }: Props) => {
return ( return (
<Tooltip label={ address.implementation_name }> <Tooltip label={ address.implementation_name }>
<Box className={ className } width="24px" display="inline-flex"> <Box className={ className } boxSize={ 6 } display="inline-flex">
<Jazzicon diameter={ 24 } seed={ jsNumberForAddress(address.hash) }/> <Jazzicon diameter={ 24 } seed={ jsNumberForAddress(address.hash) }/>
</Box> </Box>
</Tooltip> </Tooltip>
......
import { chakra, shouldForwardProp, Tooltip, Box } from '@chakra-ui/react'; import { chakra, shouldForwardProp, Tooltip, Box, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import type { HTMLAttributeAnchorTarget } from 'react'; import type { HTMLAttributeAnchorTarget } from 'react';
import React from 'react'; import React from 'react';
...@@ -17,6 +17,7 @@ type CommonProps = { ...@@ -17,6 +17,7 @@ type CommonProps = {
isDisabled?: boolean; isDisabled?: boolean;
fontWeight?: string; fontWeight?: string;
alias?: string | null; alias?: string | null;
isLoading?: boolean;
} }
type AddressTokenTxProps = { type AddressTokenTxProps = {
...@@ -39,7 +40,7 @@ type AddressTokenProps = { ...@@ -39,7 +40,7 @@ type AddressTokenProps = {
type Props = CommonProps & (AddressTokenTxProps | BlockProps | AddressTokenProps); type Props = CommonProps & (AddressTokenTxProps | BlockProps | AddressTokenProps);
const AddressLink = (props: Props) => { const AddressLink = (props: Props) => {
const { alias, type, className, truncation = 'dynamic', hash, fontWeight, target = '_self', isDisabled } = props; const { alias, type, className, truncation = 'dynamic', hash, fontWeight, target = '_self', isDisabled, isLoading } = props;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
let url; let url;
...@@ -81,6 +82,10 @@ const AddressLink = (props: Props) => { ...@@ -81,6 +82,10 @@ const AddressLink = (props: Props) => {
} }
})(); })();
if (isLoading) {
return <Skeleton className={ className } overflow="hidden" whiteSpace="nowrap">{ content }</Skeleton>;
}
if (isDisabled) { if (isDisabled) {
return ( return (
<chakra.span <chakra.span
......
import { Skeleton, Tag as ChakraTag } from '@chakra-ui/react';
import type { TagProps } from '@chakra-ui/react';
import React from 'react';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
interface Props extends TagProps {
isLoading?: boolean;
}
const Tag = ({ isLoading, ...props }: Props) => {
if (props.isTruncated && typeof props.children === 'string') {
if (!props.children) {
return null;
}
return (
<Skeleton isLoaded={ !isLoading } display="inline-block" borderRadius="sm" maxW="100%">
<TruncatedTextTooltip label={ props.children }>
<ChakraTag { ...props }/>
</TruncatedTextTooltip>
</Skeleton>
);
}
return (
<Skeleton isLoaded={ !isLoading } display="inline-block" borderRadius="sm" maxW="100%">
<ChakraTag { ...props }/>
</Skeleton>
);
};
export default React.memo(Tag);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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