Commit cdb577d3 authored by tom's avatar tom

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

parents 3387af98 1764e0b2
......@@ -257,7 +257,7 @@ const rollupSchema = yup
.when('NEXT_PUBLIC_ROLLUP_TYPE', {
is: (value: string) => value === 'optimistic',
then: (schema) => schema.test(urlTest).required(),
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined'),
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL can be used only if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' '),
}),
});
......
......@@ -402,8 +402,8 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ |
| NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0+ |
| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals | Required only for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ |
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (OP stack only) | - | - | `true` | v1.31.0+ |
| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals (Optimistic stack only) | Required for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ |
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ |
 
......
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.392 5.45c.252-.288.592-.45.948-.45h8.038a.63.63 0 0 1 .474.225l4.689 5.385a.83.83 0 0 1 .196.544v8.574l-.985 1.055-.355-.38v-8.666h-4.485a.702.702 0 0 1-.702-.702V6.538H7.34v16.924h9.44L18.217 25H7.34c-.356 0-.696-.162-.948-.45A1.661 1.661 0 0 1 6 23.461V6.538c0-.408.141-.799.392-1.087Zm9.222 1.678 2.791 3.205h-2.791V7.128ZM8.85 15.5a.65.65 0 0 1 .65-.65h7.2a.65.65 0 1 1 0 1.3H9.5a.65.65 0 0 1-.65-.65Zm0 2.4a.65.65 0 0 1 .65-.65h7.2a.65.65 0 1 1 0 1.3H9.5a.65.65 0 0 1-.65-.65Z" fill="currentColor"/>
<path d="m17.552 21.357 2.2 2.357 4.4-4.714" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
import type * as bens from '@blockscout/bens-types';
import type * as stats from '@blockscout/stats-types';
import type * as visualizer from '@blockscout/visualizer-types';
import { getFeaturePayload } from 'configs/app/features/types';
import type {
UserInfo,
......@@ -71,7 +73,7 @@ import type {
import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search';
import type { ShibariumWithdrawalsResponse, ShibariumDepositsResponse } from 'types/api/shibarium';
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
import type { HomeStats } from 'types/api/stats';
import type {
TokenCounters,
TokenInfo,
......@@ -99,7 +101,6 @@ import type { TxStateChanges } from 'types/api/txStateChanges';
import type { UserOpsResponse, UserOp, UserOpsFilters, UserOpsAccount } from 'types/api/userOps';
import type { ValidatorsCountersResponse, ValidatorsFilters, ValidatorsResponse, ValidatorsSorting } from 'types/api/validators';
import type { VerifiedContractsSorting } from 'types/api/verifiedContracts';
import type { VisualizedContract } from 'types/api/visualization';
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
import type {
ZkEvmL2DepositsResponse,
......@@ -931,9 +932,9 @@ Q extends 'homepage_zkevm_l2_batches' ? { items: Array<ZkEvmL2TxnBatchesItem> }
Q extends 'homepage_indexing_status' ? IndexingStatus :
Q extends 'homepage_zkevm_latest_batch' ? number :
Q extends 'homepage_zksync_latest_batch' ? number :
Q extends 'stats_counters' ? Counters :
Q extends 'stats_lines' ? StatsCharts :
Q extends 'stats_line' ? StatsChart :
Q extends 'stats_counters' ? stats.Counters :
Q extends 'stats_lines' ? stats.LineCharts :
Q extends 'stats_line' ? stats.LineChart :
Q extends 'blocks' ? BlocksResponse :
Q extends 'block' ? Block :
Q extends 'block_txs' ? BlockTransactionsResponse :
......@@ -986,7 +987,7 @@ Q extends 'contract' ? SmartContract :
Q extends 'contract_solidityscan_report' ? SolidityscanReport :
Q extends 'verified_contracts' ? VerifiedContractsResponse :
Q extends 'verified_contracts_counters' ? VerifiedContractsCounters :
Q extends 'visualize_sol2uml' ? VisualizedContract :
Q extends 'visualize_sol2uml' ? visualizer.VisualizeResponse :
Q extends 'contract_verification_config' ? SmartContractVerificationConfig :
Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters :
......
......@@ -14,6 +14,7 @@ const defaultOptions: UseToastOptions & { toastComponent?: React.FC<ToastProps>
containerStyle: {
margin: 8,
},
variant: 'subtle',
};
export default function useToastModified() {
......
......@@ -49,6 +49,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
// service routes, added only to make typescript happy
'/login': 'Regular page',
'/sprite': 'Regular page',
'/api/metrics': 'Regular page',
'/api/log': 'Regular page',
'/api/media-type': 'Regular page',
......@@ -56,6 +57,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/api/csrf': 'Regular page',
'/api/healthz': 'Regular page',
'/api/config': 'Regular page',
'/api/sprite': 'Regular page',
'/auth/auth0': 'Regular page',
'/auth/unverified-email': 'Regular page',
};
......
......@@ -53,6 +53,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
// service routes, added only to make typescript happy
'/login': DEFAULT_TEMPLATE,
'/sprite': DEFAULT_TEMPLATE,
'/api/metrics': DEFAULT_TEMPLATE,
'/api/log': DEFAULT_TEMPLATE,
'/api/media-type': DEFAULT_TEMPLATE,
......@@ -60,6 +61,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/api/csrf': DEFAULT_TEMPLATE,
'/api/healthz': DEFAULT_TEMPLATE,
'/api/config': DEFAULT_TEMPLATE,
'/api/sprite': DEFAULT_TEMPLATE,
'/auth/auth0': DEFAULT_TEMPLATE,
'/auth/unverified-email': DEFAULT_TEMPLATE,
};
......
......@@ -49,13 +49,15 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
// service routes, added only to make typescript happy
'/login': '%network_name% login',
'/sprite': '%network_name% SVG sprite',
'/api/metrics': '%network_name% node API prometheus metrics',
'/api/log': '%network_name% node API request log',
'/api/media-type': '%network_name% node API media type',
'/api/proxy': '%network_name% node API proxy',
'/api/csrf': '%network_name% node API CSRF token',
'/api/healthz': '%network_name% node API health check',
'/api/config': '%network_name% node API health check',
'/api/config': '%network_name% node API app config',
'/api/sprite': '%network_name% node API SVG sprite content',
'/auth/auth0': '%network_name% authentication',
'/auth/unverified-email': '%network_name% unverified email',
};
......
......@@ -47,13 +47,15 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
// service routes, added only to make typescript happy
'/login': 'Login',
'/sprite': 'Sprite',
'/api/metrics': 'Node API: Prometheus metrics',
'/api/log': 'Node API: Request log',
'/api/media-type': 'Node API: Media type',
'/api/proxy': 'Node API: Proxy',
'/api/csrf': 'Node API: CSRF token',
'/api/healthz': 'Node API: Health check',
'/api/config': 'Node API: Health check',
'/api/config': 'Node API: App config',
'/api/sprite': 'Node API: SVG sprite content',
'/auth/auth0': 'Auth',
'/auth/unverified-email': 'Unverified email',
};
......
export const averageGasPrice = {
import type * as stats from '@blockscout/stats-types';
export const averageGasPrice: stats.LineChart = {
chart: [
{
date: '2023-12-22',
value: '37.7804422597599',
is_approximate: false,
},
{
date: '2023-12-23',
value: '25.84889883009387',
is_approximate: false,
},
{
date: '2023-12-24',
value: '25.818463227198574',
is_approximate: false,
},
{
date: '2023-12-25',
value: '26.045513050051298',
is_approximate: false,
},
{
date: '2023-12-26',
value: '21.42600692652399',
is_approximate: false,
},
{
date: '2023-12-27',
value: '31.066730409846656',
is_approximate: false,
},
{
date: '2023-12-28',
value: '33.63955781902089',
is_approximate: false,
},
{
date: '2023-12-29',
value: '28.064736756058384',
is_approximate: false,
},
{
date: '2023-12-30',
value: '23.074500869678175',
is_approximate: false,
},
{
date: '2023-12-31',
value: '17.651005734615133',
is_approximate: false,
},
{
date: '2024-01-01',
value: '14.906085174476441',
is_approximate: false,
},
{
date: '2024-01-02',
value: '22.28459059038656',
is_approximate: false,
},
{
date: '2024-01-03',
value: '39.8311646806592',
is_approximate: false,
},
{
date: '2024-01-04',
value: '26.09989322256083',
is_approximate: false,
},
{
date: '2024-01-05',
value: '22.821996688111998',
is_approximate: false,
},
{
date: '2024-01-06',
value: '20.32680041262083',
is_approximate: false,
},
{
date: '2024-01-07',
value: '32.535045831809704',
is_approximate: false,
},
{
date: '2024-01-08',
value: '27.443477102139482',
is_approximate: false,
},
{
date: '2024-01-09',
value: '20.7911332558055',
is_approximate: false,
},
{
date: '2024-01-10',
value: '42.10740192523919',
is_approximate: false,
},
{
date: '2024-01-11',
value: '35.75215680343582',
is_approximate: false,
},
{
date: '2024-01-12',
value: '27.430414798093253',
is_approximate: false,
},
{
date: '2024-01-13',
value: '20.170934096589875',
is_approximate: false,
},
{
date: '2024-01-14',
value: '38.79660984371034',
is_approximate: false,
},
{
date: '2024-01-15',
value: '26.140740484554204',
is_approximate: false,
},
{
date: '2024-01-16',
value: '36.708543184194156',
is_approximate: false,
},
{
date: '2024-01-17',
value: '40.325438794298876',
is_approximate: false,
},
{
date: '2024-01-18',
value: '37.55145309930694',
is_approximate: false,
},
{
date: '2024-01-19',
value: '33.271450114434664',
is_approximate: false,
},
{
date: '2024-01-20',
value: '19.303304377685638',
is_approximate: false,
},
{
date: '2024-01-21',
value: '14.375908594704976',
is_approximate: false,
},
],
};
export const base = {
import type * as stats from '@blockscout/stats-types';
export const base: stats.LineCharts = {
sections: [
{
id: 'accounts',
......@@ -8,19 +10,19 @@ export const base = {
id: 'accountsGrowth',
title: 'Accounts growth',
description: 'Cumulative accounts number per period',
units: null,
units: undefined,
},
{
id: 'activeAccounts',
title: 'Active accounts',
description: 'Active accounts number per period',
units: null,
units: undefined,
},
{
id: 'newAccounts',
title: 'New accounts',
description: 'New accounts number per day',
units: null,
units: undefined,
},
],
},
......@@ -38,7 +40,7 @@ export const base = {
id: 'newTxns',
title: 'New transactions',
description: 'New transactions number',
units: null,
units: undefined,
},
{
id: 'txnsFee',
......@@ -50,13 +52,13 @@ export const base = {
id: 'txnsGrowth',
title: 'Transactions growth',
description: 'Cumulative transactions number',
units: null,
units: undefined,
},
{
id: 'txnsSuccessRate',
title: 'Transactions success rate',
description: 'Successful transactions rate per day',
units: null,
units: undefined,
},
],
},
......@@ -80,7 +82,7 @@ export const base = {
id: 'newBlocks',
title: 'New blocks',
description: 'New blocks number',
units: null,
units: undefined,
},
],
},
......@@ -92,7 +94,7 @@ export const base = {
id: 'newNativeCoinTransfers',
title: 'New ETH transfers',
description: 'New token transfers number for the period',
units: null,
units: undefined,
},
],
},
......@@ -104,7 +106,7 @@ export const base = {
id: 'averageGasLimit',
title: 'Average gas limit',
description: 'Average gas limit per block for the period',
units: null,
units: undefined,
},
{
id: 'averageGasPrice',
......@@ -116,7 +118,7 @@ export const base = {
id: 'gasUsedGrowth',
title: 'Gas used growth',
description: 'Cumulative gas used for the period',
units: null,
units: undefined,
},
],
},
......@@ -128,13 +130,13 @@ export const base = {
id: 'newVerifiedContracts',
title: 'New verified contracts',
description: 'New verified contracts number for the period',
units: null,
units: undefined,
},
{
id: 'verifiedContractsGrowth',
title: 'Verified contracts growth',
description: 'Cumulative number verified contracts for the period',
units: null,
units: undefined,
},
],
},
......
......@@ -245,6 +245,16 @@ export const login: GetServerSideProps<Props> = async(context) => {
return base(context);
};
export const dev: GetServerSideProps<Props> = async(context) => {
if (!config.app.isDev) {
return {
notFound: true,
};
}
return base(context);
};
export const publicTagsSubmit: GetServerSideProps<Props> = async(context) => {
if (!config.features.publicTagsSubmission.isEnabled) {
......
......@@ -22,6 +22,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/api/media-type">
| StaticRoute<"/api/metrics">
| StaticRoute<"/api/proxy">
| StaticRoute<"/api/sprite">
| StaticRoute<"/api-docs">
| DynamicRoute<"/apps/[id]", { "id": string }>
| StaticRoute<"/apps">
......@@ -48,6 +49,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/output-roots">
| StaticRoute<"/public-tags/submit">
| StaticRoute<"/search-results">
| StaticRoute<"/sprite">
| StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }>
| DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }>
......
......@@ -35,7 +35,9 @@
"monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise"
},
"dependencies": {
"@blockscout/bens-types": "v1.3.0-beta",
"@blockscout/bens-types": "1.3.4",
"@blockscout/stats-types": "1.6.0",
"@blockscout/visualizer-types": "0.2.0",
"@chakra-ui/react": "2.7.1",
"@chakra-ui/theme-tools": "^2.0.18",
"@emotion/react": "^11.10.4",
......@@ -67,13 +69,13 @@
"chakra-react-select": "^4.4.3",
"crypto-js": "^4.2.0",
"d3": "^7.6.1",
"dappscout-iframe": "0.2.1",
"dappscout-iframe": "0.2.2",
"dayjs": "^1.11.5",
"dom-to-image": "^2.6.0",
"focus-visible": "^5.2.0",
"framer-motion": "^6.5.1",
"getit-sdk": "^1.0.4",
"gradient-avatar": "^1.0.2",
"gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git",
"graphiql": "^2.2.0",
"graphql": "^16.8.1",
"graphql-ws": "^5.11.3",
......
import fs from 'fs';
import type { NextApiRequest, NextApiResponse } from 'next';
import path from 'path';
import config from 'configs/app';
const ROOT_DIR = './icons';
const NAME_PREFIX = ROOT_DIR.replace('./', '') + '/';
interface IconInfo {
name: string;
fileSize: number;
}
const getIconName = (filePath: string) => filePath.replace(NAME_PREFIX, '').replace('.svg', '');
function collectIconNames(dir: string) {
const files = fs.readdirSync(dir, { withFileTypes: true });
let icons: Array<IconInfo> = [];
files.forEach((file) => {
const filePath = path.join(dir, file.name);
const stats = fs.statSync(filePath);
file.name.endsWith('.svg') && icons.push({
name: getIconName(filePath),
fileSize: stats.size,
});
if (file.isDirectory()) {
icons = [ ...icons, ...collectIconNames(filePath) ];
}
});
return icons;
}
export default async function spriteHandler(req: NextApiRequest, res: NextApiResponse) {
if (!config.app.isDev) {
return res.status(404).json({ error: 'Not found' });
}
const icons = collectIconNames(ROOT_DIR);
res.status(200).json({
icons,
});
}
import type { NextPage } from 'next';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
import Sprite from 'ui/pages/Sprite';
const Page: NextPage = () => {
return (
<PageNextJs pathname="/sprite">
<Sprite/>
</PageNextJs>
);
};
export default Page;
export { dev as getServerSideProps } from 'nextjs/getServerSideProps';
import type { TestFixture, Page } from '@playwright/test';
import _isEqual from 'lodash/isEqual';
import type { PublicRpcSchema } from 'viem';
import { getEnvValue } from 'configs/app/utils';
type Params = PublicRpcSchema[number];
export type MockRpcResponseFixture = (params: Params) => Promise<void>;
// WIP
const fixture: TestFixture<MockRpcResponseFixture, { page: Page }> = async({ page }, use) => {
await use(async({ Method, ReturnType }) => {
const rpcUrl = getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL');
if (!rpcUrl) {
return;
}
await page.route(rpcUrl, (route) => {
const method = route.request().method();
if (method !== 'POST') {
route.continue();
return;
}
const json = route.request().postDataJSON();
const id = json?.id;
const payload = {
id,
jsonrpc: '2.0',
method: Method,
// TODO: add params to match actual payload
};
if (_isEqual(json, payload) && id !== undefined) {
return route.fulfill({
status: 200,
body: JSON.stringify({
id,
jsonrpc: '2.0',
result: ReturnType,
}),
});
}
});
});
};
export default fixture;
......@@ -8,6 +8,7 @@ import * as mockConfigResponse from './fixtures/mockConfigResponse';
import * as mockContractReadResponse from './fixtures/mockContractReadResponse';
import * as mockEnvs from './fixtures/mockEnvs';
import * as mockFeatures from './fixtures/mockFeatures';
import * as mockRpcResponse from './fixtures/mockRpcResponse';
import * as mockTextAd from './fixtures/mockTextAd';
import * as render from './fixtures/render';
import * as socketServer from './fixtures/socketServer';
......@@ -20,6 +21,7 @@ interface Fixtures {
mockContractReadResponse: mockContractReadResponse.MockContractReadResponseFixture;
mockEnvs: mockEnvs.MockEnvsFixture;
mockFeatures: mockFeatures.MockFeaturesFixture;
mockRpcResponse: mockRpcResponse.MockRpcResponseFixture;
createSocket: socketServer.CreateSocketFixture;
injectMetaMaskProvider: injectMetaMaskProvider.InjectMetaMaskProvider;
mockTextAd: mockTextAd.MockTextAdFixture;
......@@ -33,6 +35,7 @@ const test = base.extend<Fixtures>({
mockContractReadResponse: mockContractReadResponse.default,
mockEnvs: mockEnvs.default,
mockFeatures: mockFeatures.default,
mockRpcResponse: mockRpcResponse.default,
// FIXME: for some reason Playwright does not intercept requests to text ad provider when running multiple tests in parallel
// even if we have a global request interceptor (maybe it is related to service worker issue, maybe not)
// so we have to inject mockTextAd fixture in each test and mock the response where it is needed
......
......@@ -13,6 +13,7 @@
| "arrows/north-east"
| "arrows/south-east"
| "arrows/up-down"
| "arrows/up-head"
| "beta_xs"
| "beta"
| "blob"
......@@ -30,10 +31,10 @@
| "clock"
| "coins/bitcoin"
| "collection"
| "contract_verified"
| "contract"
| "contracts_verified"
| "contracts"
| "contracts/regular_many"
| "contracts/regular"
| "contracts/verified_many"
| "contracts/verified"
| "copy"
| "cross"
| "delete"
......@@ -59,7 +60,6 @@
| "files/sol"
| "files/yul"
| "filter"
| "finalized"
| "flame"
| "games"
| "gas_xl"
......@@ -99,7 +99,7 @@
| "publictags"
| "qr_code"
| "refresh"
| "repeat_arrow"
| "repeat"
| "restAPI"
| "rocket_xl"
| "rocket"
......@@ -146,14 +146,13 @@
| "transactions"
| "txn_batches_slim"
| "txn_batches"
| "unfinalized"
| "uniswap"
| "up"
| "user_op_slim"
| "user_op"
| "validator"
| "verification-steps/finalized"
| "verification-steps/unfinalized"
| "verified"
| "verify-contract"
| "wallet"
| "wallets/coinbase"
| "wallets/metamask"
......
import type { Counter, HomeStats, StatsChartsSection } from 'types/api/stats';
import type * as stats from '@blockscout/stats-types';
import type { HomeStats } from 'types/api/stats';
export const HOMEPAGE_STATS: HomeStats = {
average_block_time: 14346,
......@@ -41,7 +42,7 @@ export const HOMEPAGE_STATS: HomeStats = {
tvl: '1767425.102766552',
};
export const STATS_CHARTS_SECTION: StatsChartsSection = {
export const STATS_CHARTS_SECTION: stats.LineChartSection = {
id: 'placeholder',
title: 'Placeholder',
charts: [
......@@ -61,13 +62,13 @@ export const STATS_CHARTS_SECTION: StatsChartsSection = {
id: 'chart_2',
title: 'New transactions',
description: 'New transactions number',
units: null,
units: undefined,
},
{
id: 'chart_3',
title: 'Transactions growth',
description: 'Cumulative transactions number',
units: null,
units: undefined,
},
],
};
......@@ -76,7 +77,7 @@ export const STATS_CHARTS = {
sections: [ STATS_CHARTS_SECTION ],
};
export const STATS_COUNTER: Counter = {
export const STATS_COUNTER: stats.Counter = {
id: 'stub',
value: '9074405',
title: 'Placeholder Counter',
......
......@@ -34,39 +34,3 @@ export interface GasPriceInfo {
base_fee: number | null;
priority_fee: number | null;
}
export type Counters = {
counters: Array<Counter>;
}
export type Counter = {
id: string;
value: string;
title: string;
description?: string;
units: string;
}
export type StatsCharts = {
sections: Array<StatsChartsSection>;
}
export type StatsChartsSection = {
id: string;
title: string;
charts: Array<StatsChartInfo>;
}
export type StatsChartInfo = {
id: string;
title: string;
description: string;
units: string | null;
}
export type StatsChart = { chart: Array<StatsChartItem> };
export type StatsChartItem = {
date: string;
value: string;
}
export interface VisualizedContract {
png: string | null;
svg: string | null;
}
......@@ -222,7 +222,7 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props)
onClick={ onReset }
ml={ 1 }
>
<IconSvg name="repeat_arrow" boxSize={ 5 } mr={ 1 }/>
<IconSvg name="repeat" boxSize={ 5 } mr={ 1 }/>
Reset
</Button>
) }
......
......@@ -86,33 +86,33 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => {
</Skeleton>
) : data.tx_count }
</Td>
<Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ BigNumber(data.gas_used || 0).toFormat() }</Skeleton>
<Flex mt={ 2 }>
<Tooltip label={ isLoading ? undefined : 'Gas Used %' }>
<Box>
<Utilization
colorScheme="gray"
value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() }
isLoading={ isLoading }
/>
</Box>
</Tooltip>
{ data.gas_target_percentage && (
<>
<TextSeparator color={ separatorColor } mx={ 1 }/>
<GasUsedToTargetRatio value={ data.gas_target_percentage } isLoading={ isLoading }/>
</>
) }
</Flex>
</Td>
{ !isRollup && !config.UI.views.block.hiddenFields?.total_reward && (
<Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ BigNumber(data.gas_used || 0).toFormat() }</Skeleton>
<Flex mt={ 2 }>
<Tooltip label={ isLoading ? undefined : 'Gas Used %' }>
<Box>
<Utilization
colorScheme="gray"
value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() }
isLoading={ isLoading }
/>
</Box>
</Tooltip>
{ data.gas_target_percentage && (
<>
<TextSeparator color={ separatorColor } mx={ 1 }/>
<GasUsedToTargetRatio value={ data.gas_target_percentage } isLoading={ isLoading }/>
</>
) }
</Flex>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ totalReward.toFixed(8) }
</Skeleton>
</Td>
) }
<Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ totalReward.toFixed(8) }
</Skeleton>
</Td>
{ !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees && (
<Td fontSize="sm">
<Flex alignItems="center" columnGap={ 2 }>
......
......@@ -68,7 +68,7 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC
return (
<Skeleton isLoaded={ !stats.isPlaceholderData } ml={ 3 } display="flex" alignItems="center" color={ diffColor }>
<IconSvg name="up" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<IconSvg name="arrows/up-head" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text>
</Skeleton>
);
......
......@@ -77,7 +77,7 @@ const ChainIndicators = () => {
return (
<Skeleton isLoaded={ !statsQueryResult.isPlaceholderData } display="flex" alignItems="center" color={ diffColor } ml={ 2 }>
<IconSvg name="up" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<IconSvg name="arrows/up-head" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text>
</Skeleton>
);
......
......@@ -77,7 +77,7 @@ const AppSecurityReport = ({
<Text fontWeight="500" fontSize="xs" mb={ 2 } variant="secondary">Smart contracts info</Text>
<Flex alignItems="center" justifyContent="space-between" py={ 1.5 }>
<Flex alignItems="center">
<IconSvg name="contracts_verified" boxSize={ 5 } color="green.500" mr={ 1 }/>
<IconSvg name="contracts/verified_many" boxSize={ 5 } color="green.500" mr={ 1 }/>
<Text>Verified contracts</Text>
</Flex>
<Link fontSize="sm" fontWeight="500" onClick={ showAllContracts }>
......
......@@ -179,7 +179,7 @@ const MarketplaceAppCard = ({
showContractList={ showContractList }
isLoading={ isLoading }
source="Discovery view"
popoverPlacement={ isMobile ? 'bottom-end' : 'bottom-start' }
popoverPlacement={ isMobile ? 'bottom-end' : 'left' }
position="absolute"
right={{ base: 3, md: 5 }}
top={{ base: '10px', md: 5 }}
......
......@@ -191,7 +191,7 @@ const MarketplaceAppModal = ({
mb={ 6 }
>
<Flex alignItems="center" gap={ 2 } flexWrap="wrap">
<IconSvg name="contracts_verified" boxSize={ 5 } color="green.500"/>
<IconSvg name="contracts/verified_many" boxSize={ 5 } color="green.500"/>
<Text>Verified contracts</Text>
<Text fontWeight="500">
{ securityReport?.overallInfo.verifiedNumber ?? 0 } of { securityReport?.overallInfo.totalContractsNumber ?? 0 }
......
......@@ -53,7 +53,7 @@ const NameDomainsActionBar = ({
minW={{ base: 'auto', lg: '250px' }}
size="xs"
onChange={ onSearchChange }
placeholder="Search by name"
placeholder="Search by name or address"
initialValue={ searchTerm }
isLoading={ isInitialLoading }
/>
......
......@@ -64,21 +64,12 @@ const GasTracker = () => {
</Flex>
);
const content = (() => {
const snippets = (() => {
if (!isPlaceholderData && data?.gas_prices?.slow === null && data?.gas_prices.average === null && data.gas_prices.fast === null) {
return <Alert status="warning">No data available yet</Alert>;
return <Alert status="warning">No recent data available</Alert>;
}
return (
<>
{ data?.gas_prices && <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> }
{ config.features.stats.isEnabled && (
<Box mt={ 12 }>
<GasTrackerChart/>
</Box>
) }
</>
);
return data?.gas_prices ? <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> : null;
})();
return (
......@@ -88,7 +79,12 @@ const GasTracker = () => {
secondRow={ titleSecondRow }
withTextAd
/>
{ content }
{ snippets }
{ config.features.stats.isEnabled && (
<Box mt={ 12 }>
<GasTrackerChart/>
</Box>
) }
</>
);
};
......
......@@ -33,9 +33,9 @@ const testFn: Parameters<typeof test>[1] = async({ render, mockConfigResponse, m
await expect(component).toHaveScreenshot();
};
test('base view +@dark-mode', testFn);
test.fixme('base view +@dark-mode', testFn);
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', testFn);
test.fixme('base view', testFn);
});
import { Flex, Box, Tooltip, useClipboard, useColorModeValue } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { StaticRoute } from 'nextjs-routes';
import { route } from 'nextjs-routes';
import useFetch from 'lib/hooks/useFetch';
import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import EmptySearchResult from 'ui/shared/EmptySearchResult';
import FilterInput from 'ui/shared/filters/FilterInput';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg';
import PageTitle from 'ui/shared/Page/PageTitle';
const formatFileSize = (fileSizeInBytes: number) => `${ (fileSizeInBytes / 1_024).toFixed(2) } Kb`;
interface IconInfo {
name: string;
fileSize: number;
}
const Item = ({ name, fileSize, bgColor }: IconInfo & { bgColor: string }) => {
const { hasCopied, onCopy } = useClipboard(name, 1000);
const [ copied, setCopied ] = React.useState(false);
React.useEffect(() => {
if (hasCopied) {
setCopied(true);
} else {
setCopied(false);
}
}, [ hasCopied ]);
return (
<Flex
flexDir="column"
alignItems="center"
whiteSpace="pre-wrap"
wordBreak="break-word"
maxW="100px"
textAlign="center"
onClick={ onCopy }
cursor="pointer"
>
<IconSvg name={ name as IconName } boxSize="100px" bgColor={ bgColor } borderRadius="base"/>
<Tooltip label={ copied ? 'Copied' : 'Copy to clipboard' } isOpen={ copied }>
<Box fontWeight={ 500 } mt={ 2 }>{ name }</Box>
</Tooltip>
<Box color="text_secondary">{ formatFileSize(fileSize) }</Box>
</Flex>
);
};
const Sprite = () => {
const [ searchTerm, setSearchTerm ] = React.useState('');
const bgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');
const fetch = useFetch();
const { data, isFetching, isError } = useQuery({
queryKey: [ 'sprite' ],
queryFn: () => {
const url = route({ pathname: '/node-api/sprite' as StaticRoute<'/api/sprite'>['pathname'] });
return fetch<{ icons: Array<IconInfo> }, unknown>(url);
},
});
const content = (() => {
if (isFetching) {
return <ContentLoader/>;
}
if (isError || !data || !('icons' in data)) {
return <DataFetchAlert/>;
}
const items = data.icons.filter((icon) => icon.name.includes(searchTerm));
if (items.length === 0) {
return <EmptySearchResult text="No icons found"/>;
}
return (
<Flex flexWrap="wrap" fontSize="sm" columnGap={ 5 } rowGap={ 5 } justifyContent="flex-start">
{ items.map((item) => <Item key={ item.name } { ...item } bgColor={ bgColor }/>) }
</Flex>
);
})();
const total = React.useMemo(() => {
if (!data || !('icons' in data)) {
return;
}
return data?.icons.reduce((result, item) => {
result.num++;
result.fileSize += item.fileSize;
return result;
}, { num: 0, fileSize: 0 });
}, [ data ]);
const searchInput = <FilterInput placeholder="Search by name..." onChange={ setSearchTerm } isLoading={ isFetching } minW={{ base: '100%', lg: '300px' }}/>;
const totalEl = total ? <Box ml="auto">Items: { total.num } / Size: { formatFileSize(total.fileSize) }</Box> : null;
const contentAfter = (
<>
{ totalEl }
{ searchInput }
</>
);
return (
<div>
<PageTitle title="SVG sprite 🥤" contentAfter={ contentAfter }/>
{ content }
</div>
);
};
export default React.memo(Sprite);
......@@ -20,7 +20,7 @@ function getBgColor(status?: AlertStatus) {
}
}
const Toast = ({ onClose, title, description, id, isClosable, status }: ToastProps) => {
const Toast = ({ onClose, title, description, id, isClosable, status, icon }: ToastProps) => {
const ids = id ?
{
......@@ -48,7 +48,7 @@ const Toast = ({ onClose, title, description, id, isClosable, status }: ToastPro
maxWidth="400px"
>
<chakra.div flex="1" maxWidth="100%">
{ title && <AlertTitle id={ ids?.title }>{ title }</AlertTitle> }
{ title && <AlertTitle id={ ids?.title } display="flex" alignItems="center">{ icon }{ title }</AlertTitle> }
{ description && (
<AlertDescription id={ ids?.description } display="block">
{ description }
......
......@@ -198,7 +198,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError,
size="sm"
variant="outline"
onClick={ handleZoomResetClick }
icon={ <IconSvg name="repeat_arrow" w={ 4 } h={ 4 }/> }
icon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
/>
</Tooltip>
......
......@@ -71,7 +71,7 @@ const FullscreenChartModal = ({
{ !isZoomResetInitial && (
<Button
leftIcon={ <IconSvg name="repeat_arrow" w={ 4 } h={ 4 }/> }
leftIcon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
colorScheme="blue"
gridColumn={ 2 }
justifySelf="end"
......
......@@ -63,7 +63,7 @@ const Icon = (props: IconProps) => {
<span>
<EntityBase.Icon
{ ...props }
name="contract_verified"
name="contracts/verified"
color="green.500"
borderRadius={ 0 }
/>
......@@ -77,7 +77,7 @@ const Icon = (props: IconProps) => {
<span>
<EntityBase.Icon
{ ...props }
name="contract"
name="contracts/regular"
borderRadius={ 0 }
/>
</span>
......
......@@ -54,7 +54,7 @@ const Icon = dynamic(
// eslint-disable-next-line react/display-name
return (props: IconProps) => {
const svg = GradientAvatar(props.hash, props.size);
const svg = GradientAvatar(props.hash, props.size, 'circle');
return <div dangerouslySetInnerHTML={{ __html: svg }}/>;
};
}
......
......@@ -34,7 +34,7 @@ if (config.features.dataAvailability.isEnabled) {
}
if (config.features.nameService.isEnabled) {
searchCategories.push({ id: 'domain', title: 'Names' });
searchCategories.unshift({ id: 'domain', title: 'Names' });
}
export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleShort: string }> = {
......
......@@ -22,7 +22,7 @@ const VerificationStep = ({ step, isLast, isPassed, isPending }: Props) => {
return (
<HStack gap={ 2 } color={ stepColor }>
<IconSvg name={ isPassed ? 'finalized' : 'unfinalized' } boxSize={ 5 }/>
<IconSvg name={ isPassed ? 'verification-steps/finalized' : 'verification-steps/unfinalized' } boxSize={ 5 }/>
<Box color={ stepColor }>{ typeof step === 'string' ? step : step.content }</Box>
{ !isLast && <IconSvg name="arrows/east" boxSize={ 5 }/> }
</HStack>
......
import { chakra, Tooltip, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type * as visualizer from '@blockscout/visualizer-types';
import type { SmartContract } from 'types/api/contract';
import type { ResourceError } from 'lib/api/resources';
......@@ -13,7 +14,7 @@ interface Props {
addressHash: string;
}
function composeSources(contract: SmartContract | undefined) {
function composeSources(contract: SmartContract | undefined): visualizer.VisualizeStorageRequest['sources'] {
if (!contract) {
return {};
}
......@@ -23,7 +24,7 @@ function composeSources(contract: SmartContract | undefined) {
}, {});
return {
[contract.file_path || 'index.sol']: contract.source_code,
[contract.file_path || 'index.sol']: contract.source_code || '',
...additionalSources,
};
}
......@@ -76,7 +77,7 @@ const Sol2UmlDiagram = ({ addressHash }: Props) => {
return (
<Tooltip label="Click on image to zoom" placement="top">
<chakra.img
src={ `data:image/svg+xml;base64,${ umlQuery.data.svg }` }
src={ imgUrl }
alt={ `Contract ${ contractQuery.data.name } UML diagram` }
onClick={ handleClick }
cursor="pointer"
......
import { Box, Grid, Heading, List, ListItem, Skeleton } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react';
import type { StatsChartsSection } from 'types/api/stats';
import type * as stats from '@blockscout/stats-types';
import type { StatsIntervalIds } from 'types/client/stats';
import useApiQuery from 'lib/api/useApiQuery';
......@@ -18,7 +18,7 @@ type Props = {
initialFilterQuery: string;
isError: boolean;
isPlaceholderData: boolean;
charts?: Array<StatsChartsSection>;
charts?: Array<stats.LineChartSection>;
interval: StatsIntervalIds;
}
......
......@@ -29,7 +29,7 @@ const NumberWidgetsList = () => {
data?.counters?.map(({ id, title, value, units, description }, index) => {
let unitsStr = '';
if (UNITS_WITHOUT_SPACE.includes(units)) {
if (units && UNITS_WITHOUT_SPACE.includes(units)) {
unitsStr = units;
} else if (units) {
unitsStr = ' ' + units;
......
import { Grid, GridItem, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { StatsChartsSection } from 'types/api/stats';
import type * as stats from '@blockscout/stats-types';
import type { StatsInterval, StatsIntervalIds } from 'types/client/stats';
import FilterInput from 'ui/shared/filters/FilterInput';
......@@ -15,7 +15,7 @@ const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({
})) as Array<StatsInterval>;
type Props = {
sections?: Array<StatsChartsSection>;
sections?: Array<stats.LineChartSection>;
currentSection: string;
onSectionChange: (newSection: string) => void;
interval: StatsIntervalIds;
......
import { useRouter } from 'next/router';
import React, { useCallback, useMemo, useState } from 'react';
import type { StatsChartInfo, StatsChartsSection } from 'types/api/stats';
import type * as stats from '@blockscout/stats-types';
import type { StatsIntervalIds } from 'types/client/stats';
import useApiQuery from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString';
import { STATS_CHARTS } from 'stubs/stats';
function isSectionMatches(section: StatsChartsSection, currentSection: string): boolean {
function isSectionMatches(section: stats.LineChartSection, currentSection: string): boolean {
return currentSection === 'all' || section.id === currentSection;
}
function isChartNameMatches(q: string, chart: StatsChartInfo) {
function isChartNameMatches(q: string, chart: stats.LineChartInfo) {
return chart.title.toLowerCase().includes(q.toLowerCase());
}
......
import { chakra, Alert, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import type { ToastId } from '@chakra-ui/react';
import { chakra, Alert, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, Spinner } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import ReCaptcha from 'react-google-recaptcha';
......@@ -9,7 +10,7 @@ import type { TokenInstance } from 'types/api/token';
import config from 'configs/app';
import useApiFetch from 'lib/api/useApiFetch';
import { getResourceKey } from 'lib/api/useApiQuery';
import { MINUTE } from 'lib/consts';
import { MINUTE, SECOND } from 'lib/consts';
import useToast from 'lib/hooks/useToast';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -23,6 +24,7 @@ interface Props {
const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
const timeoutId = React.useRef<number>();
const toastId = React.useRef<ToastId>();
const { status, setStatus } = useMetadataUpdateContext() || {};
const apiFetch = useApiFetch();
......@@ -31,12 +33,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
const handleRefreshError = React.useCallback(() => {
setStatus?.('ERROR');
toast.closeAll();
toast({
toastId.current && toast.update(toastId.current, {
title: 'Error',
description: 'The refreshing process has failed. Please try again.',
status: 'warning',
variant: 'subtle',
duration: 5 * SECOND,
isClosable: true,
});
}, [ setStatus, toast ]);
......@@ -49,13 +51,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
},
})
.then(() => {
toast({
setStatus?.('WAITING_FOR_RESPONSE');
toastId.current = toast({
title: 'Please wait',
description: 'Refetching metadata request sent',
icon: <Spinner size="sm" mr={ 2 }/>,
status: 'warning',
variant: 'subtle',
duration: null,
isClosable: false,
});
setStatus?.('WAITING_FOR_RESPONSE');
timeoutId.current = window.setTimeout(handleRefreshError, 2 * MINUTE);
})
.catch(() => {
......@@ -63,7 +67,6 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
title: 'Error',
description: 'Unable to initialize metadata update',
status: 'warning',
variant: 'subtle',
});
setStatus?.('ERROR');
});
......@@ -112,12 +115,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
};
});
toast.closeAll();
toast({
toastId.current && toast.update(toastId.current, {
title: 'Success!',
description: 'Metadata has been refreshed',
status: 'success',
variant: 'subtle',
duration: 5 * SECOND,
isClosable: true,
});
setStatus?.('SUCCESS');
......@@ -138,6 +141,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
handler: handleSocketMessage,
});
React.useEffect(() => {
return () => {
timeoutId.current && window.clearTimeout(timeoutId.current);
toastId.current && toast.close(toastId.current);
};
// run only on mount/unmount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<Modal isOpen={ status === 'MODAL_OPENED' } onClose={ handleModalClose } size={{ base: 'full', lg: 'sm' }}>
<ModalOverlay/>
......
......@@ -1327,10 +1327,20 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@blockscout/bens-types@v1.3.0-beta":
version "1.3.0-beta"
resolved "https://registry.yarnpkg.com/@blockscout/bens-types/-/bens-types-1.3.0-beta.tgz#791fa102a33b1add14188beffe7c879219825424"
integrity sha512-Gh4qYrj7bNo6fzPGsdtPDPqwboxv/4OWx9QyrblkKt4YSNAlyElIS9rcpGGJ9rVP3YyhflaCh52YOvtDcRXRLw==
"@blockscout/bens-types@1.3.4":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@blockscout/bens-types/-/bens-types-1.3.4.tgz#e75b863c6d065e7d6d5d01e1a1d64da8df261640"
integrity sha512-kKRa8jKu/CBLR3QbWpRXmtwIXiIwIPDrFeEPIYUQp5bg9uY+ActOyQERixo/9FE+BHZShWUDm+75FoaAmIGIOw==
"@blockscout/stats-types@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@blockscout/stats-types/-/stats-types-1.6.0.tgz#cdb27ab3d3cb1eef7b8b069c39d4e09afda1aec9"
integrity sha512-MzItYOsLa3zgoFzRgFAgg7gynSXG0w/GqHzg5BGHcBPbPSp/g7A6mMtyIchI6TnZxxnCwziHHvzmJFXz11emUg==
"@blockscout/visualizer-types@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@blockscout/visualizer-types/-/visualizer-types-0.2.0.tgz#a8ef326d69e29417db124c78f868d68aca959ad1"
integrity sha512-gasqbEL89iH8YnH/TIEk0MBSG9SwhEJegY9tnQ1c/jFZOCYjiVkgNwm4oH0ncwCKoNX1GoKAregbkEUwDDw7FQ==
"@braintree/sanitize-url@=6.0.4":
version "6.0.4"
......@@ -8595,10 +8605,10 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
dappscout-iframe@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.1.tgz#b4718515ee4f00022af3912fac6ca1a321c156f9"
integrity sha512-EsiAAEk2I6hN+/E8o45WUn4BFd7aN8UvBwsIcOH79WOly0GOOHkPEO/puPkBCV0EcdxBsZIfssx3X0fSWVz5Bw==
dappscout-iframe@0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.2.tgz#de3df6abccad68a27c9304300b92d86ec0ab1c59"
integrity sha512-ASOimgBRG61pSYQLdYGWePdiO3IsfTEgWZ6CHpZ4XQjJRmj1+WiWF56vFTeLIo5aucp+2+6oRCJ8KgKHGVDj0A==
dependencies:
react "^18.2.0"
react-dom "^18.2.0"
......@@ -10439,10 +10449,9 @@ graceful-fs@^4.2.4, graceful-fs@^4.2.9:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
gradient-avatar@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/gradient-avatar/-/gradient-avatar-1.0.2.tgz#42bb408e402b1f21aafba3878858721055515224"
integrity sha512-Od9KI2YImV60wnsvU/u6GEyBm2fiHUUHgiLySE243GYl/T/tiJMJ5QYey8o7tepugmlnUGQRaCItHv19UnUjUg==
"gradient-avatar@git+https://github.com/blockscout/gradient-avatar.git":
version "1.0.3"
resolved "git+https://github.com/blockscout/gradient-avatar.git#86810368c2581d2dc3aca089dc648336c8e17045"
dependencies:
hsl-rgb "^1.0.0"
hsl-triad "^1.0.0"
......@@ -14846,16 +14855,7 @@ string-template@~0.2.1:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
......@@ -14983,14 +14983,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
......@@ -16150,7 +16143,7 @@ word-wrap@^1.2.5, word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
......@@ -16168,15 +16161,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
......
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