Commit 701cd8eb authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into contract-verification-form

parents eb10da29 42f60f7a
...@@ -61,6 +61,10 @@ const oldUrls = [ ...@@ -61,6 +61,10 @@ const oldUrls = [
oldPath: '/address/:id/validations', oldPath: '/address/:id/validations',
newPath: `${ PATHS.address_index }?tab=blocks_validated`, newPath: `${ PATHS.address_index }?tab=blocks_validated`,
}, },
{
oldPath: '/address/:id/tokens/:hash/token-transfers',
newPath: `${ PATHS.address_index }?tab=token_transfers&token=:hash`,
},
// contract verification // contract verification
{ {
oldPath: '/address/:id/contract_verifications/new', oldPath: '/address/:id/contract_verifications/new',
......
...@@ -473,6 +473,8 @@ frontend: ...@@ -473,6 +473,8 @@ frontend:
# - "/address" # - "/address"
- "/stats" - "/stats"
- "/search-results" - "/search-results"
- "/tokens"
- "/accounts"
resources: resources:
limits: limits:
memory: memory:
......
...@@ -322,6 +322,8 @@ frontend: ...@@ -322,6 +322,8 @@ frontend:
- "/stats" - "/stats"
- "/search-results" - "/search-results"
- "/token" - "/token"
- "/tokens"
- "/accounts"
resources: resources:
limits: limits:
......
...@@ -14,6 +14,7 @@ import type { ...@@ -14,6 +14,7 @@ import type {
AddressTokensFilter, AddressTokensFilter,
AddressTokensResponse, AddressTokensResponse,
} from 'types/api/address'; } from 'types/api/address';
import type { AddressesResponse } from 'types/api/addresses';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } from 'types/api/block'; import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } from 'types/api/block';
import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts'; import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts';
import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod } from 'types/api/contract'; import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod } from 'types/api/contract';
...@@ -24,6 +25,7 @@ import type { RawTracesResponse } from 'types/api/rawTrace'; ...@@ -24,6 +25,7 @@ import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchResult, SearchResultFilters } from 'types/api/search'; import type { SearchResult, SearchResultFilters } from 'types/api/search';
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats'; import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
import type { TokenCounters, TokenInfo, TokenHolders } from 'types/api/tokenInfo'; import type { TokenCounters, TokenInfo, TokenHolders } from 'types/api/tokenInfo';
import type { TokensResponse, TokensFilters } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer'; import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction'; import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
...@@ -127,6 +129,13 @@ export const RESOURCES = { ...@@ -127,6 +129,13 @@ export const RESOURCES = {
path: '/api/v2/transactions/:id/raw-trace', path: '/api/v2/transactions/:id/raw-trace',
}, },
// ADDRESSES
addresses: {
path: '/api/v2/addresses/',
paginationFields: [ 'fetched_coin_balance' as const, 'hash' as const, 'items_count' as const ],
filterFields: [ ],
},
// ADDRESS // ADDRESS
address: { address: {
path: '/api/v2/addresses/:id', path: '/api/v2/addresses/:id',
...@@ -150,7 +159,7 @@ export const RESOURCES = { ...@@ -150,7 +159,7 @@ export const RESOURCES = {
address_token_transfers: { address_token_transfers: {
path: '/api/v2/addresses/:id/token-transfers', path: '/api/v2/addresses/:id/token-transfers',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'transaction_index' as const ], paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'transaction_index' as const ],
filterFields: [ 'filter' as const, 'type' as const ], filterFields: [ 'filter' as const, 'type' as const, 'token' as const ],
}, },
address_blocks_validated: { address_blocks_validated: {
path: '/api/v2/addresses/:id/blocks-validated', path: '/api/v2/addresses/:id/blocks-validated',
...@@ -208,6 +217,16 @@ export const RESOURCES = { ...@@ -208,6 +217,16 @@ export const RESOURCES = {
paginationFields: [ 'items_count' as const, 'value' as const ], paginationFields: [ 'items_count' as const, 'value' as const ],
filterFields: [], filterFields: [],
}, },
token_transfers: {
path: '/api/v2/tokens/:hash/transfers',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [],
},
tokens: {
path: '/api/v2/tokens',
paginationFields: [ 'holder_count' as const, 'items_count' as const, 'name' as const ],
filterFields: [ 'filter' as const, 'type' as const ],
},
// HOMEPAGE // HOMEPAGE
homepage_stats: { homepage_stats: {
...@@ -275,10 +294,11 @@ export type ResourceErrorAccount<T> = ResourceError<{ errors: T }> ...@@ -275,10 +294,11 @@ export type ResourceErrorAccount<T> = ResourceError<{ errors: T }>
export type PaginatedResources = 'blocks' | 'block_txs' | export type PaginatedResources = 'blocks' | 'block_txs' |
'txs_validated' | 'txs_pending' | 'txs_validated' | 'txs_pending' |
'tx_internal_txs' | 'tx_logs' | 'tx_token_transfers' | 'tx_internal_txs' | 'tx_logs' | 'tx_token_transfers' |
'addresses' |
'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | 'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' |
'search' | 'search' |
'address_logs' | 'address_tokens' | 'address_logs' | 'address_tokens' |
'token_holders'; 'token_transfers' | 'token_holders' | 'tokens';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -310,6 +330,7 @@ Q extends 'tx_internal_txs' ? InternalTransactionsResponse : ...@@ -310,6 +330,7 @@ Q extends 'tx_internal_txs' ? InternalTransactionsResponse :
Q extends 'tx_logs' ? LogsResponseTx : Q extends 'tx_logs' ? LogsResponseTx :
Q extends 'tx_token_transfers' ? TokenTransferResponse : Q extends 'tx_token_transfers' ? TokenTransferResponse :
Q extends 'tx_raw_trace' ? RawTracesResponse : Q extends 'tx_raw_trace' ? RawTracesResponse :
Q extends 'addresses' ? AddressesResponse :
Q extends 'address' ? Address : Q extends 'address' ? Address :
Q extends 'address_counters' ? AddressCounters : Q extends 'address_counters' ? AddressCounters :
Q extends 'address_token_balances' ? Array<AddressTokenBalance> : Q extends 'address_token_balances' ? Array<AddressTokenBalance> :
...@@ -323,7 +344,9 @@ Q extends 'address_logs' ? LogsResponseAddress : ...@@ -323,7 +344,9 @@ Q extends 'address_logs' ? LogsResponseAddress :
Q extends 'address_tokens' ? AddressTokensResponse : Q extends 'address_tokens' ? AddressTokensResponse :
Q extends 'token' ? TokenInfo : Q extends 'token' ? TokenInfo :
Q extends 'token_counters' ? TokenCounters : Q extends 'token_counters' ? TokenCounters :
Q extends 'token_transfers' ? TokenTransferResponse :
Q extends 'token_holders' ? TokenHolders : Q extends 'token_holders' ? TokenHolders :
Q extends 'tokens' ? TokensResponse :
Q extends 'search' ? SearchResult : Q extends 'search' ? SearchResult :
Q extends 'contract' ? SmartContract : Q extends 'contract' ? SmartContract :
Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> : Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> :
...@@ -338,9 +361,11 @@ export type PaginationFilters<Q extends PaginatedResources> = ...@@ -338,9 +361,11 @@ export type PaginationFilters<Q extends PaginatedResources> =
Q extends 'blocks' ? BlockFilters : Q extends 'blocks' ? BlockFilters :
Q extends 'txs_validated' | 'txs_pending' ? TTxsFilters : Q extends 'txs_validated' | 'txs_pending' ? TTxsFilters :
Q extends 'tx_token_transfers' ? TokenTransferFilters : Q extends 'tx_token_transfers' ? TokenTransferFilters :
Q extends 'token_transfers' ? TokenTransferFilters :
Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters : Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters :
Q extends 'address_token_transfers' ? AddressTokenTransferFilters : Q extends 'address_token_transfers' ? AddressTokenTransferFilters :
Q extends 'address_tokens' ? AddressTokensFilter : Q extends 'address_tokens' ? AddressTokensFilter :
Q extends 'search' ? SearchResultFilters : Q extends 'search' ? SearchResultFilters :
Q extends 'tokens' ? TokensFilters :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
...@@ -12,6 +12,7 @@ import publicTagIcon from 'icons/publictags.svg'; ...@@ -12,6 +12,7 @@ import publicTagIcon from 'icons/publictags.svg';
import statsIcon from 'icons/stats.svg'; import statsIcon from 'icons/stats.svg';
import tokensIcon from 'icons/token.svg'; import tokensIcon from 'icons/token.svg';
import transactionsIcon from 'icons/transactions.svg'; import transactionsIcon from 'icons/transactions.svg';
import walletIcon from 'icons/wallet.svg';
import watchlistIcon from 'icons/watchlist.svg'; import watchlistIcon from 'icons/watchlist.svg';
import link from 'lib/link/link'; import link from 'lib/link/link';
import useCurrentRoute from 'lib/link/useCurrentRoute'; import useCurrentRoute from 'lib/link/useCurrentRoute';
...@@ -27,6 +28,7 @@ export default function useNavItems() { ...@@ -27,6 +28,7 @@ export default function useNavItems() {
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block'), isNewUi: false }, { text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block'), isNewUi: false },
{ text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx'), isNewUi: false }, { text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx'), isNewUi: false },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens', isNewUi: false }, { text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens', isNewUi: false },
{ text: 'Accounts', url: link('accounts'), icon: walletIcon, isActive: currentRoute === 'accounts', isNewUi: false },
isMarketplaceFilled ? isMarketplaceFilled ?
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps', isNewUi: true } : null, { text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps', isNewUi: true } : null,
{ text: 'Charts & stats', url: link('stats'), icon: statsIcon, isActive: currentRoute === 'stats', isNewUi: false }, { text: 'Charts & stats', url: link('stats'), icon: statsIcon, isActive: currentRoute === 'stats', isNewUi: false },
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
"token_instance_item": "/token/:hash/instance/:id", "token_instance_item": "/token/:hash/instance/:id",
"address_index": "/address/:id", "address_index": "/address/:id",
"address_contract_verification": "/address/:id/contract_verification", "address_contract_verification": "/address/:id/contract_verification",
"accounts": "/accounts",
"apps": "/apps", "apps": "/apps",
"app_index": "/apps/:id", "app_index": "/apps/:id",
"search_results": "/search-results", "search_results": "/search-results",
......
...@@ -77,6 +77,12 @@ export const ROUTES = { ...@@ -77,6 +77,12 @@ export const ROUTES = {
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
// ACCOUNTS
accounts: {
pattern: PATHS.accounts,
crossNetworkNavigation: true,
},
// APPS // APPS
apps: { apps: {
pattern: PATHS.apps, pattern: PATHS.apps,
......
...@@ -18,6 +18,7 @@ SocketMessage.AddressCoinBalance | ...@@ -18,6 +18,7 @@ SocketMessage.AddressCoinBalance |
SocketMessage.AddressTxs | SocketMessage.AddressTxs |
SocketMessage.AddressTxsPending | SocketMessage.AddressTxsPending |
SocketMessage.AddressTokenTransfer | SocketMessage.AddressTokenTransfer |
SocketMessage.TokenTransfers |
SocketMessage.Unknown; SocketMessage.Unknown;
interface SocketMessageParamsGeneric<Event extends string | undefined, Payload extends object | unknown> { interface SocketMessageParamsGeneric<Event extends string | undefined, Payload extends object | unknown> {
...@@ -42,5 +43,6 @@ export namespace SocketMessage { ...@@ -42,5 +43,6 @@ export namespace SocketMessage {
export type AddressTxs = SocketMessageParamsGeneric<'transaction', { transaction: Transaction }>; export type AddressTxs = SocketMessageParamsGeneric<'transaction', { transaction: Transaction }>;
export type AddressTxsPending = SocketMessageParamsGeneric<'pending_transaction', { transaction: Transaction }>; export type AddressTxsPending = SocketMessageParamsGeneric<'pending_transaction', { transaction: Transaction }>;
export type AddressTokenTransfer = SocketMessageParamsGeneric<'token_transfer', { token_transfer: TokenTransfer }>; export type AddressTokenTransfer = SocketMessageParamsGeneric<'token_transfer', { token_transfer: TokenTransfer }>;
export type TokenTransfers = SocketMessageParamsGeneric<'token_transfer', {token_transfer: number }>
export type Unknown = SocketMessageParamsGeneric<undefined, unknown>; export type Unknown = SocketMessageParamsGeneric<undefined, unknown>;
} }
import type { TokenType } from 'types/api/tokenInfo';
const TOKEN_TYPE: Array<{ title: string; id: TokenType }> = [
{ title: 'ERC-20', id: 'ERC-20' },
{ title: 'ERC-721', id: 'ERC-721' },
{ title: 'ERC-1155', id: 'ERC-1155' },
];
export default TOKEN_TYPE;
import type { AddressTokenBalance } from 'types/api/address'; import type { AddressTokenBalance } from 'types/api/address';
import * as tokens from 'mocks/tokens/tokenInfo';
export const erc20a: AddressTokenBalance = { export const erc20a: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC20a,
address: '0xb2a90505dc6680a7a695f7975d0d32EeF610f456',
decimals: '18',
exchange_rate: null,
holders: '23',
name: 'hyfi.token',
symbol: 'HyFi',
total_supply: '369000000000000000000000000',
type: 'ERC-20',
},
token_id: null, token_id: null,
value: '1169320000000000000000000', value: '1169320000000000000000000',
}; };
export const erc20b: AddressTokenBalance = { export const erc20b: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC20b,
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
decimals: '6',
exchange_rate: '0.982',
holders: '17',
name: 'USD Coin',
symbol: 'USDC',
total_supply: '900000000000000000000000000',
type: 'ERC-20',
},
token_id: null, token_id: null,
value: '872500000000', value: '872500000000',
}; };
export const erc20c: AddressTokenBalance = { export const erc20c: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC20c,
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
decimals: '18',
exchange_rate: '1328.89',
holders: '17',
name: 'Ethereum',
symbol: 'ETH',
total_supply: '1000000000000000000000000',
type: 'ERC-20',
},
token_id: null, token_id: null,
value: '9852000000000000000000', value: '9852000000000000000000',
}; };
export const erc20d: AddressTokenBalance = { export const erc20d: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC20d,
address: '0xCc7bb2D219A0FC08033E130629C2B854b7bA9195',
decimals: '18',
exchange_rate: null,
holders: '102625',
name: 'Zeta',
symbol: 'ZETA',
total_supply: '2100000000000000000000000000',
type: 'ERC-20',
},
token_id: null, token_id: null,
value: '39000000000000000000', value: '39000000000000000000',
}; };
export const erc721a: AddressTokenBalance = { export const erc721a: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC721a,
address: '0xDe7cAc71E072FCBd4453E5FB3558C2684d1F88A0',
decimals: null,
exchange_rate: null,
holders: '7',
name: 'HyFi Athena',
symbol: 'HYFI_ATHENA',
total_supply: '105',
type: 'ERC-721',
},
token_id: null, token_id: null,
value: '51', value: '51',
}; };
export const erc721b: AddressTokenBalance = { export const erc721b: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC721b,
address: '0xA8d5C7beEA8C9bB57f5fBa35fB638BF45550b11F',
decimals: null,
exchange_rate: null,
holders: '2',
name: 'World Of Women Galaxy',
symbol: 'WOWG',
total_supply: null,
type: 'ERC-721',
},
token_id: null, token_id: null,
value: '1', value: '1',
}; };
export const erc721c: AddressTokenBalance = { export const erc721c: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC721c,
address: '0x47646F1d7dc4Dd2Db5a41D092e2Cf966e27A4992',
decimals: null,
exchange_rate: null,
holders: '12',
name: 'Puma',
symbol: 'PUMA',
total_supply: null,
type: 'ERC-721',
},
token_id: null, token_id: null,
value: '5', value: '5',
}; };
export const erc1155a: AddressTokenBalance = { export const erc1155a: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC1155a,
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
decimals: null,
exchange_rate: null,
holders: '22',
name: 'HyFi Membership',
symbol: 'HYFI_MEMBERSHIP',
total_supply: '482',
type: 'ERC-1155',
},
token_id: '42', token_id: '42',
value: '24', value: '24',
}; };
export const erc1155b: AddressTokenBalance = { export const erc1155b: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC1155b,
address: '0xf4b71b179132ad457f6bcae2a55efa9e4b26eefc',
decimals: null,
exchange_rate: null,
holders: '100',
name: 'WinkyVerse Collections',
symbol: 'WVC',
total_supply: '4943',
type: 'ERC-1155',
},
token_id: '100010000000001', token_id: '100010000000001',
value: '11', value: '11',
}; };
export const erc1155withoutName: AddressTokenBalance = { export const erc1155withoutName: AddressTokenBalance = {
token: { token: tokens.tokenInfoERC1155WithoutName,
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
decimals: null,
exchange_rate: null,
holders: '22',
name: null,
symbol: null,
total_supply: '482',
type: 'ERC-1155',
},
token_id: '64532245', token_id: '64532245',
value: '42', value: '42',
}; };
......
...@@ -15,3 +15,113 @@ export const tokenCounters: TokenCounters = { ...@@ -15,3 +15,113 @@ export const tokenCounters: TokenCounters = {
token_holders_count: '8838883', token_holders_count: '8838883',
transfers_count: '88282281', transfers_count: '88282281',
}; };
export const tokenInfoERC20a: TokenInfo = {
address: '0xb2a90505dc6680a7a695f7975d0d32EeF610f456',
decimals: '18',
exchange_rate: null,
holders: '23',
name: 'hyfi.token',
symbol: 'HyFi',
total_supply: '369000000000000000000000000',
type: 'ERC-20',
};
export const tokenInfoERC20b: TokenInfo = {
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
decimals: '6',
exchange_rate: '0.982',
holders: '17',
name: 'USD Coin',
symbol: 'USDC',
total_supply: '900000000000000000000000000',
type: 'ERC-20',
};
export const tokenInfoERC20c: TokenInfo = {
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
decimals: '18',
exchange_rate: '1328.89',
holders: '17',
name: 'Ethereum',
symbol: 'ETH',
total_supply: '1000000000000000000000000',
type: 'ERC-20',
};
export const tokenInfoERC20d: TokenInfo = {
address: '0xCc7bb2D219A0FC08033E130629C2B854b7bA9195',
decimals: '18',
exchange_rate: null,
holders: '102625',
name: 'Zeta',
symbol: 'ZETA',
total_supply: '2100000000000000000000000000',
type: 'ERC-20',
};
export const tokenInfoERC721a: TokenInfo = {
address: '0xDe7cAc71E072FCBd4453E5FB3558C2684d1F88A0',
decimals: null,
exchange_rate: null,
holders: '7',
name: 'HyFi Athena',
symbol: 'HYFI_ATHENA',
total_supply: '105',
type: 'ERC-721',
};
export const tokenInfoERC721b: TokenInfo = {
address: '0xA8d5C7beEA8C9bB57f5fBa35fB638BF45550b11F',
decimals: null,
exchange_rate: null,
holders: '2',
name: 'World Of Women Galaxy',
symbol: 'WOWG',
total_supply: null,
type: 'ERC-721',
};
export const tokenInfoERC721c: TokenInfo = {
address: '0x47646F1d7dc4Dd2Db5a41D092e2Cf966e27A4992',
decimals: null,
exchange_rate: null,
holders: '12',
name: 'Puma',
symbol: 'PUMA',
total_supply: null,
type: 'ERC-721',
};
export const tokenInfoERC1155a: TokenInfo = {
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
decimals: null,
exchange_rate: null,
holders: '22',
name: 'HyFi Membership',
symbol: 'HYFI_MEMBERSHIP',
total_supply: '482',
type: 'ERC-1155',
};
export const tokenInfoERC1155b: TokenInfo = {
address: '0xf4b71b179132ad457f6bcae2a55efa9e4b26eefc',
decimals: null,
exchange_rate: null,
holders: '100',
name: 'WinkyVerse Collections',
symbol: 'WVC',
total_supply: '4943',
type: 'ERC-1155',
};
export const tokenInfoERC1155WithoutName: TokenInfo = {
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
decimals: null,
exchange_rate: null,
holders: '22',
name: null,
symbol: null,
total_supply: '482',
type: 'ERC-1155',
};
...@@ -40,6 +40,7 @@ export const erc20: TokenTransfer = { ...@@ -40,6 +40,7 @@ export const erc20: TokenTransfer = {
timestamp: '2022-10-10T14:34:30.000000Z', timestamp: '2022-10-10T14:34:30.000000Z',
block_hash: '1', block_hash: '1',
log_index: '1', log_index: '1',
method: 'updateSmartAsset',
}; };
export const erc721: TokenTransfer = { export const erc721: TokenTransfer = {
...@@ -81,6 +82,7 @@ export const erc721: TokenTransfer = { ...@@ -81,6 +82,7 @@ export const erc721: TokenTransfer = {
timestamp: '2022-10-10T14:34:30.000000Z', timestamp: '2022-10-10T14:34:30.000000Z',
block_hash: '1', block_hash: '1',
log_index: '1', log_index: '1',
method: 'updateSmartAsset',
}; };
export const erc1155: TokenTransfer = { export const erc1155: TokenTransfer = {
......
...@@ -50,7 +50,7 @@ const CustomErrorComponent = (props: Props) => { ...@@ -50,7 +50,7 @@ const CustomErrorComponent = (props: Props) => {
); );
} }
const colorModeCookie = cookies.getFromCookieString(props.cookies, cookies.NAMES.COLOR_MODE); const colorModeCookie = cookies.getFromCookieString(props.cookies || '', cookies.NAMES.COLOR_MODE);
return <NextErrorComponent statusCode={ props.statusCode } withDarkMode={ colorModeCookie === 'dark' }/>; return <NextErrorComponent statusCode={ props.statusCode } withDarkMode={ colorModeCookie === 'dark' }/>;
}; };
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Accounts from 'ui/pages/Accounts';
const AccountsPage: NextPage = () => {
const title = `Top Accounts - ${ getNetworkTitle() }`;
return (
<>
<Head><title>{ title }</title></Head>
<Accounts/>
</>
);
};
export default AccountsPage;
export { getServerSideProps } from 'lib/next/getServerSideProps';
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Tokens from 'ui/pages/Tokens';
const TokensPage: NextPage = () => {
const title = getNetworkTitle();
return (
<>
<Head>
<title>{ title }</title>
</Head>
<Tokens/>
</>
);
};
export default TokensPage;
export { getServerSideProps } from 'lib/next/getServerSideProps';
...@@ -70,15 +70,15 @@ const variantOutline = defineStyle((props) => { ...@@ -70,15 +70,15 @@ const variantOutline = defineStyle((props) => {
borderColor, borderColor,
bg: 'transparent', bg: 'transparent',
_hover: { _hover: {
color: 'blue.400', color: 'link_hovered',
borderColor: 'blue.400', borderColor: 'link_hovered',
bg: 'transparent', bg: 'transparent',
_active: { _active: {
bg: props.isActive ? activeBg : 'transparent', bg: props.isActive ? activeBg : 'transparent',
borderColor: props.isActive ? activeBg : 'blue.400', borderColor: props.isActive ? activeBg : 'link_hovered',
color: props.isActive ? activeColor : 'blue.400', color: props.isActive ? activeColor : 'link_hovered',
p: { p: {
color: 'blue.400', color: 'link_hovered',
}, },
}, },
_disabled: { _disabled: {
...@@ -86,7 +86,7 @@ const variantOutline = defineStyle((props) => { ...@@ -86,7 +86,7 @@ const variantOutline = defineStyle((props) => {
borderColor, borderColor,
}, },
p: { p: {
color: 'blue.400', color: 'link_hovered',
}, },
}, },
_disabled: { _disabled: {
...@@ -147,7 +147,7 @@ const variantSubtle = defineStyle((props) => { ...@@ -147,7 +147,7 @@ const variantSubtle = defineStyle((props) => {
bg: mode('blackAlpha.200', 'whiteAlpha.200')(props), bg: mode('blackAlpha.200', 'whiteAlpha.200')(props),
color: mode('blackAlpha.800', 'whiteAlpha.800')(props), color: mode('blackAlpha.800', 'whiteAlpha.800')(props),
_hover: { _hover: {
color: 'blue.400', color: 'link_hovered',
_disabled: { _disabled: {
color: mode('blackAlpha.800', 'whiteAlpha.800')(props), color: mode('blackAlpha.800', 'whiteAlpha.800')(props),
bg: mode('blackAlpha.200', 'whiteAlpha.200')(props), bg: mode('blackAlpha.200', 'whiteAlpha.200')(props),
...@@ -160,7 +160,7 @@ const variantSubtle = defineStyle((props) => { ...@@ -160,7 +160,7 @@ const variantSubtle = defineStyle((props) => {
bg: `${ c }.100`, bg: `${ c }.100`,
color: `${ c }.600`, color: `${ c }.600`,
_hover: { _hover: {
color: 'blue.400', color: 'link_hovered',
}, },
}; };
}); });
......
...@@ -8,9 +8,9 @@ const baseStyle = defineStyle(getDefaultTransitionProps()); ...@@ -8,9 +8,9 @@ const baseStyle = defineStyle(getDefaultTransitionProps());
const variantPrimary = defineStyle((props) => { const variantPrimary = defineStyle((props) => {
return { return {
color: mode('blue.600', 'blue.300')(props), color: 'link',
_hover: { _hover: {
color: mode('blue.400', 'blue.200')(props), color: 'link_hovered',
textDecorationStyle: props.textDecorationStyle || 'solid', textDecorationStyle: props.textDecorationStyle || 'solid',
}, },
}; };
......
...@@ -51,7 +51,7 @@ const baseStyleCloseButton = defineStyle((props) => { ...@@ -51,7 +51,7 @@ const baseStyleCloseButton = defineStyle((props) => {
height: 10, height: 10,
width: 10, width: 10,
color: mode('gray.700', 'gray.500')(props), color: mode('gray.700', 'gray.500')(props),
_hover: { color: 'blue.400' }, _hover: { color: 'link_hovered' },
_active: { bg: 'none' }, _active: { bg: 'none' },
}; };
}); });
......
...@@ -23,7 +23,7 @@ const variantSimple = definePartsStyle((props) => { ...@@ -23,7 +23,7 @@ const variantSimple = definePartsStyle((props) => {
...transitionProps, ...transitionProps,
}, },
td: { td: {
borderColor: mode('blackAlpha.200', 'whiteAlpha.200')(props), borderColor: 'divider',
...transitionProps, ...transitionProps,
}, },
}; };
......
...@@ -22,7 +22,7 @@ const variantSoftRounded = definePartsStyle((props) => { ...@@ -22,7 +22,7 @@ const variantSoftRounded = definePartsStyle((props) => {
}, },
}, },
_hover: { _hover: {
color: 'blue.400', color: 'link_hovered',
}, },
}, },
}; };
......
...@@ -10,10 +10,6 @@ const variantSecondary = defineStyle((props) => ({ ...@@ -10,10 +10,6 @@ const variantSecondary = defineStyle((props) => ({
color: mode('gray.500', 'gray.400')(props), color: mode('gray.500', 'gray.400')(props),
})); }));
const variantAlternate = defineStyle((props) => ({
color: mode('blue.600', 'blue.300')(props),
}));
const variantInherit = { const variantInherit = {
color: 'inherit', color: 'inherit',
}; };
...@@ -21,7 +17,6 @@ const variantInherit = { ...@@ -21,7 +17,6 @@ const variantInherit = {
const variants: Record<string, SystemStyleInterpolation> = { const variants: Record<string, SystemStyleInterpolation> = {
primary: variantPrimary, primary: variantPrimary,
secondary: variantSecondary, secondary: variantSecondary,
alternate: variantAlternate,
inherit: variantInherit, inherit: variantInherit,
}; };
......
const semanticTokens = {
colors: {
divider: {
'default': 'blackAlpha.200',
_dark: 'whiteAlpha.200',
},
link: {
'default': 'blue.600',
_dark: 'blue.300',
},
link_hovered: {
'default': 'blue.400',
},
},
};
export default semanticTokens;
...@@ -5,6 +5,7 @@ import config from './config'; ...@@ -5,6 +5,7 @@ import config from './config';
import borders from './foundations/borders'; import borders from './foundations/borders';
import breakpoints from './foundations/breakpoints'; import breakpoints from './foundations/breakpoints';
import colors from './foundations/colors'; import colors from './foundations/colors';
import semanticTokens from './foundations/semanticTokens';
import transition from './foundations/transition'; import transition from './foundations/transition';
import typography from './foundations/typography'; import typography from './foundations/typography';
import zIndices from './foundations/zIndices'; import zIndices from './foundations/zIndices';
...@@ -22,6 +23,7 @@ const overrides = { ...@@ -22,6 +23,7 @@ const overrides = {
breakpoints, breakpoints,
transition, transition,
zIndices, zIndices,
semanticTokens,
}; };
export default extendTheme(overrides); export default extendTheme(overrides);
...@@ -81,8 +81,9 @@ export interface AddressTokenTransferResponse { ...@@ -81,8 +81,9 @@ export interface AddressTokenTransferResponse {
} }
export type AddressTokenTransferFilters = { export type AddressTokenTransferFilters = {
filter: AddressFromToFilter; filter?: AddressFromToFilter;
type: Array<TokenType>; type?: Array<TokenType>;
token?: string;
} }
export type AddressTokensFilter = { export type AddressTokensFilter = {
......
import type { AddressParam } from './addressParams';
export type AddressesItem = AddressParam &{ tx_count: string; coin_balance: string }
export type AddressesResponse = {
items: Array<AddressesItem>;
next_page_params: {
fetched_coin_balance: string;
hash: string;
items_count: number;
};
total_supply: string;
}
...@@ -39,6 +39,7 @@ interface TokenTransferBase { ...@@ -39,6 +39,7 @@ interface TokenTransferBase {
timestamp: string; timestamp: string;
block_hash: string; block_hash: string;
log_index: string; log_index: string;
method?: string;
} }
export type TokenTransferPagination = { export type TokenTransferPagination = {
......
import type { TokenInfo, TokenType } from './tokenInfo';
export type TokensResponse = {
items: Array<TokenInfo>;
next_page_params: {
holder_count: number;
items_count: number;
name: string;
};
}
export type TokensFilters = { filter: string; type: Array<TokenType> | undefined };
...@@ -100,7 +100,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -100,7 +100,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
title="Creator" title="Creator"
hint="Transaction and address of creation." hint="Transaction and address of creation."
> >
<AddressLink hash={ addressQuery.data.creator_address_hash } truncation="constant"/> <AddressLink type="address" hash={ addressQuery.data.creator_address_hash } truncation="constant"/>
<Text whiteSpace="pre"> at txn </Text> <Text whiteSpace="pre"> at txn </Text>
<AddressLink hash={ addressQuery.data.creation_tx_hash } type="transaction" truncation="constant"/> <AddressLink hash={ addressQuery.data.creation_tx_hash } type="transaction" truncation="constant"/>
</DetailsInfoItem> </DetailsInfoItem>
......
import { Box } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { erc1155 } from 'mocks/tokens/tokenTransfer';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import AddressTokenTransfers from './AddressTokenTransfers';
const API_URL = buildApiUrl('address_token_transfers', { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' }) +
'?token=0x1189a607CEac2f0E14867de4EB15b15C9FFB5859';
const hooksConfig = {
router: {
query: { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', token: '0x1189a607CEac2f0E14867de4EB15b15C9FFB5859' },
},
};
test('with token filter and pagination +@mobile', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ items: [ erc1155 ], next_page_params: { block_number: 1 } }),
}));
const component = await mount(
<TestApp>
<Box h={{ base: '134px', lg: 6 }}/>
<AddressTokenTransfers/>
</TestApp>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
test('with token filter and no pagination +@mobile', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ items: [ erc1155 ] }),
}));
const component = await mount(
<TestApp>
<Box h={{ base: '134px', lg: 6 }}/>
<AddressTokenTransfers/>
</TestApp>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
import { Hide, Show, Text } from '@chakra-ui/react'; import { Flex, Hide, Icon, Show, Text, Tooltip, useColorModeValue } 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 from 'react'; import React from 'react';
...@@ -9,13 +9,16 @@ import type { AddressFromToFilter, AddressTokenTransferResponse } from 'types/ap ...@@ -9,13 +9,16 @@ import type { AddressFromToFilter, AddressTokenTransferResponse } from 'types/ap
import type { TokenType } from 'types/api/tokenInfo'; import type { TokenType } from 'types/api/tokenInfo';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import crossIcon from 'icons/cross.svg';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery'; import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
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 TOKEN_TYPE from 'lib/token/tokenTypes';
import EmptySearchResult from 'ui/apps/EmptySearchResult'; import EmptySearchResult from 'ui/apps/EmptySearchResult';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
...@@ -23,7 +26,8 @@ import Pagination from 'ui/shared/Pagination'; ...@@ -23,7 +26,8 @@ import Pagination from 'ui/shared/Pagination';
import SkeletonList from 'ui/shared/skeletons/SkeletonList'; import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable'; import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import { TOKEN_TYPE, flattenTotal } from 'ui/shared/TokenTransfer/helpers'; import TokenLogo from 'ui/shared/TokenLogo';
import { flattenTotal } from 'ui/shared/TokenTransfer/helpers';
import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter'; import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList'; import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList';
import TokenTransferTable from 'ui/shared/TokenTransfer/TokenTransferTable'; import TokenTransferTable from 'ui/shared/TokenTransfer/TokenTransferTable';
...@@ -61,20 +65,26 @@ const matchFilters = (filters: Filters, tokenTransfer: TokenTransfer, address?: ...@@ -61,20 +65,26 @@ const matchFilters = (filters: Filters, tokenTransfer: TokenTransfer, address?:
const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => { const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => {
const router = useRouter(); const router = useRouter();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const isMobile = useIsMobile();
const currentAddress = router.query.id?.toString(); const currentAddress = router.query.id?.toString();
const [ socketAlert, setSocketAlert ] = React.useState(''); const [ socketAlert, setSocketAlert ] = React.useState('');
const [ newItemsCount, setNewItemsCount ] = React.useState(0); const [ newItemsCount, setNewItemsCount ] = React.useState(0);
const tokenFilter = router.query.token ? router.query.token.toString() : undefined;
const [ filters, setFilters ] = React.useState<Filters>( const [ filters, setFilters ] = React.useState<Filters>(
{ type: getTokenFilterValue(router.query.type) || [], filter: getAddressFilterValue(router.query.filter) }, {
type: getTokenFilterValue(router.query.type) || [],
filter: getAddressFilterValue(router.query.filter),
},
); );
const { isError, isLoading, data, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({ const { isError, isLoading, data, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_token_transfers', resourceName: 'address_token_transfers',
pathParams: { id: currentAddress }, pathParams: { id: currentAddress },
filters: filters, filters: tokenFilter ? { token: tokenFilter } : filters,
scrollRef, scrollRef,
}); });
...@@ -89,6 +99,13 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD ...@@ -89,6 +99,13 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
setFilters((prevState) => ({ ...prevState, filter: filterVal })); setFilters((prevState) => ({ ...prevState, filter: filterVal }));
}, [ filters, onFilterChange ]); }, [ filters, onFilterChange ]);
const resetTokenFilter = React.useCallback(() => {
onFilterChange({});
}, [ onFilterChange ]);
const resetTokenIconColor = useColorModeValue('blue.600', 'blue.300');
const resetTokenIconHoverColor = useColorModeValue('blue.400', 'blue.200');
const handleNewSocketMessage: SocketMessage.AddressTokenTransfer['handler'] = (payload) => { const handleNewSocketMessage: SocketMessage.AddressTokenTransfer['handler'] = (payload) => {
setSocketAlert(''); setSocketAlert('');
...@@ -131,7 +148,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD ...@@ -131,7 +148,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
topic: `addresses:${ (router.query.id as string).toLowerCase() }`, topic: `addresses:${ (router.query.id as string).toLowerCase() }`,
onSocketClose: handleSocketClose, onSocketClose: handleSocketClose,
onSocketError: handleSocketError, onSocketError: handleSocketError,
isDisabled: pagination.page !== 1, isDisabled: pagination.page !== 1 || Boolean(tokenFilter),
}); });
useSocketMessage({ useSocketMessage({
...@@ -141,7 +158,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD ...@@ -141,7 +158,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
}); });
const numActiveFilters = (filters.type?.length || 0) + (filters.filter ? 1 : 0); const numActiveFilters = (filters.type?.length || 0) + (filters.filter ? 1 : 0);
const isActionBarHidden = !numActiveFilters && !data?.items.length; const isActionBarHidden = !tokenFilter && !numActiveFilters && !data?.items.length;
const content = (() => { const content = (() => {
if (isLoading) { if (isLoading) {
...@@ -179,13 +196,13 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD ...@@ -179,13 +196,13 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
showTxInfo showTxInfo
top={ 80 } top={ 80 }
enableTimeIncrement enableTimeIncrement
showSocketInfo={ pagination.page === 1 } showSocketInfo={ pagination.page === 1 && !tokenFilter }
socketInfoAlert={ socketAlert } socketInfoAlert={ socketAlert }
socketInfoNum={ newItemsCount } socketInfoNum={ newItemsCount }
/> />
</Hide> </Hide>
<Show below="lg"> <Show below="lg">
{ pagination.page === 1 && ( { pagination.page === 1 && !tokenFilter && (
<SocketNewItemsNotice <SocketNewItemsNotice
url={ window.location.href } url={ window.location.href }
num={ newItemsCount } num={ newItemsCount }
...@@ -205,18 +222,43 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD ...@@ -205,18 +222,43 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
); );
})(); })();
const tokenFilterComponent = tokenFilter && (
<Flex alignItems="center" py={ 1 } flexWrap="wrap" mb={{ base: isPaginationVisible ? 6 : 3, lg: 0 }}>
Filtered by token
<TokenLogo hash={ tokenFilter } boxSize={ 6 } mx={ 2 }/>
{ isMobile ? tokenFilter.slice(0, 4) + '...' + tokenFilter.slice(-4) : tokenFilter }
<Tooltip label="Reset filter">
<Flex>
<Icon
as={ crossIcon }
boxSize={ 6 }
ml={ 1 }
color={ resetTokenIconColor }
cursor="pointer"
_hover={{ color: resetTokenIconHoverColor }}
onClick={ resetTokenFilter }
/>
</Flex>
</Tooltip>
</Flex>
);
return ( return (
<> <>
{ isMobile && tokenFilterComponent }
{ !isActionBarHidden && ( { !isActionBarHidden && (
<ActionBar mt={ -6 }> <ActionBar mt={ -6 }>
<TokenTransferFilter { !isMobile && tokenFilterComponent }
defaultTypeFilters={ filters.type } { !tokenFilter && (
onTypeFilterChange={ handleTypeFilterChange } <TokenTransferFilter
appliedFiltersNum={ numActiveFilters } defaultTypeFilters={ filters.type }
withAddressFilter onTypeFilterChange={ handleTypeFilterChange }
onAddressFilterChange={ handleAddressFilterChange } appliedFiltersNum={ numActiveFilters }
defaultAddressFilter={ filters.filter } withAddressFilter
/> onAddressFilterChange={ handleAddressFilterChange }
defaultAddressFilter={ filters.filter }
/>
) }
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> } { isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar> </ActionBar>
) } ) }
......
...@@ -10,7 +10,7 @@ import React from 'react'; ...@@ -10,7 +10,7 @@ import React from 'react';
import type { AddressFromToFilter } from 'types/api/address'; import type { AddressFromToFilter } from 'types/api/address';
import FilterButton from 'ui/shared/FilterButton'; import FilterButton from 'ui/shared/filters/FilterButton';
interface Props { interface Props {
isActive: boolean; isActive: boolean;
......
...@@ -123,7 +123,7 @@ const ContractCode = () => { ...@@ -123,7 +123,7 @@ const ContractCode = () => {
<span>Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB </span> <span>Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB </span>
<Address> <Address>
<AddressIcon address={{ hash: data.verified_twin_address_hash, is_contract: true, implementation_name: null }}/> <AddressIcon address={{ hash: data.verified_twin_address_hash, is_contract: true, implementation_name: null }}/>
<AddressLink hash={ data.verified_twin_address_hash } truncation="constant" ml={ 2 }/> <AddressLink type="address" hash={ data.verified_twin_address_hash } truncation="constant" ml={ 2 }/>
</Address> </Address>
<chakra.span mt={ 1 }>All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with </chakra.span> <chakra.span mt={ 1 }>All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with </chakra.span>
<Link href={ link('address_contract_verification', { id: data.verified_twin_address_hash }) }>Verify & Publish</Link> <Link href={ link('address_contract_verification', { id: data.verified_twin_address_hash }) }>Verify & Publish</Link>
...@@ -135,7 +135,7 @@ const ContractCode = () => { ...@@ -135,7 +135,7 @@ const ContractCode = () => {
<span>Minimal Proxy Contract for </span> <span>Minimal Proxy Contract for </span>
<Address> <Address>
<AddressIcon address={{ hash: data.minimal_proxy_address_hash, is_contract: true, implementation_name: null }}/> <AddressIcon address={{ hash: data.minimal_proxy_address_hash, is_contract: true, implementation_name: null }}/>
<AddressLink hash={ data.minimal_proxy_address_hash } truncation="constant" ml={ 2 }/> <AddressLink type="address" hash={ data.minimal_proxy_address_hash } truncation="constant" ml={ 2 }/>
</Address> </Address>
<span>. </span> <span>. </span>
<Box> <Box>
......
...@@ -36,7 +36,7 @@ const ContractConnectWallet = () => { ...@@ -36,7 +36,7 @@ const ContractConnectWallet = () => {
<Flex alignItems="center"> <Flex alignItems="center">
<span>Connected to </span> <span>Connected to </span>
<AddressIcon address={{ hash: address, is_contract: false, implementation_name: null }} mx={ 2 }/> <AddressIcon address={{ hash: address, is_contract: false, implementation_name: null }} mx={ 2 }/>
<AddressLink fontWeight={ 600 } hash={ address } truncation={ isMobile ? 'constant' : 'dynamic' }/> <AddressLink type="address" fontWeight={ 600 } hash={ address } truncation={ isMobile ? 'constant' : 'dynamic' }/>
</Flex> </Flex>
<Button onClick={ handleDisconnect } size="sm" variant="outline">Disconnect</Button> <Button onClick={ handleDisconnect } size="sm" variant="outline">Disconnect</Button>
</Flex> </Flex>
......
...@@ -24,7 +24,7 @@ const ContractImplementationAddress = ({ hash }: Props) => { ...@@ -24,7 +24,7 @@ const ContractImplementationAddress = ({ hash }: Props) => {
return ( return (
<Address whiteSpace="pre-wrap" flexWrap="wrap" mb={ 6 }> <Address whiteSpace="pre-wrap" flexWrap="wrap" mb={ 6 }>
<span>Implementation address: </span> <span>Implementation address: </span>
<AddressLink hash={ data.implementation_address }/> <AddressLink type="address" hash={ data.implementation_address }/>
</Address> </Address>
); );
}; };
......
...@@ -34,7 +34,7 @@ const ContractMethodStatic = ({ data }: Props) => { ...@@ -34,7 +34,7 @@ const ContractMethodStatic = ({ data }: Props) => {
const content = (() => { const content = (() => {
if (data.type === 'address' && data.value) { if (data.type === 'address' && data.value) {
return <AddressLink hash={ data.value }/>; return <AddressLink type="address" hash={ data.value }/>;
} }
return <chakra.span wordBreak="break-all">({ data.type }): { value }</chakra.span>; return <chakra.span wordBreak="break-all">({ data.type }): { value }</chakra.span>;
......
...@@ -50,7 +50,7 @@ const AddressAddToMetaMask = ({ className, token }: Props) => { ...@@ -50,7 +50,7 @@ const AddressAddToMetaMask = ({ className, token }: Props) => {
} }
}, [ toast, token ]); }, [ toast, token ]);
if (token.type !== 'ERC-20' || !('ethereum' in window)) { if (!('ethereum' in window)) {
return null; return null;
} }
......
...@@ -54,7 +54,7 @@ const TxInternalsListItem = ({ ...@@ -54,7 +54,7 @@ const TxInternalsListItem = ({
<Box w="100%" display="flex" columnGap={ 3 }> <Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
<AddressIcon address={ from }/> <AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ isOut }/> <AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ isOut }/>
</Address> </Address>
{ (isIn || isOut) ? { (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut }/> : <InOutTag isIn={ isIn } isOut={ isOut }/> :
...@@ -62,7 +62,7 @@ const TxInternalsListItem = ({ ...@@ -62,7 +62,7 @@ const TxInternalsListItem = ({
} }
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
<AddressIcon address={ toData }/> <AddressIcon address={ toData }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ toData.hash } isDisabled={ isIn }/> <AddressLink type="address" ml={ 2 } fontWeight="500" hash={ toData.hash } isDisabled={ isIn }/>
</Address> </Address>
</Box> </Box>
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
......
import { Skeleton, SkeletonCircle, Flex, Box, useColorModeValue } from '@chakra-ui/react'; import { Skeleton, SkeletonCircle, Flex, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const TxInternalsSkeletonMobile = () => { const TxInternalsSkeletonMobile = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<Box> <Box>
{ Array.from(Array(2)).map((item, index) => ( { Array.from(Array(2)).map((item, index) => (
...@@ -13,7 +11,7 @@ const TxInternalsSkeletonMobile = () => { ...@@ -13,7 +11,7 @@ const TxInternalsSkeletonMobile = () => {
flexDirection="column" flexDirection="column"
paddingY={ 6 } paddingY={ 6 }
borderTopWidth="1px" borderTopWidth="1px"
borderColor={ borderColor } borderColor="divider"
_last={{ _last={{
borderBottomWidth: '1px', borderBottomWidth: '1px',
}} }}
......
...@@ -62,7 +62,7 @@ const AddressIntTxsTableItem = ({ ...@@ -62,7 +62,7 @@ const AddressIntTxsTableItem = ({
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%"> <Address display="inline-flex" maxW="100%">
<AddressIcon address={ from }/> <AddressIcon address={ from }/>
<AddressLink 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 }/>
</Address> </Address>
</Td> </Td>
<Td px={ 0 } verticalAlign="middle"> <Td px={ 0 } verticalAlign="middle">
...@@ -74,7 +74,7 @@ const AddressIntTxsTableItem = ({ ...@@ -74,7 +74,7 @@ const AddressIntTxsTableItem = ({
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%"> <Address display="inline-flex" maxW="100%">
<AddressIcon address={ toData }/> <AddressIcon address={ toData }/>
<AddressLink 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 }/>
</Address> </Address>
</Td> </Td>
<Td isNumeric verticalAlign="middle"> <Td isNumeric verticalAlign="middle">
......
...@@ -49,7 +49,7 @@ const TokenSelectItem = ({ data }: Props) => { ...@@ -49,7 +49,7 @@ const TokenSelectItem = ({ data }: Props) => {
display="flex" display="flex"
flexDir="column" flexDir="column"
rowGap={ 2 } rowGap={ 2 }
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } borderColor="divider"
borderBottomWidth="1px" borderBottomWidth="1px"
_hover={{ _hover={{
bgColor: useColorModeValue('blue.50', 'gray.800'), bgColor: useColorModeValue('blue.50', 'gray.800'),
......
import { Flex, Tag, Text, HStack } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressesItem } from 'types/api/addresses';
import appConfig from 'configs/app/config';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile';
type Props = {
item: AddressesItem;
index: number;
totalSupply: string;
}
const AddressesListItem = ({
item,
index,
totalSupply,
}: Props) => {
const addressBalance = BigNumber(item.coin_balance).div(BigNumber(10 ** appConfig.network.currency.decimals));
return (
<ListItemMobile rowGap={ 3 }>
<Flex alignItems="center" justifyContent="space-between" w="100%">
<Address maxW="100%" mr={ 8 }>
<AddressIcon address={ item } mr={ 2 }/>
<AddressLink
fontWeight={ 700 }
flexGrow={ 1 }
w="calc(100% - 32px)"
hash={ item.hash }
alias={ item.name }
type="address"
/>
</Address>
<Text fontSize="sm" ml="auto" variant="secondary">{ index }</Text>
</Flex>
{ item.public_tags !== null && item.public_tags.length > 0 && item.public_tags.map(tag => (
<Tag key={ tag.label }>{ tag.display_name }</Tag>
)) }
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>{ `Balance ${ appConfig.network.currency.symbol }` }</Text>
<Text fontSize="sm" variant="secondary">{ addressBalance.dp(8).toFormat() }</Text>
</HStack>
{ totalSupply && totalSupply !== '0' && (
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Percentage</Text>
<Text fontSize="sm" variant="secondary">{ addressBalance.div(BigNumber(totalSupply)).multipliedBy(100).dp(8).toFormat() + '%' }</Text>
</HStack>
) }
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Txn count</Text>
<Text fontSize="sm" variant="secondary">{ Number(item.tx_count).toLocaleString('en') }</Text>
</HStack>
</ListItemMobile>
);
};
export default React.memo(AddressesListItem);
import { Table, Tbody, Tr, Th } from '@chakra-ui/react';
import React from 'react';
import type { AddressesItem } from 'types/api/addresses';
import appConfig from 'configs/app/config';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressesTableItem from './AddressesTableItem';
interface Props {
items: Array<AddressesItem>;
totalSupply: string;
pageStartIndex: number;
}
const AddressesTable = ({ items, totalSupply, pageStartIndex }: Props) => {
const hasPercentage = Boolean(totalSupply && totalSupply !== '0');
return (
<Table variant="simple" size="sm">
<Thead top={ 80 }>
<Tr>
<Th width="64px">Rank</Th>
<Th width={ hasPercentage ? '30%' : '40%' }>Address</Th>
<Th width="20%" pl={ 10 }>Public tag</Th>
<Th width={ hasPercentage ? '20%' : '25%' } isNumeric>{ `Balance ${ appConfig.network.currency.symbol }` }</Th>
{ hasPercentage && <Th width="15%" isNumeric>Percentage</Th> }
<Th width="15%" isNumeric>Txn count</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item, index) => (
<AddressesTableItem
key={ item.hash }
item={ item }
totalSupply={ totalSupply }
index={ pageStartIndex + index }
hasPercentage={ hasPercentage }
/>
)) }
</Tbody>
</Table>
);
};
export default AddressesTable;
import { Tr, Td, Tag, Text } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressesItem } from 'types/api/addresses';
import appConfig from 'configs/app/config';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
type Props = {
item: AddressesItem;
index: number;
totalSupply: string;
hasPercentage: boolean;
}
const AddressesTableItem = ({
item,
index,
totalSupply,
hasPercentage,
}: Props) => {
const addressBalance = BigNumber(item.coin_balance).div(BigNumber(10 ** appConfig.network.currency.decimals));
const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.');
return (
<Tr>
<Td>
<Text lineHeight="24px">{ index }</Text>
</Td>
<Td>
<Address display="inline-flex" maxW="100%">
<AddressIcon address={ item } mr={ 2 }/>
<AddressLink
fontWeight={ 700 }
flexGrow={ 1 }
w="calc(100% - 32px)"
hash={ item.hash }
alias={ item.name }
type="address"
/>
</Address>
</Td>
<Td pl={ 10 }>
{ item.public_tags && item.public_tags.length ? item.public_tags.map(tag => (
<Tag key={ tag.label }>{ tag.display_name }</Tag>
)) : <Text lineHeight="24px">-</Text> }
</Td>
<Td isNumeric>
<Text lineHeight="24px" as="span">{ addressBalanceChunks[0] }</Text>
{ addressBalanceChunks[1] && <Text lineHeight="24px" as="span">.</Text> }
<Text lineHeight="24px" variant="secondary" as="span">{ addressBalanceChunks[1] }</Text>
</Td>
{ hasPercentage && (
<Td isNumeric>
<Text lineHeight="24px">{ addressBalance.div(BigNumber(totalSupply)).multipliedBy(100).dp(8).toFormat() + '%' }</Text>
</Td>
) }
<Td isNumeric>
<Text lineHeight="24px">{ Number(item.tx_count).toLocaleString('en') }</Text>
</Td>
</Tr>
);
};
export default React.memo(AddressesTableItem);
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip, useColorModeValue } from '@chakra-ui/react'; import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import NextLink from 'next/link'; import NextLink from 'next/link';
...@@ -52,8 +52,6 @@ const BlockDetails = () => { ...@@ -52,8 +52,6 @@ const BlockDetails = () => {
router.push(url, undefined); router.push(url, undefined);
}, [ router ]); }, [ router ]);
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
if (isLoading) { if (isLoading) {
return <BlockDetailsSkeleton/>; return <BlockDetailsSkeleton/>;
} }
...@@ -76,7 +74,7 @@ const BlockDetails = () => { ...@@ -76,7 +74,7 @@ const BlockDetails = () => {
mt={{ base: 2, lg: 3 }} mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }} mb={{ base: 0, lg: 3 }}
borderBottom="1px solid" borderBottom="1px solid"
borderColor={ borderColor } borderColor="divider"
/> />
); );
const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data); const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data);
...@@ -129,7 +127,7 @@ const BlockDetails = () => { ...@@ -129,7 +127,7 @@ const BlockDetails = () => {
hint="A block producer who successfully included the block onto the blockchain." hint="A block producer who successfully included the block onto the blockchain."
columnGap={ 1 } columnGap={ 1 }
> >
<AddressLink hash={ data.miner.hash }/> <AddressLink type="address" hash={ data.miner.hash }/>
{ data.miner.name && <Text>{ `(${ capitalize(validatorTitle) }: ${ data.miner.name })` }</Text> } { data.miner.name && <Text>{ `(${ capitalize(validatorTitle) }: ${ data.miner.name })` }</Text> }
{ /* api doesn't return the block processing time yet */ } { /* api doesn't return the block processing time yet */ }
{ /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ } { /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ }
......
import { Grid, GridItem, Skeleton, useColorModeValue } from '@chakra-ui/react'; import { Grid, GridItem, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow'; import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow';
const BlockDetailsSkeleton = () => { const BlockDetailsSkeleton = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const sectionGap = ( const sectionGap = (
<GridItem <GridItem
colSpan={{ base: undefined, lg: 2 }} colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }} mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }} mb={{ base: 0, lg: 3 }}
borderBottom="1px solid" borderBottom="1px solid"
borderColor={ borderColor } borderColor="divider"
/> />
); );
......
...@@ -48,7 +48,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => { ...@@ -48,7 +48,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
</Flex> </Flex>
<Flex columnGap={ 2 }> <Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>{ capitalize(getNetworkValidatorTitle()) }</Text> <Text fontWeight={ 500 }>{ capitalize(getNetworkValidatorTitle()) }</Text>
<AddressLink alias={ data.miner.name } hash={ data.miner.hash } truncation="constant"/> <AddressLink type="address" alias={ data.miner.name } hash={ data.miner.hash } truncation="constant"/>
</Flex> </Flex>
<Flex columnGap={ 2 }> <Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Txn</Text> <Text fontWeight={ 500 }>Txn</Text>
......
...@@ -50,7 +50,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => { ...@@ -50,7 +50,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
</Td> </Td>
<Td fontSize="sm">{ data.size.toLocaleString('en') }</Td> <Td fontSize="sm">{ data.size.toLocaleString('en') }</Td>
<Td fontSize="sm"> <Td fontSize="sm">
<AddressLink alias={ data.miner.name } hash={ data.miner.hash } truncation="constant" display="inline-flex" maxW="100%"/> <AddressLink type="address" alias={ data.miner.name } hash={ data.miner.hash } truncation="constant" display="inline-flex" maxW="100%"/>
</Td> </Td>
<Td isNumeric fontSize="sm">{ data.tx_count }</Td> <Td isNumeric fontSize="sm">{ data.tx_count }</Td>
<Td fontSize="sm"> <Td fontSize="sm">
......
...@@ -84,11 +84,11 @@ const IndexingAlert = ({ className }: { className?: string }) => { ...@@ -84,11 +84,11 @@ const IndexingAlert = ({ className }: { className?: string }) => {
let content; let content;
if (data.finished_indexing_blocks === false) { if (data.finished_indexing_blocks === false) {
content = `${ data.indexed_blocks_ratio && `${ (Number(data.indexed_blocks_ratio) * 100).toFixed() }% Blocks Indexed${ nbsp }${ ndash } ` } content = `${ data.indexed_blocks_ratio && `${ Math.floor(Number(data.indexed_blocks_ratio) * 100) }% Blocks Indexed${ nbsp }${ ndash } ` }
We're indexing this chain right now. Some of the counts may be inaccurate.` ; We're indexing this chain right now. Some of the counts may be inaccurate.` ;
} else if (data.finished_indexing === false) { } else if (data.finished_indexing === false) {
content = `${ data.indexed_inernal_transactions_ratio && content = `${ data.indexed_inernal_transactions_ratio &&
`${ (Number(data.indexed_inernal_transactions_ratio) * 100).toFixed() }% Blocks With Internal Transactions Indexed${ nbsp }${ ndash } ` } `${ Math.floor(Number(data.indexed_inernal_transactions_ratio) * 100) }% Blocks With Internal Transactions Indexed${ nbsp }${ ndash } ` }
We're indexing this chain right now. Some of the counts may be inaccurate.`; We're indexing this chain right now. Some of the counts may be inaccurate.`;
} }
......
...@@ -7,7 +7,6 @@ import { ...@@ -7,7 +7,6 @@ import {
Icon, Icon,
Link, Link,
Text, Text,
useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import React from 'react'; import React from 'react';
...@@ -36,14 +35,14 @@ const LatestBlocksItem = ({ block, h }: Props) => { ...@@ -36,14 +35,14 @@ const LatestBlocksItem = ({ block, h }: Props) => {
transitionTimingFunction="linear" transitionTimingFunction="linear"
borderRadius="12px" borderRadius="12px"
border="1px solid" border="1px solid"
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } borderColor="divider"
p={ 6 } p={ 6 }
h={ `${ h }px` } h={ `${ h }px` }
minWidth={{ base: '100%', lg: '280px' }} minWidth={{ base: '100%', lg: '280px' }}
> >
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }> <Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
<HStack spacing={ 2 }> <HStack spacing={ 2 }>
<Icon as={ blockIcon } boxSize="30px" color={ useColorModeValue('blue.600', 'blue.300') }/> <Icon as={ blockIcon } boxSize="30px" color="link"/>
<Link <Link
href={ link('block', { id: String(block.height) }) } href={ link('block', { id: String(block.height) }) }
fontSize="xl" fontSize="xl"
...@@ -61,7 +60,7 @@ const LatestBlocksItem = ({ block, h }: Props) => { ...@@ -61,7 +60,7 @@ const LatestBlocksItem = ({ block, h }: Props) => {
<GridItem>Reward</GridItem> <GridItem>Reward</GridItem>
<GridItem><Text variant="secondary">{ totalReward.toFixed() }</Text></GridItem> <GridItem><Text variant="secondary">{ totalReward.toFixed() }</Text></GridItem>
<GridItem>Miner</GridItem> <GridItem>Miner</GridItem>
<GridItem><AddressLink alias={ block.miner.name } hash={ block.miner.hash } truncation="constant" maxW="100%"/></GridItem> <GridItem><AddressLink type="address" alias={ block.miner.name } hash={ block.miner.hash } truncation="constant" maxW="100%"/></GridItem>
</Grid> </Grid>
</Box> </Box>
); );
......
...@@ -5,7 +5,6 @@ import { ...@@ -5,7 +5,6 @@ import {
GridItem, GridItem,
HStack, HStack,
Skeleton, Skeleton,
useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -15,7 +14,7 @@ const LatestBlocksItemSkeleton = () => { ...@@ -15,7 +14,7 @@ const LatestBlocksItemSkeleton = () => {
minWidth={{ base: '100%', lg: '280px' }} minWidth={{ base: '100%', lg: '280px' }}
borderRadius="12px" borderRadius="12px"
border="1px solid" border="1px solid"
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } borderColor="divider"
p={ 6 } p={ 6 }
> >
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }> <Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
......
...@@ -11,7 +11,6 @@ import { ...@@ -11,7 +11,6 @@ import {
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
useColorModeValue,
useDisclosure, useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -37,9 +36,6 @@ type Props = { ...@@ -37,9 +36,6 @@ type Props = {
} }
const LatestBlocksItem = ({ tx }: Props) => { const LatestBlocksItem = ({ tx }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const iconColor = useColorModeValue('blue.600', 'blue.300');
const dataTo = tx.to ? tx.to : tx.created_contract; const dataTo = tx.to ? tx.to : tx.created_contract;
const timeAgo = useTimeAgoIncrement(tx.timestamp || '0', true); const timeAgo = useTimeAgoIncrement(tx.timestamp || '0', true);
...@@ -50,10 +46,10 @@ const LatestBlocksItem = ({ tx }: Props) => { ...@@ -50,10 +46,10 @@ const LatestBlocksItem = ({ tx }: Props) => {
<Box <Box
width="100%" width="100%"
borderTop="1px solid" borderTop="1px solid"
borderColor={ borderColor } borderColor="divider"
py={ 4 } py={ 4 }
px={{ base: 0, lg: 4 }} px={{ base: 0, lg: 4 }}
_last={{ borderBottom: '1px solid', borderColor }} _last={{ borderBottom: '1px solid', borderColor: 'divider' }}
> >
<Flex justifyContent="space-between" width="100%" alignItems="start" flexDirection={{ base: 'column', lg: 'row' }}> <Flex justifyContent="space-between" width="100%" alignItems="start" flexDirection={{ base: 'column', lg: 'row' }}>
{ !isMobile && ( { !isMobile && (
...@@ -63,7 +59,7 @@ const LatestBlocksItem = ({ tx }: Props) => { ...@@ -63,7 +59,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
<PopoverTrigger> <PopoverTrigger>
<AdditionalInfoButton isOpen={ isOpen } mr={ 3 }/> <AdditionalInfoButton isOpen={ isOpen } mr={ 3 }/>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent border="1px solid" borderColor={ borderColor }> <PopoverContent border="1px solid" borderColor="divider">
<PopoverBody> <PopoverBody>
<TxAdditionalInfo tx={ tx }/> <TxAdditionalInfo tx={ tx }/>
</PopoverBody> </PopoverBody>
...@@ -92,7 +88,7 @@ const LatestBlocksItem = ({ tx }: Props) => { ...@@ -92,7 +88,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
as={ transactionIcon } as={ transactionIcon }
boxSize="30px" boxSize="30px"
mr={ 2 } mr={ 2 }
color={ iconColor } color="link"
/> />
<Address width="100%"> <Address width="100%">
<AddressLink <AddressLink
...@@ -111,6 +107,7 @@ const LatestBlocksItem = ({ tx }: Props) => { ...@@ -111,6 +107,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
<Address> <Address>
<AddressIcon address={ tx.from }/> <AddressIcon address={ tx.from }/>
<AddressLink <AddressLink
type="address"
hash={ tx.from.hash } hash={ tx.from.hash }
alias={ tx.from.name } alias={ tx.from.name }
fontWeight="500" fontWeight="500"
...@@ -128,6 +125,7 @@ const LatestBlocksItem = ({ tx }: Props) => { ...@@ -128,6 +125,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
<Address> <Address>
<AddressIcon address={ dataTo }/> <AddressIcon address={ dataTo }/>
<AddressLink <AddressLink
type="address"
hash={ dataTo.hash } hash={ dataTo.hash }
alias={ dataTo.name } alias={ dataTo.name }
fontWeight="500" fontWeight="500"
......
...@@ -4,21 +4,18 @@ import { ...@@ -4,21 +4,18 @@ import {
HStack, HStack,
Skeleton, Skeleton,
SkeletonCircle, SkeletonCircle,
useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const LatestTxsItemSkeleton = () => { const LatestTxsItemSkeleton = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<Box <Box
width="100%" width="100%"
borderTop="1px solid" borderTop="1px solid"
borderColor={ borderColor } borderColor="divider"
py={ 4 } py={ 4 }
px={{ base: 0, lg: 4 }} px={{ base: 0, lg: 4 }}
_last={{ borderBottom: '1px solid', borderColor }} _last={{ borderBottom: '1px solid', borderColor: 'divider' }}
> >
<Flex justifyContent="space-between" width="100%" alignItems="start" flexDirection={{ base: 'column', lg: 'row' }}> <Flex justifyContent="space-between" width="100%" alignItems="start" flexDirection={{ base: 'column', lg: 'row' }}>
<Box width="100%"> <Box width="100%">
......
...@@ -30,13 +30,14 @@ const Stats = () => { ...@@ -30,13 +30,14 @@ const Stats = () => {
let content; let content;
const lastItemTouchStyle = { gridColumn: { base: 'span 2', lg: 'unset' } };
if (isLoading) { if (isLoading) {
content = Array.from(Array(itemsCount)).map((item, index) => <StatsItemSkeleton key={ index }/>); content = Array.from(Array(itemsCount)).map((item, index) => <StatsItemSkeleton key={ index } _last={ itemsCount % 2 ? lastItemTouchStyle : undefined }/>);
} }
const lastItemTouchStyle = { gridColumn: { base: 'span 2', lg: 'unset' } };
if (data) { if (data) {
const isOdd = Boolean(hasGasTracker && !data.gas_prices ? (itemsCount - 1) % 2 : itemsCount % 2);
const gasLabel = hasGasTracker && data.gas_prices ? <StatsGasPrices gasPrices={ data.gas_prices }/> : null; const gasLabel = hasGasTracker && data.gas_prices ? <StatsGasPrices gasPrices={ data.gas_prices }/> : null;
content = ( content = (
<> <>
...@@ -63,14 +64,14 @@ const Stats = () => { ...@@ -63,14 +64,14 @@ const Stats = () => {
icon={ walletIcon } icon={ walletIcon }
title="Wallet addresses" title="Wallet addresses"
value={ Number(data.total_addresses).toLocaleString() } value={ Number(data.total_addresses).toLocaleString() }
_last={ itemsCount % 2 ? lastItemTouchStyle : undefined } _last={ isOdd ? lastItemTouchStyle : undefined }
/> />
{ hasGasTracker && data.gas_prices && ( { hasGasTracker && data.gas_prices && (
<StatsItem <StatsItem
icon={ gasIcon } icon={ gasIcon }
title="Gas tracker" title="Gas tracker"
value={ `${ Number(data.gas_prices.average).toLocaleString() } Gwei` } value={ `${ Number(data.gas_prices.average).toLocaleString() } Gwei` }
_last={ itemsCount % 2 ? lastItemTouchStyle : undefined } _last={ isOdd ? lastItemTouchStyle : undefined }
tooltipLabel={ gasLabel } tooltipLabel={ gasLabel }
/> />
) } ) }
......
import { Flex, Skeleton, useColorModeValue } from '@chakra-ui/react'; import { Flex, Skeleton, useColorModeValue, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const StatsItemSkeleton = () => { const StatsItemSkeleton = ({ className }: {className?: string}) => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return ( return (
...@@ -13,6 +13,7 @@ const StatsItemSkeleton = () => { ...@@ -13,6 +13,7 @@ const StatsItemSkeleton = () => {
alignItems="center" alignItems="center"
columnGap={ 3 } columnGap={ 3 }
rowGap={ 2 } rowGap={ 2 }
className={ className }
> >
<Skeleton <Skeleton
w="40px" w="40px"
...@@ -26,4 +27,4 @@ const StatsItemSkeleton = () => { ...@@ -26,4 +27,4 @@ const StatsItemSkeleton = () => {
); );
}; };
export default StatsItemSkeleton; export default chakra(StatsItemSkeleton);
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import type { AddressesResponse } from 'types/api/addresses';
import * as addressMocks from 'mocks/address/address';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import Accounts from './Accounts';
const ADDRESSES_API_URL = buildApiUrl('addresses');
const addresses: AddressesResponse = {
items: [
{
...addressMocks.withName,
tx_count: '1',
coin_balance: '12345678901234567890000',
}, {
...addressMocks.token,
tx_count: '109123890123',
coin_balance: '22222345678901234567890000',
}, {
...addressMocks.withoutName,
tx_count: '11',
coin_balance: '1000000000000000000',
},
],
total_supply: '25222000',
next_page_params: {
items_count: 50,
fetched_coin_balance: '123',
hash: 'aa',
},
};
test('base view +@mobile +@dark-mode', async({ mount, page }) => {
await page.route(ADDRESSES_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(addresses),
}));
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: '',
}));
const component = await mount(
<TestApp>
<Accounts/>
</TestApp>,
);
await expect(component.locator('main')).toHaveScreenshot();
});
import { Hide, Show } from '@chakra-ui/react';
import React from 'react';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import AddressesListItem from 'ui/addresses/AddressesListItem';
import AddressesTable from 'ui/addresses/AddressesTable';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
const PAGE_SIZE = 50;
const Accounts = () => {
const { isError, isLoading, data, isPaginationVisible, pagination } = useQueryWithPages({
resourceName: 'addresses',
});
const content = (() => {
if (isError) {
return <DataFetchAlert/>;
}
const bar = isPaginationVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
);
if (isLoading) {
return (
<>
{ bar }
<Show below="lg">
<SkeletonList/>
</Show>
<Hide below="lg">
<SkeletonTable columns={ [ '64px', '30%', '20%', '20%', '15%', '15%' ] }/>
</Hide>
</>
);
}
const pageStartIndex = (pagination.page - 1) * PAGE_SIZE + 1;
return (
<>
{ bar }
<Hide below="lg" ssr={ false }>
<AddressesTable
items={ data.items }
totalSupply={ data.total_supply }
pageStartIndex={ pageStartIndex }
/>
</Hide>
<Show below="lg" ssr={ false }>
{ data.items.map((item, index) => {
return (
<AddressesListItem
key={ item.hash }
item={ item }
index={ pageStartIndex + index }
totalSupply={ data.total_supply }
/>
);
}) }
</Show>
</>
);
})();
return (
<Page>
<PageTitle text="Top accounts" withTextAd/>
{ content }
</Page>
);
};
export default Accounts;
...@@ -6,7 +6,7 @@ import PlusIcon from 'icons/plus.svg'; ...@@ -6,7 +6,7 @@ import PlusIcon from 'icons/plus.svg';
import AppList from 'ui/apps/AppList'; import AppList from 'ui/apps/AppList';
import AppListSkeleton from 'ui/apps/AppListSkeleton'; import AppListSkeleton from 'ui/apps/AppListSkeleton';
import CategoriesMenu from 'ui/apps/CategoriesMenu'; import CategoriesMenu from 'ui/apps/CategoriesMenu';
import FilterInput from 'ui/shared/FilterInput'; import FilterInput from 'ui/shared/filters/FilterInput';
import useMarketplaceApps from '../apps/useMarketplaceApps'; import useMarketplaceApps from '../apps/useMarketplaceApps';
......
...@@ -10,6 +10,7 @@ import Token from './Token'; ...@@ -10,6 +10,7 @@ import Token from './Token';
const TOKEN_API_URL = buildApiUrl('token', { hash: '1' }); const TOKEN_API_URL = buildApiUrl('token', { hash: '1' });
const TOKEN_COUNTERS_API_URL = buildApiUrl('token_counters', { hash: '1' }); const TOKEN_COUNTERS_API_URL = buildApiUrl('token_counters', { hash: '1' });
const TOKEN_TRANSFERS_API_URL = buildApiUrl('token_transfers', { hash: '1' });
const ADDRESS_API_URL = buildApiUrl('address', { id: '1' }); const ADDRESS_API_URL = buildApiUrl('address', { id: '1' });
const hooksConfig = { const hooksConfig = {
router: { router: {
...@@ -33,6 +34,10 @@ test('base view +@dark-mode', async({ mount, page }) => { ...@@ -33,6 +34,10 @@ test('base view +@dark-mode', async({ mount, page }) => {
status: 200, status: 200,
body: JSON.stringify(tokenCounters), body: JSON.stringify(tokenCounters),
})); }));
await page.route(TOKEN_TRANSFERS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({}),
}));
const component = await mount( const component = await mount(
<TestApp> <TestApp>
...@@ -41,5 +46,5 @@ test('base view +@dark-mode', async({ mount, page }) => { ...@@ -41,5 +46,5 @@ test('base view +@dark-mode', async({ mount, page }) => {
{ hooksConfig }, { hooksConfig },
); );
await expect(component).toHaveScreenshot(); await expect(component.locator('main')).toHaveScreenshot();
}); });
...@@ -15,6 +15,7 @@ import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; ...@@ -15,6 +15,7 @@ import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TokenContractInfo from 'ui/token/TokenContractInfo'; import TokenContractInfo from 'ui/token/TokenContractInfo';
import TokenDetails from 'ui/token/TokenDetails'; import TokenDetails from 'ui/token/TokenDetails';
import TokenHolders from 'ui/token/TokenHolders/TokenHolders'; import TokenHolders from 'ui/token/TokenHolders/TokenHolders';
import TokenTransfer from 'ui/token/TokenTransfer/TokenTransfer';
export type TokenTabs = 'token_transfers' | 'holders' export type TokenTabs = 'token_transfers' | 'holders'
...@@ -29,13 +30,14 @@ const TokenPageContent = () => { ...@@ -29,13 +30,14 @@ const TokenPageContent = () => {
queryOptions: { enabled: Boolean(router.query.hash) }, queryOptions: { enabled: Boolean(router.query.hash) },
}); });
// const transfersQuery = useQueryWithPages({ const transfersQuery = useQueryWithPages({
// resourceName: 'token_transfers', resourceName: 'token_transfers',
// pathParams: { hash: router.query.hash?.toString() }, pathParams: { hash: router.query.hash?.toString() },
// options: { scrollRef,
// enabled: Boolean(router.query.hash && router.query.tab === 'holders' && tokenQuery.data), options: {
// }, enabled: Boolean(router.query.hash && (!router.query.tab || router.query.tab === 'token_transfers') && tokenQuery.data),
// }); },
});
const holdersQuery = useQueryWithPages({ const holdersQuery = useQueryWithPages({
resourceName: 'token_holders', resourceName: 'token_holders',
...@@ -47,16 +49,18 @@ const TokenPageContent = () => { ...@@ -47,16 +49,18 @@ const TokenPageContent = () => {
}); });
const tabs: Array<RoutedTab> = [ const tabs: Array<RoutedTab> = [
{ id: 'token_transfers', title: 'Token transfers', component: null }, { id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery } token={ tokenQuery.data }/> },
{ id: 'holders', title: 'Holders', component: <TokenHolders tokenQuery={ tokenQuery } holdersQuery={ holdersQuery }/> }, { id: 'holders', title: 'Holders', component: <TokenHolders tokenQuery={ tokenQuery } holdersQuery={ holdersQuery }/> },
]; ];
let hasPagination; let hasPagination;
let pagination; let pagination;
// if (router.query.tab === 'token_transfers') {
// hasPagination = transfersQuery.isPaginationVisible; if (!router.query.tab || router.query.tab === 'token_transfers') {
// pagination = transfersQuery.pagination; hasPagination = transfersQuery.isPaginationVisible;
// } pagination = transfersQuery.pagination;
}
if (router.query.tab === 'holders') { if (router.query.tab === 'holders') {
hasPagination = holdersQuery.isPaginationVisible; hasPagination = holdersQuery.isPaginationVisible;
pagination = holdersQuery.pagination; pagination = holdersQuery.pagination;
......
import React from 'react';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import TokensList from 'ui/tokens/Tokens';
const Tokens = () => {
return (
<Page>
<PageTitle text="Tokens" withTextAd/>
<TokensList/>
</Page>
);
};
export default Tokens;
...@@ -43,7 +43,7 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => { ...@@ -43,7 +43,7 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => {
<Address> <Address>
<AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/> <AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/>
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" whiteSpace="nowrap" overflow="hidden"> <Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" whiteSpace="nowrap" overflow="hidden">
<AddressLink hash={ data.address } fontWeight={ 700 } display="block" w="100%"/> <AddressLink type="address" hash={ data.address } fontWeight={ 700 } display="block" w="100%"/>
</Box> </Box>
</Address> </Address>
); );
......
...@@ -18,6 +18,10 @@ const ActionBar = ({ children, className }: Props) => { ...@@ -18,6 +18,10 @@ const ActionBar = ({ children, className }: Props) => {
const isSticky = useIsSticky(ref, TOP_UP + 5); const isSticky = useIsSticky(ref, TOP_UP + 5);
const bgColor = useColorModeValue('white', 'black'); const bgColor = useColorModeValue('white', 'black');
if (!React.Children.toArray(children).filter(Boolean).length) {
return null;
}
return ( return (
<Flex <Flex
className={ className } className={ className }
......
...@@ -17,7 +17,6 @@ interface Props { ...@@ -17,7 +17,6 @@ interface Props {
const AdditionalInfoButton = ({ isOpen, onClick, className }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => { const AdditionalInfoButton = ({ isOpen, onClick, className }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const infoBgColor = useColorModeValue('blue.50', 'gray.600'); const infoBgColor = useColorModeValue('blue.50', 'gray.600');
const infoColor = useColorModeValue('blue.600', 'blue.300');
return ( return (
<Button <Button
...@@ -36,8 +35,8 @@ const AdditionalInfoButton = ({ isOpen, onClick, className }: Props, ref: React. ...@@ -36,8 +35,8 @@ const AdditionalInfoButton = ({ isOpen, onClick, className }: Props, ref: React.
<Icon <Icon
as={ infoIcon } as={ infoIcon }
boxSize={ 5 } boxSize={ 5 }
color={ infoColor } color="link"
_hover={{ color: 'blue.400' }} _hover={{ color: 'link_hovered' }}
/> />
</Button> </Button>
); );
......
...@@ -18,7 +18,7 @@ const AddressSnippet = ({ address, subtitle }: Props) => { ...@@ -18,7 +18,7 @@ const AddressSnippet = ({ address, subtitle }: Props) => {
<Box maxW="100%"> <Box maxW="100%">
<Address> <Address>
<AddressIcon address={ address }/> <AddressIcon address={ address }/>
<AddressLink hash={ address.hash } fontWeight="600" ml={ 2 }/> <AddressLink type="address" hash={ address.hash } fontWeight="600" ml={ 2 }/>
<CopyToClipboard text={ address.hash } ml={ 1 }/> <CopyToClipboard text={ address.hash } ml={ 1 }/>
</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> }
......
import { Flex, useColorModeValue, chakra } from '@chakra-ui/react'; import { Flex, chakra } from '@chakra-ui/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import React from 'react'; import React from 'react';
...@@ -20,7 +20,7 @@ const ListItemMobile = ({ children, className, isAnimated }: Props) => { ...@@ -20,7 +20,7 @@ const ListItemMobile = ({ children, className, isAnimated }: Props) => {
alignItems="flex-start" alignItems="flex-start"
flexDirection="column" flexDirection="column"
paddingY={ 6 } paddingY={ 6 }
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } borderColor="divider"
borderTopWidth="1px" borderTopWidth="1px"
_last={{ _last={{
borderBottomWidth: '1px', borderBottomWidth: '1px',
......
...@@ -11,7 +11,7 @@ import { ...@@ -11,7 +11,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import type { StyleProps } from '@chakra-ui/styled-system'; import type { StyleProps } from '@chakra-ui/styled-system';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import type { RoutedTab } from './types'; import type { RoutedTab } from './types';
...@@ -43,6 +43,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, . ...@@ -43,6 +43,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, .
const [ activeTabIndex, setActiveTabIndex ] = useState<number>(tabs.length + 1); const [ activeTabIndex, setActiveTabIndex ] = useState<number>(tabs.length + 1);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const tabsRef = useRef<HTMLDivElement>(null);
const { tabsCut, tabsList, tabsRefs, listRef, rightSlotRef } = useAdaptiveTabs(tabs, isMobile); const { tabsCut, tabsList, tabsRefs, listRef, rightSlotRef } = useAdaptiveTabs(tabs, isMobile);
const isSticky = useIsSticky(listRef, 5, stickyEnabled); const isSticky = useIsSticky(listRef, 5, stickyEnabled);
const listBgColor = useColorModeValue('white', 'black'); const listBgColor = useColorModeValue('white', 'black');
...@@ -57,6 +58,23 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, . ...@@ -57,6 +58,23 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, .
); );
}, [ tabs, router ]); }, [ tabs, router ]);
useEffect(() => {
if (router.query.scroll_to_tabs) {
tabsRef?.current?.scrollIntoView(true);
delete router.query.scroll_to_tabs;
router.push(
{
pathname: router.pathname,
query: router.query,
},
undefined,
{ shallow: true },
);
}
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => { useEffect(() => {
if (router.isReady) { if (router.isReady) {
let tabIndex = 0; let tabIndex = 0;
...@@ -104,6 +122,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, . ...@@ -104,6 +122,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, .
index={ activeTabIndex } index={ activeTabIndex }
position="relative" position="relative"
size={ themeProps.size || 'md' } size={ themeProps.size || 'md' }
ref={ tabsRef }
> >
<TabList <TabList
marginBottom={{ base: 6, lg: 8 }} marginBottom={{ base: 6, lg: 8 }}
......
import { import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
CheckboxGroup,
Checkbox,
Text, Text,
useDisclosure,
Radio, Radio,
RadioGroup, RadioGroup,
Stack, Stack,
useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressFromToFilter } from 'types/api/address'; import type { AddressFromToFilter } from 'types/api/address';
import type { TokenType } from 'types/api/tokenInfo'; import type { TokenType } from 'types/api/tokenInfo';
import FilterButton from 'ui/shared/FilterButton'; import PopoverFilter from 'ui/shared/filters/PopoverFilter';
import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter';
import { TOKEN_TYPE } from './helpers';
interface Props { interface Props {
appliedFiltersNum?: number; appliedFiltersNum?: number;
...@@ -38,47 +29,31 @@ const TokenTransferFilter = ({ ...@@ -38,47 +29,31 @@ const TokenTransferFilter = ({
onAddressFilterChange, onAddressFilterChange,
defaultAddressFilter, defaultAddressFilter,
}: Props) => { }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy> <PopoverFilter appliedFiltersNum={ appliedFiltersNum } contentProps={{ w: '200px' }}>
<PopoverTrigger> { withAddressFilter && (
<FilterButton <>
isActive={ isOpen || Number(appliedFiltersNum) > 0 } <Text variant="secondary" fontWeight={ 600 }>Address</Text>
onClick={ onToggle } <RadioGroup
appliedFiltersNum={ appliedFiltersNum } size="lg"
/> onChange={ onAddressFilterChange }
</PopoverTrigger> defaultValue={ defaultAddressFilter || 'all' }
<PopoverContent w="200px"> paddingBottom={ 4 }
<PopoverBody px={ 4 } py={ 6 } display="flex" flexDir="column" rowGap={ 5 }> borderBottom="1px solid"
{ withAddressFilter && ( borderColor="divider"
<> >
<Text variant="secondary" fontWeight={ 600 }>Address</Text> <Stack spacing={ 4 }>
<RadioGroup <Radio value="all"><Text fontSize="md">All</Text></Radio>
size="lg" <Radio value="from"><Text fontSize="md">From</Text></Radio>
onChange={ onAddressFilterChange } <Radio value="to"><Text fontSize="md">To</Text></Radio>
defaultValue={ defaultAddressFilter || 'all' } </Stack>
paddingBottom={ 4 } </RadioGroup>
borderBottom="1px solid" </>
borderColor={ borderColor } ) }
> <Text variant="secondary" fontWeight={ 600 }>Type</Text>
<Stack spacing={ 4 }> <TokenTypeFilter onChange={ onTypeFilterChange } defaultValue={ defaultTypeFilters }/>
<Radio value="all"><Text fontSize="md">All</Text></Radio> </PopoverFilter>
<Radio value="from"><Text fontSize="md">From</Text></Radio>
<Radio value="to"><Text fontSize="md">To</Text></Radio>
</Stack>
</RadioGroup>
</>
) }
<Text variant="secondary" fontWeight={ 600 }>Type</Text>
<CheckboxGroup size="lg" onChange={ onTypeFilterChange } defaultValue={ defaultTypeFilters }>
{ TOKEN_TYPE.map(({ title, id }) => <Checkbox key={ id } value={ id }><Text fontSize="md">{ title }</Text></Checkbox>) }
</CheckboxGroup>
</PopoverBody>
</PopoverContent>
</Popover>
); );
}; };
......
import { Text, Flex, Tag, Icon, useColorModeValue } from '@chakra-ui/react'; import { Text, Flex, Tag, Icon } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -43,8 +43,6 @@ const TokenTransferListItem = ({ ...@@ -43,8 +43,6 @@ const TokenTransferListItem = ({
return BigNumber(total.value).div(BigNumber(10 ** Number(total.decimals))).dp(8).toFormat(); return BigNumber(total.value).div(BigNumber(10 ** Number(total.decimals))).dp(8).toFormat();
})(); })();
const iconColor = useColorModeValue('blue.600', 'blue.300');
const timeAgo = useTimeAgoIncrement(timestamp, enableTimeIncrement); const timeAgo = useTimeAgoIncrement(timestamp, enableTimeIncrement);
const addressWidth = `calc((100% - ${ baseAddress ? '50px' : '0px' }) / 2)`; const addressWidth = `calc((100% - ${ baseAddress ? '50px' : '0px' }) / 2)`;
...@@ -64,7 +62,7 @@ const TokenTransferListItem = ({ ...@@ -64,7 +62,7 @@ const TokenTransferListItem = ({
as={ transactionIcon } as={ transactionIcon }
boxSize="30px" boxSize="30px"
mr={ 2 } mr={ 2 }
color={ iconColor } color="link"
/> />
<Address width="100%"> <Address width="100%">
<AddressLink <AddressLink
...@@ -81,7 +79,7 @@ const TokenTransferListItem = ({ ...@@ -81,7 +79,7 @@ const TokenTransferListItem = ({
<Flex w="100%" columnGap={ 3 }> <Flex w="100%" columnGap={ 3 }>
<Address width={ addressWidth }> <Address width={ addressWidth }>
<AddressIcon address={ from }/> <AddressIcon address={ from }/>
<AddressLink 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 }/>
</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"/> :
...@@ -89,7 +87,7 @@ const TokenTransferListItem = ({ ...@@ -89,7 +87,7 @@ const TokenTransferListItem = ({
} }
<Address width={ addressWidth }> <Address width={ addressWidth }>
<AddressIcon address={ to }/> <AddressIcon address={ to }/>
<AddressLink 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 }/>
</Address> </Address>
</Flex> </Flex>
{ value && ( { value && (
......
import { Box, Icon, Link } from '@chakra-ui/react'; import { Box, Icon, Link, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import nftPlaceholder from 'icons/nft_shield.svg'; import nftPlaceholder from 'icons/nft_shield.svg';
...@@ -8,11 +8,20 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; ...@@ -8,11 +8,20 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props { interface Props {
hash: string; hash: string;
id: string; id: string;
className?: string;
} }
const TokenTransferNft = ({ hash, id }: Props) => { const TokenTransferNft = ({ hash, id, className }: Props) => {
return ( return (
<Link href={ link('token_instance_item', { hash, id }) } overflow="hidden" whiteSpace="nowrap" display="flex" alignItems="center" w="100%"> <Link
href={ link('token_instance_item', { hash, id }) }
overflow="hidden"
whiteSpace="nowrap"
display="flex"
alignItems="center"
w="100%"
className={ className }
>
<Icon as={ nftPlaceholder } boxSize="30px" mr={ 1 } color="inherit"/> <Icon as={ nftPlaceholder } boxSize="30px" mr={ 1 } color="inherit"/>
<Box maxW="calc(100% - 34px)"> <Box maxW="calc(100% - 34px)">
<HashStringShortenDynamic hash={ id } fontWeight={ 500 }/> <HashStringShortenDynamic hash={ id } fontWeight={ 500 }/>
...@@ -21,4 +30,4 @@ const TokenTransferNft = ({ hash, id }: Props) => { ...@@ -21,4 +30,4 @@ const TokenTransferNft = ({ hash, id }: Props) => {
); );
}; };
export default React.memo(TokenTransferNft); export default React.memo(chakra(TokenTransferNft));
...@@ -70,7 +70,7 @@ const TokenTransferTableItem = ({ ...@@ -70,7 +70,7 @@ const TokenTransferTableItem = ({
<Td> <Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px"> <Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ from }/> <AddressIcon address={ from }/>
<AddressLink 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 }/>
</Address> </Address>
</Td> </Td>
{ baseAddress && ( { baseAddress && (
...@@ -81,7 +81,7 @@ const TokenTransferTableItem = ({ ...@@ -81,7 +81,7 @@ const TokenTransferTableItem = ({
<Td> <Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px"> <Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ to }/> <AddressIcon address={ to }/>
<AddressLink 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 }/>
</Address> </Address>
</Td> </Td>
<Td isNumeric verticalAlign="top" lineHeight="30px"> <Td isNumeric verticalAlign="top" lineHeight="30px">
......
import type { TokenType } from 'types/api/tokenInfo';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
export const flattenTotal = (result: Array<TokenTransfer>, item: TokenTransfer): Array<TokenTransfer> => { export const flattenTotal = (result: Array<TokenTransfer>, item: TokenTransfer): Array<TokenTransfer> => {
...@@ -25,9 +24,3 @@ export const getTokenTransferTypeText = (type: TokenTransfer['type']) => { ...@@ -25,9 +24,3 @@ export const getTokenTransferTypeText = (type: TokenTransfer['type']) => {
return 'Token transfer'; return 'Token transfer';
} }
}; };
export const TOKEN_TYPE: Array<{ title: string; id: TokenType }> = [
{ title: 'ERC-20', id: 'ERC-20' },
{ title: 'ERC-721', id: 'ERC-721' },
{ title: 'ERC-1155', id: 'ERC-1155' },
];
...@@ -7,49 +7,76 @@ import link from 'lib/link/link'; ...@@ -7,49 +7,76 @@ import link from 'lib/link/link';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props { import TruncatedTextTooltip from '../TruncatedTextTooltip';
type?: 'address' | 'transaction' | 'token' | 'block' | 'token_instance_item';
alias?: string | null; type CommonProps = {
className?: string; className?: string;
hash: string;
truncation?: 'constant' | 'dynamic'| 'none'; truncation?: 'constant' | 'dynamic'| 'none';
fontWeight?: string;
id?: string;
target?: HTMLAttributeAnchorTarget; target?: HTMLAttributeAnchorTarget;
isDisabled?: boolean; isDisabled?: boolean;
fontWeight?: string;
alias?: string | null;
}
type AddressTokenTxProps = {
type: 'address' | 'token' | 'transaction';
hash: 'hash';
} }
const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, fontWeight, target = '_self', isDisabled }: Props) => { type BlockProps = {
type: 'block';
hash: string;
id: string;
}
type AddressTokenProps = {
type: 'address_token';
hash: string;
tokenHash: string;
}
type Props = CommonProps & (AddressTokenTxProps | BlockProps | AddressTokenProps);
const AddressLink = (props: Props) => {
const { alias, type, className, truncation = 'dynamic', hash, fontWeight, target = '_self', isDisabled } = props;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
let url; let url;
if (type === 'transaction') { if (type === 'transaction') {
url = link('tx', { id: id || hash }); url = link('tx', { id: hash });
} else if (type === 'token') { } else if (type === 'token') {
url = link('token_index', { hash: id || hash }); url = link('token_index', { hash: hash });
} else if (type === 'token_instance_item') {
url = link('token_instance_item', { hash, id });
} else if (type === 'block') { } else if (type === 'block') {
url = link('block', { id: id || hash }); url = link('block', { id: props.id });
} else if (type === 'address_token') {
url = link('address_index', { id: hash }, { tab: 'token_transfers', token: props.tokenHash, scroll_to_tabs: 'true' });
} else { } else {
url = link('address_index', { id: id || hash }); url = link('address_index', { id: hash });
} }
const content = (() => { const content = (() => {
if (alias) { if (alias) {
const text = <Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">{ alias }</Box>;
if (type === 'token') {
return (
<TruncatedTextTooltip label={ alias }>
{ text }
</TruncatedTextTooltip>
);
}
return ( return (
<Tooltip label={ hash } isDisabled={ isMobile }> <Tooltip label={ hash } isDisabled={ isMobile }>
<Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">{ alias }</Box> { text }
</Tooltip> </Tooltip>
); );
} }
switch (truncation) { switch (truncation) {
case 'constant': case 'constant':
return <HashStringShorten hash={ id || hash } isTooltipDisabled={ isMobile }/>; return <HashStringShorten hash={ hash } isTooltipDisabled={ isMobile }/>;
case 'dynamic': case 'dynamic':
return <HashStringShortenDynamic hash={ id || hash } fontWeight={ fontWeight } isTooltipDisabled={ isMobile }/>; return <HashStringShortenDynamic hash={ hash } fontWeight={ fontWeight } isTooltipDisabled={ isMobile }/>;
case 'none': case 'none':
return <span>{ id || hash }</span>; return <span>{ hash }</span>;
} }
})(); })();
......
import { useColorModeValue, useToken } from '@chakra-ui/react'; import { useToken } from '@chakra-ui/react';
import * as d3 from 'd3'; import * as d3 from 'd3';
import React from 'react'; import React from 'react';
...@@ -13,8 +13,7 @@ interface Props extends Omit<React.SVGProps<SVGGElement>, 'scale'> { ...@@ -13,8 +13,7 @@ interface Props extends Omit<React.SVGProps<SVGGElement>, 'scale'> {
const ChartGridLine = ({ type, scale, ticks, size, disableAnimation, ...props }: Props) => { const ChartGridLine = ({ type, scale, ticks, size, disableAnimation, ...props }: Props) => {
const ref = React.useRef<SVGGElement>(null); const ref = React.useRef<SVGGElement>(null);
const strokeColorToken = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const strokeColor = useToken('colors', 'divider');
const strokeColor = useToken('colors', strokeColorToken);
React.useEffect(() => { React.useEffect(() => {
if (!ref.current) { if (!ref.current) {
......
...@@ -10,10 +10,11 @@ type Props = { ...@@ -10,10 +10,11 @@ type Props = {
className?: string; className?: string;
size?: 'xs' | 'sm' | 'md' | 'lg'; size?: 'xs' | 'sm' | 'md' | 'lg';
placeholder: string; placeholder: string;
initialValue?: string;
} }
const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) => { const FilterInput = ({ onChange, className, size = 'sm', placeholder, initialValue }: Props) => {
const [ filterQuery, setFilterQuery ] = useState(''); const [ filterQuery, setFilterQuery ] = useState(initialValue || '');
const inputRef = React.useRef<HTMLInputElement>(null); const inputRef = React.useRef<HTMLInputElement>(null);
const iconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600'); const iconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
......
import type { PopoverContentProps } from '@chakra-ui/react';
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import FilterButton from 'ui/shared/filters/FilterButton';
interface Props {
appliedFiltersNum?: number;
isActive?: boolean;
children: React.ReactNode;
contentProps?: PopoverContentProps;
}
const PopoverFilter = ({ appliedFiltersNum, children, contentProps, isActive }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<FilterButton
isActive={ isOpen || isActive || Number(appliedFiltersNum) > 0 }
onClick={ onToggle }
appliedFiltersNum={ appliedFiltersNum }
/>
</PopoverTrigger>
<PopoverContent { ...contentProps }>
<PopoverBody px={ 4 } py={ 6 } display="flex" flexDir="column" rowGap={ 5 }>
{ children }
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default React.memo(PopoverFilter);
import { CheckboxGroup, Checkbox, Text } from '@chakra-ui/react';
import React from 'react';
import type { TokenType } from 'types/api/tokenInfo';
import TOKEN_TYPE from 'lib/token/tokenTypes';
type Props = {
onChange: (nextValue: Array<TokenType>) => void;
defaultValue?: Array<TokenType>;
}
const TokenTypeFilter = ({ onChange, defaultValue }: Props) => {
return (
<CheckboxGroup size="lg" onChange={ onChange } defaultValue={ defaultValue }>
{ TOKEN_TYPE.map(({ title, id }) => (
<Checkbox key={ id } value={ id }>
<Text fontSize="md">{ title }</Text>
</Checkbox>
)) }
</CheckboxGroup>
);
};
export default TokenTypeFilter;
...@@ -159,7 +159,7 @@ const LogDecodedInputData = ({ data }: Props) => { ...@@ -159,7 +159,7 @@ const LogDecodedInputData = ({ data }: Props) => {
<TableRow key={ name } name={ name } type={ type } isLast={ index === data.parameters.length - 1 } indexed={ indexed }> <TableRow key={ name } name={ name } type={ type } isLast={ index === data.parameters.length - 1 } indexed={ indexed }>
{ type === 'address' ? ( { type === 'address' ? (
<Address justifyContent="space-between"> <Address justifyContent="space-between">
<AddressLink hash={ value }/> <AddressLink type="address" hash={ value }/>
<CopyToClipboard text={ value }/> <CopyToClipboard text={ value }/>
</Address> </Address>
) : ( ) : (
......
...@@ -53,7 +53,7 @@ const TxLogItem = ({ address, index, topics, data, decoded, type }: Props) => { ...@@ -53,7 +53,7 @@ const TxLogItem = ({ address, index, topics, data, decoded, type }: Props) => {
<GridItem display="flex" alignItems="center"> <GridItem display="flex" alignItems="center">
<Address mr={{ base: 9, lg: 0 }}> <Address mr={{ base: 9, lg: 0 }}>
<AddressIcon address={ address }/> <AddressIcon address={ address }/>
<AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/> <AddressLink type="address" hash={ address.hash } alias={ address.name } ml={ 2 }/>
</Address> </Address>
{ /* api doesn't have find topic feature yet */ } { /* api doesn't have find topic feature yet */ }
{ /* <Tooltip label="Find matches topic"> { /* <Tooltip label="Find matches topic">
......
...@@ -51,7 +51,7 @@ const LogTopic = ({ hex, index }: Props) => { ...@@ -51,7 +51,7 @@ const LogTopic = ({ hex, index }: Props) => {
case 'address': { case 'address': {
return ( return (
<Address> <Address>
<AddressLink hash={ value }/> <AddressLink type="address" hash={ value }/>
<CopyToClipboard text={ value }/> <CopyToClipboard text={ value }/>
</Address> </Address>
); );
......
import { Box, Flex, Skeleton, SkeletonCircle, useColorModeValue } from '@chakra-ui/react'; import { Box, Flex, Skeleton, SkeletonCircle } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const SkeletonList = () => { const SkeletonList = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<Box> <Box>
{ Array.from(Array(2)).map((item, index) => ( { Array.from(Array(2)).map((item, index) => (
...@@ -13,7 +11,7 @@ const SkeletonList = () => { ...@@ -13,7 +11,7 @@ const SkeletonList = () => {
flexDirection="column" flexDirection="column"
paddingY={ 6 } paddingY={ 6 }
borderTopWidth="1px" borderTopWidth="1px"
borderColor={ borderColor } borderColor="divider"
_last={{ _last={{
borderBottomWidth: '0px', borderBottomWidth: '0px',
}} }}
......
import { Box, Flex, Skeleton, SkeletonCircle, useColorModeValue } from '@chakra-ui/react'; import { Box, Flex, Skeleton, SkeletonCircle } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
interface Props { interface Props {
...@@ -6,8 +6,6 @@ interface Props { ...@@ -6,8 +6,6 @@ interface Props {
} }
const SkeletonListAccount = ({ showFooterSlot }: Props) => { const SkeletonListAccount = ({ showFooterSlot }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<Box> <Box>
{ Array.from(Array(2)).map((item, index) => ( { Array.from(Array(2)).map((item, index) => (
...@@ -17,7 +15,7 @@ const SkeletonListAccount = ({ showFooterSlot }: Props) => { ...@@ -17,7 +15,7 @@ const SkeletonListAccount = ({ showFooterSlot }: Props) => {
flexDirection="column" flexDirection="column"
paddingY={ 6 } paddingY={ 6 }
borderTopWidth="1px" borderTopWidth="1px"
borderColor={ borderColor } borderColor="divider"
_last={{ _last={{
borderBottomWidth: '0px', borderBottomWidth: '0px',
}} }}
......
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.
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