Commit bcc3cb75 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into tom2drum/issue-2029

parents f9e590b8 49bfadeb
......@@ -16,6 +16,7 @@ const bannerContentUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_BANNE
const bannerLinkUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL');
const ratingAirtableApiKey = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY');
const ratingAirtableBaseId = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID');
const graphLinksUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL');
const title = 'Marketplace';
......@@ -30,6 +31,7 @@ const config: Feature<(
featuredApp: string | undefined;
banner: { contentUrl: string; linkUrl: string } | undefined;
rating: { airtableApiKey: string; airtableBaseId: string } | undefined;
graphLinksUrl: string | undefined;
}> = (() => {
if (enabled === 'true' && chain.rpcUrl && submitFormUrl) {
const props = {
......@@ -46,6 +48,7 @@ const config: Feature<(
airtableApiKey: ratingAirtableApiKey,
airtableBaseId: ratingAirtableBaseId,
} : undefined,
graphLinksUrl,
};
if (configUrl) {
......
......@@ -42,6 +42,7 @@ NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/refs/heads/marketplace-graph-test/test-configs/marketplace-graph-links.json
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_METASUITES_ENABLED=true
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
......
......@@ -18,6 +18,7 @@ ASSETS_ENVS=(
"NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL"
"NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL"
"NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL"
"NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL"
"NEXT_PUBLIC_FEATURED_NETWORKS"
"NEXT_PUBLIC_FOOTER_LINKS"
"NEXT_PUBLIC_NETWORK_LOGO"
......
......@@ -39,6 +39,7 @@ async function validateEnvs(appEnvs: Record<string, string>) {
'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL',
'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL',
'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL',
'NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL',
'NEXT_PUBLIC_FOOTER_LINKS',
];
......
......@@ -243,6 +243,14 @@ const marketplaceSchema = yup
// eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}),
NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL: yup
.string()
.when('NEXT_PUBLIC_MARKETPLACE_ENABLED', {
is: true,
then: (schema) => schema,
// eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}),
});
const beaconChainSchema = yup
......
......@@ -506,6 +506,7 @@ This feature is **always enabled**, but you can disable it by passing `none` val
| NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL | `string` | URL of the page the banner leads to | - | - | `https://example.com` | v1.29.0+ |
| NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY | `string` | Airtable API key | - | - | - | v1.33.0+ |
| NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID | `string` | Airtable base ID with dapp ratings | - | - | - | v1.33.0+ |
| NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL | `string` | URL of the file (`.json` format only) which contains the list of The Graph links to be displayed on the Marketplace page | - | - | `https://example.com/graph_links.json` | v1.36.0+ |
#### Marketplace app configuration properties
......
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path fill="#6747ED" d="M10 20c5.523 0 10-4.477 10-10S15.523 0 10 0 0 4.477 0 10s4.477 10 10 10"/>
<path fill="#fff" fill-rule="evenodd" d="M9.854 11.292a2.66 2.66 0 0 1-2.666-2.667 2.66 2.66 0 0 1 2.666-2.667 2.66 2.66 0 0 1 2.667 2.667 2.66 2.66 0 0 1-2.667 2.667m0-6.667a4.001 4.001 0 0 1 0 8 4.001 4.001 0 0 1 0-8m3.813 8.208c.27.271.27.688 0 .938L11 16.437c-.27.271-.687.271-.937 0-.271-.27-.271-.687 0-.937l2.666-2.667c.25-.27.688-.27.938 0m1.541-7.541a.66.66 0 0 1-.666.666.66.66 0 0 1-.667-.666c0-.375.292-.667.667-.667.354 0 .666.292.666.667" clip-rule="evenodd"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 30 30">
<path fill="currentColor" fill-rule="evenodd" d="m6.134 10.067 3.722-3.828a.97.97 0 0 1 1.337.06c.177.182.283.428.292.69a1.05 1.05 0 0 1-.234.704L9.243 9.765h14.371c.261 0 .513.107.7.299s.294.454.294.73c0 .274-.107.537-.294.729a.98.98 0 0 1-.7.299H6.831a.97.97 0 0 1-.55-.17 1 1 0 0 1-.368-.46 1.06 1.06 0 0 1 .22-1.126m18.723 6a.965.965 0 0 1 .55.17c.163.111.291.27.368.459a1.06 1.06 0 0 1-.22 1.125l-3.737 3.842-.006.008a1 1 0 0 1-.323.255.97.97 0 0 1-1.13-.197q-.146-.152-.224-.353a1.06 1.06 0 0 1 .281-1.16l.007-.007 2.022-2.086h-5.882c.104-.685.184-1.404 0-2.073z" clip-rule="evenodd"/>
<path fill="currentColor" d="M10 19.513c-1.272 0-2.48-.276-3.395-.778C5.57 18.169 5 17.374 5 16.497c0-.878.57-1.672 1.605-2.239.917-.502 2.123-.778 3.395-.778s2.48.276 3.395.778C14.43 14.825 15 15.622 15 16.497s-.57 1.671-1.605 2.238c-.917.502-2.123.778-3.395.778m0-4.793c-1.052 0-2.073.228-2.8.626-.61.334-.96.753-.96 1.15 0 .398.35.818.96 1.151.727.397 1.746.626 2.8.626s2.073-.228 2.8-.626c.61-.333.96-.753.96-1.15 0-.398-.35-.817-.96-1.151-.727-.398-1.746-.626-2.8-.626"/>
<path stroke="currentColor" stroke-width=".3" d="M10 19.513c-1.272 0-2.48-.276-3.395-.778C5.57 18.169 5 17.374 5 16.497c0-.878.57-1.672 1.605-2.239.917-.502 2.123-.778 3.395-.778s2.48.276 3.395.778C14.43 14.825 15 15.622 15 16.497s-.57 1.671-1.605 2.238c-.917.502-2.123.778-3.395.778Zm0-4.793c-1.052 0-2.073.228-2.8.626-.61.334-.96.753-.96 1.15 0 .398.35.818.96 1.151.727.397 1.746.626 2.8.626s2.073-.228 2.8-.626c.61-.333.96-.753.96-1.15 0-.398-.35-.817-.96-1.151-.727-.398-1.746-.626-2.8-.626Z"/>
<path fill="currentColor" d="M10 23.962c-1.272 0-2.48-.276-3.395-.778C5.57 22.618 5 21.823 5 20.946v-4.45a.62.62 0 0 1 1.24 0v4.45c0 .397.35.817.96 1.151.727.397 1.748.626 2.8.626 1.053 0 2.073-.229 2.8-.626.61-.334.96-.754.96-1.151v-4.45a.62.62 0 0 1 1.24 0v4.45c0 .877-.57 1.672-1.605 2.238-.917.502-2.123.778-3.395.778"/>
<path stroke="currentColor" stroke-width=".3" d="M10 23.962c-1.272 0-2.48-.276-3.395-.778C5.57 22.618 5 21.823 5 20.946v-4.45a.62.62 0 0 1 1.24 0v4.45c0 .397.35.817.96 1.151.727.397 1.748.626 2.8.626 1.053 0 2.073-.229 2.8-.626.61-.334.96-.754.96-1.151v-4.45a.62.62 0 0 1 1.24 0v4.45c0 .877-.57 1.672-1.605 2.238-.917.502-2.123.778-3.395.778Z"/>
<path fill="currentColor" d="M10 21.738c-1.272 0-2.48-.277-3.395-.778C5.57 20.393 5 19.598 5 18.72a.62.62 0 1 1 1.24 0c0 .397.35.817.96 1.151.727.398 1.748.626 2.8.626 1.053 0 2.073-.228 2.8-.626.61-.334.96-.754.96-1.15a.62.62 0 1 1 1.24 0c0 .876-.57 1.671-1.605 2.238-.917.501-2.123.778-3.395.778"/>
<path stroke="currentColor" stroke-width=".3" d="M10 21.738c-1.272 0-2.48-.277-3.395-.778C5.57 20.393 5 19.598 5 18.72a.62.62 0 1 1 1.24 0c0 .397.35.817.96 1.151.727.398 1.748.626 2.8.626 1.053 0 2.073-.228 2.8-.626.61-.334.96-.754.96-1.15a.62.62 0 1 1 1.24 0c0 .876-.57 1.671-1.605 2.238-.917.501-2.123.778-3.395.778Z"/>
</svg>
......@@ -622,6 +622,12 @@ export const RESOURCES = {
filterFields: [],
},
// TOKEN TRANSFERS
token_transfers_all: {
path: '/api/v2/token-transfers',
filterFields: [ 'type' as const ],
},
// APP STATS
stats: {
path: '/api/v2/stats',
......@@ -1038,7 +1044,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' |
'withdrawals' | 'address_withdrawals' | 'block_withdrawals' |
'watchlist' | 'private_tags_address' | 'private_tags_tx' |
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history';
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history' |
'token_transfers_all';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
......@@ -1208,6 +1215,7 @@ Q extends 'address_mud_record' ? AddressMudRecord :
Q extends 'address_epoch_rewards' ? AddressEpochRewardsResponse :
Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters :
Q extends 'token_transfers_all' ? TokenTransferResponse :
never;
/* eslint-enable @typescript-eslint/indent */
......@@ -1242,6 +1250,7 @@ Q extends 'user_ops' ? UserOpsFilters :
Q extends 'validators_stability' ? ValidatorsStabilityFilters :
Q extends 'address_mud_tables' ? AddressMudTablesFilter :
Q extends 'address_mud_records' ? AddressMudRecordsFilter :
Q extends 'token_transfers_all' ? TokenTransferFilters :
never;
/* eslint-enable @typescript-eslint/indent */
......
import { useQuery } from '@tanstack/react-query';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import useFetch from 'lib/hooks/useFetch';
const feature = config.features.marketplace;
export default function useGraphLinks() {
const fetch = useFetch();
return useQuery<unknown, ResourceError<unknown>, Record<string, Array<{text: string; url: string}>>>({
queryKey: [ 'graph-links' ],
queryFn: async() => fetch((feature.isEnabled && feature.graphLinksUrl) ? feature.graphLinksUrl : '', undefined, { resource: 'graph-links' }),
enabled: feature.isEnabled && Boolean(feature.graphLinksUrl),
staleTime: Infinity,
placeholderData: {},
});
}
......@@ -179,6 +179,21 @@ export default function useNavItems(): ReturnType {
].filter(Boolean);
}
const tokensNavItems = [
{
text: 'Tokens',
nextRoute: { pathname: '/tokens' as const },
icon: 'token',
isActive: pathname.startsWith('/token'),
},
{
text: 'Token transfers',
nextRoute: { pathname: '/token-transfers' as const },
icon: 'token-transfers',
isActive: pathname === '/token-transfers',
},
];
const apiNavItems: Array<NavItem> = [
config.features.restApiDocs.isEnabled ? {
text: 'REST API',
......@@ -232,9 +247,9 @@ export default function useNavItems(): ReturnType {
},
{
text: 'Tokens',
nextRoute: { pathname: '/tokens' as const },
icon: 'token',
isActive: pathname.startsWith('/token'),
isActive: tokensNavItems.flat().some(item => isInternalItem(item) && item.isActive),
subItems: tokensNavItems,
},
config.features.marketplace.isEnabled ? {
text: 'DApps',
......
......@@ -51,6 +51,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/validators': 'Root page',
'/gas-tracker': 'Root page',
'/mud-worlds': 'Root page',
'/token-transfers': 'Root page',
// service routes, added only to make typescript happy
'/login': 'Regular page',
......
......@@ -55,6 +55,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/validators': DEFAULT_TEMPLATE,
'/gas-tracker': DEFAULT_TEMPLATE,
'/mud-worlds': DEFAULT_TEMPLATE,
'/token-transfers': DEFAULT_TEMPLATE,
// service routes, added only to make typescript happy
'/login': DEFAULT_TEMPLATE,
......
......@@ -51,6 +51,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/validators': '%network_name% validators list',
'/gas-tracker': '%network_name% gas tracker - Current gas fees',
'/mud-worlds': '%network_name% MUD worlds list',
'/token-transfers': '%network_name% token transfers',
// service routes, added only to make typescript happy
'/login': '%network_name% login',
......
......@@ -49,6 +49,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/validators': 'Validators list',
'/gas-tracker': 'Gas tracker',
'/mud-worlds': 'MUD worlds',
'/token-transfers': 'Token transfers',
// service routes, added only to make typescript happy
'/login': 'Login',
......
......@@ -42,6 +42,7 @@ export const erc20: TokenTransfer = {
tx_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193',
type: 'token_transfer',
timestamp: '2022-10-10T14:34:30.000000Z',
block_number: '12345',
block_hash: '1',
log_index: '1',
method: 'updateSmartAsset',
......@@ -88,6 +89,7 @@ export const erc721: TokenTransfer = {
tx_hash: '0xf13bc7afe5e02b494dd2f22078381d36a4800ef94a0ccc147431db56c301e6cc',
type: 'token_transfer',
timestamp: '2022-10-10T14:34:30.000000Z',
block_number: '12345',
block_hash: '1',
log_index: '1',
method: 'updateSmartAsset',
......@@ -136,6 +138,7 @@ export const erc1155A: TokenTransfer = {
tx_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746',
type: 'token_minting',
timestamp: '2022-10-10T14:34:30.000000Z',
block_number: '12345',
block_hash: '1',
log_index: '1',
};
......@@ -214,6 +217,7 @@ export const erc404A: TokenTransfer = {
type: 'token_transfer',
method: 'swap',
timestamp: '2022-10-10T14:34:30.000000Z',
block_number: '12345',
block_hash: '1',
log_index: '1',
};
......
......@@ -57,6 +57,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }>
| DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }>
| StaticRoute<"/token-transfers">
| StaticRoute<"/tokens">
| DynamicRoute<"/tx/[hash]", { "hash": string }>
| StaticRoute<"/txs">
......
......@@ -29,6 +29,7 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => {
setCookie?.forEach((value) => {
nextRes.appendHeader('set-cookie', value);
});
nextRes.setHeader('content-type', apiRes.headers.get('content-type') || '');
nextRes.status(apiRes.status).send(apiRes.body);
};
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const TokenTransfers = dynamic(() => import('ui/pages/TokenTransfers'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/token-transfers">
<TokenTransfers/>
</PageNextJs>
);
};
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -26,6 +26,7 @@
| "block"
| "brands/blockscout"
| "brands/celenium"
| "brands/graph"
| "brands/safe"
| "brands/solidity_scan"
| "burger"
......@@ -151,6 +152,7 @@
| "swap"
| "testnet"
| "token-placeholder"
| "token-transfers"
| "token"
| "tokens"
| "tokens/xdai"
......
......@@ -91,6 +91,7 @@ export const getTokenInstanceHoldersStub = (type?: TokenType, pagination: TokenH
export const TOKEN_TRANSFER_ERC_20: TokenTransfer = {
block_hash: BLOCK_HASH,
block_number: '123456',
from: ADDRESS_PARAMS,
log_index: '4',
method: 'addLiquidity',
......
......@@ -30,39 +30,30 @@ const variantSimple = definePartsStyle((props) => {
});
const sizes = {
md: definePartsStyle({
th: {
px: 4,
fontSize: 'sm',
},
td: {
p: 4,
},
}),
sm: definePartsStyle({
th: {
px: '10px',
py: '10px',
fontSize: 'sm',
},
td: {
px: '10px',
py: 4,
fontSize: 'sm',
fontWeight: 500,
},
}),
xs: definePartsStyle({
th: {
px: '6px',
py: '10px',
fontSize: 'sm',
_first: {
pl: 3,
},
_last: {
pr: 3,
},
},
td: {
px: '6px',
py: 4,
fontSize: 'sm',
fontWeight: 500,
lineHeight: 5,
_first: {
pl: 3,
},
_last: {
pr: 3,
},
},
}),
};
......@@ -104,6 +95,10 @@ const Table = defineMultiStyleConfig({
baseStyle,
sizes,
variants,
defaultProps: {
size: 'sm',
variant: 'simple',
},
});
export default Table;
......@@ -51,6 +51,7 @@ interface TokenTransferBase {
from: AddressParam;
to: AddressParam;
timestamp: string;
block_number: string;
block_hash: string;
log_index: string;
method?: string;
......
......@@ -87,7 +87,7 @@ const AddressAccountHistory = ({ scrollRef, shouldRender = true, isQueryEnabled
</Hide>
<Show above="lg" ssr={ false }>
<Table variant="simple" >
<Table>
<TheadSticky top={ 75 }>
<Tr>
<Th width="120px">
......
......@@ -105,7 +105,7 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
const content = query.data?.items ? (
<>
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}>
<Table style={{ tableLayout: 'auto' }}>
<Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<Tr>
<Th>Block</Th>
......
......@@ -26,7 +26,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
const content = query.data?.items ? (
<>
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm">
<Table>
<Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<Tr>
<Th width="20%">Block</Th>
......
......@@ -15,7 +15,7 @@ import AddressEpochRewardsTableItem from './AddressEpochRewardsTableItem';
const AddressEpochRewardsTable = ({ items, isLoading, top }: Props) => {
return (
<Table variant="simple" size="sm" minW="1000px" style={{ tableLayout: 'auto' }}>
<Table minW="1000px" style={{ tableLayout: 'auto' }}>
<Thead top={ top }>
<Tr>
<Th>Block</Th>
......
......@@ -18,7 +18,7 @@ interface Props {
const AddressIntTxsTable = ({ data, currentAddress, isLoading }: Props) => {
return (
<AddressHighlightProvider>
<Table variant="simple" size="sm">
<Table>
<Thead top={ 68 }>
<Tr>
<Th width="15%">Parent txn hash</Th>
......
......@@ -26,9 +26,9 @@ const AddressMudRecordValues = ({ data }: Props) => {
{
data?.schema.value_names.map((valName, index) => (
<Tr key={ valName } backgroundColor={ valuesBgColor } borderBottomStyle="hidden">
<Td fontSize="sm" w="100px" py={ 0 } pb={ 4 } pr={ 0 }wordBreak="break-all">{ valName }</Td>
<Td fontSize="sm" w="90px" py={ 0 } pb={ 4 } wordBreak="break-all">{ data.schema.value_types[index] }</Td>
<Td fontSize="sm" wordBreak="break-word" py={ 0 } pb={ 4 }>
<Td fontWeight={ 400 } w="100px" py={ 0 } pb={ 4 } pr={ 0 }wordBreak="break-all">{ valName }</Td>
<Td fontWeight={ 400 } w="90px" py={ 0 } pb={ 4 } wordBreak="break-all">{ data.schema.value_types[index] }</Td>
<Td fontWeight={ 400 } wordBreak="break-word" py={ 0 } pb={ 4 }>
<Box>
{ getValueString(data.record.decoded[valName]) }
</Box>
......
......@@ -140,7 +140,7 @@ const AddressMudRecordsTable = ({
return (
// can't implement both horizontal table scroll and sticky header
<Box maxW="100%" overflowX={ hasHorizontalScroll ? 'scroll' : 'unset' } whiteSpace="nowrap" ref={ tableRef }>
<Table variant="simple" size="sm" style={{ tableLayout: 'fixed' }}>
<Table style={{ tableLayout: 'fixed' }}>
<Thead top={ hasHorizontalScroll ? 0 : top } display={ hasHorizontalScroll ? 'table' : 'table-header-group' } w="100%">
<Tr >
{ keys.map((keyName, index) => {
......
......@@ -18,7 +18,7 @@ type Props = {
//sorry for the naming
const AddressMudTablesTable = ({ items, isLoading, top, scrollRef, hash }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}>
<Table style={{ tableLayout: 'auto' }}>
<Thead top={ top }>
<Tr>
<Th width="24px"></Th>
......
......@@ -15,7 +15,7 @@ interface Props {
const ERC20TokensTable = ({ data, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm">
<Table>
<Thead top={ top }>
<Tr>
<Th width="30%">Asset</Th>
......
......@@ -21,7 +21,7 @@ interface Props {
const AddressesTable = ({ items, totalSupply, pageStartIndex, top, isLoading }: Props) => {
const hasPercentage = !totalSupply.eq(ZERO);
return (
<Table variant="simple" size="sm">
<Table>
<Thead top={ top }>
<Tr>
<Th width="64px">Rank</Th>
......
......@@ -16,7 +16,7 @@ interface Props {
const AddressesLabelSearchTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm">
<Table>
<Thead top={ top }>
<Tr>
<Th width="70%">Address</Th>
......
......@@ -21,7 +21,7 @@ interface Props {
const ApiKeyTable = ({ data, isLoading, onDeleteClick, onEditClick, limit }: Props) => {
return (
<Table variant="simple" minWidth="600px">
<Table minWidth="600px">
<Thead>
<Tr>
<Th>{ `API key token (limit ${ limit } keys)` }</Th>
......
......@@ -16,7 +16,7 @@ const BlockEpochElectionRewards = ({ data, isLoading }: Props) => {
<Box mt={ 8 }>
<Heading as="h4" size="sm" mb={ 3 }>Election rewards</Heading>
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}>
<Table style={{ tableLayout: 'auto' }}>
<Thead>
<Tr>
<Th width="24px"/>
......
......@@ -40,7 +40,7 @@ const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum
return (
<AddressHighlightProvider>
<Table variant="simple" minWidth="1040px" size="md" fontWeight={ 500 }>
<Table minWidth="1040px" fontWeight={ 500 }>
<Thead top={ top }>
<Tr>
<Th width="150px">Block</Th>
......
......@@ -20,7 +20,7 @@ interface Props {
const CustomAbiTable = ({ data, isLoading, onDeleteClick, onEditClick }: Props) => {
return (
<Table variant="simple" minWidth="600px">
<Table minWidth="600px">
<Thead>
<Tr>
<Th>ABI for Smart contract address (0x...)</Th>
......
......@@ -15,7 +15,7 @@ import OptimisticDepositsTableItem from './OptimisticDepositsTableItem';
const OptimisticDepositsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>L1 block No</Th>
......
......@@ -15,7 +15,7 @@ import DepositsTableItem from './DepositsTableItem';
const DepositsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>L1 block No</Th>
......
......@@ -15,7 +15,7 @@ import ZkEvmL2DepositsTableItem from './ZkEvmL2DepositsTableItem';
const ZkEvmL2DepositsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>L1 block</Th>
......
......@@ -15,7 +15,7 @@ type Props = {
const OptimisticL2DisputeGamesTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>Index</Th>
......
......@@ -11,6 +11,7 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AppSecurityReport from './AppSecurityReport';
import FavoriteIcon from './FavoriteIcon';
import MarketplaceAppCardLink from './MarketplaceAppCardLink';
import MarketplaceAppGraphLinks from './MarketplaceAppGraphLinks';
import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
import Rating from './Rating/Rating';
import type { RateFunction } from './Rating/useRatings';
......@@ -28,6 +29,7 @@ interface Props extends MarketplaceAppWithSecurityReport {
isRatingSending: boolean;
isRatingLoading: boolean;
canRate: boolean | undefined;
graphLinks: Array<{text: string; url: string}>;
}
const MarketplaceAppCard = ({
......@@ -54,6 +56,7 @@ const MarketplaceAppCard = ({
isRatingSending,
isRatingLoading,
canRate,
graphLinks,
}: Props) => {
const isMobile = useIsMobile();
const categoriesLabel = categories.join(', ');
......@@ -118,11 +121,7 @@ const MarketplaceAppCard = ({
>
<Skeleton
isLoaded={ !isLoading }
fontSize={{ base: 'sm', md: 'lg' }}
lineHeight={{ base: '20px', md: '28px' }}
paddingRight={{ base: '40px', md: 0 }}
fontWeight="semibold"
fontFamily="heading"
display="inline-block"
>
<MarketplaceAppCardLink
......@@ -131,8 +130,18 @@ const MarketplaceAppCard = ({
external={ external }
title={ title }
onClick={ onAppClick }
fontWeight="semibold"
fontFamily="heading"
fontSize={{ base: 'sm', md: 'lg' }}
lineHeight={{ base: '20px', md: '28px' }}
/>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
<MarketplaceAppGraphLinks
links={ graphLinks }
ml={ 2 }
verticalAlign="middle"
mb={{ base: 0, md: 1 }}
/>
</Skeleton>
<Skeleton
......
import { LinkOverlay } from '@chakra-ui/react';
import { LinkOverlay, chakra } from '@chakra-ui/react';
import NextLink from 'next/link';
import React from 'react';
import type { MouseEvent } from 'react';
......@@ -9,24 +9,25 @@ type Props = {
external?: boolean;
title: string;
onClick?: (event: MouseEvent, id: string) => void;
className?: string;
}
const MarketplaceAppCardLink = ({ url, external, id, title, onClick }: Props) => {
const MarketplaceAppCardLink = ({ url, external, id, title, onClick, className }: Props) => {
const handleClick = React.useCallback((event: MouseEvent) => {
onClick?.(event, id);
}, [ onClick, id ]);
return external ? (
<LinkOverlay href={ url } isExternal={ true } marginRight={ 2 }>
<LinkOverlay href={ url } isExternal={ true } marginRight={ 2 } className={ className }>
{ title }
</LinkOverlay>
) : (
<NextLink href={{ pathname: '/apps/[id]', query: { id } }} passHref legacyBehavior>
<LinkOverlay onClick={ handleClick } marginRight={ 2 }>
<LinkOverlay onClick={ handleClick } marginRight={ 2 } className={ className }>
{ title }
</LinkOverlay>
</NextLink>
);
};
export default MarketplaceAppCardLink;
export default chakra(MarketplaceAppCardLink);
import {
Text,
PopoverTrigger,
PopoverBody,
PopoverContent,
chakra,
Box,
VStack,
} from '@chakra-ui/react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal';
interface Props {
className?: string;
links?: Array<{ title: string; url: string }>;
}
const MarketplaceAppGraphLinks = ({ className, links }: Props) => {
const isMobile = useIsMobile();
const handleButtonClick = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
}, []);
if (!links || links.length === 0) {
return null;
}
return (
<Box position="relative" className={ className } display="inline-flex" alignItems="center" height={ 7 } onClick={ handleButtonClick }>
<Popover
placement={ isMobile ? 'bottom-end' : 'bottom' }
isLazy
trigger="hover"
>
<PopoverTrigger>
<IconSvg name="brands/graph" boxSize={ 5 } onClick={ handleButtonClick }/>
</PopoverTrigger>
<PopoverContent w="260px">
<PopoverBody fontSize="sm">
<VStack gap={ 4 } align="start">
<Text>{ `This dapp uses ${ links.length > 1 ? 'several subgraphs' : 'a subgraph' } powered by The Graph` }</Text>
{ links.map(link => (
<LinkExternal key={ link.url } href={ link.url }>{ link.title }</LinkExternal>
)) }
</VStack>
</PopoverBody>
</PopoverContent>
</Popover>
</Box>
);
};
export default React.memo(chakra(MarketplaceAppGraphLinks));
......@@ -18,6 +18,8 @@ import IconSvg from 'ui/shared/IconSvg';
import AppSecurityReport from './AppSecurityReport';
import FavoriteIcon from './FavoriteIcon';
import MarketplaceAppGraphLinks from './MarketplaceAppGraphLinks';
import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
import MarketplaceAppModalLink from './MarketplaceAppModalLink';
import Rating from './Rating/Rating';
import type { RateFunction } from './Rating/useRatings';
......@@ -36,6 +38,7 @@ type Props = {
isRatingSending: boolean;
isRatingLoading: boolean;
canRate: boolean | undefined;
graphLinks?: Array<{text: string; url: string}>;
}
const MarketplaceAppModal = ({
......@@ -49,6 +52,7 @@ const MarketplaceAppModal = ({
isRatingSending,
isRatingLoading,
canRate,
graphLinks,
}: Props) => {
const {
id,
......@@ -67,6 +71,7 @@ const MarketplaceAppModal = ({
categories,
securityReport,
rating,
internalWallet,
} = data;
const socialLinks = [
......@@ -148,16 +153,19 @@ const MarketplaceAppModal = ({
/>
</Flex>
<Heading
as="h2"
gridColumn={ 2 }
fontSize={{ base: '2xl', md: '32px' }}
fontWeight="medium"
lineHeight={{ md: 10 }}
mb={{ md: 2 }}
>
{ title }
</Heading>
<Flex alignItems="center" mb={{ md: 2 }} gridColumn={ 2 }>
<Heading
as="h2"
fontSize={{ base: '2xl', md: '32px' }}
fontWeight="medium"
lineHeight={{ md: 10 }}
mr={ 2 }
>
{ title }
</Heading>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
<MarketplaceAppGraphLinks links={ graphLinks } ml={ 2 }/>
</Flex>
<Text
variant="secondary"
......
import { Grid, Box } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React, { useCallback } from 'react';
import type { MouseEvent } from 'react';
......@@ -25,11 +26,13 @@ type Props = {
isRatingSending: boolean;
isRatingLoading: boolean;
canRate: boolean | undefined;
graphLinksQuery: UseQueryResult<Record<string, Array<{text: string; url: string}>>, unknown>;
}
const MarketplaceList = ({
apps, showAppInfo, favoriteApps, onFavoriteClick, isLoading, selectedCategoryId,
onAppClick, showContractList, userRatings, rateApp, isRatingSending, isRatingLoading, canRate,
graphLinksQuery,
}: Props) => {
const { cutRef, renderedItemsNum } = useLazyRenderedList(apps, !isLoading, 16);
......@@ -75,6 +78,7 @@ const MarketplaceList = ({
isRatingSending={ isRatingSending }
isRatingLoading={ isRatingLoading }
canRate={ canRate }
graphLinks={ graphLinksQuery.data?.[app.id] }
/>
)) }
</Grid>
......
......@@ -17,7 +17,7 @@ import ArbitrumL2MessagesTableItem from './ArbitrumL2MessagesTableItem';
const ArbitrumL2MessagesTable = ({ items, direction, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
{ direction === 'to-rollup' && <Th>L1 block</Th> }
......
......@@ -16,7 +16,7 @@ type Props = {
const MudWorldsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}>
<Table style={{ tableLayout: 'auto' }}>
<Thead top={ top }>
<Tr>
<Th>Address</Th>
......
......@@ -22,7 +22,7 @@ const NameDomainHistoryTable = ({ history, domain, isLoading, sort, onSortToggle
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return (
<Table variant="simple" size="sm">
<Table>
<Thead top={ 0 }>
<Tr>
<Th width="25%">Txn hash</Th>
......
......@@ -21,7 +21,7 @@ const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => {
const sortIconTransform = sort?.toLowerCase().includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return (
<Table variant="simple" size="sm">
<Table>
<Thead top={ ACTION_BAR_HEIGHT_DESKTOP }>
<Tr>
<Th width="25%">Domain</Th>
......
......@@ -15,7 +15,7 @@ type Props = {
const OptimisticL2OutputRootsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" minW="900px">
<Table minW="900px">
<Thead top={ top }>
<Tr>
<Th width="160px">L2 output index</Th>
......
......@@ -7,6 +7,7 @@ import type { TabItem } from 'ui/shared/Tabs/types';
import config from 'configs/app';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useGraphLinks from 'lib/hooks/useGraphLinks';
import useIsMobile from 'lib/hooks/useIsMobile';
import Banner from 'ui/marketplace/Banner';
import ContractListModal from 'ui/marketplace/ContractListModal';
......@@ -80,6 +81,8 @@ const Marketplace = () => {
const isMobile = useIsMobile();
const graphLinksQuery = useGraphLinks();
const categoryTabs = React.useMemo(() => {
const tabs: Array<TabItem> = categories.map(category => ({
id: category.name,
......@@ -236,6 +239,7 @@ const Marketplace = () => {
isRatingSending={ isRatingSending }
isRatingLoading={ isRatingLoading }
canRate={ canRate }
graphLinksQuery={ graphLinksQuery }
/>
{ (selectedApp && isAppInfoModalOpen) && (
......@@ -250,6 +254,7 @@ const Marketplace = () => {
isRatingSending={ isRatingSending }
isRatingLoading={ isRatingLoading }
canRate={ canRate }
graphLinks={ graphLinksQuery.data?.[selectedApp.id] }
/>
) }
......
......@@ -148,7 +148,7 @@ const SearchResultsPageContent = () => {
)) }
</Show>
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="md" fontWeight={ 500 }>
<Table fontWeight={ 500 }>
<Thead top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<Tr>
<Th width="30%">Search result</Th>
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { mixTokens } from 'mocks/tokens/tokenTransfer';
import { test, expect } from 'playwright/lib';
import TokenTransfers from './TokenTransfers';
test('base view +@mobile', async({ render, mockTextAd, mockApiResponse }) => {
await mockTextAd();
await mockApiResponse('token_transfers_all', mixTokens, { queryParams: { type: [] } });
const component = await render(<Box pt={{ base: '106px', lg: 0 }}> <TokenTransfers/> </Box>);
await expect(component).toHaveScreenshot();
});
import { Show, Hide } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { TokenType } from 'types/api/token';
import { getTokenTransfersStub } from 'stubs/token';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PopoverFilter from 'ui/shared/filters/PopoverFilter';
import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import { getTokenFilterValue } from 'ui/tokens/utils';
import TokenTransfersListItem from 'ui/tokenTransfers/TokenTransfersListItem';
import TokenTransfersTable from 'ui/tokenTransfers/TokenTransfersTable';
const TokenTransfers = () => {
const router = useRouter();
const [ typeFilter, setTypeFilter ] = React.useState<Array<TokenType>>(getTokenFilterValue(router.query.type) || []);
const tokenTransfersQuery = useQueryWithPages({
resourceName: 'token_transfers_all',
filters: { type: typeFilter },
options: {
placeholderData: getTokenTransfersStub(),
},
});
const handleTokenTypesChange = React.useCallback((value: Array<TokenType>) => {
tokenTransfersQuery.onFilterChange({ type: value });
setTypeFilter(value);
}, [ tokenTransfersQuery ]);
const content = (
<>
<Show below="lg" ssr={ false }>
{ tokenTransfersQuery.data?.items.map((item, index) => (
<TokenTransfersListItem
key={ item.block_number + item.log_index + (tokenTransfersQuery.isPlaceholderData ? index : '') }
isLoading={ tokenTransfersQuery.isPlaceholderData }
item={ item }
/>
)) }
</Show>
<Hide below="lg" ssr={ false }>
<TokenTransfersTable
items={ tokenTransfersQuery.data?.items }
top={ tokenTransfersQuery.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
isLoading={ tokenTransfersQuery.isPlaceholderData }
/>
</Hide>
</>
);
const filter = (
<PopoverFilter contentProps={{ w: '200px' }} appliedFiltersNum={ typeFilter.length }>
<TokenTypeFilter<TokenType> onChange={ handleTokenTypesChange } defaultValue={ typeFilter } nftOnly={ false }/>
</PopoverFilter>
);
const actionBar = (
<ActionBar mt={ -6 }>
{ filter }
<Pagination { ...tokenTransfersQuery.pagination }/>
</ActionBar>
);
return (
<>
<PageTitle
title="Token Transfers"
withTextAd
/>
<DataListDisplay
isError={ tokenTransfersQuery.isError }
items={ tokenTransfersQuery.data?.items }
emptyText="There are no token transfers."
content={ content }
actionBar={ actionBar }
/>
</>
);
};
export default TokenTransfers;
......@@ -22,7 +22,7 @@ interface Props {
const AddressTagTable = ({ data, onDeleteClick, onEditClick, isLoading, top }: Props) => {
return (
<Table variant="simple" minWidth="600px">
<Table minWidth="600px">
<TheadSticky top={ top }>
<Tr>
<Th width="60%">Address</Th>
......
......@@ -22,7 +22,7 @@ interface Props {
const AddressTagTable = ({ data, isLoading, onDeleteClick, onEditClick, top }: Props) => {
return (
<Table variant="simple" minWidth="600px">
<Table minWidth="600px">
<TheadSticky top={ top }>
<Tr>
<Th width="75%">Transaction</Th>
......
......@@ -88,7 +88,7 @@ export const Desktop = ({ ...props }: Props) => {
my={ props.isLoading ? '6px' : 0 }
{ ...props }
>
{ ({ content }) => <Tr><Td colSpan={ 100 } p={ 0 }>{ content }</Td></Tr> }
{ ({ content }) => <Tr><Td colSpan={ 100 } p={ 0 } _first={{ p: 0 }} _last={{ p: 0 }}>{ content }</Td></Tr> }
</SocketNewItemsNotice>
);
};
......
......@@ -34,7 +34,7 @@ const TokenTransferTable = ({
return (
<AddressHighlightProvider>
<Table variant="simple" size="sm" minW="950px">
<Table minW="950px">
<Thead top={ top }>
<Tr>
{ showTxInfo && <Th width="44px"></Th> }
......
......@@ -99,7 +99,7 @@ test.describe('with tooltips', () => {
await component.locator('header').hover();
await page.locator('div[aria-label="Expand/Collapse menu"]').click();
await page.locator('a[aria-label="Tokens link"]').hover();
await page.locator('a[aria-label="DApps link"]').hover();
await expect(component).toHaveScreenshot();
});
......
......@@ -15,7 +15,7 @@ interface Props {
const TokenHoldersTable = ({ data, token, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" layout="auto">
<Table layout="auto">
<Thead top={ top }>
<Tr>
<Th>Holder</Th>
......
......@@ -27,7 +27,7 @@ const TokenTransferTable = ({ data, top, showSocketInfo, socketInfoAlert, socket
return (
<AddressHighlightProvider>
<Table variant="simple" size="sm" minW="950px">
<Table minW="950px">
<Thead top={ top }>
<Tr>
<Th width="280px">Txn hash</Th>
......
import { Flex, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import getCurrencyValue from 'lib/getCurrencyValue';
import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes';
import Tag from 'ui/shared/chakra/Tag';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import NftEntity from 'ui/shared/entities/nft/NftEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
type Props = {
item: TokenTransfer;
isLoading: boolean;
}
const TokenTransfersListItem = ({ item, isLoading }: Props) => {
const { valueStr } = 'value' in item.total && item.total.value !== null ? getCurrencyValue({
value: item.total.value,
exchangeRate: item.token.exchange_rate,
accuracy: 8,
accuracyUsd: 2,
decimals: item.total.decimals || '0',
}) : { valueStr: null };
return (
<ListItemMobileGrid.Container>
<ListItemMobileGrid.Label isLoading={ isLoading }>Txn hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TxEntity hash={ item.tx_hash } isLoading={ isLoading } truncation="constant_long" noIcon/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TimeAgoWithTooltip
timestamp={ item.timestamp }
enableIncrement
isLoading={ isLoading }
/>
</ListItemMobileGrid.Value>
{ item.method && (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Method</ListItemMobileGrid.Label><ListItemMobileGrid.Value>
<Tag isLoading={ isLoading }>{ item.method }</Tag>
</ListItemMobileGrid.Value>
</>
) }
<ListItemMobileGrid.Label isLoading={ isLoading }>Block</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<BlockEntity number={ item.block_number } isLoading={ isLoading } noIcon/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>From</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntity address={ item.from } isLoading={ isLoading } truncation="constant"/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>To</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntity address={ item.to } isLoading={ isLoading } truncation="constant"/>
</ListItemMobileGrid.Value>
{ 'token_id' in item.total && (NFT_TOKEN_TYPE_IDS.includes(item.token.type)) && item.total.token_id !== null && (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Token ID</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value overflow="hidden">
<NftEntity
hash={ item.token.address }
id={ item.total.token_id }
isLoading={ isLoading }
noIcon
/>
</ListItemMobileGrid.Value>
</>
) }
{ valueStr && (item.token.type === 'ERC-20' || item.token.type === 'ERC-1155') && (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Amount</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Flex gap={ 2 } overflow="hidden">
<Skeleton isLoaded={ !isLoading } wordBreak="break-all">
{ valueStr }
</Skeleton>
<TokenEntity
token={ item.token }
isLoading={ isLoading }
onlySymbol
noCopy
width="auto"
minW="auto"
maxW="100px"
/>
</Flex>
</ListItemMobileGrid.Value>
</>
) }
</ListItemMobileGrid.Container>
);
};
export default TokenTransfersListItem;
import { Table, Tbody, Tr, Th } from '@chakra-ui/react';
import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import { default as Thead } from 'ui/shared/TheadSticky';
import TokenTransferTableItem from 'ui/tokenTransfers/TokenTransfersTableItem';
interface Props {
items?: Array<TokenTransfer>;
top: number;
isLoading?: boolean;
}
const TokenTransferTable = ({ items, top, isLoading }: Props) => {
return (
<AddressHighlightProvider>
<Table variant="simple" size="sm" minW="950px" style={{ tableLayout: 'auto' }}>
<Thead top={ top }>
<Tr>
<Th>Txn hash</Th>
<Th>Method</Th>
<Th>Block</Th>
<Th>From/To</Th>
<Th>Token ID</Th>
<Th isNumeric>Amount</Th>
</Tr>
</Thead>
<Tbody>
{ items?.map((item, index) => (
<TokenTransferTableItem
key={ item.block_number + item.log_index + (isLoading ? index : '') }
item={ item }
isLoading={ isLoading }
/>
)) }
</Tbody>
</Table>
</AddressHighlightProvider>
);
};
export default React.memo(TokenTransferTable);
import { Tr, Td, Flex, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import getCurrencyValue from 'lib/getCurrencyValue';
import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import NftEntity from 'ui/shared/entities/nft/NftEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
type Props = {
item: TokenTransfer;
isLoading?: boolean;
}
const TokenTransferTableItem = ({ item, isLoading }: Props) => {
const { valueStr } = 'value' in item.total && item.total.value !== null ? getCurrencyValue({
value: item.total.value,
exchangeRate: item.token.exchange_rate,
accuracy: 8,
accuracyUsd: 2,
decimals: item.total.decimals || '0',
}) : { valueStr: null };
return (
<Tr>
<Td>
<TxEntity
hash={ item.tx_hash }
isLoading={ isLoading }
fontWeight={ 600 }
noIcon
truncation="constant_long"
/>
<TimeAgoWithTooltip
timestamp={ item.timestamp }
enableIncrement
isLoading={ isLoading }
color="text_secondary"
fontWeight="400"
display="inline-block"
/>
</Td>
<Td maxW="120px">
{ item.method && <Tag isLoading={ isLoading }>{ item.method }</Tag> }
</Td>
<Td>
<BlockEntity number={ item.block_number } isLoading={ isLoading } noIcon/>
</Td>
<Td>
<AddressFromTo
maxW={{ lg: '220px', xl: '320px' }}
from={ item.from }
to={ item.to }
isLoading={ isLoading }
mode={{ lg: 'compact', xl: 'long' }}
/>
</Td>
<Td>
{ 'token_id' in item.total && (NFT_TOKEN_TYPE_IDS.includes(item.token.type)) && item.total.token_id !== null ? (
<NftEntity
hash={ item.token.address }
id={ item.total.token_id }
isLoading={ isLoading }
maxW="140px"
/>
) : '-' }
</Td>
<Td isNumeric verticalAlign="top">
{ valueStr ? (
<Flex gap={ 2 } overflow="hidden" justifyContent="flex-end">
<Skeleton isLoaded={ !isLoading } wordBreak="break-all">
{ valueStr }
</Skeleton>
<TokenEntity
token={ item.token }
isLoading={ isLoading }
onlySymbol
noCopy
width="auto"
minW="auto"
maxW="100px"
/>
</Flex>
) : '-'
}
</Td>
</Tr>
);
};
export default React.memo(TokenTransferTableItem);
......@@ -82,7 +82,7 @@ export default function TxAssetFlows(props: FlowViewProps) {
</Hide>
<Show above="lg">
<Table variant="simple" size="sm">
<Table>
<TheadSticky top={ 75 }>
<Tr>
<Th>
......
......@@ -16,7 +16,7 @@ interface Props {
const TxInternalsTable = ({ data, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm">
<Table>
<Thead top={ top }>
<Tr>
<Th width="60%">Blob hash</Th>
......
......@@ -23,7 +23,7 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) =
return (
<AddressHighlightProvider>
<Table variant="simple" size="sm">
<Table>
<Thead top={ top }>
<Tr>
<Th width="28%">Type</Th>
......
......@@ -21,7 +21,7 @@ interface Props {
const TxStateTable = ({ data, isLoading, top }: Props) => {
return (
<AddressHighlightProvider>
<Table variant="simple" minWidth="1000px" size="sm" w="100%">
<Table minWidth="1000px" w="100%">
<Thead top={ top }>
<Tr>
<Th width="140px">Type</Th>
......
......@@ -15,7 +15,7 @@ type Props = {
const ArbitrumL2TxnBatchesTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" minW="1000px" style={{ tableLayout: 'auto' }}>
<Table minW="1000px" style={{ tableLayout: 'auto' }}>
<Thead top={ top }>
<Tr>
<Th>Batch #</Th>
......
......@@ -15,7 +15,7 @@ type Props = {
const OptimisticL2TxnBatchesTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" minW="850px" layout="auto">
<Table minW="850px" layout="auto">
<Thead top={ top }>
<Tr>
<Th>Batch ID</Th>
......
......@@ -15,7 +15,7 @@ type Props = {
const TxnBatchesTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" minW="1000px">
<Table minW="1000px">
<Thead top={ top }>
<Tr>
<Th width="33%">Batch #</Th>
......
......@@ -15,7 +15,7 @@ type Props = {
const ZkSyncTxnBatchesTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" minW="1000px">
<Table minW="1000px">
<Thead top={ top }>
<Tr>
<Th width="40%">Batch #</Th>
......
......@@ -49,7 +49,7 @@ const TxsTable = ({
return (
<AddressHighlightProvider>
<Table variant="simple" minWidth="950px" size="xs">
<Table minWidth="950px">
<TheadSticky top={ top }>
<Tr>
<Th width="54px"></Th>
......
......@@ -112,7 +112,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
</Td>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<Td isNumeric>
<Td isNumeric maxW="220px">
<TxFee
tx={ tx }
accuracy={ 8 }
......
......@@ -18,7 +18,7 @@ import UserOpsTableItem from './UserOpsTableItem';
const UserOpsTable = ({ items, isLoading, top, showTx, showSender }: Props) => {
return (
<Table variant="simple" size="sm" minW="1000px">
<Table minW="1000px">
<Thead top={ top }>
<Tr>
<Th w="60%">User op hash</Th>
......
......@@ -33,7 +33,7 @@ const ValidatorsTable = ({ data, sort, setSorting, isLoading, top }: Props) => {
}, [ sort, setSorting ]);
return (
<Table variant="simple" size="sm">
<Table>
<Thead top={ top }>
<Tr>
<Th>
......
......@@ -32,7 +32,7 @@ const ValidatorsTable = ({ data, sort, setSorting, isLoading }: Props) => {
}, [ sort, setSorting ]);
return (
<Table variant="simple" size="sm">
<Table>
<Thead top={ ACTION_BAR_HEIGHT_DESKTOP }>
<Tr>
<Th width="50%">Validator’s address</Th>
......
......@@ -15,7 +15,7 @@ interface Props {
const VerifiedAddressesTable = ({ data, applications, onItemEdit, onItemAdd, isLoading }: Props) => {
return (
<Table variant="simple">
<Table>
<Thead>
<Tr>
<Th>Address</Th>
......
......@@ -29,7 +29,7 @@ const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) =>
}, [ sort, setSorting ]);
return (
<Table variant="simple" size="sm" minW="915px">
<Table minW="915px">
<Thead top={ ACTION_BAR_HEIGHT_DESKTOP }>
<Tr>
<Th width="50%">Contract</Th>
......
......@@ -23,7 +23,7 @@ interface Props {
const WatchlistTable = ({ data, isLoading, onDeleteClick, onEditClick, top, hasEmail }: Props) => {
return (
<Table variant="simple" minWidth="600px">
<Table minWidth="600px">
<TheadSticky top={ top }>
<Tr>
<Th width="70%">Address</Th>
......
......@@ -35,7 +35,7 @@ const BeaconChainWithdrawalsTable = ({ items, isLoading, top, view }: Props) =>
}
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th minW="140px">Index</Th>
......
......@@ -15,7 +15,7 @@ import OptimisticL2WithdrawalsTableItem from './OptimisticL2WithdrawalsTableItem
const OptimisticL2WithdrawalsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>Msg nonce</Th>
......
......@@ -15,7 +15,7 @@ import WithdrawalsTableItem from './WithdrawalsTableItem';
const WithdrawalsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>L2 block No</Th>
......
......@@ -15,7 +15,7 @@ import ZkEvmL2WithdrawalsTableItem from './ZkEvmL2WithdrawalsTableItem';
const ZkEvmL2DepositsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>Block</Th>
......
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