Commit 6c83a0b0 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into page-layout

parents 2352a973 150c98f4
const RESTRICTED_MODULES = { const RESTRICTED_MODULES = {
paths: [ paths: [
{ name: 'dayjs', message: 'Please use lib/date/dayjs.ts' }, { name: 'dayjs', message: 'Please use lib/date/dayjs.ts instead of directly importing dayjs' },
{ name: '@chakra-ui/icons', message: 'Using @chakra-ui/icons is prohibited. Please use regular svg-icon instead (see examples in "icons/" folder)' },
], ],
}; };
module.exports = { module.exports = {
......
/* eslint-disable max-len */ /* eslint-disable max-len */
export const tx = { export const tx = {
hash: '0x1ea365d2144796f793883534aa51bf20d23292b19478994eede23dfc599e7c34', hash: '0x1ea365d2144796f793883534aa51bf20d23292b19478994eede23dfc599e7c34',
status: 'success', status: 'success' as TxStatus,
block_num: 15006918, block_num: 15006918,
confirmation_num: 283, confirmation_num: 283,
confirmation_duration: 30, confirmation_duration: 30,
timestamp: 1662623567695, timestamp: 1662623567695,
address_from: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830', address_from: {
address_to: '0x35317007D203b8a86CA727ad44E473E40450E378', hash: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830',
type: 'Address',
alias: '',
},
address_to: {
hash: '0x35317007D203b8a86CA727ad44E473E40450E378',
type: 'Contract',
alias: '',
},
amount: { amount: {
value: 0.03, value: 0.03,
value_usd: 35.5, value_usd: 35.5,
...@@ -39,4 +47,9 @@ export const tx = { ...@@ -39,4 +47,9 @@ export const tx = {
{ from: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', to: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e', token: 'USDT', amount: 192.7, usd: 194.05 }, { from: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', to: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e', token: 'USDT', amount: 192.7, usd: 194.05 },
{ from: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', to: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', token: 'TOKE', amount: 76.1851851851846, usd: 194.05 }, { from: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', to: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', token: 'TOKE', amount: 76.1851851851846, usd: 194.05 },
], ],
txType: 'transaction' as TxType,
}; };
export type TxType = 'contract-call' | 'transaction' | 'token-transfer' | 'internal-tx' | 'multicall';
export type TxStatus = 'success' | 'failed' | 'pending';
...@@ -5,26 +5,26 @@ export const data = [ ...@@ -5,26 +5,26 @@ export const data = [
id: 1, id: 1,
type: 'call' as TxInternalsType, type: 'call' as TxInternalsType,
status: 'success' as const, status: 'success' as const,
from: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', from: { hash: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01' },
to: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e', to: { hash: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e' },
value: 0.25207646303, value: 0.25207646303,
gasLimit: 369472, gasLimit: 369472,
}, },
{ {
id: 2, id: 2,
type: 'delegate_call' as TxInternalsType, type: 'delegate_call' as TxInternalsType,
status: 'error' as const, status: 'success' as const,
from: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', from: { hash: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' },
to: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', to: { hash: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01' },
value: 0.5633333, value: 0.5633333,
gasLimit: 340022, gasLimit: 340022,
}, },
{ {
id: 3, id: 3,
type: 'static_call' as TxInternalsType, type: 'static_call' as TxInternalsType,
status: 'success' as const, status: 'failed' as const,
from: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830', from: { hash: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830' },
to: '0x35317007D203b8a86CA727ad44E473E40450E378', to: { hash: '0x35317007D203b8a86CA727ad44E473E40450E378' },
value: 0.421152366, value: 0.421152366,
gasLimit: 509333, gasLimit: 509333,
}, },
......
import { tx } from './tx';
import type { TxType, TxStatus } from './tx';
export const txs = [
{
...tx,
method: 'Withdraw',
txType: 'transaction' as TxType,
errorText: '',
},
{
...tx,
status: 'failed' as TxStatus,
errorText: 'Error: (Awaiting internal transactions for reason)',
txType: 'contract-call' as TxType,
method: 'CommitHash CommitHash CommitHash CommitHash',
amount: {
value: 0.04,
value_usd: 35.5,
},
fee: {
value: 0.002295904453623692,
value_usd: 2.84,
},
},
{
...tx,
status: 'pending' as TxStatus,
txType: 'token-transfer' as TxType,
method: 'Multicall',
address_from: {
hash: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830',
alias: 'tkdkdkdkdkdkdkdkdkdkdkdkdkdkd.eth',
type: 'ENS name',
},
amount: {
value: 0.02,
value_usd: 35.5,
},
fee: {
value: 0.002495904453623692,
value_usd: 2.84,
},
errorText: '',
},
];
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.535 11.293a1 1 0 0 0 0 1.414l3.536 3.536a1 1 0 1 1-1.414 1.414l-4.95-4.95a1 1 0 0 1 0-1.414l4.95-4.95a1 1 0 1 1 1.414 1.414l-3.536 3.536Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.774 13.088a.936.936 0 0 0 0 1.325l2.814 2.812a.935.935 0 0 0 1.324 0l2.813-2.812A.935.935 0 1 0 8.4 13.088l-1.213 1.21v-9.95a.954.954 0 0 0-.938-.937.954.954 0 0 0-.937.938v9.95L4.1 13.088a.937.937 0 0 0-1.327 0Zm10.039 2.538a.937.937 0 1 0 1.875 0V5.702l1.213 1.21a.935.935 0 1 0 1.324-1.324l-2.812-2.813a.936.936 0 0 0-1.325 0l-2.812 2.813A.935.935 0 1 0 11.6 6.912l1.213-1.21v9.924Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#link_svg__a)">
<path d="M5.547 6.073c1.303-1.303 3.469-1.303 4.772 0 1.171 1.172 1.345 3.04.382 4.387l-.026.038a.75.75 0 0 1-1.221-.872l.026-.038a1.89 1.89 0 0 0-2.874-2.435l-2.63 2.632c-.738.717-.738 1.934 0 2.672a1.887 1.887 0 0 0 2.433.202l.038-.047a.772.772 0 0 1 1.045.194.75.75 0 0 1-.173 1.048l-.038.026a3.389 3.389 0 0 1-4.366-5.154l2.632-2.653Zm6.914 5.833A3.388 3.388 0 0 1 7.307 7.54l.026-.038c.22-.335.689-.415 1.045-.173.338.22.417.689.176 1.045l-.026.038c-.537.731-.452 1.781.202 2.435a1.893 1.893 0 0 0 2.671 0l2.63-2.632c.738-.738.738-1.955 0-2.672a1.887 1.887 0 0 0-2.433-.202l-.037.026c-.338.242-.806.143-1.046-.174a.749.749 0 0 1 .174-1.046l.037-.026a3.389 3.389 0 0 1 4.367 5.153l-2.632 2.632Z" fill="currentColor"/>
</g>
<defs>
<clipPath id="link_svg__a">
<path fill="#fff" transform="translate(1.504 3)" d="M0 0h15v12H0z"/>
</clipPath>
</defs>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m15.026 13.848 2.98 2.978a.834.834 0 1 1-1.18 1.18l-2.978-2.98a7.467 7.467 0 0 1-4.681 1.64c-4.14 0-7.5-3.36-7.5-7.5 0-4.14 3.36-7.5 7.5-7.5 4.14 0 7.5 3.36 7.5 7.5a7.466 7.466 0 0 1-1.641 4.681Zm-1.672-.619A5.814 5.814 0 0 0 15 9.167a5.832 5.832 0 0 0-5.833-5.834 5.831 5.831 0 0 0-5.834 5.834A5.832 5.832 0 0 0 9.167 15a5.814 5.814 0 0 0 4.062-1.646l.125-.125Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.76 17.333a.603.603 0 0 1-.294-.075L9 14.798l-4.467 2.46a.606.606 0 0 1-.663-.051.657.657 0 0 1-.213-.285.69.69 0 0 1-.038-.36l.854-5.21-3.616-3.69a.69.69 0 0 1-.16-.677.663.663 0 0 1 .194-.301c.09-.08.2-.131.316-.149l4.994-.76 2.234-4.74a.65.65 0 0 1 .232-.269.61.61 0 0 1 .666 0c.1.065.18.158.233.269l2.233 4.74 4.994.76c.117.018.226.07.316.149a.66.66 0 0 1 .193.3.69.69 0 0 1-.16.678l-3.614 3.69.853 5.21a.692.692 0 0 1-.14.537.634.634 0 0 1-.216.173.605.605 0 0 1-.265.061Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.76 17.333a.604.604 0 0 1-.294-.075l.293.075Zm.004 0a.625.625 0 0 0 .477-.234.671.671 0 0 0 .14-.538l-.853-5.21 3.615-3.689a.69.69 0 0 0 .16-.677.663.663 0 0 0-.194-.301.617.617 0 0 0-.316-.149l-4.884-.743a.208.208 0 0 1-.157-.117l-2.186-4.64a.65.65 0 0 0-.233-.269.61.61 0 0 0-.666 0 .65.65 0 0 0-.232.269l-2.186 4.64a.208.208 0 0 1-.158.117l-4.884.743a.618.618 0 0 0-.316.149.663.663 0 0 0-.193.3.69.69 0 0 0 .16.678l3.54 3.614a.208.208 0 0 1 .058.18l-.837 5.105a.69.69 0 0 0 .038.36.657.657 0 0 0 .213.286.613.613 0 0 0 .663.05L8.9 14.854a.208.208 0 0 1 .2 0l4.366 2.405m-7.795-2.915c-.028.172.154.3.307.216L8.9 12.95a.208.208 0 0 1 .2 0l2.923 1.61a.208.208 0 0 0 .306-.216l-.566-3.452a.208.208 0 0 1 .057-.18l2.486-2.536a.208.208 0 0 0-.118-.351l-3.408-.519a.208.208 0 0 1-.157-.117L9.189 4.145a.208.208 0 0 0-.377 0L7.378 7.19a.208.208 0 0 1-.158.117l-3.408.519a.208.208 0 0 0-.117.351l2.485 2.537a.208.208 0 0 1 .057.18l-.566 3.45Zm8.093 2.99h-.003.003Z" fill="#4A5568"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M13.76 17.333a.604.604 0 0 1-.294-.075l.293.075Zm.004 0a.625.625 0 0 0 .477-.234.671.671 0 0 0 .14-.538l-.853-5.21 3.615-3.689a.69.69 0 0 0 .16-.677.663.663 0 0 0-.194-.301.617.617 0 0 0-.316-.149l-4.884-.743a.208.208 0 0 1-.157-.117l-2.186-4.64a.65.65 0 0 0-.233-.269.61.61 0 0 0-.666 0 .65.65 0 0 0-.232.269l-2.186 4.64a.208.208 0 0 1-.158.117l-4.884.743a.618.618 0 0 0-.316.149.663.663 0 0 0-.193.3.69.69 0 0 0 .16.678l3.54 3.614a.208.208 0 0 1 .058.18l-.837 5.105a.69.69 0 0 0 .038.36.657.657 0 0 0 .213.286.613.613 0 0 0 .663.05L8.9 14.854a.208.208 0 0 1 .2 0l4.366 2.405m-7.795-2.915c-.028.172.154.3.307.216L8.9 12.95a.208.208 0 0 1 .2 0l2.923 1.61a.208.208 0 0 0 .306-.216l-.566-3.452a.208.208 0 0 1 .057-.18l2.486-2.536a.208.208 0 0 0-.118-.351l-3.408-.519a.208.208 0 0 1-.157-.117L9.189 4.145a.208.208 0 0 0-.377 0L7.378 7.19a.208.208 0 0 1-.158.117l-3.408.519a.208.208 0 0 0-.117.351l2.485 2.537a.208.208 0 0 1 .057.18l-.566 3.45Zm8.093 2.99h-.003.003Z"/>
</svg> </svg>
<svg viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="5" cy="5" r="5" fill="#D9DBE0"/>
<circle cx="5" cy="5" r="2.5" fill="#707886"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m4.7 14.167-.592.591a.833.833 0 0 0 0 1.175.833.833 0 0 0 1.175 0l.592-.591A.833.833 0 0 0 4.7 14.167ZM4.166 10a.833.833 0 0 0-.833-.833H2.5a.833.833 0 1 0 0 1.666h.833A.833.833 0 0 0 4.166 10ZM10 4.167a.833.833 0 0 0 .833-.834V2.5a.833.833 0 1 0-1.667 0v.833a.833.833 0 0 0 .834.834ZM4.7 5.875a.833.833 0 0 0 1.175 0 .833.833 0 0 0 0-1.175l-.592-.592a.833.833 0 0 0-1.175 1.175l.592.592Zm10 .242a.833.833 0 0 0 .583-.242l.592-.592A.832.832 0 1 0 14.7 4.108l-.534.592a.833.833 0 0 0 0 1.175.833.833 0 0 0 .55.242H14.7Zm2.8 3.05h-.834a.833.833 0 0 0 0 1.666h.834a.833.833 0 1 0 0-1.666ZM10 15.833a.833.833 0 0 0-.834.834v.833a.833.833 0 1 0 1.667 0v-.833a.833.833 0 0 0-.833-.834Zm5.3-1.666a.833.833 0 0 0-1.134 1.133l.592.592a.833.833 0 0 0 1.175 0 .833.833 0 0 0 0-1.175l-.633-.55ZM10 5.417A4.583 4.583 0 1 0 14.583 10 4.592 4.592 0 0 0 10 5.417Zm0 7.5a2.917 2.917 0 1 1 0-5.833 2.917 2.917 0 0 1 0 5.833Z" fill="currentColor"/>
</svg>
...@@ -4,7 +4,7 @@ import abiIcon from 'icons/ABI.svg'; ...@@ -4,7 +4,7 @@ import abiIcon from 'icons/ABI.svg';
import apiKeysIcon from 'icons/API.svg'; import apiKeysIcon from 'icons/API.svg';
import appsIcon from 'icons/apps.svg'; import appsIcon from 'icons/apps.svg';
import blocksIcon from 'icons/block.svg'; import blocksIcon from 'icons/block.svg';
import gearIcon from 'icons/gear.svg'; // import gearIcon from 'icons/gear.svg';
import privateTagIcon from 'icons/privattags.svg'; import privateTagIcon from 'icons/privattags.svg';
import profileIcon from 'icons/profile.svg'; import profileIcon from 'icons/profile.svg';
import publicTagIcon from 'icons/publictags.svg'; import publicTagIcon from 'icons/publictags.svg';
...@@ -21,10 +21,13 @@ export default function useNavItems() { ...@@ -21,10 +21,13 @@ export default function useNavItems() {
return React.useMemo(() => { return React.useMemo(() => {
const mainNavItems = [ const mainNavItems = [
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute === 'blocks' }, { text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute === 'blocks' },
{ text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx') }, { text: 'Transactions', url: link('txs_validated'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx') },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens' }, { text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens' },
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps' }, { text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps' },
{ text: 'Other', url: link('other'), icon: gearIcon, isActive: currentRoute === 'other' }, // there should be custom site sections like Stats, Faucet, More, etc but never an 'other'
// examples https://explorer-edgenet.polygon.technology/ and https://explorer.celo.org/
// at this stage custom menu items is under development, we will implement it later
// { text: 'Other', url: link('other'), icon: gearIcon, isActive: currentRoute === 'other' },
]; ];
const accountNavItems = [ const accountNavItems = [
......
...@@ -45,10 +45,14 @@ export const ROUTES = { ...@@ -45,10 +45,14 @@ export const ROUTES = {
}, },
// TRANSACTIONS // TRANSACTIONS
txs: { txs_validated: {
pattern: `${ BASE_PATH }/txs`, pattern: `${ BASE_PATH }/txs`,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
txs_pending: {
pattern: `${ BASE_PATH }/pending-transactions`,
crossNetworkNavigation: true,
},
tx_index: { tx_index: {
pattern: `${ BASE_PATH }/tx/[id]`, pattern: `${ BASE_PATH }/tx/[id]`,
}, },
...@@ -70,6 +74,9 @@ export const ROUTES = { ...@@ -70,6 +74,9 @@ export const ROUTES = {
pattern: `${ BASE_PATH }/blocks`, pattern: `${ BASE_PATH }/blocks`,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
block: {
pattern: `${ BASE_PATH }/block/[id]`,
},
// TOKENS // TOKENS
tokens: { tokens: {
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
"format-svg": "./node_modules/.bin/svgo -r ./icons" "format-svg": "./node_modules/.bin/svgo -r ./icons"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/icons": "^2.0.2",
"@chakra-ui/react": "2.3.1", "@chakra-ui/react": "2.3.1",
"@chakra-ui/theme-tools": "^2.0.2", "@chakra-ui/theme-tools": "^2.0.2",
"@emotion/react": "^11", "@emotion/react": "^11",
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Transactions from 'ui/pages/Transactions';
type PageParams = {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const AddressTagsPage: NextPage<Props> = ({ pageParams }: Props) => {
const title = getNetworkTitle(pageParams);
return (
<>
<Head><title>{ title }</title></Head>
<Transactions tab="txs_pending"/>
</>
);
};
export default AddressTagsPage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Transactions from 'ui/pages/Transactions';
type PageParams = {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const AddressTagsPage: NextPage<Props> = ({ pageParams }: Props) => {
const title = getNetworkTitle(pageParams);
return (
<>
<Head><title>{ title }</title></Head>
<Transactions tab="txs_validated"/>
</>
);
};
export default AddressTagsPage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -47,7 +47,7 @@ const variantOutline = defineStyle((props) => { ...@@ -47,7 +47,7 @@ const variantOutline = defineStyle((props) => {
const isGrayTheme = c === 'gray' || c === 'gray-dark'; const isGrayTheme = c === 'gray' || c === 'gray-dark';
const color = isGrayTheme ? mode('blackAlpha.800', 'whiteAlpha.800')(props) : mode(`${ c }.600`, `${ c }.300`)(props); const color = isGrayTheme ? mode('blackAlpha.800', 'whiteAlpha.800')(props) : mode(`${ c }.600`, `${ c }.300`)(props);
const borderColor = isGrayTheme ? mode('gray.200', 'gray.600')(props) : mode(`${ c }.600`, `${ c }.300`)(props); const borderColor = isGrayTheme ? mode('gray.300', 'gray.600')(props) : mode(`${ c }.600`, `${ c }.300`)(props);
const activeBg = isGrayTheme ? mode('blue.50', 'gray.600')(props) : mode(`${ c }.50`, 'gray.600')(props); const activeBg = isGrayTheme ? mode('blue.50', 'gray.600')(props) : mode(`${ c }.50`, 'gray.600')(props);
const activeColor = (() => { const activeColor = (() => {
if (c === 'gray') { if (c === 'gray') {
...@@ -106,9 +106,8 @@ const variantSimple = defineStyle((props) => { ...@@ -106,9 +106,8 @@ const variantSimple = defineStyle((props) => {
}; };
}); });
const variantSubtle = defineStyle((props) => { const variantGhost = defineStyle((props) => {
const { colorScheme: c } = props; const { colorScheme: c } = props;
const activeBg = mode(`${ c }.50`, 'gray.800')(props); const activeBg = mode(`${ c }.50`, 'gray.800')(props);
return { return {
...@@ -128,10 +127,33 @@ const variantSubtle = defineStyle((props) => { ...@@ -128,10 +127,33 @@ const variantSubtle = defineStyle((props) => {
}; };
}); });
const variantSubtle = defineStyle((props) => {
const { colorScheme: c } = props;
if (c === 'gray') {
return {
bg: mode('blackAlpha.200', 'whiteAlpha.200')(props),
color: mode('blackAlpha.800', 'whiteAlpha.800')(props),
_hover: {
color: 'blue.400',
},
};
}
return {
bg: `${ c }.100`,
color: `${ c }.600`,
_hover: {
color: 'blue.400',
},
};
});
const variants = { const variants = {
solid: variantSolid, solid: variantSolid,
outline: variantOutline, outline: variantOutline,
simple: variantSimple, simple: variantSimple,
ghost: variantGhost,
subtle: variantSubtle, subtle: variantSubtle,
}; };
......
...@@ -53,6 +53,19 @@ const sizes = { ...@@ -53,6 +53,19 @@ const sizes = {
fontWeight: 500, fontWeight: 500,
}, },
}), }),
xs: definePartsStyle({
th: {
px: '6px',
py: '10px',
fontSize: 'sm',
},
td: {
px: '6px',
py: 6,
fontSize: 'sm',
fontWeight: 500,
},
}),
}; };
const variants = { const variants = {
......
...@@ -23,11 +23,8 @@ const sizes = { ...@@ -23,11 +23,8 @@ const sizes = {
minH: 6, minH: 6,
minW: 6, minW: 6,
fontSize: 'sm', fontSize: 'sm',
lineHeight: 'sm',
px: 2, px: 2,
py: '2px', py: '2px',
},
label: {
lineHeight: 5, lineHeight: 5,
}, },
}), }),
......
export type Sort = 'val-desc' | 'val-asc' | 'fee-desc' | 'fee-asc' | undefined;
import { Box, Heading, Icon, Image, Link, LinkBox, LinkOverlay, Text, useColorModeValue } from '@chakra-ui/react'; import { Box, Heading, Icon, IconButton, Image, Link, LinkBox, LinkOverlay, Text, useColorModeValue } from '@chakra-ui/react';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { AppItemPreview } from 'types/client/apps'; import type { AppItemPreview } from 'types/client/apps';
import northEastIcon from 'icons/arrows/north-east.svg'; import northEastIcon from 'icons/arrows/north-east.svg';
import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg';
interface Props extends AppItemPreview { interface Props extends AppItemPreview {
onInfoClick: (id: string) => void; onInfoClick: (id: string) => void;
isFavorite: boolean;
onFavoriteClick: (id: string, isFavorite: boolean) => void;
} }
const AppCard = ({ id, title, logo, shortDescription, categories, onInfoClick }: Props) => { const AppCard = ({ id,
title,
logo,
shortDescription,
categories,
onInfoClick,
isFavorite,
onFavoriteClick,
}: Props) => {
const categoriesLabel = categories.map(c => c.name).join(', '); const categoriesLabel = categories.map(c => c.name).join(', ');
const handleInfoClick = useCallback((event: MouseEvent) => { const handleInfoClick = useCallback((event: MouseEvent) => {
...@@ -18,12 +31,23 @@ const AppCard = ({ id, title, logo, shortDescription, categories, onInfoClick }: ...@@ -18,12 +31,23 @@ const AppCard = ({ id, title, logo, shortDescription, categories, onInfoClick }:
onInfoClick(id); onInfoClick(id);
}, [ onInfoClick, id ]); }, [ onInfoClick, id ]);
const handleFavoriteClick = useCallback(() => {
onFavoriteClick(id, isFavorite);
}, [ onFavoriteClick, id, isFavorite ]);
return ( return (
<LinkBox <LinkBox
_hover={{
boxShadow: 'md',
}}
_focusWithin={{
boxShadow: 'md',
}}
borderRadius="md" borderRadius="md"
height="100%" height="100%"
padding={{ base: 3, sm: '20px' }} padding={{ base: 3, sm: '20px' }}
boxShadow={ `0 0 0 1px ${ useColorModeValue('var(--chakra-colors-gray-200)', 'var(--chakra-colors-gray-600)') }` } border="1px"
borderColor={ useColorModeValue('gray.200', 'gray.600') }
> >
<Box <Box
display={{ base: 'grid', sm: 'block' }} display={{ base: 'grid', sm: 'block' }}
...@@ -54,9 +78,6 @@ const AppCard = ({ id, title, logo, shortDescription, categories, onInfoClick }: ...@@ -54,9 +78,6 @@ const AppCard = ({ id, title, logo, shortDescription, categories, onInfoClick }:
> >
<LinkOverlay <LinkOverlay
href="#" href="#"
_hover={{
textDecoration: 'underline',
}}
> >
{ title } { title }
</LinkOverlay> </LinkOverlay>
...@@ -104,6 +125,23 @@ const AppCard = ({ id, title, logo, shortDescription, categories, onInfoClick }: ...@@ -104,6 +125,23 @@ const AppCard = ({ id, title, logo, shortDescription, categories, onInfoClick }:
/> />
</Link> </Link>
</Box> </Box>
<IconButton
position="absolute"
right={{ base: 3, sm: '20px' }}
top={{ base: 3, sm: '20px' }}
aria-label="Mark as favorite"
title="Mark as favorite"
variant="ghost"
colorScheme="gray"
w={ 9 }
h={ 8 }
onClick={ handleFavoriteClick }
icon={ isFavorite ?
<Icon as={ starFilledIcon } w={ 4 } h={ 4 } color="yellow.400"/> :
<Icon as={ starOutlineIcon } w={ 4 } h={ 4 } color="gray.300"/>
}
/>
</Box> </Box>
</LinkBox> </LinkBox>
); );
......
import { Grid, GridItem, VisuallyHidden, Heading } from '@chakra-ui/react'; import { Grid, GridItem, VisuallyHidden, Heading } from '@chakra-ui/react';
import React from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import type { AppItemPreview } from 'types/client/apps'; import type { AppItemPreview } from 'types/client/apps';
...@@ -7,12 +7,44 @@ import { apos } from 'lib/html-entities'; ...@@ -7,12 +7,44 @@ import { apos } from 'lib/html-entities';
import AppCard from 'ui/apps/AppCard'; import AppCard from 'ui/apps/AppCard';
import EmptySearchResult from 'ui/apps/EmptySearchResult'; import EmptySearchResult from 'ui/apps/EmptySearchResult';
import AppModal from './AppModal';
type Props = { type Props = {
apps: Array<AppItemPreview>; apps: Array<AppItemPreview>;
onAppClick: (id: string) => void; onAppClick: (id: string) => void;
displayedAppId: string | null;
onModalClose: () => void;
}
function getFavoriteApps() {
try {
return JSON.parse(localStorage.getItem('favoriteApps') || '[]');
} catch (e) {
return [];
}
} }
const AppList = ({ apps, onAppClick }: Props) => { const AppList = ({ apps, onAppClick, displayedAppId, onModalClose }: Props) => {
const [ favoriteApps, setFavoriteApps ] = useState<Array<string>>([]);
const handleFavoriteClick = useCallback((id: string, isFavorite: boolean) => {
const favoriteApps = getFavoriteApps();
if (isFavorite) {
const result = favoriteApps.filter((appId: string) => appId !== id);
setFavoriteApps(result);
localStorage.setItem('favoriteApps', JSON.stringify(result));
} else {
favoriteApps.push(id);
localStorage.setItem('favoriteApps', JSON.stringify(favoriteApps));
setFavoriteApps(favoriteApps);
}
}, [ ]);
useEffect(() => {
setFavoriteApps(getFavoriteApps());
}, [ ]);
return ( return (
<> <>
<VisuallyHidden> <VisuallyHidden>
...@@ -39,6 +71,8 @@ const AppList = ({ apps, onAppClick }: Props) => { ...@@ -39,6 +71,8 @@ const AppList = ({ apps, onAppClick }: Props) => {
logo={ app.logo } logo={ app.logo }
shortDescription={ app.shortDescription } shortDescription={ app.shortDescription }
categories={ app.categories } categories={ app.categories }
isFavorite={ favoriteApps.includes(app.id) }
onFavoriteClick={ handleFavoriteClick }
/> />
</GridItem> </GridItem>
)) } )) }
...@@ -46,6 +80,15 @@ const AppList = ({ apps, onAppClick }: Props) => { ...@@ -46,6 +80,15 @@ const AppList = ({ apps, onAppClick }: Props) => {
) : ( ) : (
<EmptySearchResult text={ `Couldn${ apos }t find an app that matches your filter query.` }/> <EmptySearchResult text={ `Couldn${ apos }t find an app that matches your filter query.` }/>
) } ) }
{ displayedAppId && (
<AppModal
id={ displayedAppId }
onClose={ onModalClose }
isFavorite={ favoriteApps.includes(displayedAppId) }
onFavoriteClick={ handleFavoriteClick }
/>
) }
</> </>
); );
}; };
......
import { LinkIcon, StarIcon } from '@chakra-ui/icons';
import { import {
Box, Button, Heading, Icon, IconButton, Image, Link, List, Modal, ModalBody, Box, Button, Heading, Icon, IconButton, Image, Link, List, Modal, ModalBody,
ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Tag, Text, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Tag, Text,
...@@ -9,29 +8,27 @@ import React, { useCallback } from 'react'; ...@@ -9,29 +8,27 @@ import React, { useCallback } from 'react';
import type { AppCategory, AppItemOverview } from 'types/client/apps'; import type { AppCategory, AppItemOverview } from 'types/client/apps';
import { TEMPORARY_DEMO_APPS } from 'data/apps'; import { TEMPORARY_DEMO_APPS } from 'data/apps';
import linkIcon from 'icons/link.svg';
import ghIcon from 'icons/social/git.svg'; import ghIcon from 'icons/social/git.svg';
import tgIcon from 'icons/social/telega.svg'; import tgIcon from 'icons/social/telega.svg';
import twIcon from 'icons/social/tweet.svg'; import twIcon from 'icons/social/tweet.svg';
import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg'; import starOutlineIcon from 'icons/star_outline.svg';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
type Props = { type Props = {
id: string | null; id: string;
onClose: () => void; onClose: () => void;
isFavorite: boolean;
onFavoriteClick: (id: string, isFavorite: boolean) => void;
} }
const AppModal = ({ const AppModal = ({
id, id,
onClose, onClose,
isFavorite,
onFavoriteClick,
}: Props) => { }: Props) => {
const handleFavorite = useCallback(() => {
// TODO: implement
}, []);
if (!id) {
return null;
}
const { const {
title, title,
author, author,
...@@ -45,8 +42,6 @@ const AppModal = ({ ...@@ -45,8 +42,6 @@ const AppModal = ({
categories, categories,
} = TEMPORARY_DEMO_APPS.find(app => app.id === id) as AppItemOverview; } = TEMPORARY_DEMO_APPS.find(app => app.id === id) as AppItemOverview;
const isFavorite = false;
const socialLinks = [ const socialLinks = [
Boolean(telegram) && { Boolean(telegram) && {
icon: tgIcon, icon: tgIcon,
...@@ -62,6 +57,10 @@ const AppModal = ({ ...@@ -62,6 +57,10 @@ const AppModal = ({
}, },
].filter(Boolean) as Array<{ icon: FunctionComponent; url: string }>; ].filter(Boolean) as Array<{ icon: FunctionComponent; url: string }>;
const handleFavoriteClick = useCallback(() => {
onFavoriteClick(id, isFavorite);
}, [ onFavoriteClick, id, isFavorite ]);
return ( return (
<Modal <Modal
isOpen={ Boolean(id) } isOpen={ Boolean(id) }
...@@ -135,10 +134,10 @@ const AppModal = ({ ...@@ -135,10 +134,10 @@ const AppModal = ({
colorScheme="gray" colorScheme="gray"
w={ 9 } w={ 9 }
h={ 8 } h={ 8 }
onClick={ handleFavorite } onClick={ handleFavoriteClick }
icon={ isFavorite ? icon={ isFavorite ?
<Icon as={ StarIcon } w={ 4 } h={ 4 } color="yellow.400"/> : <Icon as={ starFilledIcon } w={ 4 } h={ 4 } color="yellow.400"/> :
<Icon as={ starOutlineIcon } w={ 4 } h={ 4 }/> } <Icon as={ starOutlineIcon } w={ 4 } h={ 4 } color="gray.300"/> }
/> />
</Box> </Box>
</Box> </Box>
...@@ -188,10 +187,10 @@ const AppModal = ({ ...@@ -188,10 +187,10 @@ const AppModal = ({
overflow="hidden" overflow="hidden"
> >
<Icon <Icon
as={ LinkIcon } as={ linkIcon }
display="inline" display="inline"
verticalAlign="baseline" verticalAlign="baseline"
boxSize={ 3 } boxSize="18px"
marginRight={ 2 } marginRight={ 2 }
/> />
......
...@@ -5,7 +5,6 @@ import type { AppItemOverview } from 'types/client/apps'; ...@@ -5,7 +5,6 @@ import type { AppItemOverview } from 'types/client/apps';
import { TEMPORARY_DEMO_APPS } from 'data/apps'; import { TEMPORARY_DEMO_APPS } from 'data/apps';
import AppList from 'ui/apps/AppList'; import AppList from 'ui/apps/AppList';
import AppModal from 'ui/apps/AppModal';
import FilterInput from 'ui/shared/FilterInput'; import FilterInput from 'ui/shared/FilterInput';
const defaultDisplayedApps = [ ...TEMPORARY_DEMO_APPS ] const defaultDisplayedApps = [ ...TEMPORARY_DEMO_APPS ]
...@@ -34,10 +33,11 @@ const Apps = () => { ...@@ -34,10 +33,11 @@ const Apps = () => {
return ( return (
<> <>
<FilterInput onChange={ debounceFilterApps } marginBottom={{ base: '4', lg: '6' }} placeholder="Find app"/> <FilterInput onChange={ debounceFilterApps } marginBottom={{ base: '4', lg: '6' }} placeholder="Find app"/>
<AppList apps={ displayedApps } onAppClick={ showAppInfo }/> <AppList
<AppModal apps={ displayedApps }
id={ displayedAppId } onAppClick={ showAppInfo }
onClose={ clearDisplayedAppId } displayedAppId={ displayedAppId }
onModalClose={ clearDisplayedAppId }
/> />
</> </>
); );
......
import { ArrowBackIcon } from '@chakra-ui/icons'; import { Link, Text, Icon } from '@chakra-ui/react';
import { Link, Text } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { animateScroll } from 'react-scroll'; import { animateScroll } from 'react-scroll';
import type { PublicTag } from 'types/api/account'; import type { PublicTag } from 'types/api/account';
import eastArrowIcon from 'icons/arrows/east.svg';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useToast from 'lib/hooks/useToast'; import useToast from 'lib/hooks/useToast';
import PublicTagsData from 'ui/publicTags/PublicTagsData'; import PublicTagsData from 'ui/publicTags/PublicTagsData';
...@@ -79,7 +79,7 @@ const PublicTagsComponent: React.FC = () => { ...@@ -79,7 +79,7 @@ const PublicTagsComponent: React.FC = () => {
<Page> <Page>
{ isMobile && screen === 'form' && ( { isMobile && screen === 'form' && (
<Link display="inline-flex" alignItems="center" mb={ 6 } onClick={ onGoBack }> <Link display="inline-flex" alignItems="center" mb={ 6 } onClick={ onGoBack }>
<ArrowBackIcon w={ 6 } h={ 6 }/> <Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/>
<Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text> <Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text>
</Link> </Link>
) } ) }
......
import { Flex, Link, Icon } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import eastArrowIcon from 'icons/arrows/east.svg';
import useLink from 'lib/link/useLink';
import ExternalLink from 'ui/shared/ExternalLink';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
...@@ -24,10 +28,25 @@ export interface Props { ...@@ -24,10 +28,25 @@ export interface Props {
} }
const TransactionPageContent = ({ tab }: Props) => { const TransactionPageContent = ({ tab }: Props) => {
const link = useLink();
return ( return (
<Page> <Page>
{ /* TODO should be shown only when navigating from txs list */ }
<Link mb={ 6 } display="inline-flex" href={ link('txs_validated') }>
<Icon as={ eastArrowIcon } boxSize={ 6 } mr={ 2 } transform="rotate(180deg)"/>
Transactions
</Link>
<PageTitle text="Transaction details"/> <PageTitle text="Transaction details"/>
<RoutedTabs tabs={ TABS } defaultActiveTab={ tab }/> <Flex marginLeft="auto" alignItems="center" flexWrap="wrap" columnGap={ 6 } rowGap={ 3 } mb={ 6 }>
<ExternalLink title="Open in Tenderly" href="#"/>
<ExternalLink title="Open in Blockchair" href="#"/>
<ExternalLink title="Open in Etherscan" href="#"/>
</Flex>
<RoutedTabs
tabs={ TABS }
defaultActiveTab={ tab }
/>
</Page> </Page>
); );
}; };
......
import {
Box,
} from '@chakra-ui/react';
import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TxsPending from 'ui/txs/TxsPending';
import TxsValidated from 'ui/txs/TxsValidated';
const TABS: Array<RoutedTab> = [
{ routeName: 'txs_validated', title: 'Validated', component: <TxsValidated/> },
{ routeName: 'txs_pending', title: 'Pending', component: <TxsPending/> },
];
type Props = {
tab: RoutedTab['routeName'];
}
const Transactions = ({ tab }: Props) => {
return (
<Page>
<Box h="100%">
<PageTitle text="Transactions"/>
<RoutedTabs tabs={ TABS } defaultActiveTab={ tab }/>
</Box>
</Page>
);
};
export default Transactions;
import { VStack, useColorModeValue } from '@chakra-ui/react'; import { Flex, useColorModeValue, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
className?: string;
} }
const AccountListItemMobile = ({ children }: Props) => { const AccountListItemMobile = ({ children, className }: Props) => {
return ( return (
<VStack <Flex
gap={ 4 } rowGap={ 6 }
alignItems="flex-start" alignItems="flex-start"
flexDirection="column"
paddingY={ 6 } paddingY={ 6 }
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderTopWidth="1px" borderTopWidth="1px"
_last={{ _last={{
borderBottomWidth: '1px', borderBottomWidth: '1px',
}} }}
className={ className }
> >
{ children } { children }
</VStack> </Flex>
); );
}; };
export default AccountListItemMobile; export default chakra(AccountListItemMobile);
...@@ -19,7 +19,7 @@ const AddressSnippet = ({ address, subtitle }: Props) => { ...@@ -19,7 +19,7 @@ const AddressSnippet = ({ address, subtitle }: Props) => {
<AddressLink hash={ address } fontWeight="600" ml={ 2 }/> <AddressLink hash={ address } fontWeight="600" ml={ 2 }/>
<CopyToClipboard text={ address } ml={ 1 }/> <CopyToClipboard text={ address } ml={ 1 }/>
</Address> </Address>
{ subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 } ml={{ base: 0, lg: 8 }}>{ subtitle }</Text> } { subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 } ml={ 8 }>{ subtitle }</Text> }
</Box> </Box>
); );
}; };
......
...@@ -13,7 +13,7 @@ interface Props extends HTMLChakraProps<'div'> { ...@@ -13,7 +13,7 @@ interface Props extends HTMLChakraProps<'div'> {
const DetailsInfoItem = ({ title, hint, children, ...styles }: Props) => { const DetailsInfoItem = ({ title, hint, children, ...styles }: Props) => {
return ( return (
<> <>
<GridItem py={ 2 } lineHeight={ 5 } { ...styles } whiteSpace="nowrap"> <GridItem py={{ base: 1, lg: 2 }} lineHeight={ 5 } { ...styles } whiteSpace="nowrap" _notFirst={{ mt: { base: 3, lg: 0 } }}>
<Flex columnGap={ 2 } alignItems="center"> <Flex columnGap={ 2 } alignItems="center">
<Tooltip <Tooltip
label={ hint } label={ hint }
...@@ -24,10 +24,20 @@ const DetailsInfoItem = ({ title, hint, children, ...styles }: Props) => { ...@@ -24,10 +24,20 @@ const DetailsInfoItem = ({ title, hint, children, ...styles }: Props) => {
<Icon as={ infoIcon } boxSize={ 5 }/> <Icon as={ infoIcon } boxSize={ 5 }/>
</Box> </Box>
</Tooltip> </Tooltip>
<Text fontWeight={ 500 }>{ title }</Text> <Text fontWeight={{ base: 700, lg: 500 }}>{ title }</Text>
</Flex> </Flex>
</GridItem> </GridItem>
<GridItem display="flex" alignItems="center" py={ 2 } lineHeight={ 5 } whiteSpace="nowrap" { ...styles }> <GridItem
display="flex"
alignItems="center"
flexWrap="wrap"
rowGap={ 3 }
pl={{ base: 7, lg: 0 }}
py={{ base: 1, lg: 2 }}
lineHeight={ 5 }
whiteSpace="nowrap"
{ ...styles }
>
{ children } { children }
</GridItem> </GridItem>
</> </>
......
import { Link, Icon } from '@chakra-ui/react';
import React from 'react';
import arrowIcon from 'icons/arrows/north-east.svg';
interface Props {
href: string;
title: string;
}
const ExternalLink = ({ href, title }: Props) => {
return (
<Link fontSize="sm" display="inline-flex" alignItems="center" target="_blank" href={ href }>
{ title }
<Icon as={ arrowIcon } boxSize={ 4 }/>
</Link>
);
};
export default React.memo(ExternalLink);
...@@ -22,11 +22,13 @@ const FilterButton = ({ isActive, appliedFiltersNum, onClick, isCollapsed }: Pro ...@@ -22,11 +22,13 @@ const FilterButton = ({ isActive, appliedFiltersNum, onClick, isCollapsed }: Pro
leftIcon={ isCollapsed ? undefined : FilterIcon } leftIcon={ isCollapsed ? undefined : FilterIcon }
rightIcon={ appliedFiltersNum ? <Circle bg={ badgeBgColor } size={ 5 } color={ badgeColor }>{ appliedFiltersNum }</Circle> : undefined } rightIcon={ appliedFiltersNum ? <Circle bg={ badgeBgColor } size={ 5 } color={ badgeColor }>{ appliedFiltersNum }</Circle> : undefined }
size="sm" size="sm"
fontWeight="500"
variant="outline" variant="outline"
colorScheme="gray-dark" colorScheme="gray-dark"
onClick={ onClick } onClick={ onClick }
isActive={ isActive } isActive={ isActive }
px={ 1.5 } px={ 1.5 }
flexShrink={ 0 }
> >
{ isCollapsed ? FilterIcon : 'Filter' } { isCollapsed ? FilterIcon : 'Filter' }
</Button> </Button>
......
import { SearchIcon } from '@chakra-ui/icons'; import { Input, InputGroup, InputLeftElement, Icon, useColorModeValue, chakra } from '@chakra-ui/react';
import { Input, InputGroup, InputLeftElement, useColorModeValue, chakra } from '@chakra-ui/react';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import searchIcon from 'icons/search.svg';
type Props = { type Props = {
onChange: (searchTerm: string) => void; onChange: (searchTerm: string) => void;
className?: string; className?: string;
...@@ -28,7 +29,7 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) = ...@@ -28,7 +29,7 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) =
<InputLeftElement <InputLeftElement
pointerEvents="none" pointerEvents="none"
> >
<SearchIcon color={ useColorModeValue('blackAlpha.600', 'whiteAlpha.600') }/> <Icon as={ searchIcon } color={ useColorModeValue('blackAlpha.600', 'whiteAlpha.600') }/>
</InputLeftElement> </InputLeftElement>
<Input <Input
...@@ -36,6 +37,8 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) = ...@@ -36,6 +37,8 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) =
value={ filterQuery } value={ filterQuery }
onChange={ handleFilterQueryChange } onChange={ handleFilterQueryChange }
placeholder={ placeholder } placeholder={ placeholder }
borderWidth="2px"
textOverflow="ellipsis"
/> />
</InputGroup> </InputGroup>
); );
......
// DEPRECATED
// migrate to separate components
// ui/shared/FilterButton.tsx + custom filter
// ui/shared/FilterInput.tsx
import { SearchIcon } from '@chakra-ui/icons';
import { Flex, Icon, Button, Circle, InputGroup, InputLeftElement, Input, useColorModeValue } from '@chakra-ui/react';
import type { ChangeEvent } from 'react';
import React from 'react';
import filterIcon from 'icons/filter.svg';
const FilterIcon = <Icon as={ filterIcon } boxSize={ 5 }/>;
const Filters = () => {
const [ isActive, setIsActive ] = React.useState(false);
const [ value, setValue ] = React.useState('');
const handleClick = React.useCallback(() => {
setIsActive(flag => !flag);
}, []);
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
}, []);
const badgeColor = useColorModeValue('white', 'black');
const badgeBgColor = useColorModeValue('blue.700', 'gray.50');
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
return (
<Flex>
<Button
leftIcon={ FilterIcon }
rightIcon={ isActive ? <Circle bg={ badgeBgColor } size={ 5 } color={ badgeColor }>2</Circle> : undefined }
size="sm"
variant="outline"
colorScheme="gray-dark"
borderWidth="1px"
onClick={ handleClick }
isActive={ isActive }
px={ 1.5 }
>
Filter
</Button>
<InputGroup size="xs" ml={ 3 } maxW="360px">
<InputLeftElement ml={ 1 }>
<SearchIcon w={ 5 } h={ 5 } color={ searchIconColor }/>
</InputLeftElement>
<Input
paddingInlineStart="38px"
placeholder="Search by addresses, hash, method..."
ml="1px"
onChange={ handleChange }
borderColor={ inputBorderColor }
value={ value }
size="xs"
/>
</InputGroup>
</Flex>
);
};
export default Filters;
...@@ -6,9 +6,9 @@ import React from 'react'; ...@@ -6,9 +6,9 @@ import React from 'react';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import Header from 'ui/blocks/header/Header';
import NavigationDesktop from 'ui/blocks/navigation/NavigationDesktop';
import PageContent from 'ui/shared/Page/PageContent'; import PageContent from 'ui/shared/Page/PageContent';
import Header from 'ui/snippets/header/Header';
import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop';
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
......
import { Button, Flex, Input, Icon, IconButton } from '@chakra-ui/react';
import React from 'react';
import arrowIcon from 'icons/arrows/east-mini.svg';
type Props = {
currentPage: number;
maxPage?: number;
isMobile?: boolean;
}
const MAX_PAGE_DEFAULT = 50;
const Pagination = ({ currentPage, maxPage, isMobile }: Props) => {
const pageNumber = (
<Flex alignItems="center">
<Button
variant="outline"
colorScheme="gray"
size="sm"
isActive
borderWidth="1px"
fontWeight={ 400 }
mr={ 3 }
h={ 8 }
>
{ currentPage }
</Button>
of
<Button
variant="outline"
colorScheme="gray"
size="sm"
width={ 8 }
borderWidth="1px"
fontWeight={ 400 }
ml={ 3 }
>
{ maxPage || MAX_PAGE_DEFAULT }
</Button>
</Flex>
);
if (isMobile) {
return (
<Flex
fontSize="sm"
width="100%"
justifyContent="space-between"
alignItems="center"
>
<IconButton
variant="outline"
size="sm"
aria-label="Next page"
w="36px"
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 }/> }
/>
{ pageNumber }
<IconButton
variant="outline"
size="sm"
aria-label="Next page"
w="36px"
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 } transform="rotate(180deg)"/> }
/>
</Flex>
);
}
return (
<Flex
fontSize="sm"
>
<Flex alignItems="center" justifyContent="space-between">
<Button
variant="outline"
size="sm"
aria-label="Next page"
leftIcon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 }/> }
mr={ 8 }
pl={ 1 }
>
Previous
</Button>
{ pageNumber }
<Button
variant="outline"
size="sm"
aria-label="Next page"
rightIcon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 } transform="rotate(180deg)"/> }
ml={ 8 }
pr={ 1 }
>
Next
</Button>
</Flex>
<Flex alignItems="center" width="132px" ml={ 16 }>
Go to <Input w="84px" size="xs" ml={ 2 }/>
</Flex>
</Flex>
);
};
export default Pagination;
import { Box, Icon, IconButton, chakra } from '@chakra-ui/react';
import React from 'react';
import eastArrow from 'icons/arrows/east-mini.svg';
interface Props {
className?: string;
}
const PrevNext = ({ className }: Props) => {
return (
<Box className={ className }>
<IconButton
aria-label="prev"
icon={ <Icon as={ eastArrow } boxSize={ 6 }/> }
h={ 6 }
borderRadius="sm"
variant="subtle"
colorScheme="gray"
/>
<IconButton
aria-label="next"
icon={ <Icon as={ eastArrow }boxSize={ 6 } transform="rotate(180deg)"/> }
h={ 6 }
borderRadius="sm"
variant="subtle"
colorScheme="gray"
ml="10px"
/>
</Box>
);
};
export default chakra(PrevNext);
...@@ -52,7 +52,7 @@ const RoutedTabs = ({ tabs, defaultActiveTab }: Props) => { ...@@ -52,7 +52,7 @@ const RoutedTabs = ({ tabs, defaultActiveTab }: Props) => {
return ( return (
<Tabs variant="soft-rounded" colorScheme="blue" isLazy onChange={ handleTabChange } index={ activeTab }> <Tabs variant="soft-rounded" colorScheme="blue" isLazy onChange={ handleTabChange } index={ activeTab }>
<TabList <TabList
marginBottom={{ base: 6, lg: 8 }} marginBottom={{ base: 6, lg: 12 }}
flexWrap="nowrap" flexWrap="nowrap"
whiteSpace="nowrap" whiteSpace="nowrap"
ref={ listRef } ref={ listRef }
......
...@@ -37,7 +37,7 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe ...@@ -37,7 +37,7 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe
<Popover isLazy placement="bottom-end" key="more" isOpen={ isOpen } onClose={ onClose } onOpen={ onOpen } closeDelay={ 0 }> <Popover isLazy placement="bottom-end" key="more" isOpen={ isOpen } onClose={ onClose } onOpen={ onOpen } closeDelay={ 0 }>
<PopoverTrigger> <PopoverTrigger>
<Button <Button
variant="subtle" variant="ghost"
isActive={ isOpen || isActive } isActive={ isOpen || isActive }
ref={ buttonRef } ref={ buttonRef }
{ ...styles } { ...styles }
...@@ -50,7 +50,7 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe ...@@ -50,7 +50,7 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe
{ tabs.slice(tabsCut).map((tab, index) => ( { tabs.slice(tabsCut).map((tab, index) => (
<Button <Button
key={ tab.routeName } key={ tab.routeName }
variant="subtle" variant="ghost"
onClick={ handleItemClick } onClick={ handleItemClick }
isActive={ activeTab.routeName === tab.routeName } isActive={ activeTab.routeName === tab.routeName }
justifyContent="left" justifyContent="left"
......
import { Icon, IconButton } from '@chakra-ui/react';
import React from 'react';
import upDownArrow from 'icons/arrows/up-down.svg';
type Props = {
handleSort: () => void;
isSortActive: boolean;
}
const SortButton = ({ handleSort, isSortActive }: Props) => {
return (
<IconButton
icon={ <Icon as={ upDownArrow } boxSize={ 5 }/> }
aria-label="sort"
size="sm"
variant="outline"
colorScheme="gray-dark"
ml={ 2 }
minWidth="36px"
onClick={ handleSort }
isActive={ isSortActive }
/>
);
};
export default SortButton;
import { chakra } from '@chakra-ui/react';
import type { StyleProps } from '@chakra-ui/styled-system';
import React from 'react';
const TextSeparator = (props: StyleProps) => {
return <chakra.span mx={ 3 } { ...props }>|</chakra.span>;
};
export default React.memo(TextSeparator);
...@@ -43,7 +43,7 @@ const Token = ({ symbol, className }: Props) => { ...@@ -43,7 +43,7 @@ const Token = ({ symbol, className }: Props) => {
<Link href={ url } target="_blank" ml={ 1 }> <Link href={ url } target="_blank" ml={ 1 }>
{ token.fullName } { token.fullName }
</Link> </Link>
<Text ml={ 1 }>({ token.symbol })</Text> <Text ml={ 1 } variant="secondary">({ token.symbol })</Text>
</Center> </Center>
); );
}; };
......
import { Tag, TagLabel, TagLeftIcon, Tooltip } from '@chakra-ui/react';
import React from 'react';
import errorIcon from 'icons/status/error.svg';
import pendingIcon from 'icons/status/pending.svg';
import successIcon from 'icons/status/success.svg';
export interface Props {
status: 'success' | 'failed' | 'pending';
errorText?: string;
}
const TxStatus = ({ status, errorText }: Props) => {
let label;
let icon;
let colorScheme;
switch (status) {
case 'success':
label = 'Success';
icon = successIcon;
colorScheme = 'green';
break;
case 'failed':
label = 'Failed';
icon = errorIcon;
colorScheme = 'red';
break;
case 'pending':
label = 'Pending';
icon = pendingIcon;
// FIXME: it's not gray on mockups
// need to implement new color scheme or redefine colors here
colorScheme = 'gray';
break;
}
return (
<Tooltip label={ errorText }>
<Tag colorScheme={ colorScheme } display="inline-flex">
<TagLeftIcon boxSize={ 2.5 } as={ icon }/>
<TagLabel>{ label }</TagLabel>
</Tag>
</Tooltip>
);
};
export default TxStatus;
import { Box, Flex, Text, chakra } from '@chakra-ui/react'; import { Box, Flex, Text, chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
interface Props { interface Props {
...@@ -12,7 +12,7 @@ const Utilization = ({ className, value }: Props) => { ...@@ -12,7 +12,7 @@ const Utilization = ({ className, value }: Props) => {
const valueString = (value * 100).toFixed(2) + '%'; const valueString = (value * 100).toFixed(2) + '%';
return ( return (
<Flex className={ className } alignItems="center"> <Flex className={ className } alignItems="center">
<Box bg="gray.100" w={ `${ WIDTH }px` } h="4px" borderRadius="full" overflow="hidden"> <Box bg={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } w={ `${ WIDTH }px` } h="4px" borderRadius="full" overflow="hidden">
<Box bg="green.500" w={ valueString } h="100%"/> <Box bg="green.500" w={ valueString } h="100%"/>
</Box> </Box>
<Text color="green.500" ml="10px" fontWeight="bold">{ valueString }</Text> <Text color="green.500" ml="10px" fontWeight="bold">{ valueString }</Text>
......
import { Link, chakra, shouldForwardProp } from '@chakra-ui/react'; import { Link, chakra, shouldForwardProp, Tooltip, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useLink from 'lib/link/useLink'; import useLink from 'lib/link/useLink';
...@@ -7,13 +7,14 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; ...@@ -7,13 +7,14 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props { interface Props {
type?: 'address' | 'transaction' | 'token'; type?: 'address' | 'transaction' | 'token';
alias?: string;
className?: string; className?: string;
hash: string; hash: string;
truncation?: 'constant' | 'dynamic'| 'none'; truncation?: 'constant' | 'dynamic'| 'none';
fontWeight?: string; fontWeight?: string;
} }
const AddressLink = ({ type, className, truncation = 'dynamic', hash, fontWeight }: Props) => { const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, fontWeight }: Props) => {
const link = useLink(); const link = useLink();
let url; let url;
if (type === 'transaction') { if (type === 'transaction') {
...@@ -25,6 +26,13 @@ const AddressLink = ({ type, className, truncation = 'dynamic', hash, fontWeight ...@@ -25,6 +26,13 @@ const AddressLink = ({ type, className, truncation = 'dynamic', hash, fontWeight
} }
const content = (() => { const content = (() => {
if (alias) {
return (
<Tooltip label={ hash }>
<Box overflow="hidden" textOverflow="ellipsis">{ alias }</Box>
</Tooltip>
);
}
switch (truncation) { switch (truncation) {
case 'constant': case 'constant':
return <HashStringShorten hash={ hash }/>; return <HashStringShorten hash={ hash }/>;
......
...@@ -2,10 +2,10 @@ import { Icon, Box, Flex, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useC ...@@ -2,10 +2,10 @@ import { Icon, Box, Flex, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useC
import React from 'react'; import React from 'react';
import burgerIcon from 'icons/burger.svg'; import burgerIcon from 'icons/burger.svg';
import NavigationMobile from 'ui/blocks/navigation/NavigationMobile'; import NavigationMobile from 'ui/snippets/navigation/NavigationMobile';
import NetworkLogo from 'ui/blocks/networkMenu/NetworkLogo'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import NetworkMenuButton from 'ui/blocks/networkMenu/NetworkMenuButton'; import NetworkMenuButton from 'ui/snippets/networkMenu/NetworkMenuButton';
import NetworkMenuContentMobile from 'ui/blocks/networkMenu/NetworkMenuContentMobile'; import NetworkMenuContentMobile from 'ui/snippets/networkMenu/NetworkMenuContentMobile';
const Burger = () => { const Burger = () => {
const iconColor = useColorModeValue('gray.600', 'white'); const iconColor = useColorModeValue('gray.600', 'white');
......
import type { UseCheckboxProps } from '@chakra-ui/checkbox'; import type { UseCheckboxProps } from '@chakra-ui/checkbox';
import { useCheckbox } from '@chakra-ui/checkbox'; import { useCheckbox } from '@chakra-ui/checkbox';
import { SunIcon } from '@chakra-ui/icons';
import { useColorMode, useColorModeValue, Icon } from '@chakra-ui/react'; import { useColorMode, useColorModeValue, Icon } from '@chakra-ui/react';
import type { import type {
SystemStyleObject, SystemStyleObject,
...@@ -16,6 +15,7 @@ import { dataAttr, __DEV__ } from '@chakra-ui/utils'; ...@@ -16,6 +15,7 @@ import { dataAttr, __DEV__ } from '@chakra-ui/utils';
import * as React from 'react'; import * as React from 'react';
import moonIcon from 'icons/moon.svg'; import moonIcon from 'icons/moon.svg';
import sunIcon from 'icons/sun.svg';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
export interface ColorModeTogglerProps export interface ColorModeTogglerProps
...@@ -101,10 +101,11 @@ const ColorModeToggler = forwardRef<ColorModeTogglerProps, 'input'>((props, ref) ...@@ -101,10 +101,11 @@ const ColorModeToggler = forwardRef<ColorModeTogglerProps, 'input'>((props, ref)
data-hover={ dataAttr(state.isHovered) } data-hover={ dataAttr(state.isHovered) }
__css={ thumbStyles } __css={ thumbStyles }
/> />
<SunIcon <Icon
boxSize={ 4 } boxSize={ 5 }
margin={ 2 } margin={ 1.5 }
zIndex="docked" zIndex="docked"
as={ sunIcon }
color={ useColorModeValue('gray.500', 'blue.600') } color={ useColorModeValue('gray.500', 'blue.600') }
{ ...transitionProps } { ...transitionProps }
/> />
......
...@@ -2,10 +2,10 @@ import { HStack, Box, Flex, useColorModeValue } from '@chakra-ui/react'; ...@@ -2,10 +2,10 @@ import { HStack, Box, Flex, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import NetworkLogo from 'ui/blocks/networkMenu/NetworkLogo'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import ProfileMenuDesktop from 'ui/blocks/profileMenu/ProfileMenuDesktop'; import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
import ProfileMenuMobile from 'ui/blocks/profileMenu/ProfileMenuMobile'; import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile';
import SearchBar from 'ui/blocks/searchBar/SearchBar'; import SearchBar from 'ui/snippets/searchBar/SearchBar';
import Burger from './Burger'; import Burger from './Burger';
import ColorModeToggler from './ColorModeToggler'; import ColorModeToggler from './ColorModeToggler';
......
import { ChevronLeftIcon } from '@chakra-ui/icons'; import { Flex, Box, VStack, Icon, useColorModeValue, useBreakpointValue } from '@chakra-ui/react';
import { Flex, Box, VStack, useColorModeValue, useBreakpointValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import chevronIcon from 'icons/arrows/east-mini.svg';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useNavItems from 'lib/hooks/useNavItems'; import useNavItems from 'lib/hooks/useNavItems';
import useNetwork from 'lib/hooks/useNetwork'; import useNetwork from 'lib/hooks/useNetwork';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import NetworkLogo from 'ui/blocks/networkMenu/NetworkLogo'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import NetworkMenu from 'ui/blocks/networkMenu/NetworkMenu'; import NetworkMenu from 'ui/snippets/networkMenu/NetworkMenu';
import NavFooter from './NavFooter'; import NavFooter from './NavFooter';
import NavLink from './NavLink'; import NavLink from './NavLink';
...@@ -78,7 +78,8 @@ const NavigationDesktop = () => { ...@@ -78,7 +78,8 @@ const NavigationDesktop = () => {
</Box> </Box>
) } ) }
<NavFooter isCollapsed={ isCollapsed } hasAccount={ hasAccount }/> <NavFooter isCollapsed={ isCollapsed } hasAccount={ hasAccount }/>
<ChevronLeftIcon <Icon
as={ chevronIcon }
width={ 6 } width={ 6 }
height={ 6 } height={ 6 }
border="1px" border="1px"
...@@ -88,7 +89,7 @@ const NavigationDesktop = () => { ...@@ -88,7 +89,7 @@ const NavigationDesktop = () => {
transform={ isCollapsed ? 'rotate(180deg)' : 'rotate(0)' } transform={ isCollapsed ? 'rotate(180deg)' : 'rotate(0)' }
{ ...getDefaultTransitionProps({ transitionProperty: 'transform, left' }) } { ...getDefaultTransitionProps({ transitionProperty: 'transform, left' }) }
transformOrigin="center" transformOrigin="center"
position="fixed" position="absolute"
top="104px" top="104px"
left={ isCollapsed ? '80px' : '216px' } left={ isCollapsed ? '80px' : '216px' }
cursor="pointer" cursor="pointer"
......
...@@ -4,8 +4,8 @@ import React from 'react'; ...@@ -4,8 +4,8 @@ import React from 'react';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useNavItems from 'lib/hooks/useNavItems'; import useNavItems from 'lib/hooks/useNavItems';
import useNetwork from 'lib/hooks/useNetwork'; import useNetwork from 'lib/hooks/useNetwork';
import NavFooter from 'ui/blocks/navigation/NavFooter'; import NavFooter from 'ui/snippets/navigation/NavFooter';
import NavLink from 'ui/blocks/navigation/NavLink'; import NavLink from 'ui/snippets/navigation/NavLink';
const NavigationMobile = () => { const NavigationMobile = () => {
const { mainNavItems, accountNavItems } = useNavItems(); const { mainNavItems, accountNavItems } = useNavItems();
......
...@@ -5,7 +5,7 @@ import type { UserInfo } from 'types/api/account'; ...@@ -5,7 +5,7 @@ import type { UserInfo } from 'types/api/account';
import useNavItems from 'lib/hooks/useNavItems'; import useNavItems from 'lib/hooks/useNavItems';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import NavLink from 'ui/blocks/navigation/NavLink'; import NavLink from 'ui/snippets/navigation/NavLink';
type Props = UserInfo; type Props = UserInfo;
......
...@@ -2,8 +2,8 @@ import { Popover, PopoverContent, PopoverBody, PopoverTrigger, Button } from '@c ...@@ -2,8 +2,8 @@ import { Popover, PopoverContent, PopoverBody, PopoverTrigger, Button } from '@c
import React from 'react'; import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import ProfileMenuContent from 'ui/blocks/profileMenu/ProfileMenuContent';
import UserAvatar from 'ui/shared/UserAvatar'; import UserAvatar from 'ui/shared/UserAvatar';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuDesktop = () => { const ProfileMenuDesktop = () => {
const { data } = useFetchProfileInfo(); const { data } = useFetchProfileInfo();
......
...@@ -2,9 +2,9 @@ import { Flex, Box, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclos ...@@ -2,9 +2,9 @@ import { Flex, Box, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclos
import React from 'react'; import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import ColorModeToggler from 'ui/blocks/header/ColorModeToggler';
import ProfileMenuContent from 'ui/blocks/profileMenu/ProfileMenuContent';
import UserAvatar from 'ui/shared/UserAvatar'; import UserAvatar from 'ui/shared/UserAvatar';
import ColorModeToggler from 'ui/snippets/header/ColorModeToggler';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuMobile = () => { const ProfileMenuMobile = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
......
import { SearchIcon } from '@chakra-ui/icons'; import { InputGroup, Input, InputLeftAddon, InputLeftElement, Icon, useColorModeValue } from '@chakra-ui/react';
import { InputGroup, Input, InputLeftAddon, InputLeftElement, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { ChangeEvent, FormEvent } from 'react'; import type { ChangeEvent, FormEvent } from 'react';
import searchIcon from 'icons/search.svg';
interface Props { interface Props {
onChange: (event: ChangeEvent<HTMLInputElement>) => void; onChange: (event: ChangeEvent<HTMLInputElement>) => void;
onSubmit: (event: FormEvent<HTMLFormElement>) => void; onSubmit: (event: FormEvent<HTMLFormElement>) => void;
...@@ -14,7 +15,7 @@ const SearchBarDesktop = ({ onChange, onSubmit }: Props) => { ...@@ -14,7 +15,7 @@ const SearchBarDesktop = ({ onChange, onSubmit }: Props) => {
<InputGroup> <InputGroup>
<InputLeftAddon w="111px">All filters</InputLeftAddon> <InputLeftAddon w="111px">All filters</InputLeftAddon>
<InputLeftElement w={ 6 } ml="132px" mr={ 2.5 }> <InputLeftElement w={ 6 } ml="132px" mr={ 2.5 }>
<SearchIcon w={ 5 } h={ 5 } color={ useColorModeValue('blackAlpha.600', 'whiteAlpha.600') }/> <Icon as={ searchIcon } boxSize={ 6 } color={ useColorModeValue('blackAlpha.600', 'whiteAlpha.600') }/>
</InputLeftElement> </InputLeftElement>
<Input <Input
paddingInlineStart="50px" paddingInlineStart="50px"
......
import { SearchIcon } from '@chakra-ui/icons'; import { InputGroup, Input, InputLeftElement, Icon, useColorModeValue, chakra } from '@chakra-ui/react';
import { InputGroup, Input, InputLeftElement, useColorModeValue, chakra } from '@chakra-ui/react';
import clamp from 'lodash/clamp'; import clamp from 'lodash/clamp';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import React from 'react'; import React from 'react';
import type { ChangeEvent, FormEvent } from 'react'; import type { ChangeEvent, FormEvent } from 'react';
import searchIcon from 'icons/search.svg';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
const SCROLL_DIFF_THRESHOLD = 20; const SCROLL_DIFF_THRESHOLD = 20;
...@@ -64,7 +64,7 @@ const SearchBarMobile = ({ onChange, onSubmit }: Props) => { ...@@ -64,7 +64,7 @@ const SearchBarMobile = ({ onChange, onSubmit }: Props) => {
> >
<InputGroup size="sm"> <InputGroup size="sm">
<InputLeftElement > <InputLeftElement >
<SearchIcon w={ 4 } h={ 4 } color={ searchIconColor }/> <Icon as={ searchIcon } boxSize={ 4 } color={ searchIconColor }/>
</InputLeftElement> </InputLeftElement>
<Input <Input
paddingInlineStart="38px" paddingInlineStart="38px"
......
import { Center, Icon, Text } from '@chakra-ui/react'; import { Flex, Icon, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import rightArrowIcon from 'icons/arrows/east.svg'; import rightArrowIcon from 'icons/arrows/east.svg';
...@@ -16,16 +16,18 @@ interface Props { ...@@ -16,16 +16,18 @@ interface Props {
const TokenTransfer = ({ from, to, amount, usd, token }: Props) => { const TokenTransfer = ({ from, to, amount, usd, token }: Props) => {
return ( return (
<Center> <Flex alignItems="center" flexWrap="wrap" columnGap={ 3 } rowGap={ 3 }>
<AddressLink fontWeight="500" hash={ from } truncation="constant"/> <Flex alignItems="center">
<Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } color="gray.500"/> <AddressLink fontWeight="500" hash={ from } truncation="constant"/>
<AddressLink fontWeight="500" hash={ to } truncation="constant"/> <Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } color="gray.500"/>
<Text fontWeight={ 500 } as="span" ml={ 4 }>For:{ space } <AddressLink fontWeight="500" hash={ to } truncation="constant"/>
</Flex>
<Text fontWeight={ 500 } as="span">For:{ space }
<Text fontWeight={ 600 } as="span">{ amount }</Text>{ space } <Text fontWeight={ 600 } as="span">{ amount }</Text>{ space }
<Text fontWeight={ 400 } variant="secondary" as="span">(${ usd.toFixed(2) })</Text> <Text fontWeight={ 400 } variant="secondary" as="span">(${ usd.toFixed(2) })</Text>
</Text> </Text>
<Token symbol={ token } ml={ 3 }/> <Token symbol={ token }/>
</Center> </Flex>
); );
}; };
......
...@@ -10,12 +10,13 @@ interface RowProps { ...@@ -10,12 +10,13 @@ interface RowProps {
isLast?: boolean; isLast?: boolean;
name: string; name: string;
type: string; type: string;
indexed?: boolean;
} }
const PADDING = 4; const PADDING = 4;
const GAP = 5; const GAP = 5;
const TableRow = ({ isLast, name, type, children }: RowProps) => { const TableRow = ({ isLast, name, type, children, indexed }: RowProps) => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return ( return (
...@@ -38,6 +39,14 @@ const TableRow = ({ isLast, name, type, children }: RowProps) => { ...@@ -38,6 +39,14 @@ const TableRow = ({ isLast, name, type, children }: RowProps) => {
> >
{ type } { type }
</GridItem> </GridItem>
<GridItem
pr={ GAP }
pt={ GAP }
pb={ isLast ? PADDING : 0 }
bgColor={ bgColor }
>
{ indexed ? 'true' : 'false' }
</GridItem>
<GridItem <GridItem
pr={ PADDING } pr={ PADDING }
pt={ GAP } pt={ GAP }
...@@ -55,28 +64,31 @@ const TxDecodedInputData = () => { ...@@ -55,28 +64,31 @@ const TxDecodedInputData = () => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return ( return (
<Grid gridTemplateColumns="minmax(80px, auto) minmax(80px, auto) 1fr" fontSize="sm" lineHeight={ 5 } w="100%"> <Grid gridTemplateColumns="minmax(80px, auto) minmax(80px, auto) minmax(80px, auto) minmax(0, 1fr)" fontSize="sm" lineHeight={ 5 } w="100%">
{ /* FIRST PART OF BLOCK */ } { /* FIRST PART OF BLOCK */ }
<GridItem fontWeight={ 600 } pl={ PADDING } pr={ GAP }>Method Id</GridItem> <GridItem fontWeight={ 600 } pl={{ base: 0, lg: PADDING }} pr={{ base: 0, lg: GAP }} colSpan={{ base: 4, lg: undefined }}>Method Id</GridItem>
<GridItem colSpan={ 2 } pr={ PADDING }>0xddf252ad</GridItem> <GridItem colSpan={{ base: 4, lg: 3 }} pr={{ base: 0, lg: PADDING }} mt={{ base: 2, lg: 0 }}>0xddf252ad</GridItem>
<GridItem <GridItem
py={ 2 } py={ 2 }
mt={ 2 } mt={ 2 }
pl={ PADDING } pl={{ base: 0, lg: PADDING }}
pr={ GAP } pr={{ base: 0, lg: GAP }}
fontWeight={ 600 } fontWeight={ 600 }
borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderTopWidth="1px" borderTopWidth="1px"
colSpan={{ base: 4, lg: undefined }}
> >
Call Call
</GridItem> </GridItem>
<GridItem <GridItem
py={ 2 } py={{ base: 0, lg: 2 }}
mt={ 2 } mt={{ base: 0, lg: 2 }}
colSpan={ 2 } mb={{ base: 2, lg: 0 }}
pr={ PADDING } colSpan={{ base: 4, lg: 3 }}
pr={{ base: 0, lg: PADDING }}
borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderTopWidth="1px" borderTopWidth={{ base: '0px', lg: '1px' }}
whiteSpace="normal"
> >
Transfer(address indexed from, address indexed to, uint256 indexed tokenId) Transfer(address indexed from, address indexed to, uint256 indexed tokenId)
</GridItem> </GridItem>
...@@ -100,6 +112,15 @@ const TxDecodedInputData = () => { ...@@ -100,6 +112,15 @@ const TxDecodedInputData = () => {
> >
Type Type
</GridItem> </GridItem>
<GridItem
pr={ GAP }
pt={ PADDING }
pb={ 1 }
bgColor={ bgColor }
fontWeight={ 600 }
>
Inde<wbr/>xed?
</GridItem>
<GridItem <GridItem
pr={ PADDING } pr={ PADDING }
pt={ PADDING } pt={ PADDING }
...@@ -115,7 +136,7 @@ const TxDecodedInputData = () => { ...@@ -115,7 +136,7 @@ const TxDecodedInputData = () => {
<CopyToClipboard text="0x0000000000000000000000000000000000000000"/> <CopyToClipboard text="0x0000000000000000000000000000000000000000"/>
</Address> </Address>
</TableRow> </TableRow>
<TableRow name="from" type="address"> <TableRow name="to" type="address" indexed>
<Address justifyContent="space-between"> <Address justifyContent="space-between">
<AddressLink hash="0xcf0c50b7ea8af37d57380a0ac199d55b0782c718"/> <AddressLink hash="0xcf0c50b7ea8af37d57380a0ac199d55b0782c718"/>
<CopyToClipboard text="0xcf0c50b7ea8af37d57380a0ac199d55b0782c718"/> <CopyToClipboard text="0xcf0c50b7ea8af37d57380a0ac199d55b0782c718"/>
......
This diff is collapsed.
import { Box, Flex, Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TxInternalsType } from 'types/api/tx'; import type { TxInternalsType } from 'types/api/tx';
import type ArrayElement from 'types/utils/ArrayElement'; import type ArrayElement from 'types/utils/ArrayElement';
import { data } from 'data/txInternal'; import { data } from 'data/txInternal';
import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import EmptySearchResult from 'ui/apps/EmptySearchResult'; import EmptySearchResult from 'ui/apps/EmptySearchResult';
import FilterInput from 'ui/shared/FilterInput'; import FilterInput from 'ui/shared/FilterInput';
import TxInternalsFilter from 'ui/tx/internals/TxInternalsFilter'; import TxInternalsFilter from 'ui/tx/internals/TxInternalsFilter';
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem'; import TxInternalsList from 'ui/tx/internals/TxInternalsList';
import TxInternalsTable from 'ui/tx/internals/TxInternalsTable';
const searchFn = (searchTerm: string) => (item: ArrayElement<typeof data>): boolean => { const searchFn = (searchTerm: string) => (item: ArrayElement<typeof data>): boolean => {
const formattedSearchTerm = searchTerm.toLowerCase(); const formattedSearchTerm = searchTerm.toLowerCase();
return item.type.toLowerCase().includes(formattedSearchTerm) || return item.type.toLowerCase().includes(formattedSearchTerm) ||
item.from.toLowerCase().includes(formattedSearchTerm) || item.from.hash.toLowerCase().includes(formattedSearchTerm) ||
item.to.toLowerCase().includes(formattedSearchTerm); item.to.hash.toLowerCase().includes(formattedSearchTerm);
}; };
const TxInternals = () => { const TxInternals = () => {
const [ filters, setFilters ] = React.useState<Array<TxInternalsType>>([]); const [ filters, setFilters ] = React.useState<Array<TxInternalsType>>([]);
const [ searchTerm, setSearchTerm ] = React.useState<string>(''); const [ searchTerm, setSearchTerm ] = React.useState<string>('');
const isMobile = useIsMobile();
const handleFilterChange = React.useCallback((nextValue: Array<TxInternalsType>) => { const handleFilterChange = React.useCallback((nextValue: Array<TxInternalsType>) => {
setFilters(nextValue); setFilters(nextValue);
...@@ -35,24 +38,7 @@ const TxInternals = () => { ...@@ -35,24 +38,7 @@ const TxInternals = () => {
return <EmptySearchResult text={ `Couldn${ apos }t find any transaction that matches your query.` }/>; return <EmptySearchResult text={ `Couldn${ apos }t find any transaction that matches your query.` }/>;
} }
return ( return isMobile ? <TxInternalsList data={ filteredData }/> : <TxInternalsTable data={ filteredData }/>;
<TableContainer width="100%">
<Table variant="simple" minWidth="950px" size="sm">
<Thead>
<Tr>
<Th width="20%">Type</Th>
<Th width="calc(20% + 40px)" pr="0">From</Th>
<Th width="calc(20% - 40px)" pl="0">To</Th>
<Th width="20%" isNumeric>Value</Th>
<Th width="20%" isNumeric>Gas limit</Th>
</Tr>
</Thead>
<Tbody>
{ filteredData.map((item) => <TxInternalsTableItem key={ item.id } { ...item }/>) }
</Tbody>
</Table>
</TableContainer>
);
})(); })();
return ( return (
......
import { import { Accordion, Text } from '@chakra-ui/react';
Accordion,
Text,
Table,
Thead,
Tbody,
Tr,
Th,
TableContainer,
} from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { data } from 'data/txState'; import useIsMobile from 'lib/hooks/useIsMobile';
import TxStateList from 'ui/tx/state/TxStateList';
import TxStateTableItem from './state/TxStateTableItem'; import TxStateTable from 'ui/tx/state/TxStateTable';
const CURRENCY = 'ETH';
const TxState = () => { const TxState = () => {
const isMobile = useIsMobile();
const list = isMobile ? <TxStateList/> : <TxStateTable/>;
return ( return (
<> <>
<Text> <Text>
A set of information that represents the current state is updated when a transaction takes place on the network. The below is a summary of those changes A set of information that represents the current state is updated when a transaction takes place on the network. The below is a summary of those changes
</Text> </Text>
<Accordion allowToggle allowMultiple> <Accordion allowMultiple defaultIndex={ [] }>
<TableContainer width="100%" mt={ 6 }> { list }
<Table variant="simple" minWidth="950px" size="sm">
<Thead>
<Tr>
<Th width="92px">Storage</Th>
<Th width="146px">Address</Th>
<Th width="120px">Miner</Th>
<Th width="33%" isNumeric>{ `After ${ CURRENCY }` }</Th>
<Th width="33%" isNumeric>{ `Before ${ CURRENCY }` }</Th>
<Th width="33%" isNumeric>{ `State difference ${ CURRENCY }` }</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item, index) => <TxStateTableItem txStateItem={ item } key={ index }/>) }
</Tbody>
</Table>
</TableContainer>
</Accordion> </Accordion>
</> </>
); );
......
import { Tag, TagLabel, TagLeftIcon } from '@chakra-ui/react';
import React from 'react';
import errorIcon from 'icons/status/error.svg';
import successIcon from 'icons/status/success.svg';
export interface Props {
status: 'success' | 'error';
}
const TxStatus = ({ status }: Props) => {
const label = status === 'success' ? 'Success' : 'Error';
const icon = status === 'success' ? successIcon : errorIcon;
const colorScheme = status === 'success' ? 'green' : 'red';
return (
<Tag colorScheme={ colorScheme } display="inline-flex">
<TagLeftIcon boxSize={ 2.5 } as={ icon }/>
<TagLabel>{ label }</TagLabel>
</Tag>
);
};
export default TxStatus;
import { Box } from '@chakra-ui/react';
import React from 'react';
import type { data as txData } from 'data/txInternal';
import TxInternalsListItem from 'ui/tx/internals/TxInternalsListItem';
const TxInternalsList = ({ data }: { data: typeof txData}) => {
return (
<Box mt={ 6 }>
{ data.map((item) => <TxInternalsListItem key={ item.id } { ...item }/>) }
</Box>
);
};
export default TxInternalsList;
import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { data } from 'data/txInternal';
import eastArrowIcon from 'icons/arrows/east.svg';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import TxStatus from 'ui/shared/TxStatus';
type Props = ArrayElement<typeof data>;
const TxInternalsListItem = ({ type, status, from, to, value, gasLimit }: Props) => {
return (
<AccountListItemMobile rowGap={ 3 }>
<Flex>
<Tag colorScheme="cyan" mr={ 2 }>{ capitalize(type) }</Tag>
<TxStatus status={ status }/>
</Flex>
<Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ from.hash }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
</Address>
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
<Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ to.hash }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash }/>
</Address>
</Box>
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Value xDAI</Text>
<Text fontSize="sm" variant="secondary">{ value }</Text>
</HStack>
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Gas limit</Text>
<Text fontSize="sm" variant="secondary">{ gasLimit.toLocaleString('en') }</Text>
</HStack>
</AccountListItemMobile>
);
};
export default TxInternalsListItem;
import { Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react';
import React from 'react';
import type { data as txData } from 'data/txInternal';
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem';
const TxInternalsTable = ({ data }: { data: typeof txData}) => {
return (
<TableContainer width="100%" mt={ 6 }>
<Table variant="simple" size="sm">
<Thead>
<Tr>
<Th width="28%">Type</Th>
<Th width="20%">From</Th>
<Th width="24px" px={ 0 }/>
<Th width="20%">To</Th>
<Th width="16%" isNumeric>Value</Th>
<Th width="16%" isNumeric>Gas limit</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item) => (
<TxInternalsTableItem key={ item.id } { ...item }/>
)) }
</Tbody>
</Table>
</TableContainer>
);
};
export default TxInternalsTable;
import { Tr, Td, Tag, Icon } from '@chakra-ui/react'; import { Tr, Td, Tag, Icon, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import rightArrowIcon from 'icons/arrows/east.svg'; import rightArrowIcon from 'icons/arrows/east.svg';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import TxStatus from 'ui/shared/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
import TxStatus from 'ui/tx/TxStatus';
interface Props { interface Props {
type: string; type: string;
status: 'success' | 'error'; status: 'success' | 'failed' | 'pending';
from: string; from: { hash: string; alias?: string};
to: string; to: { hash: string; alias?: string};
value: number; value: number;
gasLimit: number; gasLimit: number;
} }
...@@ -23,20 +23,26 @@ const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props) ...@@ -23,20 +23,26 @@ const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props)
return ( return (
<Tr alignItems="top"> <Tr alignItems="top">
<Td> <Td>
{ typeTitle && <Tag colorScheme="cyan" mr={ 2 }>{ typeTitle }</Tag> } { typeTitle && (
<Box w="126px" display="inline-block">
<Tag colorScheme="cyan" mr={ 5 }>{ typeTitle }</Tag>
</Box>
) }
<TxStatus status={ status }/> <TxStatus status={ status }/>
</Td> </Td>
<Td pr="0"> <Td>
<Address> <Address>
<AddressIcon hash={ from }/> <AddressIcon hash={ from.hash }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from }/> <AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.alias } flexGrow={ 1 }/>
<Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } flexShrink={ 0 } color="gray.500"/>
</Address> </Address>
</Td> </Td>
<Td pl="0"> <Td px={ 0 }>
<Icon as={ rightArrowIcon } boxSize={ 6 } color="gray.500"/>
</Td>
<Td>
<Address> <Address>
<AddressIcon hash={ to }/> <AddressIcon hash={ to.hash }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to }/> <AddressLink hash={ to.hash } alias={ to.alias } fontWeight="500" ml={ 2 }/>
</Address> </Address>
</Td> </Td>
<Td isNumeric> <Td isNumeric>
......
import { SearchIcon } from '@chakra-ui/icons'; import { Text, Grid, GridItem, Link, Tooltip, Button, Icon, useColorModeValue } from '@chakra-ui/react';
import { Text, Grid, GridItem, Link, Tooltip, Button, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import searchIcon from 'icons/search.svg';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -15,14 +15,29 @@ interface Props { ...@@ -15,14 +15,29 @@ interface Props {
index: number; index: number;
} }
const RowHeader = ({ children }: { children: React.ReactNode }) => <GridItem><Text fontWeight={ 500 }>{ children }</Text></GridItem>; const RowHeader = ({ children }: { children: React.ReactNode }) => (
<GridItem _notFirst={{ my: { base: 4, lg: 0 } }}>
<Text fontWeight={ 500 }>{ children }</Text>
</GridItem>
);
const TxLogItem = ({ address, index, topics, data }: Props) => { const TxLogItem = ({ address, index, topics, data }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return ( return (
<Grid gridTemplateColumns="200px 1fr" gap={ 8 } py={ 8 } _notFirst={{ borderTopWidth: '1px', borderTopColor: borderColor }}> <Grid
gridTemplateColumns={{ base: 'minmax(0, 1fr)', lg: '200px minmax(0, 1fr)' }}
gap={{ base: 2, lg: 8 }}
py={ 8 }
_notFirst={{
borderTopWidth: '1px',
borderTopColor: borderColor,
}}
_first={{
pt: 0,
}}
>
<RowHeader>Address</RowHeader> <RowHeader>Address</RowHeader>
<GridItem display="flex" alignItems="center"> <GridItem display="flex" alignItems="center">
<Address> <Address>
...@@ -30,12 +45,12 @@ const TxLogItem = ({ address, index, topics, data }: Props) => { ...@@ -30,12 +45,12 @@ const TxLogItem = ({ address, index, topics, data }: Props) => {
<AddressLink hash={ address } ml={ 2 }/> <AddressLink hash={ address } ml={ 2 }/>
</Address> </Address>
<Tooltip label="Find matches topic"> <Tooltip label="Find matches topic">
<Link ml={ 2 }> <Link ml={ 2 } display="inline-flex">
<SearchIcon w={ 5 } h={ 5 }/> <Icon as={ searchIcon } boxSize={ 5 }/>
</Link> </Link>
</Tooltip> </Tooltip>
<Tooltip label="Log index"> <Tooltip label="Log index">
<Button variant="outline" isActive ml="auto" size="sm" fontWeight={ 400 }> <Button variant="outline" colorScheme="gray" isActive ml={{ base: 9, lg: 'auto' }} size="sm" fontWeight={ 400 }>
{ index } { index }
</Button> </Button>
</Tooltip> </Tooltip>
......
import { Flex, Button, Text, Select } from '@chakra-ui/react'; import { Flex, Button, Select, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props { interface Props {
hex: string; hex: string;
index: number; index: number;
...@@ -17,18 +19,29 @@ const TxLogTopic = ({ hex, index }: Props) => { ...@@ -17,18 +19,29 @@ const TxLogTopic = ({ hex, index }: Props) => {
}, []); }, []);
return ( return (
<Flex alignItems="center" px={ 3 } _notFirst={{ mt: 3 }}> <Flex alignItems="center" px={{ base: 0, lg: 3 }} _notFirst={{ mt: 3 }} overflow="hidden" maxW="100%">
<Button variant="outline" isActive size="xs" fontWeight={ 400 } mr={ 3 } w={ 6 }> <Button variant="outline" colorScheme="gray" isActive size="xs" fontWeight={ 400 } mr={ 3 } w={ 6 }>
{ index } { index }
</Button> </Button>
{ /* temporary condition juse to show different states of the component */ } { /* temporary condition juse to show different states of the component */ }
{ /* delete when ther will be real data */ } { /* delete when ther will be real data */ }
{ index > 0 && ( { index > 0 && (
<Select size="sm" borderRadius="base" value={ selectedDataType } onChange={ handleSelectChange } focusBorderColor="none" w="75px" mr={ 3 }> <Select
size="sm"
borderRadius="base"
value={ selectedDataType }
onChange={ handleSelectChange }
focusBorderColor="none"
w="75px"
mr={ 3 }
flexShrink={ 0 }
>
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) } { OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select> </Select>
) } ) }
<Text>{ hex }</Text> <Box overflow="hidden" whiteSpace="nowrap">
<HashStringShortenDynamic hash={ hex }/>
</Box>
</Flex> </Flex>
); );
}; };
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { data } from 'data/txState';
import TxStateListItem from 'ui/tx/state/TxStateListItem';
const TxStateList = () => {
return (
<Box mt={ 6 }>
{ data.map((item, index) => <TxStateListItem key={ index } { ...item }/>) }
</Box>
);
};
export default TxStateList;
import { AccordionItem, AccordionButton, AccordionIcon, Button, Flex, Text, Link, StatArrow, Stat, AccordionPanel } from '@chakra-ui/react';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { data } from 'data/txState';
import { nbsp } from 'lib/html-entities';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import TextSeparator from 'ui/shared/TextSeparator';
import TxStateStorageItem from './TxStateStorageItem';
type Props = ArrayElement<typeof data>;
const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props) => {
const hasStorageData = Boolean(storage?.length);
return (
<AccountListItemMobile>
<AccordionItem isDisabled={ !hasStorageData } border={ 0 } w="100%" display="flex" flexDirection="column" rowGap={ 3 }>
{ ({ isExpanded }) => (
<>
<Flex>
<Address flexGrow={ 1 }>
<AddressIcon hash={ address }/>
<AddressLink hash={ address } fontWeight="500" ml={ 2 }/>
</Address>
<AccordionButton
_hover={{ background: 'unset' }}
padding="0"
ml={ 4 }
w="auto"
>
<Button
variant="outline"
borderWidth="1px"
// button can't be inside button (AccordionButton)
as="div"
isActive={ isExpanded }
size="sm"
fontWeight={ 400 }
isDisabled={ !hasStorageData }
colorScheme="gray"
// AccordionButton has its own opacity rule when disabled
_disabled={{ opacity: 1 }}
>
{ storage?.length || '0' }
</Button>
<AccordionIcon color="blue.600" width="30px"/>
</AccordionButton>
</Flex>
<Flex rowGap={ 2 } flexDir="column" fontSize="sm">
<Text fontWeight={ 600 }>Miner</Text>
<Link>{ miner }</Link>
</Flex>
<Flex rowGap={ 2 } flexDir="column" fontSize="sm">
<Text fontWeight={ 600 }>Before</Text>
<Flex>
<Text>{ before.balance } ETH</Text>
<TextSeparator/>
{ typeof before.nonce !== 'undefined' && <Text>Nonce:{ nbsp }{ before.nonce }</Text> }
</Flex>
</Flex>
<Flex rowGap={ 2 } flexDir="column" fontSize="sm">
<Text fontWeight={ 600 }>After</Text>
<Text>{ after.balance } ETH</Text>
{ typeof after.nonce !== 'undefined' && <Text>Nonce:{ nbsp }{ after.nonce }</Text> }
</Flex>
<Flex rowGap={ 2 } flexDir="column" fontSize="sm">
<Text fontWeight={ 600 }>State difference</Text>
<Stat>
{ diff } ETH
<StatArrow ml={ 2 } type={ Number(diff) > 0 ? 'increase' : 'decrease' }/>
</Stat>
</Flex>
{ hasStorageData && (
<AccordionPanel fontWeight={ 500 } p={ 0 }>
{ storage?.map((storageItem, index) => <TxStateStorageItem key={ index } storageItem={ storageItem }/>) }
</AccordionPanel>
) }
</>
) }
</AccordionItem>
</AccountListItemMobile>
);
};
export default TxStateListItem;
...@@ -2,6 +2,7 @@ import { ...@@ -2,6 +2,7 @@ import {
Grid, Grid,
GridItem, GridItem,
Select, Select,
Box,
useColorModeValue, useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -20,7 +21,7 @@ const TxStateStorageItem = ({ storageItem }: {storageItem: TTxStateItemStorage}) ...@@ -20,7 +21,7 @@ const TxStateStorageItem = ({ storageItem }: {storageItem: TTxStateItemStorage})
const OPTIONS = [ 'Hex', 'Number', 'Text', 'Address' ]; const OPTIONS = [ 'Hex', 'Number', 'Text', 'Address' ];
return ( return (
<Grid <Grid
gridTemplateColumns="auto 1fr" gridTemplateColumns={{ base: '70px minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }}
columnGap={ 3 } columnGap={ 3 }
rowGap={ 4 } rowGap={ 4 }
px={ 6 } px={ 6 }
...@@ -28,11 +29,12 @@ const TxStateStorageItem = ({ storageItem }: {storageItem: TTxStateItemStorage}) ...@@ -28,11 +29,12 @@ const TxStateStorageItem = ({ storageItem }: {storageItem: TTxStateItemStorage})
background="blackAlpha.50" background="blackAlpha.50"
borderRadius="12px" borderRadius="12px"
mb={ 4 } mb={ 4 }
fontSize="sm"
> >
{ gridData.map((item) => ( { gridData.map((item) => (
<> <React.Fragment key={ item.name }>
<GridItem alignSelf="center" fontWeight={ 600 } textAlign="end">{ item.name }</GridItem> <GridItem alignSelf={{ base: 'start', lg: 'center' }} fontWeight={{ base: 500, lg: 600 }} textAlign="end">{ item.name }</GridItem>
<GridItem> <GridItem display="flex" flexDir={{ base: 'column', lg: 'row' }} rowGap={ 2 } alignItems={{ base: 'flex-start', lg: 'center' }} >
{ item.select && ( { item.select && (
<Select <Select
size="sm" size="sm"
...@@ -46,9 +48,11 @@ const TxStateStorageItem = ({ storageItem }: {storageItem: TTxStateItemStorage}) ...@@ -46,9 +48,11 @@ const TxStateStorageItem = ({ storageItem }: {storageItem: TTxStateItemStorage})
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) } { OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select> </Select>
) } ) }
{ item.value } <Box fontWeight={{ base: 400, lg: 500 }} maxW="100%">
{ item.value }
</Box>
</GridItem> </GridItem>
</> </React.Fragment>
)) } )) }
</Grid> </Grid>
); );
......
import {
Table,
Thead,
Tbody,
Tr,
Th,
TableContainer,
} from '@chakra-ui/react';
import React from 'react';
import { data } from 'data/txState';
import TxStateTableItem from 'ui/tx/state/TxStateTableItem';
const CURRENCY = 'ETH';
const TxStateTable = () => {
return (
<TableContainer width="100%" mt={ 6 }>
<Table variant="simple" minWidth="950px" size="sm">
<Thead>
<Tr>
<Th width="92px">Storage</Th>
<Th width="146px">Address</Th>
<Th width="120px">Miner</Th>
<Th width="33%" isNumeric>{ `After ${ CURRENCY }` }</Th>
<Th width="33%" isNumeric>{ `Before ${ CURRENCY }` }</Th>
<Th width="33%" isNumeric>{ `State difference ${ CURRENCY }` }</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item, index) => <TxStateTableItem txStateItem={ item } key={ index }/>) }
</Tbody>
</Table>
</TableContainer>
);
};
export default TxStateTable;
import { Box, Heading, Text, Flex, Link, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { txs } from 'data/txs';
import useLink from 'lib/link/useLink';
import TextSeparator from 'ui/shared/TextSeparator';
import Utilization from 'ui/shared/Utilization';
const TxAdditionalInfo = ({ tx }: { tx: ArrayElement<typeof txs> }) => {
const sectionBorderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const sectionProps = {
borderBottom: '1px solid',
borderColor: sectionBorderColor,
paddingBottom: 4,
};
const sectionTitleProps = {
color: 'gray.500',
fontWeight: 600,
marginBottom: 3,
};
const link = useLink();
return (
<>
<Heading as="h4" fontSize="18px" mb={ 6 }>Additional info </Heading>
<Box { ...sectionProps } mb={ 4 }>
<Text { ...sectionTitleProps }>Transaction fee</Text>
<Flex>
<Text>{ tx.fee.value } Ether</Text>
<Text variant="secondary" ml={ 1 }>(${ tx.fee.value_usd.toFixed(2) })</Text>
</Flex>
</Box>
<Box { ...sectionProps } mb={ 4 }>
<Text { ...sectionTitleProps }>Gas limit & usage by transaction</Text>
<Flex>
<Text>{ tx.gas_used.toLocaleString('en') }</Text>
<TextSeparator/>
<Text>{ tx.gas_limit.toLocaleString('en') }</Text>
<Utilization ml={ 4 } value={ tx.gas_used / tx.gas_limit }/>
</Flex>
</Box>
<Box { ...sectionProps } mb={ 4 }>
<Text { ...sectionTitleProps }>Gas fees (Gwei)</Text>
<Box>
<Text as="span" fontWeight="500">Base: </Text>
<Text fontWeight="600" as="span">{ tx.gas_fees.base }</Text>
</Box>
<Box>
<Text as="span" fontWeight="500">Max: </Text>
<Text fontWeight="600" as="span">{ tx.gas_fees.max }</Text>
</Box>
<Box>
<Text as="span" fontWeight="500">Max priority: </Text>
<Text fontWeight="600" as="span">{ tx.gas_fees.max_priority }</Text>
</Box>
</Box>
<Box { ...sectionProps } mb={ 4 }>
<Text { ...sectionTitleProps }>Others</Text>
<Box>
<Text as="span" fontWeight="500">Txn type: </Text>
<Text fontWeight="600" as="span">{ tx.type.value }</Text>
<Text fontWeight="400" as="span" ml={ 1 }>({ tx.type.eip })</Text>
</Box>
<Box>
<Text as="span" fontWeight="500">Nonce: </Text>
<Text fontWeight="600" as="span">{ tx.nonce }</Text>
</Box>
<Box>
<Text as="span" fontWeight="500">Position: </Text>
<Text fontWeight="600" as="span">{ tx.position }</Text>
</Box>
</Box>
<Link href={ link('tx_index', { id: tx.hash }) }>More details</Link>
</>
);
};
export default TxAdditionalInfo;
import {
Icon,
Center,
useColorModeValue,
} from '@chakra-ui/react';
import React from 'react';
import infoIcon from 'icons/info.svg';
const TxAdditionalInfoButton = ({ isOpen, onClick }: {isOpen?: boolean; onClick?: () => void}, ref: React.ForwardedRef<HTMLDivElement>) => {
const infoBgColor = useColorModeValue('blue.50', 'gray.600');
const infoColor = useColorModeValue('blue.600', 'blue.300');
return (
<Center ref={ ref } background={ isOpen ? infoBgColor : 'unset' } borderRadius="8px" w="30px" h="30px" onClick={ onClick }>
<Icon
as={ infoIcon }
boxSize={ 5 }
color={ infoColor }
_hover={{ color: 'blue.400' }}
/>
</Center>
);
};
export default React.forwardRef(TxAdditionalInfoButton);
import { Tag } from '@chakra-ui/react';
import React from 'react';
export interface Props {
type: 'contract-call' | 'transaction' | 'token-transfer' | 'internal-tx' | 'multicall';
}
const TxStatus = ({ type }: Props) => {
let label;
let colorScheme;
switch (type) {
case 'contract-call':
label = 'Contract call';
colorScheme = 'blue';
break;
case 'transaction':
label = 'Transaction';
colorScheme = 'purple';
break;
case 'token-transfer':
label = 'Token transfer';
colorScheme = 'orange';
break;
case 'internal-tx':
label = 'Internal txn';
colorScheme = 'cyan';
break;
case 'multicall':
label = 'Multicall';
colorScheme = 'teal';
break;
}
return (
<Tag colorScheme={ colorScheme }>
{ label }
</Tag>
);
};
export default TxStatus;
import { Box, HStack } from '@chakra-ui/react';
import React, { useCallback, useEffect, useState } from 'react';
import type { Sort } from 'types/client/txs-sort';
import { txs } from 'data/txs';
import useIsMobile from 'lib/hooks/useIsMobile';
import FilterButton from 'ui/shared/FilterButton';
import FilterInput from 'ui/shared/FilterInput';
import Pagination from 'ui/shared/Pagination';
import SortButton from 'ui/shared/SortButton';
import TxsListItem from './TxsListItem';
import TxsTable from './TxsTable';
type Props = {
isPending?: boolean;
}
const TxsContent = ({ isPending }: Props) => {
const isMobile = useIsMobile();
const [ sorting, setSorting ] = useState<Sort>();
const [ sortedTxs, setSortedTxs ] = useState(txs);
// sorting should be preserved with pagination!
const sort = useCallback((field: 'val' | 'fee') => () => {
if (field === 'val') {
setSorting((prevVal => {
if (prevVal === 'val-asc') {
return undefined;
}
if (prevVal === 'val-desc') {
return 'val-asc';
}
return 'val-desc';
}));
}
if (field === 'fee') {
setSorting((prevVal => {
if (prevVal === 'fee-asc') {
return undefined;
}
if (prevVal === 'fee-desc') {
return 'fee-asc';
}
return 'fee-desc';
}));
}
}, []);
useEffect(() => {
switch (sorting) {
case 'val-desc':
setSortedTxs([ ...txs ].sort((tx1, tx2) => tx1.amount.value - tx2.amount.value));
break;
case 'val-asc':
setSortedTxs([ ...txs ].sort((tx1, tx2) => tx2.amount.value - tx1.amount.value));
break;
case 'fee-desc':
setSortedTxs([ ...txs ].sort((tx1, tx2) => tx1.fee.value - tx2.fee.value));
break;
case 'fee-asc':
setSortedTxs([ ...txs ].sort((tx1, tx2) => tx2.fee.value - tx1.fee.value));
break;
default:
setSortedTxs(txs);
}
}, [ sorting ]);
return (
<>
{ !isPending && <Box mb={ 12 }>Only the first 10,000 elements are displayed</Box> }
<HStack mb={ 6 }>
{ /* TODO */ }
<FilterButton
isActive={ false }
isCollapsed={ isMobile }
// eslint-disable-next-line react/jsx-no-bind
onClick={ () => {} }
appliedFiltersNum={ 0 }
/>
<SortButton
// eslint-disable-next-line react/jsx-no-bind
handleSort={ () => {} }
isSortActive={ Boolean(sorting) }
/>
<FilterInput
// eslint-disable-next-line react/jsx-no-bind
onChange={ () => {} }
maxW="360px"
size="xs"
placeholder="Search by addresses, hash, method..."
/>
</HStack>
{ isMobile ?
sortedTxs.map(tx => <TxsListItem tx={ tx } key={ tx.hash }/>) :
<TxsTable txs={ sortedTxs } sort={ sort } sorting={ sorting }/> }
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
<Pagination currentPage={ 1 } isMobile={ isMobile }/>
</Box>
</>
);
};
export default TxsContent;
import {
HStack,
Box,
Flex,
Icon,
Link,
Modal,
ModalContent,
ModalCloseButton,
Text,
useColorModeValue,
useDisclosure } from '@chakra-ui/react';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { txs } from 'data/txs';
import rightArrowIcon from 'icons/arrows/east.svg';
import transactionIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
import useLink from 'lib/link/useLink';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import TxStatus from 'ui/shared/TxStatus';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxAdditionalInfoButton from 'ui/txs/TxAdditionalInfoButton';
import TxType from 'ui/txs/TxType';
const TxsListItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const iconColor = useColorModeValue('blue.600', 'blue.300');
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const link = useLink();
return (
<>
<Box width="100%" borderBottom="1px solid" borderColor={ borderColor } _first={{ borderTop: '1px solid', borderColor: { borderColor } }}>
<Flex justifyContent="space-between" mt={ 4 }>
<HStack>
<TxType type={ tx.txType }/>
<TxStatus status={ tx.status } errorText={ tx.errorText }/>
</HStack>
<TxAdditionalInfoButton onClick={ onOpen }/>
</Flex>
<Flex justifyContent="space-between" lineHeight="24px" mt={ 3 }>
<Flex>
<Icon
as={ transactionIcon }
boxSize="30px"
mr={ 2 }
color={ iconColor }
/>
<Address width="100%">
<AddressLink
hash={ tx.hash }
type="transaction"
fontWeight="700"
truncation="constant"
/>
</Address>
</Flex>
<Text variant="secondary" fontWeight="400">{ dayjs(tx.timestamp).fromNow() }</Text>
</Flex>
<Flex mt={ 3 }>
<Text as="span" whiteSpace="pre">Method </Text>
<Text
as="span"
variant="secondary"
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
{ tx.method }
</Text>
</Flex>
<Box mt={ 2 }>
<Text as="span">Block </Text>
<Link href={ link('block', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link>
</Box>
<Flex alignItems="center" height={ 6 } mt={ 6 }>
<Address width="calc((100%-40px)/2)">
<AddressIcon hash={ tx.address_from.hash }/>
<AddressLink
hash={ tx.address_from.hash }
alias={ tx.address_from.alias }
fontWeight="500"
ml={ 2 }
/>
</Address>
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
mx={ 2 }
color="gray.500"
/>
<Address width="calc((100%-40px)/2)">
<AddressIcon hash={ tx.address_to.hash }/>
<AddressLink
hash={ tx.address_to.hash }
alias={ tx.address_to.alias }
fontWeight="500"
ml={ 2 }
/>
</Address>
</Flex>
<Box mt={ 2 }>
<Text as="span">Value xDAI </Text>
<Text as="span" variant="secondary">{ tx.amount.value.toFixed(8) }</Text>
</Box>
<Box mt={ 2 } mb={ 3 }>
<Text as="span">Fee xDAI </Text>
<Text as="span" variant="secondary">{ tx.fee.value.toFixed(8) }</Text>
</Box>
</Box>
<Modal isOpen={ isOpen } onClose={ onClose } size="full">
<ModalContent paddingTop={ 4 }>
<ModalCloseButton/>
<TxAdditionalInfo tx={ tx }/>
</ModalContent>
</Modal>
</>
);
};
export default TxsListItem;
import React from 'react';
import TxsContent from './TxsContent';
const TxsPending = () => {
return <TxsContent isPending/>;
};
export default TxsPending;
import { Link, Table, Thead, Tbody, Tr, Th, TableContainer, Icon } from '@chakra-ui/react';
import React from 'react';
import type { Sort } from 'types/client/txs-sort';
import type { txs as data } from 'data/txs';
import rightArrowIcon from 'icons/arrows/east.svg';
import TxsTableItem from './TxsTableItem';
const CURRENCY = 'xDAI';
type Props = {
txs: typeof data;
sort: (field: 'val' | 'fee') => () => void;
sorting: Sort;
}
const TxsTable = ({ txs, sort, sorting }: Props) => {
return (
<TableContainer width="100%" mt={ 6 }>
<Table variant="simple" minWidth="810px" size="xs">
<Thead>
<Tr>
<Th width="54px"></Th>
<Th width="20%">Type</Th>
<Th width="18%">Txn hash</Th>
<Th width="15%">Method</Th>
<Th width="11%">Block</Th>
<Th width={{ xl: '128px', base: '58px' }}>From</Th>
<Th width={{ xl: '36px', base: '0' }}></Th>
<Th width={{ xl: '128px', base: '58px' }}>To</Th>
<Th width="18%" isNumeric>
<Link onClick={ sort('val') } display="flex" justifyContent="end">
{ sorting === 'val-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ sorting === 'val-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
{ `Value ${ CURRENCY }` }
</Link>
</Th>
<Th width="18%" isNumeric pr={ 5 }>
<Link onClick={ sort('fee') } display="flex" justifyContent="end">
{ sorting === 'fee-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ sorting === 'fee-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
{ `Fee ${ CURRENCY }` }
</Link>
</Th>
</Tr>
</Thead>
<Tbody>
{ txs.map((item) => (
<TxsTableItem
key={ item.hash }
tx={ item }
/>
)) }
</Tbody>
</Table>
</TableContainer>
);
};
export default TxsTable;
import {
Box,
Tr,
Td,
Tag,
Link,
Icon,
VStack,
Text,
Tooltip,
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
Portal,
useBreakpointValue,
useColorModeValue,
} from '@chakra-ui/react';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { txs } from 'data/txs';
import rightArrowIcon from 'icons/arrows/east.svg';
import dayjs from 'lib/date/dayjs';
import useLink from 'lib/link/useLink';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import TxStatus from 'ui/shared/TxStatus';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxAdditionalInfoButton from 'ui/txs/TxAdditionalInfoButton';
import TxType from './TxType';
const TxsTableItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => {
const link = useLink();
const isLargeScreen = useBreakpointValue({ base: false, xl: true });
const addressFrom = (
<Address>
<Tooltip label={ tx.address_from.type }>
<Box display="flex"><AddressIcon hash={ tx.address_from.hash }/></Box>
</Tooltip>
<AddressLink hash={ tx.address_from.hash } alias={ tx.address_from.alias } fontWeight="500" ml={ 2 }/>
</Address>
);
const addressTo = (
<Address>
<Tooltip label={ tx.address_to.type }>
<Box display="flex"> <AddressIcon hash={ tx.address_to.hash }/></Box>
</Tooltip>
<AddressLink hash={ tx.address_to.hash } alias={ tx.address_to.alias } fontWeight="500" ml={ 2 }/>
</Address>
);
const infoBorderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
return (
<Tr>
<Td pl={ 4 }>
<Popover placement="right-start" openDelay={ 300 }>
{ ({ isOpen }) => (
<>
<PopoverTrigger>
<TxAdditionalInfoButton isOpen={ isOpen }/>
</PopoverTrigger>
<Portal>
<PopoverContent border="1px solid" borderColor={ infoBorderColor }>
<PopoverBody>
<TxAdditionalInfo tx={ tx }/>
</PopoverBody>
</PopoverContent>
</Portal>
</>
) }
</Popover>
</Td>
<Td>
<VStack alignItems="start">
<TxType type={ tx.txType }/>
<TxStatus status={ tx.status } errorText={ tx.errorText }/>
</VStack>
</Td>
<Td>
<VStack alignItems="start" lineHeight="24px">
<Address width="100%">
<AddressLink
hash={ tx.hash }
type="transaction"
fontWeight="700"
/>
</Address>
<Text color="gray.500" fontWeight="400">{ dayjs(tx.timestamp).fromNow() }</Text>
</VStack>
</Td>
<Td>
<TruncatedTextTooltip label={ tx.method }>
<Tag
colorScheme={ tx.method === 'Multicall' ? 'teal' : 'gray' }
>
{ tx.method }
</Tag>
</TruncatedTextTooltip>
</Td>
<Td>
<Link href={ link('block', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link>
</Td>
{ isLargeScreen ? (
<>
<Td>
{ addressFrom }
</Td>
<Td>
<Icon as={ rightArrowIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
</Td>
<Td>
{ addressTo }
</Td>
</>
) : (
<Td colSpan={ 3 }>
<Box>
{ addressFrom }
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
mt={ 2 }
mb={ 1 }
color="gray.500"
transform="rotate(90deg)"
/>
{ addressTo }
</Box>
</Td>
) }
<Td isNumeric>
{ tx.amount.value.toFixed(8) }
</Td>
<Td isNumeric>
{ tx.fee.value.toFixed(8) }
</Td>
</Tr>
);
};
export default TxsTableItem;
import React from 'react';
import TxsContent from './TxsContent';
const TxsValidated = () => {
return <TxsContent/>;
};
export default TxsValidated;
...@@ -584,13 +584,6 @@ ...@@ -584,13 +584,6 @@
dependencies: dependencies:
"@chakra-ui/shared-utils" "2.0.1" "@chakra-ui/shared-utils" "2.0.1"
"@chakra-ui/icons@^2.0.2":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/icons/-/icons-2.0.9.tgz#35edcf9c61b8e158a5d03aeda6bd4e756960dfc7"
integrity sha512-6xvV2rC8wATgfnRH+fC9mi0nLcgKjhHKO29lV1pGioVI0yWK0dqc//zjcyBhMMpW5ABnSfig7ujVBf3op/Syzg==
dependencies:
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/image@2.0.10": "@chakra-ui/image@2.0.10":
version "2.0.10" version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/image/-/image-2.0.10.tgz#712c0e1c579d959225bd8316d8d8f66cbeb95bb8" resolved "https://registry.yarnpkg.com/@chakra-ui/image/-/image-2.0.10.tgz#712c0e1c579d959225bd8316d8d8f66cbeb95bb8"
......
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