Commit 000bf0d6 authored by tom's avatar tom

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

parents 261724c4 10e5e934
......@@ -30,7 +30,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20.11.0
node-version: 20.17.0
cache: 'yarn'
- name: Cache node_modules
......@@ -43,7 +43,7 @@ jobs:
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile --ignore-optional
run: yarn --frozen-lockfile
- name: Run ESLint
run: yarn lint:eslint
......@@ -62,7 +62,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20.11.0
node-version: 20.17.0
cache: 'yarn'
- name: Cache node_modules
......@@ -75,10 +75,10 @@ jobs:
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile --ignore-optional
run: yarn --frozen-lockfile
- name: Install script dependencies
run: cd ./deploy/tools/envs-validator && yarn --frozen-lockfile --ignore-optional
run: cd ./deploy/tools/envs-validator && yarn --frozen-lockfile
- name: Run validation tests
run: |
......@@ -101,7 +101,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20.11.0
node-version: 20.17.0
cache: 'yarn'
- name: Cache node_modules
......@@ -114,7 +114,7 @@ jobs:
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile --ignore-optional
run: yarn --frozen-lockfile
- name: Run Jest
run: yarn test:jest ${{ github.event_name == 'pull_request' && '--changedSince=origin/main' || '' }} --passWithNoTests
......@@ -133,7 +133,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20.11.0
node-version: 20.17.0
cache: 'yarn'
- name: Cache node_modules
......@@ -146,7 +146,7 @@ jobs:
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile --ignore-optional
run: yarn --frozen-lockfile
- name: Install script dependencies
run: cd ./deploy/tools/affected-tests && yarn --frozen-lockfile
......@@ -171,7 +171,7 @@ jobs:
(needs.pw_affected_tests.result == 'success' || needs.pw_affected_tests.result == 'skipped')
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.41.1-focal
image: mcr.microsoft.com/playwright:v1.47.2-focal
strategy:
fail-fast: false
......@@ -190,7 +190,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20.11.0
node-version: 20.17.0
cache: 'yarn'
- name: Cache node_modules
......@@ -203,7 +203,7 @@ jobs:
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile --ignore-optional
run: yarn --frozen-lockfile
- name: Download affected tests list
if: ${{ needs.pw_affected_tests.result == 'success' }}
......
......@@ -21,7 +21,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20.11.0
node-version: 20.17.0
cache: 'yarn'
- name: Cache node_modules
......@@ -34,7 +34,7 @@ jobs:
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile --ignore-optional
run: yarn --frozen-lockfile
- name: Make production build with source maps
run: yarn build
......
20.11.0
20.17.0
\ No newline at end of file
# *****************************
# *** STAGE 1: Dependencies ***
# *****************************
FROM node:20.11.0-alpine AS deps
FROM node:20.17.0-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat python3 make g++
RUN ln -sf /usr/bin/python3 /usr/bin/python
......@@ -31,7 +31,7 @@ RUN yarn --frozen-lockfile
# *****************************
# ****** STAGE 2: Build *******
# *****************************
FROM node:20.11.0-alpine AS builder
FROM node:20.17.0-alpine AS builder
RUN apk add --no-cache --upgrade libc6-compat bash
# pass build args to env variables
......@@ -81,7 +81,7 @@ RUN cd ./deploy/tools/envs-validator && yarn build
# ******* STAGE 3: Run ********
# *****************************
# Production image, copy all the files and run next
FROM node:20.11.0-alpine AS runner
FROM node:20.17.0-alpine AS runner
RUN apk add --no-cache --upgrade bash curl jq unzip
### APP
......
......@@ -14,6 +14,10 @@
- Updated dependency: PackageName 1 to version x.x.x.
- Updated dependency: PackageName 2 to version x.x.x.
## 🎨 Design updates
- New style 1.
- New style 2.
## ✨ Other Changes
- Another minor change 1.
- Another minor change 2.
......
import type { Feature } from './types';
import type { AddressProfileAPIConfig } from 'types/client/addressProfileAPIConfig';
import { getEnvValue, parseEnvJson } from '../utils';
const value = parseEnvJson<AddressProfileAPIConfig>(getEnvValue('NEXT_PUBLIC_ADDRESS_USERNAME_TAG'));
function checkApiUrlTemplate(apiUrlTemplate: string): boolean {
try {
const testUrl = apiUrlTemplate.replace('{address}', '0x0000000000000000000000000000000000000000');
new URL(testUrl).toString();
return true;
} catch (error) {
return false;
}
}
const title = 'User profile API';
const config: Feature<{
apiUrlTemplate: string;
tagLinkTemplate?: string;
tagIcon?: string;
tagBgColor?: string;
tagTextColor?: string;
}> = (() => {
if (value && checkApiUrlTemplate(value.api_url_template)) {
return Object.freeze({
title,
isEnabled: true,
apiUrlTemplate: value.api_url_template,
tagLinkTemplate: value.tag_link_template,
tagIcon: value.tag_icon,
tagBgColor: value.tag_bg_color,
tagTextColor: value.tag_text_color,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
......@@ -32,6 +32,7 @@ export { default as stats } from './stats';
export { default as suave } from './suave';
export { default as txInterpretation } from './txInterpretation';
export { default as userOps } from './userOps';
export { default as addressProfileAPI } from './addressProfileAPI';
export { default as validators } from './validators';
export { default as verifiedTokens } from './verifiedTokens';
export { default as web3Wallet } from './web3Wallet';
# Set of ENVs for BXN Testnet network explorer
# https://blackfort-testnet.blockscout.com
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=blackfort_testnet"
# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=blackfort-testnet.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/blackfort-testnet.json
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/blackfort.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xcb4140e22cde3412eb5aecdedf2403032c7a251f5c96b11122aca5b1b88ed953
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(92deg, rgb(3, 150, 254) 0.24%, rgb(36, 209, 245) 98.31%)
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=TBXN
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=TBXN
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/blackfort.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/blackfort-dark.svg
NEXT_PUBLIC_NETWORK_ID=4777
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/blackfort.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/blackfort-dark.svg
NEXT_PUBLIC_NETWORK_NAME=BXN Testnet
NEXT_PUBLIC_NETWORK_RPC_URL=https://testnet.blackfort.network/rpc
NEXT_PUBLIC_NETWORK_SHORT_NAME=BXN Testnet
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/blackfort.png
NEXT_PUBLIC_STATS_API_HOST=https://stats-blackfort-testnet.k8s.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=blackfort
\ No newline at end of file
......@@ -14,7 +14,7 @@ NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" }
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=eth-sepolia.blockscout.com
NEXT_PUBLIC_API_HOST=eth-sepolia.k8s-dev.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
......@@ -59,7 +59,7 @@ NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-c
NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}]
NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global
NEXT_PUBLIC_SENTRY_ENABLE_TRACING=true
NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s.blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-dev.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=noves
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
\ No newline at end of file
# Set of ENVs for Zora Mainnet network explorer
# https://explorer.zora.energy
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=zora"
# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs
NEXT_PUBLIC_AD_BANNER_PROVIDER=none
NEXT_PUBLIC_AD_TEXT_PROVIDER=none
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=explorer.zora.energy
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'}]
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/zora.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x6d54c0226a57f5bc854f8aa589bb15113388f984f318c9e1b2722115e4e35873
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true
NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(89deg, rgb(63, 36, 22) 0.56%, rgb(44, 56, 105) 98.31%)
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_LOGOUT_URL=https://zora-blockscout.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=patbqG4V2CI998jAq.9810c58c9de973ba2650621c94559088cbdfa1a914498e385621ed035d33c0d0
NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps']
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/zora-network/pools'}}]
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zora.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zora-dark.svg
NEXT_PUBLIC_NETWORK_ID=7777777
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zora.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zora-dark.svg
NEXT_PUBLIC_NETWORK_NAME=Zora Mainnet
NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.zora.energy
NEXT_PUBLIC_NETWORK_SHORT_NAME=Zora Mainnet
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/zora-mainnet.png
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://bridge.zora.energy
NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_STATS_API_HOST=https://stats-l2-zora-mainnet.k8s-prod-1.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
NEXT_PUBLIC_ADDRESS_USERNAME_TAG={'api_url_template': 'https://api.zora.co/discover/user/{address}', 'tag_link_template': 'httpszora.co/{username}', 'tag_icon': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zora.svg', 'tag_bg_color': 'rgba(0,0,0)', 'tag_text_color': 'rgba(255,255,255)'}
\ No newline at end of file
......@@ -10,6 +10,7 @@ declare module 'yup' {
import * as yup from 'yup';
import type { AdButlerConfig } from '../../../types/client/adButlerConfig';
import type { AddressProfileAPIConfig } from '../../../types/client/addressProfileAPIConfig';
import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS, SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS } from '../../../types/client/adProviders';
import type { AdTextProviders, AdBannerProviders, AdBannerAdditionalProviders } from '../../../types/client/adProviders';
import { SMART_CONTRACT_EXTRA_VERIFICATION_METHODS, type ContractCodeIde, type SmartContractVerificationMethodExtra } from '../../../types/client/contract';
......@@ -803,6 +804,20 @@ const schema = yup
),
}),
NEXT_PUBLIC_SAVE_ON_GAS_ENABLED: yup.boolean(),
NEXT_PUBLIC_ADDRESS_USERNAME_TAG: yup
.mixed()
.test('shape', 'Invalid schema were provided for NEXT_PUBLIC_ADDRESS_USERNAME_TAG, it should have api_url_template', (data) => {
const isUndefined = data === undefined;
const valueSchema = yup.object<AddressProfileAPIConfig>().transform(replaceQuotes).json().shape({
api_url_template: yup.string().required(),
tag_link_template: yup.string(),
tag_icon: yup.string(),
tag_bg_color: yup.string(),
tag_text_color: yup.string(),
});
return isUndefined || valueSchema.isValidSync(data);
}),
// 6. External services envs
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(),
......
......@@ -54,6 +54,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
- [Data availability](ENVS.md#data-availability)
- [Bridged tokens](ENVS.md#bridged-tokens)
- [Safe{Core} address tags](ENVS.md#safecore-address-tags)
- [Address profile API](ENVS.md#address-profile-api)
- [SUAVE chain](ENVS.md#suave-chain)
- [MetaSuites extension](ENVS.md#metasuites-extension)
- [Validators list](ENVS.md#validators-list)
......@@ -653,6 +654,28 @@ For the smart contract addresses which are [Safe{Core} accounts](https://safe.gl
&nbsp;
### Address profile API
This feature allows the integration of an external API to fetch user info for addresses or contracts. When configured, if the API returns a username, a public tag with a custom link will be displayed in the address page header.
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_ADDRESS_USERNAME_TAG | `{api_url: string; tag_link_template: string; tag_icon: string; tag_bg_color: string; tag_text_color: string}` | Address profile API tag configuration properties. See [below](#user-profile-api-configuration-properties). | - | - | `uniswap` | v1.35.0+ |
&nbsp;
#### Address profile API configuration properties
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| api_url_template | `string` | User profile API URL. Should be a template with `{address}` variable | Required | - | `https://example-api.com/{address}` |
| tag_link_template | `string` | External link to the profile. Should be a template with `{username}` variable | - | - | `https://example.com/{address}` |
| tag_icon | `string` | Public tag icon (.svg) url | - | - | `https://example.com/icon.svg` |
| tag_bg_color | `string` | Public tag background color (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | - | `\#000000` |
| tag_text_color | `string` | Public tag text color (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | - | `\#FFFFFF` |
&nbsp;
### SUAVE chain
For blockchains that implement SUAVE architecture additional fields will be shown on the transaction page ("Allowed peekers", "Kettle"). Users also will be able to see the list of all transactions for a particular Kettle in the separate view.
......@@ -679,7 +702,7 @@ The feature enables the Validators page which provides detailed information abou
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE | `'stability'` | Chain type | Required | - | `'stability'` | v1.25.0+ |
| NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE | `'stability' \| 'blackfort'` | Chain type | Required | - | `'stability'` | v1.25.0+ |
&nbsp;
......
......@@ -42,12 +42,12 @@ import type { AddressesResponse, AddressesMetadataSearchResult, AddressesMetadat
import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata';
import type {
ArbitrumL2MessagesResponse,
ArbitrumL2MessagesItem,
ArbitrumL2TxnBatch,
ArbitrumL2TxnBatchesResponse,
ArbitrumL2BatchTxs,
ArbitrumL2BatchBlocks,
ArbitrumL2TxnBatchesItem,
ArbitrumLatestDepositsResponse,
} from 'types/api/arbitrumL2';
import type { TxBlobs, Blob } from 'types/api/blobs';
import type {
......@@ -118,7 +118,15 @@ import type { TxInterpretationResponse } from 'types/api/txInterpretation';
import type { TTxsFilters, TTxsWithBlobsFilters } from 'types/api/txsFilters';
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 {
ValidatorsStabilityCountersResponse,
ValidatorsStabilityFilters,
ValidatorsStabilityResponse,
ValidatorsStabilitySorting,
ValidatorsBlackfortCountersResponse,
ValidatorsBlackfortResponse,
ValidatorsBlackfortSorting,
} from 'types/api/validators';
import type { VerifiedContractsSorting } from 'types/api/verifiedContracts';
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
import type {
......@@ -911,14 +919,19 @@ export const RESOURCES = {
},
// VALIDATORS
validators: {
path: '/api/v2/validators/:chainType',
pathParams: [ 'chainType' as const ],
validators_stability: {
path: '/api/v2/validators/stability',
filterFields: [ 'address_hash' as const, 'state_filter' as const ],
},
validators_counters: {
path: '/api/v2/validators/:chainType/counters',
pathParams: [ 'chainType' as const ],
validators_stability_counters: {
path: '/api/v2/validators/stability/counters',
},
validators_blackfort: {
path: '/api/v2/validators/blackfort',
filterFields: [],
},
validators_blackfort_counters: {
path: '/api/v2/validators/blackfort/counters',
},
// BLOBS
......@@ -1016,7 +1029,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' |
'withdrawals' | 'address_withdrawals' | 'block_withdrawals' |
'watchlist' | 'private_tags_address' | 'private_tags_tx' |
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators' | 'noves_address_history';
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
......@@ -1042,7 +1055,7 @@ Q extends 'homepage_blocks' ? Array<Block> :
Q extends 'homepage_txs' ? Array<Transaction> :
Q extends 'homepage_txs_watchlist' ? Array<Transaction> :
Q extends 'homepage_optimistic_deposits' ? Array<OptimisticL2DepositsItem> :
Q extends 'homepage_arbitrum_deposits' ? { items: Array<ArbitrumL2MessagesItem> } :
Q extends 'homepage_arbitrum_deposits' ? ArbitrumLatestDepositsResponse :
Q extends 'homepage_zkevm_l2_batches' ? { items: Array<ZkEvmL2TxnBatchesItem> } :
Q extends 'homepage_arbitrum_l2_batches' ? { items: Array<ArbitrumL2TxnBatchesItem>} :
Q extends 'homepage_indexing_status' ? IndexingStatus :
......@@ -1137,8 +1150,10 @@ Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse :
Q extends 'blob' ? Blob :
Q extends 'marketplace_dapps' ? Array<MarketplaceAppOverview> :
Q extends 'marketplace_dapp' ? MarketplaceAppOverview :
Q extends 'validators' ? ValidatorsResponse :
Q extends 'validators_counters' ? ValidatorsCountersResponse :
Q extends 'validators_stability' ? ValidatorsStabilityResponse :
Q extends 'validators_stability_counters' ? ValidatorsStabilityCountersResponse :
Q extends 'validators_blackfort' ? ValidatorsBlackfortResponse :
Q extends 'validators_blackfort_counters' ? ValidatorsBlackfortCountersResponse :
Q extends 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse :
Q extends 'shibarium_deposits' ? ShibariumDepositsResponse :
Q extends 'shibarium_withdrawals_count' ? number :
......@@ -1214,7 +1229,7 @@ Q extends 'verified_contracts' ? VerifiedContractsFilters :
Q extends 'addresses_lookup' ? EnsAddressLookupFilters :
Q extends 'domains_lookup' ? EnsDomainLookupFilters :
Q extends 'user_ops' ? UserOpsFilters :
Q extends 'validators' ? ValidatorsFilters :
Q extends 'validators_stability' ? ValidatorsStabilityFilters :
Q extends 'address_mud_tables' ? AddressMudTablesFilter :
Q extends 'address_mud_records' ? AddressMudRecordsFilter :
never;
......@@ -1228,7 +1243,8 @@ Q extends 'verified_contracts' ? VerifiedContractsSorting :
Q extends 'address_txs' ? TransactionsSorting :
Q extends 'addresses_lookup' ? EnsLookupSorting :
Q extends 'domains_lookup' ? EnsLookupSorting :
Q extends 'validators' ? ValidatorsSorting :
Q extends 'validators_stability' ? ValidatorsStabilitySorting :
Q extends 'validators_blackfort' ? ValidatorsBlackfortSorting :
Q extends 'address_mud_records' ? AddressMudRecordsSorting :
never;
/* eslint-enable @typescript-eslint/indent */
import { useQuery } from '@tanstack/react-query';
import * as v from 'valibot';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import useFetch from 'lib/hooks/useFetch';
const feature = config.features.addressProfileAPI;
type AddressInfoApiQueryResponse = v.InferOutput<typeof AddressInfoSchema>;
const AddressInfoSchema = v.object({
user_profile: v.object({
username: v.union([ v.string(), v.null() ]),
}),
});
const ERROR_NAME = 'Invalid response schema';
export default function useAddressProfileApiQuery(hash: string | undefined, isEnabled = true) {
const fetch = useFetch();
return useQuery<unknown, ResourceError<unknown>, AddressInfoApiQueryResponse>({
queryKey: [ 'username_api', hash ],
queryFn: async() => {
if (!feature.isEnabled || !hash) {
return Promise.reject();
}
return fetch(feature.apiUrlTemplate.replace('{address}', hash), undefined, { omitSentryErrorLog: true });
},
enabled: isEnabled && Boolean(hash),
refetchOnMount: false,
select: (response) => {
const parsedResponse = v.safeParse(AddressInfoSchema, response);
if (!parsedResponse.success) {
throw Error(ERROR_NAME);
}
return parsedResponse.output;
},
});
}
......@@ -246,7 +246,7 @@ export default function useNavItems(): ReturnType {
text: 'Charts & stats',
nextRoute: { pathname: '/stats' as const },
icon: 'stats',
isActive: pathname === '/stats',
isActive: pathname.startsWith('/stats'),
} : null,
apiNavItems.length > 0 && {
text: 'API',
......
......@@ -20,7 +20,7 @@ export default function generate<Pathname extends Route['pathname']>(route: Rout
};
const title = compileValue(templates.title.make(route.pathname, Boolean(apiData)), params);
const description = compileValue(templates.description.make(route.pathname), params);
const description = compileValue(templates.description.make(route.pathname, Boolean(apiData)), params);
const pageOgType = getPageOgType(route.pathname);
......
......@@ -23,6 +23,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/apps': 'Root page',
'/apps/[id]': 'Regular page',
'/stats': 'Root page',
'/stats/[id]': 'Regular page',
'/api-docs': 'Regular page',
'/graphiql': 'Regular page',
'/search-results': 'Regular page',
......
......@@ -27,6 +27,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/apps': DEFAULT_TEMPLATE,
'/apps/[id]': DEFAULT_TEMPLATE,
'/stats': DEFAULT_TEMPLATE,
'/stats/[id]': DEFAULT_TEMPLATE,
'/api-docs': DEFAULT_TEMPLATE,
'/graphiql': DEFAULT_TEMPLATE,
'/search-results': DEFAULT_TEMPLATE,
......@@ -68,8 +69,10 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/api/sprite': DEFAULT_TEMPLATE,
};
export function make(pathname: Route['pathname']) {
const template = TEMPLATE_MAP[pathname];
const TEMPLATE_MAP_ENHANCED: Partial<Record<Route['pathname'], string>> = {
'/stats/[id]': '%description%',
};
return template ?? '';
export function make(pathname: Route['pathname'], isEnriched = false) {
return (isEnriched ? TEMPLATE_MAP_ENHANCED[pathname] : undefined) ?? TEMPLATE_MAP[pathname] ?? '';
}
......@@ -23,6 +23,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/apps': '%network_name% DApps - Explore top apps',
'/apps/[id]': '%network_name% marketplace app',
'/stats': '%network_name% stats - %network_name% network insights',
'/stats/[id]': '%network_name% stats - %id% chart',
'/api-docs': '%network_name% API docs - %network_name% developer tools',
'/graphiql': 'GraphQL for %network_name% - %network_name% data query',
'/search-results': '%network_name% search result for %q%',
......@@ -69,6 +70,7 @@ const TEMPLATE_MAP_ENHANCED: Partial<Record<Route['pathname'], string>> = {
'/token/[hash]/instance/[id]': '%network_name% token instance for %symbol%',
'/apps/[id]': '%network_name% - %app_name%',
'/address/[hash]': '%network_name% address details for %domain_name%',
'/stats/[id]': '%title% chart on %network_name%',
};
export function make(pathname: Route['pathname'], isEnriched = false) {
......
import type { LineChart } from '@blockscout/stats-types';
import type { TokenInfo } from 'types/api/token';
import type { Route } from 'nextjs-routes';
......@@ -9,6 +10,7 @@ export type ApiData<Pathname extends Route['pathname']> =
Pathname extends '/token/[hash]' ? TokenInfo :
Pathname extends '/token/[hash]/instance/[id]' ? { symbol: string } :
Pathname extends '/apps/[id]' ? { app_name: string } :
Pathname extends '/stats/[id]' ? LineChart['info'] :
never
) | null;
......
......@@ -21,6 +21,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/apps': 'DApps',
'/apps/[id]': 'DApp',
'/stats': 'Stats',
'/stats/[id]': 'Stats chart',
'/api-docs': 'REST API',
'/graphiql': 'GraphQL',
'/search-results': 'Search results',
......
......@@ -138,6 +138,9 @@ Type extends EventTypes.PAGE_WIDGET ? (
'Type': 'Address tag';
'Info': string;
'URL': string;
} | {
'Type': 'Share chart';
'Info': string;
}
) :
Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? {
......
import type { ArbitrumL2MessagesResponse } from 'types/api/arbitrumL2';
import type { ArbitrumL2MessagesResponse, ArbitrumLatestDepositsResponse } from 'types/api/arbitrumL2';
export const baseResponse: ArbitrumL2MessagesResponse = {
items: [
......@@ -27,3 +27,20 @@ export const baseResponse: ArbitrumL2MessagesResponse = {
direction: 'to-rollup',
},
};
export const latestDepositsResponse: ArbitrumLatestDepositsResponse = {
items: [
{
completion_transaction_hash: '0x3ccdf87449d3de6a9dcd3eddb7bc9ecdf1770d4631f03cdf12a098911618d138',
origination_transaction_block_number: 123400,
origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436',
origination_timestamp: '2023-06-01T14:46:48.000000Z',
},
{
completion_transaction_hash: '0xd16d918b2f95a5cdf66824f6291b6d5eb80b6f4acab3f9fb82ee0ec4109646a0',
origination_timestamp: null,
origination_transaction_block_number: null,
origination_transaction_hash: null,
},
],
};
......@@ -4,158 +4,195 @@ export const averageGasPrice: stats.LineChart = {
chart: [
{
date: '2023-12-22',
date_to: '2023-12-22',
value: '37.7804422597599',
is_approximate: false,
},
{
date: '2023-12-23',
date_to: '2023-12-23',
value: '25.84889883009387',
is_approximate: false,
},
{
date: '2023-12-24',
date_to: '2023-12-24',
value: '25.818463227198574',
is_approximate: false,
},
{
date: '2023-12-25',
date_to: '2023-12-25',
value: '26.045513050051298',
is_approximate: false,
},
{
date: '2023-12-26',
date_to: '2023-12-26',
value: '21.42600692652399',
is_approximate: false,
},
{
date: '2023-12-27',
date_to: '2023-12-27',
value: '31.066730409846656',
is_approximate: false,
},
{
date: '2023-12-28',
date_to: '2023-12-28',
value: '33.63955781902089',
is_approximate: false,
},
{
date: '2023-12-29',
date_to: '2023-12-29',
value: '28.064736756058384',
is_approximate: false,
},
{
date: '2023-12-30',
date_to: '2023-12-30',
value: '23.074500869678175',
is_approximate: false,
},
{
date: '2023-12-31',
date_to: '2023-12-31',
value: '17.651005734615133',
is_approximate: false,
},
{
date: '2024-01-01',
date_to: '2023-01-01',
value: '14.906085174476441',
is_approximate: false,
},
{
date: '2024-01-02',
date_to: '2023-01-02',
value: '22.28459059038656',
is_approximate: false,
},
{
date: '2024-01-03',
date_to: '2023-01-03',
value: '39.8311646806592',
is_approximate: false,
},
{
date: '2024-01-04',
date_to: '2023-01-04',
value: '26.09989322256083',
is_approximate: false,
},
{
date: '2024-01-05',
date_to: '2023-01-05',
value: '22.821996688111998',
is_approximate: false,
},
{
date: '2024-01-06',
date_to: '2023-01-06',
value: '20.32680041262083',
is_approximate: false,
},
{
date: '2024-01-07',
date_to: '2023-01-07',
value: '32.535045831809704',
is_approximate: false,
},
{
date: '2024-01-08',
date_to: '2023-01-08',
value: '27.443477102139482',
is_approximate: false,
},
{
date: '2024-01-09',
date_to: '2023-01-09',
value: '20.7911332558055',
is_approximate: false,
},
{
date: '2024-01-10',
date_to: '2023-01-10',
value: '42.10740192523919',
is_approximate: false,
},
{
date: '2024-01-11',
date_to: '2023-01-11',
value: '35.75215680343582',
is_approximate: false,
},
{
date: '2024-01-12',
date_to: '2023-01-12',
value: '27.430414798093253',
is_approximate: false,
},
{
date: '2024-01-13',
date_to: '2023-01-13',
value: '20.170934096589875',
is_approximate: false,
},
{
date: '2024-01-14',
date_to: '2023-01-14',
value: '38.79660984371034',
is_approximate: false,
},
{
date: '2024-01-15',
date_to: '2023-01-15',
value: '26.140740484554204',
is_approximate: false,
},
{
date: '2024-01-16',
date_to: '2023-01-16',
value: '36.708543184194156',
is_approximate: false,
},
{
date: '2024-01-17',
date_to: '2023-01-17',
value: '40.325438794298876',
is_approximate: false,
},
{
date: '2024-01-18',
date_to: '2023-01-18',
value: '37.55145309930694',
is_approximate: false,
},
{
date: '2024-01-19',
date_to: '2023-01-19',
value: '33.271450114434664',
is_approximate: false,
},
{
date: '2024-01-20',
date_to: '2023-01-20',
value: '19.303304377685638',
is_approximate: false,
},
{
date: '2024-01-21',
date_to: '2023-01-21',
value: '14.375908594704976',
is_approximate: false,
},
],
info: {
title: 'Chart title',
description: 'Chart description',
id: 'chart',
resolutions: [ 'DAY', 'MONTH' ],
},
};
......@@ -11,18 +11,21 @@ export const base: stats.LineCharts = {
title: 'Accounts growth',
description: 'Cumulative accounts number per period',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'activeAccounts',
title: 'Active accounts',
description: 'Active accounts number per period',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'newAccounts',
title: 'New accounts',
description: 'New accounts number per day',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
],
},
......@@ -35,30 +38,35 @@ export const base: stats.LineCharts = {
title: 'Average transaction fee',
description: 'The average amount in ETH spent per transaction',
units: 'ETH',
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'newTxns',
title: 'New transactions',
description: 'New transactions number',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'txnsFee',
title: 'Transactions fees',
description: 'Amount of tokens paid as fees',
units: 'ETH',
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'txnsGrowth',
title: 'Transactions growth',
description: 'Cumulative transactions number',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'txnsSuccessRate',
title: 'Transactions success rate',
description: 'Successful transactions rate per day',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
],
},
......@@ -71,18 +79,21 @@ export const base: stats.LineCharts = {
title: 'Average block rewards',
description: 'Average amount of distributed reward in tokens per day',
units: 'ETH',
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'averageBlockSize',
title: 'Average block size',
description: 'Average size of blocks in bytes',
units: 'Bytes',
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'newBlocks',
title: 'New blocks',
description: 'New blocks number',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
],
},
......@@ -95,6 +106,7 @@ export const base: stats.LineCharts = {
title: 'New ETH transfers',
description: 'New token transfers number for the period',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
],
},
......@@ -107,18 +119,21 @@ export const base: stats.LineCharts = {
title: 'Average gas limit',
description: 'Average gas limit per block for the period',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'averageGasPrice',
title: 'Average gas price',
description: 'Average gas price for the period (Gwei)',
units: 'Gwei',
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'gasUsedGrowth',
title: 'Gas used growth',
description: 'Cumulative gas used for the period',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
],
},
......@@ -131,12 +146,14 @@ export const base: stats.LineCharts = {
title: 'New verified contracts',
description: 'New verified contracts number for the period',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'verifiedContractsGrowth',
title: 'Verified contracts growth',
description: 'Cumulative number verified contracts for the period',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
],
},
......
import type {
ValidatorBlackfort,
ValidatorsBlackfortCountersResponse,
ValidatorsBlackfortResponse,
} from 'types/api/validators';
import * as addressMock from '../address/address';
export const validator1: ValidatorBlackfort = {
address: addressMock.withName,
name: 'testnet-3',
commission: 10,
delegated_amount: '0',
self_bonded_amount: '10000',
};
export const validator2: ValidatorBlackfort = {
address: addressMock.withEns,
name: 'GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG',
commission: 5000,
delegated_amount: '10000',
self_bonded_amount: '100',
};
export const validator3: ValidatorBlackfort = {
address: addressMock.withoutName,
name: 'testnet-1',
commission: 0,
delegated_amount: '0',
self_bonded_amount: '10000',
};
export const validatorsResponse: ValidatorsBlackfortResponse = {
items: [ validator1, validator2, validator3 ],
next_page_params: null,
};
export const validatorsCountersResponse: ValidatorsBlackfortCountersResponse = {
new_validators_counter_24h: '11',
validators_counter: '140',
};
import type { Validator, ValidatorsCountersResponse, ValidatorsResponse } from 'types/api/validators';
import type {
ValidatorStability,
ValidatorsStabilityCountersResponse,
ValidatorsStabilityResponse,
} from 'types/api/validators';
import * as addressMock from '../address/address';
export const validator1: Validator = {
export const validator1: ValidatorStability = {
address: addressMock.withName,
blocks_validated_count: 7334224,
state: 'active',
};
export const validator2: Validator = {
export const validator2: ValidatorStability = {
address: addressMock.withEns,
blocks_validated_count: 8937453,
state: 'probation',
};
export const validator3: Validator = {
export const validator3: ValidatorStability = {
address: addressMock.withoutName,
blocks_validated_count: 1234,
state: 'inactive',
};
export const validatorsResponse: ValidatorsResponse = {
export const validatorsResponse: ValidatorsStabilityResponse = {
items: [ validator1, validator2, validator3 ],
next_page_params: null,
};
export const validatorsCountersResponse: ValidatorsCountersResponse = {
export const validatorsCountersResponse: ValidatorsStabilityCountersResponse = {
active_validators_counter: '42',
active_validators_percentage: 7.14,
new_validators_counter_24h: '11',
......
......@@ -16,6 +16,7 @@ function generateCspPolicy() {
descriptors.monaco(),
descriptors.safe(),
descriptors.sentry(),
descriptors.usernameApi(),
descriptors.walletConnect(),
);
......
......@@ -11,4 +11,5 @@ export { mixpanel } from './mixpanel';
export { monaco } from './monaco';
export { safe } from './safe';
export { sentry } from './sentry';
export { usernameApi } from './usernameApi';
export { walletConnect } from './walletConnect';
import type CspDev from 'csp-dev';
import config from 'configs/app';
const feature = config.features.addressProfileAPI;
export function usernameApi(): CspDev.DirectiveDescriptor {
if (!feature.isEnabled) {
return {};
}
const apiOrigin = (() => {
try {
const url = new URL(feature.apiUrlTemplate);
return url.origin;
} catch (error) {
return '';
}
})();
return {
'connect-src': [
apiOrigin,
],
};
}
......@@ -52,6 +52,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/public-tags/submit">
| StaticRoute<"/search-results">
| StaticRoute<"/sprite">
| DynamicRoute<"/stats/[id]", { "id": string }>
| StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }>
| DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }>
......
......@@ -12,6 +12,7 @@ type Params<R extends ResourceName> = (
{
resource: R;
pathParams?: ResourcePathParams<R>;
queryParams?: Record<string, string | number | undefined>;
} | {
url: string;
route: string;
......@@ -22,12 +23,11 @@ type Params<R extends ResourceName> = (
export default async function fetchApi<R extends ResourceName = never, S = ResourcePayload<R>>(params: Params<R>): Promise<S | undefined> {
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, params.timeout || SECOND);
const url = 'url' in params ? params.url : buildUrl(params.resource, params.pathParams);
const url = 'url' in params ? params.url : buildUrl(params.resource, params.pathParams, params.queryParams);
const route = 'route' in params ? params.route : RESOURCES[params.resource]['path'];
const end = metrics?.apiRequestDuration.startTimer();
......
......@@ -4,8 +4,8 @@
"private": false,
"homepage": "https://github.com/blockscout/frontend#readme",
"engines": {
"node": "20.11.0",
"npm": "10.2.4"
"node": "20.17.0",
"npm": "10.8.2"
},
"scripts": {
"dev": "./tools/scripts/dev.sh",
......@@ -26,8 +26,8 @@
"svg:build-sprite": "icons build -i ./icons -o ./public/icons --optimize",
"test:pw": "./tools/scripts/pw.sh",
"test:pw:local": "export NODE_PATH=$(pwd)/node_modules && yarn test:pw",
"test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.sh",
"test:pw:docker:deps": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.deps.sh",
"test:pw:docker": "docker run --rm --ipc=host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.47.2-focal ./tools/scripts/pw.docker.sh",
"test:pw:docker:deps": "docker run --rm --ipc=host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.47.2-focal ./tools/scripts/pw.docker.deps.sh",
"test:pw:ci": "yarn test:pw --project=$PW_PROJECT",
"test:pw:detect-affected": "node ./deploy/tools/affected-tests/index.js",
"test:jest": "jest",
......@@ -38,7 +38,7 @@
},
"dependencies": {
"@blockscout/bens-types": "1.4.1",
"@blockscout/stats-types": "1.6.0",
"@blockscout/stats-types": "2.0.0",
"@blockscout/visualizer-types": "0.2.0",
"@chakra-ui/react": "2.7.1",
"@chakra-ui/theme-tools": "^2.0.18",
......@@ -87,7 +87,7 @@
"magic-bytes.js": "1.8.0",
"mixpanel-browser": "^2.47.0",
"monaco-editor": "^0.34.1",
"next": "14.2.9",
"next": "14.2.13",
"nextjs-routes": "^1.0.8",
"node-fetch": "^3.2.9",
"papaparse": "^5.3.2",
......@@ -115,8 +115,8 @@
"xss": "^1.0.14"
},
"devDependencies": {
"@playwright/experimental-ct-react": "1.41.1",
"@playwright/test": "1.41.1",
"@playwright/experimental-ct-react": "1.47.2",
"@playwright/test": "1.47.2",
"@svgr/webpack": "^6.5.1",
"@tanstack/eslint-plugin-query": "^5.0.5",
"@testing-library/react": "^14.0.0",
......@@ -128,7 +128,7 @@
"@types/jest": "^29.2.0",
"@types/js-cookie": "^3.0.2",
"@types/mixpanel-browser": "^2.38.1",
"@types/node": "20.11.0",
"@types/node": "20.16.7",
"@types/phoenix": "^1.5.4",
"@types/qrcode": "^1.5.0",
"@types/react": "18.0.9",
......@@ -161,7 +161,7 @@
"ts-node": "^10.9.1",
"typescript": "5.4.2",
"vite-plugin-svgr": "^2.2.2",
"vite-tsconfig-paths": "^3.5.2",
"vite-tsconfig-paths": "4.3.2",
"ws": "^8.17.1"
},
"lint-staged": {
......
import type { GetServerSideProps, NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Route } from 'nextjs-routes';
import * as gSSP from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
import detectBotRequest from 'nextjs/utils/detectBotRequest';
import fetchApi from 'nextjs/utils/fetchApi';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import getQueryParamString from 'lib/router/getQueryParamString';
const Chart = dynamic(() => import('ui/pages/Chart'), { ssr: false });
const pathname: Route['pathname'] = '/stats/[id]';
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
return (
<PageNextJs pathname="/stats/[id]" query={ props.query } apiData={ props.apiData }>
<Chart/>
</PageNextJs>
);
};
export default Page;
export const getServerSideProps: GetServerSideProps<Props<typeof pathname>> = async(ctx) => {
const baseResponse = await gSSP.base<typeof pathname>(ctx);
if ('props' in baseResponse) {
if (
config.meta.seo.enhancedDataEnabled ||
(config.meta.og.enhancedDataEnabled && detectBotRequest(ctx.req)?.type === 'social_preview')
) {
const chartData = await fetchApi({
resource: 'stats_line',
pathParams: { id: getQueryParamString(ctx.query.id) },
queryParams: { from: dayjs().format('YYYY-MM-DD'), to: dayjs().format('YYYY-MM-DD') },
timeout: 1000,
});
(await baseResponse.props).apiData = chartData?.info ?? null;
}
}
return baseResponse;
};
......@@ -4,7 +4,21 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const Validators = dynamic(() => import('ui/pages/Validators'), { ssr: false });
import config from 'configs/app';
const validatorsFeature = config.features.validators;
const Validators = dynamic(() => {
if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'stability') {
return import('ui/pages/ValidatorsStability');
}
if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'blackfort') {
return import('ui/pages/ValidatorsBlackfort');
}
throw new Error('Validators feature is not enabled.');
}, { ssr: false });
const Page: NextPage = () => {
return (
......
......@@ -50,7 +50,7 @@ const config: PlaywrightTestConfig = defineConfig({
ctViteConfig: {
plugins: [
tsconfigPaths(),
tsconfigPaths({ loose: true, ignoreConfigErrors: true }),
react(),
svgr({
exportAsDefault: true,
......@@ -82,6 +82,9 @@ const config: PlaywrightTestConfig = defineConfig({
// We don't call this function in TestApp and since we use useWeb3Modal() and useWeb3ModalState() hooks in the code, we have to mock the module
// Otherwise it will complain that createWeb3Modal() is no called before the hooks are used
{ find: /^@web3modal\/wagmi\/react$/, replacement: './playwright/mocks/modules/@web3modal/wagmi/react.js' },
{ find: '/playwright/index.ts', replacement: './playwright/index.ts' },
{ find: '/playwright/envs.js', replacement: './playwright/envs.js' },
],
},
define: {
......
......@@ -4,8 +4,6 @@ import type { Locator, TestFixture } from '@playwright/test';
import type router from 'next/router';
import React from 'react';
import type { JsonObject } from '@playwright/experimental-ct-core/types/component';
import type { Props as TestAppProps } from 'playwright/TestApp';
import TestApp from 'playwright/TestApp';
......@@ -14,15 +12,13 @@ interface MountResult extends Locator {
update(component: JSX.Element): Promise<void>;
}
type Mount = <HooksConfig extends JsonObject>(component: JSX.Element, options?: MountOptions<HooksConfig>) => Promise<MountResult>;
interface Options extends JsonObject {
hooksConfig?: {
router: Partial<Pick<typeof router, 'query' | 'isReady' | 'asPath' | 'pathname'>>;
};
interface AppHooksConfig {
router: Partial<Pick<typeof router, 'query' | 'isReady' | 'asPath' | 'pathname'>>;
}
export type RenderFixture = (component: JSX.Element, options?: Options, props?: Omit<TestAppProps, 'children'>) => Promise<MountResult>
type Mount = <HooksConfig extends AppHooksConfig>(component: JSX.Element, options?: MountOptions<HooksConfig>) => Promise<MountResult>
export type RenderFixture = (component: JSX.Element, options?: MountOptions<AppHooksConfig>, props?: Omit<TestAppProps, 'children'>) => Promise<MountResult>
const fixture: TestFixture<RenderFixture, { mount: Mount }> = async({ mount }, use) => {
await use((component, options, props) => {
......
/* eslint-disable no-console */
import { test as base } from '@playwright/experimental-ct-react';
import type { Page } from '@playwright/test';
import * as injectMetaMaskProvider from './fixtures/injectMetaMaskProvider';
import * as mockApiResponse from './fixtures/mockApiResponse';
......@@ -13,7 +14,7 @@ import * as mockTextAd from './fixtures/mockTextAd';
import * as render from './fixtures/render';
import * as socketServer from './fixtures/socketServer';
interface Fixtures {
export interface Fixtures {
render: render.RenderFixture;
mockApiResponse: mockApiResponse.MockApiResponseFixture;
mockAssetResponse: mockAssetResponse.MockAssetResponseFixture;
......@@ -27,6 +28,8 @@ interface Fixtures {
mockTextAd: mockTextAd.MockTextAdFixture;
}
export type TestFnArgs = Fixtures & { page: Page };
const test = base.extend<Fixtures>({
render: render.default,
mockApiResponse: mockApiResponse.default,
......
......@@ -51,24 +51,28 @@ export const STATS_CHARTS_SECTION: stats.LineChartSection = {
title: 'Average transaction fee',
description: 'The average amount in ETH spent per transaction',
units: 'ETH',
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'chart_1',
title: 'Transactions fees',
description: 'Amount of tokens paid as fees',
units: 'ETH',
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'chart_2',
title: 'New transactions',
description: 'New transactions number',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
{
id: 'chart_3',
title: 'Transactions growth',
description: 'Cumulative transactions number',
units: undefined,
resolutions: [ 'DAY', 'MONTH' ],
},
],
};
......
import type { Validator, ValidatorsCountersResponse } from 'types/api/validators';
import type {
ValidatorStability,
ValidatorsStabilityCountersResponse,
ValidatorBlackfort,
ValidatorsBlackfortCountersResponse,
} from 'types/api/validators';
import { ADDRESS_PARAMS } from './addressParams';
export const VALIDATOR: Validator = {
export const VALIDATOR_STABILITY: ValidatorStability = {
address: ADDRESS_PARAMS,
blocks_validated_count: 25987,
state: 'active',
};
export const VALIDATORS_COUNTERS: ValidatorsCountersResponse = {
export const VALIDATORS_STABILITY_COUNTERS: ValidatorsStabilityCountersResponse = {
active_validators_counter: '42',
active_validators_percentage: 7.14,
new_validators_counter_24h: '11',
validators_counter: '140',
};
export const VALIDATOR_BLACKFORT: ValidatorBlackfort = {
address: ADDRESS_PARAMS,
name: 'testnet-1',
commission: 10,
delegated_amount: '0',
self_bonded_amount: '10000',
};
export const VALIDATORS_BLACKFORT_COUNTERS: ValidatorsBlackfortCountersResponse = {
new_validators_counter_24h: '11',
validators_counter: '140',
};
......@@ -47,6 +47,16 @@ const sizes = {
lineHeight: 5,
},
}),
md: definePartsStyle({
container: {
minH: 8,
minW: 8,
fontSize: 'sm',
px: '6px',
py: '6px',
lineHeight: 5,
},
}),
};
const baseStyleContainer = defineStyle({
......
......@@ -5,6 +5,7 @@ import path from 'path';
const PRESETS = {
arbitrum: 'https://arbitrum.blockscout.com',
base: 'https://base.blockscout.com',
blackfort_testnet: 'https://blackfort-testnet.blockscout.com',
celo_alfajores: 'https://celo-alfajores.blockscout.com',
eth: 'https://eth.blockscout.com',
eth_goerli: 'https://eth-goerli.blockscout.com',
......@@ -18,6 +19,7 @@ const PRESETS = {
stability_testnet: 'https://stability-testnet.blockscout.com',
zkevm: 'https://zkevm.blockscout.com',
zksync: 'https://zksync.blockscout.com',
zora: 'https://explorer.zora.energy',
// main === staging
main: 'https://eth-sepolia.k8s-dev.blockscout.com',
};
......
import type { Block } from './block';
import type { Transaction } from './transaction';
export interface ArbitrumLatestDepositsItem {
completion_transaction_hash: string;
origination_timestamp: string | null;
origination_transaction_block_number: number | null;
origination_transaction_hash: string | null;
}
export interface ArbitrumLatestDepositsResponse {
items: Array<ArbitrumLatestDepositsItem>;
}
export type ArbitrumL2MessagesItem = {
completion_transaction_hash: string | null;
id: number;
origination_address: string;
origination_timestamp: string | null;
origination_transaction_block_number: number;
origination_transaction_block_number: number | null;
origination_transaction_hash: string;
status: 'initiated' | 'sent' | 'confirmed' | 'relayed';
}
......
import type { AddressParam } from './addressParams';
export interface Validator {
export interface ValidatorStability {
address: AddressParam;
blocks_validated_count: number;
state: 'active' | 'probation' | 'inactive';
}
export interface ValidatorsResponse {
items: Array<Validator>;
export interface ValidatorsStabilityResponse {
items: Array<ValidatorStability>;
next_page_params: {
'address_hash': string;
'blocks_validated': string;
'items_count': string;
'state': Validator['state'];
'state': ValidatorStability['state'];
} | null;
}
export interface ValidatorsCountersResponse {
export interface ValidatorsStabilityCountersResponse {
active_validators_counter: string;
active_validators_percentage: number;
new_validators_counter_24h: string;
validators_counter: string;
}
export interface ValidatorsFilters {
export interface ValidatorsStabilityFilters {
// address_hash: string | undefined; // right now API doesn't support filtering by address_hash
state_filter: Validator['state'] | undefined;
state_filter: ValidatorStability['state'] | undefined;
}
export interface ValidatorsSorting {
export interface ValidatorsStabilitySorting {
sort: 'state' | 'blocks_validated';
order: 'asc' | 'desc';
}
export type ValidatorsSortingField = ValidatorsSorting['sort'];
export type ValidatorsStabilitySortingField = ValidatorsStabilitySorting['sort'];
export type ValidatorsSortingValue = `${ ValidatorsSortingField }-${ ValidatorsSorting['order'] }`;
export type ValidatorsStabilitySortingValue = `${ ValidatorsStabilitySortingField }-${ ValidatorsStabilitySorting['order'] }`;
export interface ValidatorBlackfort {
address: AddressParam;
name: string;
commission: number;
delegated_amount: string;
self_bonded_amount: string;
}
export interface ValidatorsBlackfortResponse {
items: Array<ValidatorBlackfort>;
next_page_params: {
'address_hash': string;
} | null;
}
export interface ValidatorsBlackfortCountersResponse {
new_validators_counter_24h: string;
validators_counter: string;
}
export interface ValidatorsBlackfortSorting {
sort: 'address_hash';
order: 'asc' | 'desc';
}
export type ValidatorsBlackfortSortingField = ValidatorsBlackfortSorting['sort'];
export type ValidatorsBlackfortSortingValue = `${ ValidatorsBlackfortSortingField }-${ ValidatorsBlackfortSorting['order'] }`;
export type AddressProfileAPIConfig = {
api_url_template: string;
tag_link_template?: string;
tag_icon?: string;
tag_bg_color?: string;
tag_text_color?: string;
};
......@@ -2,6 +2,7 @@ import type { ArrayElement } from 'types/utils';
export const VALIDATORS_CHAIN_TYPE = [
'stability',
'blackfort',
] as const;
export type ValidatorsChainType = ArrayElement<typeof VALIDATORS_CHAIN_TYPE>;
import React from 'react';
import * as balanceHistoryMock from 'mocks/address/coinBalanceHistory';
import { test, expect } from 'playwright/lib';
import { test, expect, devices } from 'playwright/lib';
import AddressCoinBalance from './AddressCoinBalance';
......@@ -12,7 +12,7 @@ const hooksConfig = {
},
};
test('base view +@dark-mode +@mobile', async({ render, page, mockApiResponse }) => {
test('base view +@dark-mode', async({ render, page, mockApiResponse }) => {
await mockApiResponse('address_coin_balance', balanceHistoryMock.baseResponse, { pathParams: { hash: addressHash } });
await mockApiResponse('address_coin_balance_chart', balanceHistoryMock.chartResponse, { pathParams: { hash: addressHash } });
const component = await render(<AddressCoinBalance/>, { hooksConfig });
......@@ -23,3 +23,19 @@ test('base view +@dark-mode +@mobile', async({ render, page, mockApiResponse })
await page.mouse.move(240, 100);
await expect(component).toHaveScreenshot();
});
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', async({ render, page, mockApiResponse }) => {
await mockApiResponse('address_coin_balance', balanceHistoryMock.baseResponse, { pathParams: { hash: addressHash } });
await mockApiResponse('address_coin_balance_chart', balanceHistoryMock.chartResponse, { pathParams: { hash: addressHash } });
const component = await render(<AddressCoinBalance/>, { hooksConfig });
await page.waitForFunction(() => {
return document.querySelector('path[data-name="chart-Balances-small"]')?.getAttribute('opacity') === '1';
});
await page.mouse.move(100, 100);
await page.mouse.move(240, 100);
await expect(component).toHaveScreenshot();
});
});
......@@ -42,6 +42,7 @@ const ContractMethodFieldInputTuple = ({ data, basePath, level, isDisabled, isOp
basePath={ `${ basePath }:${ index }` }
level={ level + 1 }
isDisabled={ isDisabled }
isOptional={ isOptional }
/>
);
}
......
......@@ -74,4 +74,27 @@ describe('transformFormDataToMethodArgs', () => {
],
]);
});
it('should transform all nested empty arrays to empty arrays', () => {
const formData = {
'0': '0x1D415D28380ff51A507F7B176ca5F27833F7FffD',
'1': '0x1D415D28380ff51A507F7B176ca5F27833F7FffD',
'2': '3160',
'3': true,
// tuple array without elements
'4:0:0:0': undefined,
'4:0:1:0': undefined,
'4:0:1:1': undefined,
'4:0:1:2': undefined,
'4:0:1:3': undefined,
};
const result = transformFormDataToMethodArgs(formData);
expect(result).toEqual([
'0x1D415D28380ff51A507F7B176ca5F27833F7FffD',
'0x1D415D28380ff51A507F7B176ca5F27833F7FffD',
'3160',
true,
[],
]);
});
});
......@@ -81,7 +81,9 @@ export function transformFormDataToMethodArgs(formData: ContractMethodFormFields
_set(result, field.replaceAll(':', '.'), value);
}
return filterOutEmptyItems(result);
const filteredResult = filterOutEmptyItems(result);
const mappedResult = mapEmptyNestedArrays(filteredResult);
return mappedResult;
}
function filterOutEmptyItems(array: Array<unknown>): Array<unknown> {
......@@ -90,11 +92,26 @@ function filterOutEmptyItems(array: Array<unknown>): Array<unknown> {
// The only optional field is the native coin value, which is safely handled in the form submit handler.
// 2. When the user adds and removes items from a field array.
// In this scenario, empty items need to be filtered out to maintain the correct sequence of arguments.
// We don't use isEmptyField() function here because of the second case otherwise it will not keep the correct order of arguments.
return array
.map((item) => Array.isArray(item) ? filterOutEmptyItems(item) : item)
.filter((item) => item !== undefined);
}
function isEmptyField(field: unknown): boolean {
// the empty string is meant that the field was touched but left empty
// the undefined is meant that the field was not touched
return field === undefined || field === '';
}
function isEmptyNestedArray(array: Array<unknown>): boolean {
return array.flat(Infinity).filter((item) => !isEmptyField(item)).length === 0;
}
function mapEmptyNestedArrays(array: Array<unknown>): Array<unknown> {
return array.map((item) => Array.isArray(item) && isEmptyNestedArray(item) ? [] : item);
}
export function getFieldLabel(input: ContractAbiItemInput, isRequired?: boolean) {
const name = input.name || input.internalType || '<unnamed argument>';
return `${ name } (${ input.type })${ isRequired ? '*' : '' }`;
......
......@@ -28,11 +28,14 @@ export default function useCallMethodPublicClient(): (params: Params) => Promise
}
const address = getAddress(addressHash);
// for write payable methods we add additional input for native coin value
// so in simulate mode we need to strip it off
const _args = args.slice(0, item.inputs.length);
const params = {
abi: [ item ],
functionName: item.name,
args: args,
args: _args,
address,
account,
};
......
......@@ -61,7 +61,6 @@ test.describe('mobile', () => {
);
await page.getByRole('button', { name: /select/i }).click();
await page.getByText('USD Coin').hover();
await expect(page).toHaveScreenshot();
});
......
import React from 'react';
import * as depositMock from 'mocks/arbitrum/deposits';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect } from 'playwright/lib';
import LatestArbitrumDeposits from './LatestArbitrumDeposits';
test('default view +@mobile', async({ render, mockApiResponse, mockEnvs }) => {
await mockEnvs(ENVS_MAP.arbitrumRollup);
mockApiResponse('homepage_arbitrum_deposits', depositMock.latestDepositsResponse);
const component = await render(<LatestArbitrumDeposits/>);
await expect(component).toHaveScreenshot();
});
......@@ -2,6 +2,7 @@ import {
Box,
Flex,
Grid,
GridItem,
Skeleton,
} from '@chakra-ui/react';
import React from 'react';
......@@ -17,9 +18,9 @@ import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
type DepositsItem = {
l1BlockNumber: number;
l1TxHash: string;
l2TxHash: string | null;
l1BlockNumber: number | null;
l1TxHash: string | null;
l2TxHash: string;
timestamp: string | null;
}
......@@ -38,7 +39,7 @@ type ItemProps = {
const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
const isMobile = useIsMobile();
const l1BlockLink = (
const l1BlockLink = item.l1BlockNumber ? (
<BlockEntityL1
number={ item.l1BlockNumber }
isLoading={ isLoading }
......@@ -46,9 +47,18 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
lineHeight={ 5 }
fontWeight={ 700 }
/>
) : (
<BlockEntityL1
number="TBD"
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 700 }
noLink
/>
);
const l1TxLink = (
const l1TxLink = item.l1TxHash ? (
<TxEntityL1
isLoading={ isLoading }
hash={ item.l1TxHash }
......@@ -56,9 +66,18 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
lineHeight={ 5 }
truncation={ isMobile ? 'constant_long' : 'dynamic' }
/>
) : (
<TxEntityL1
isLoading={ isLoading }
hash="To be determined"
fontSize="sm"
lineHeight={ 5 }
truncation="none"
noLink
/>
);
const l2TxLink = item.l2TxHash ? (
const l2TxLink = (
<TxEntity
isLoading={ isLoading }
hash={ item.l2TxHash }
......@@ -66,7 +85,7 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
lineHeight={ 5 }
truncation={ isMobile ? 'constant_long' : 'dynamic' }
/>
) : null;
);
const content = (() => {
if (isMobile) {
......@@ -74,11 +93,13 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
<>
<Flex justifyContent="space-between" alignItems="center" mb={ 1 }>
{ l1BlockLink }
<TimeAgoWithTooltip
timestamp={ item.timestamp }
isLoading={ isLoading }
color="text_secondary"
/>
{ item.timestamp ? (
<TimeAgoWithTooltip
timestamp={ item.timestamp }
isLoading={ isLoading }
color="text_secondary"
/>
) : <GridItem/> }
</Flex>
<Grid gridTemplateColumns="56px auto">
<Skeleton isLoaded={ !isLoading } my="5px" w="fit-content">
......@@ -101,14 +122,16 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
L1 txn
</Skeleton>
{ l1TxLink }
<TimeAgoWithTooltip
timestamp={ item.timestamp }
isLoading={ isLoading }
color="text_secondary"
w="fit-content"
h="fit-content"
my="2px"
/>
{ item.timestamp ? (
<TimeAgoWithTooltip
timestamp={ item.timestamp }
isLoading={ isLoading }
color="text_secondary"
w="fit-content"
h="fit-content"
my="2px"
/>
) : <GridItem/> }
<Skeleton isLoaded={ !isLoading } w="fit-content" h="fit-content" my="2px">
L2 txn
</Skeleton>
......
......@@ -4,6 +4,7 @@ import type { MarketplaceAppWithSecurityReport } from 'types/client/marketplace'
import { apps as appsMock } from 'mocks/apps/apps';
import { securityReports as securityReportsMock } from 'mocks/apps/securityReports';
import type { TestFnArgs } from 'playwright/lib';
import { test, expect, devices } from 'playwright/lib';
import MarketplaceAppModal from './MarketplaceAppModal';
......@@ -28,7 +29,7 @@ const props = {
canRate: undefined,
};
const testFn: Parameters<typeof test>[1] = async({ render, page, mockAssetResponse, mockEnvs }) => {
const testFn = async({ render, page, mockAssetResponse, mockEnvs }: TestFnArgs) => {
await mockEnvs([
[ 'NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY', 'test' ],
[ 'NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID', 'test' ],
......
......@@ -33,13 +33,15 @@ const ArbitrumL2MessagesListItem = ({ item, isLoading, direction }: Props) => {
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 block</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<BlockEntityL1
number={ item.origination_transaction_block_number }
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
/>
{ item.origination_transaction_block_number ? (
<BlockEntityL1
number={ item.origination_transaction_block_number }
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
/>
) : <chakra.span>N/A</chakra.span> }
</ListItemMobileGrid.Value>
</>
) }
......@@ -84,14 +86,18 @@ const ArbitrumL2MessagesListItem = ({ item, isLoading, direction }: Props) => {
) }
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TimeAgoWithTooltip
timestamp={ item.origination_timestamp }
isLoading={ isLoading }
display="inline-block"
/>
</ListItemMobileGrid.Value>
{ item.origination_timestamp && (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TimeAgoWithTooltip
timestamp={ item.origination_timestamp }
isLoading={ isLoading }
display="inline-block"
/>
</ListItemMobileGrid.Value>
</>
) }
<ListItemMobileGrid.Label isLoading={ isLoading }>Status</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
......
......@@ -29,14 +29,16 @@ const ArbitrumL2MessagesTableItem = ({ item, direction, isLoading }: Props) => {
<Tr>
{ direction === 'to-rollup' && (
<Td verticalAlign="middle">
<BlockEntityL1
number={ item.origination_transaction_block_number }
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
noIcon
/>
{ item.origination_transaction_block_number ? (
<BlockEntityL1
number={ item.origination_transaction_block_number }
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
noIcon
/>
) : <chakra.span color="text_secondary">N/A</chakra.span> }
</Td>
) }
{ direction === 'from-rollup' && (
......
......@@ -10,6 +10,7 @@ import getCheckedSummedAddress from 'lib/address/getCheckedSummedAddress';
import useAddressMetadataInfoQuery from 'lib/address/useAddressMetadataInfoQuery';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app';
import useAddressProfileApiQuery from 'lib/hooks/useAddressProfileApiQuery';
import useContractTabs from 'lib/hooks/useContractTabs';
import useIsSafeAddress from 'lib/hooks/useIsSafeAddress';
import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText';
......@@ -54,6 +55,7 @@ import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
const TOKEN_TABS = [ 'tokens_erc20', 'tokens_nfts', 'tokens_nfts_collection', 'tokens_nfts_list' ];
const txInterpretation = config.features.txInterpretation;
const addressProfileAPIFeature = config.features.addressProfileAPI;
const AddressPageContent = () => {
const router = useRouter();
......@@ -92,6 +94,7 @@ const AddressPageContent = () => {
const addressesForMetadataQuery = React.useMemo(() => ([ hash ].filter(Boolean)), [ hash ]);
const addressMetadataQuery = useAddressMetadataInfoQuery(addressesForMetadataQuery, areQueriesEnabled);
const userPropfileApiQuery = useAddressProfileApiQuery(hash, addressProfileAPIFeature.isEnabled && areQueriesEnabled);
const addressEnsDomainsQuery = useApiQuery('addresses_lookup', {
pathParams: { chainId: config.chain.id },
......@@ -248,6 +251,8 @@ const AddressPageContent = () => {
mudTablesCountQuery.data,
]);
const usernameApiTag = userPropfileApiQuery.data?.user_profile?.username;
const tags: Array<EntityTag> = React.useMemo(() => {
return [
...(addressQuery.data?.public_tags?.map((tag) => ({ slug: tag.label, name: tag.display_name, tagType: 'custom' as const, ordinal: -1 })) || []),
......@@ -258,6 +263,18 @@ const AddressPageContent = () => {
addressQuery.data?.implementations?.length ? { slug: 'proxy', name: 'Proxy', tagType: 'custom' as const, ordinal: -1 } : undefined,
addressQuery.data?.token ? { slug: 'token', name: 'Token', tagType: 'custom' as const, ordinal: -1 } : undefined,
isSafeAddress ? { slug: 'safe', name: 'Multisig: Safe', tagType: 'custom' as const, ordinal: -10 } : undefined,
addressProfileAPIFeature.isEnabled && usernameApiTag ? {
slug: 'username_api',
name: usernameApiTag,
tagType: 'custom' as const,
ordinal: 11,
meta: {
tagIcon: addressProfileAPIFeature.tagIcon,
bgColor: addressProfileAPIFeature.tagBgColor,
textColor: addressProfileAPIFeature.tagTextColor,
tagUrl: addressProfileAPIFeature.tagLinkTemplate ? addressProfileAPIFeature.tagLinkTemplate.replace('{username}', usernameApiTag) : undefined,
},
} : undefined,
config.features.userOps.isEnabled && userOpsAccountQuery.data ?
{ slug: 'user_ops_acc', name: 'Smart contract wallet', tagType: 'custom' as const, ordinal: -10 } :
undefined,
......@@ -267,7 +284,7 @@ const AddressPageContent = () => {
...formatUserTags(addressQuery.data),
...(addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags || []),
].filter(Boolean).sort(sortEntityTags);
}, [ addressMetadataQuery.data, addressQuery.data, hash, isSafeAddress, userOpsAccountQuery.data, mudTablesCountQuery.data ]);
}, [ addressMetadataQuery.data, addressQuery.data, hash, isSafeAddress, userOpsAccountQuery.data, mudTablesCountQuery.data, usernameApiTag ]);
const titleContentAfter = (
<EntityTags
......@@ -275,7 +292,8 @@ const AddressPageContent = () => {
isLoading={
isLoading ||
(config.features.userOps.isEnabled && userOpsAccountQuery.isPlaceholderData) ||
(config.features.addressMetadata.isEnabled && addressMetadataQuery.isPending)
(config.features.addressMetadata.isEnabled && addressMetadataQuery.isPending) ||
(addressProfileAPIFeature.isEnabled && userPropfileApiQuery.isPending)
}
/>
);
......
import React from 'react';
import * as statsLineMock from 'mocks/stats/line';
import { test, expect } from 'playwright/lib';
import formatDate from 'ui/shared/chart/utils/formatIntervalDate';
import Chart from './Chart';
const CHART_ID = 'averageGasPrice';
test.beforeEach(async({ mockTextAd }) => {
await mockTextAd();
});
const hooksConfig = {
router: {
query: { id: CHART_ID },
},
};
test('base view +@dark-mode +@mobile', async({ render, mockApiResponse, page }) => {
const date = new Date();
date.setMonth(date.getMonth() - 1);
const chartApiUrl = await mockApiResponse(
'stats_line',
statsLineMock.averageGasPrice,
{
pathParams: { id: CHART_ID },
queryParams: {
from: formatDate(date),
to: '2022-11-11',
resolution: 'DAY',
},
},
);
const component = await render(<Chart/>, { hooksConfig });
await page.waitForResponse(chartApiUrl);
await page.waitForFunction(() => {
return document.querySelector('path[data-name="chart-Charttitle-fullscreen"]')?.getAttribute('opacity') === '1';
});
await expect(component).toHaveScreenshot();
});
import { Button, Flex, Link, Text } from '@chakra-ui/react';
import type { NextRouter } from 'next/router';
import { useRouter } from 'next/router';
import React from 'react';
import { Resolution } from '@blockscout/stats-types';
import type { StatsIntervalIds } from 'types/client/stats';
import { StatsIntervalId } from 'types/client/stats';
import config from 'configs/app';
import { useAppContext } from 'lib/contexts/app';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useIsMobile from 'lib/hooks/useIsMobile';
import isBrowser from 'lib/isBrowser';
import * as metadata from 'lib/metadata';
import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect';
import ChartMenu from 'ui/shared/chart/ChartMenu';
import ChartResolutionSelect from 'ui/shared/chart/ChartResolutionSelect';
import ChartWidgetContent from 'ui/shared/chart/ChartWidgetContent';
import useChartQuery from 'ui/shared/chart/useChartQuery';
import useZoom from 'ui/shared/chart/useZoom';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import IconSvg from 'ui/shared/IconSvg';
import PageTitle from 'ui/shared/Page/PageTitle';
const DEFAULT_RESOLUTION = Resolution.DAY;
const getIntervalByResolution = (resolution: Resolution): StatsIntervalIds => {
switch (resolution) {
case 'DAY':
return 'oneMonth';
case 'WEEK':
return 'oneMonth';
case 'MONTH':
return 'oneYear';
case 'YEAR':
return 'all';
default:
return 'oneMonth';
}
};
const getIntervalFromQuery = (router: NextRouter): StatsIntervalIds | undefined => {
const intervalFromQuery = getQueryParamString(router.query.interval);
if (!intervalFromQuery || !Object.values(StatsIntervalId).includes(intervalFromQuery as StatsIntervalIds)) {
return undefined;
}
return intervalFromQuery as StatsIntervalIds;
};
const getResolutionFromQuery = (router: NextRouter) => {
const resolutionFromQuery = getQueryParamString(router.query.resolution);
if (!resolutionFromQuery || !Resolution[resolutionFromQuery as keyof typeof Resolution]) {
return DEFAULT_RESOLUTION;
}
return resolutionFromQuery as Resolution;
};
const Chart = () => {
const router = useRouter();
const id = getQueryParamString(router.query.id);
const intervalFromQuery = getIntervalFromQuery(router);
const resolutionFromQuery = getResolutionFromQuery(router);
const [ intervalState, setIntervalState ] = React.useState<StatsIntervalIds | undefined>(intervalFromQuery);
const [ resolution, setResolution ] = React.useState<Resolution>(resolutionFromQuery || DEFAULT_RESOLUTION);
const { zoomRange, handleZoom, handleZoomReset } = useZoom();
const interval = intervalState || getIntervalByResolution(resolution);
const ref = React.useRef(null);
const isMobile = useIsMobile();
const isInBrowser = isBrowser();
const appProps = useAppContext();
const backLink = React.useMemo(() => {
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/stats');
if (!hasGoBackLink) {
return;
}
return {
label: 'Back to charts list',
url: appProps.referrer,
};
}, [ appProps.referrer ]);
const onIntervalChange = React.useCallback((interval: StatsIntervalIds) => {
setIntervalState(interval);
router.push(
{
pathname: router.pathname,
query: { ...router.query, interval },
},
undefined,
{ shallow: true },
);
}, [ setIntervalState, router ]);
const onResolutionChange = React.useCallback((resolution: Resolution) => {
setResolution(resolution);
router.push({
pathname: router.pathname,
query: { ...router.query, resolution },
});
}, [ setResolution, router ]);
const handleReset = React.useCallback(() => {
handleZoomReset();
onResolutionChange(DEFAULT_RESOLUTION);
}, [ handleZoomReset, onResolutionChange ]);
const { items, info, lineQuery } = useChartQuery(id, resolution, interval);
React.useEffect(() => {
if (info && !config.meta.seo.enhancedDataEnabled) {
metadata.update({ pathname: '/stats/[id]', query: { id } }, info);
}
}, [ info, id ]);
const onShare = React.useCallback(async() => {
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Share chart', Info: id });
try {
await window.navigator.share({
title: info?.title,
text: info?.description,
url: window.location.href,
});
} catch (error) {}
}, [ info, id ]);
if (lineQuery.isError) {
if (isCustomAppError(lineQuery.error)) {
throwOnResourceLoadError({ resource: 'stats_line', error: lineQuery.error, isError: true });
}
}
const hasItems = (items && items.length > 2) || lineQuery.isPending;
const isInfoLoading = !info && lineQuery.isPlaceholderData;
const shareButton = (
<Button
leftIcon={ <IconSvg name="share" w={ 4 } h={ 4 }/> }
colorScheme="blue"
size="sm"
variant="outline"
onClick={ onShare }
ml={ 6 }
>
Share
</Button>
);
return (
<>
<PageTitle
title={ info?.title || lineQuery.data?.info?.title || '' }
mb={ 3 }
isLoading={ isInfoLoading }
backLink={ backLink }
secondRow={ info?.description || lineQuery.data?.info?.description }
withTextAd
/>
<Flex alignItems="center" justifyContent="space-between">
<Flex alignItems="center" gap={{ base: 3, lg: 6 }} maxW="100%" overflow="hidden">
<Flex alignItems="center" gap={ 3 }>
{ !isMobile && <Text>Period</Text> }
<ChartIntervalSelect interval={ interval } onIntervalChange={ onIntervalChange }/>
</Flex>
{ lineQuery.data?.info?.resolutions && lineQuery.data?.info?.resolutions.length > 1 && (
<Flex alignItems="center" gap={ 3 }>
<Text>{ isMobile ? 'Res.' : 'Resolution' }</Text>
<ChartResolutionSelect
resolution={ resolution }
onResolutionChange={ onResolutionChange }
resolutions={ lineQuery.data?.info?.resolutions || [] }
/>
</Flex>
) }
{ (Boolean(zoomRange)) && (
<Link
onClick={ handleReset }
display="flex"
alignItems="center"
gap={ 2 }
>
<IconSvg name="repeat" w={ 5 } h={ 5 }/>
{ !isMobile && 'Reset' }
</Link>
) }
</Flex>
<Flex alignItems="center" gap={ 3 }>
{ /* TS thinks window.navigator.share can't be undefined, but it can */ }
{ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ }
{ !isMobile && (isInBrowser && ((window.navigator.share as any) ?
shareButton :
(
<CopyToClipboard
text={ config.app.baseUrl + router.asPath }
size={ 5 }
type="link"
variant="outline"
colorScheme="blue"
display="flex"
borderRadius="8px"
width={ 8 }
height={ 8 }
/>
)
)) }
{ (hasItems || lineQuery.isPlaceholderData) && (
<ChartMenu
items={ items }
title={ info?.title || '' }
isLoading={ lineQuery.isPlaceholderData }
chartRef={ ref }
resolution={ resolution }
zoomRange={ zoomRange }
handleZoom={ handleZoom }
handleZoomReset={ handleZoomReset }
chartUrl={ isMobile ? window.location.href : undefined }
/>
) }
</Flex>
</Flex>
<Flex
ref={ ref }
flexGrow={ 1 }
h="50vh"
mt={ 3 }
position="relative"
>
<ChartWidgetContent
isError={ lineQuery.isError }
items={ items }
title={ info?.title || '' }
units={ info?.units || undefined }
isEnlarged
isLoading={ lineQuery.isPlaceholderData }
zoomRange={ zoomRange }
handleZoom={ handleZoom }
emptyText="No data for the selected resolution & interval."
resolution={ resolution }
/>
</Flex>
</>
);
};
export default Chart;
......@@ -6,6 +6,7 @@ import config from 'configs/app';
import { apps as appsMock } from 'mocks/apps/apps';
import { ratings as ratingsMock } from 'mocks/apps/ratings';
import { securityReports as securityReportsMock } from 'mocks/apps/securityReports';
import type { TestFnArgs } from 'playwright/lib';
import { test, expect, devices } from 'playwright/lib';
import MarketplaceApp from './MarketplaceApp';
......@@ -20,7 +21,7 @@ const hooksConfig = {
const MARKETPLACE_CONFIG_URL = 'http://localhost:4000/marketplace-config.json';
const MARKETPLACE_SECURITY_REPORTS_URL = 'http://localhost:4000/marketplace-security-reports.json';
const testFn: Parameters<typeof test>[1] = async({ render, mockConfigResponse, mockAssetResponse, mockEnvs, mockRpcResponse, page }) => {
const testFn = async({ render, mockConfigResponse, mockAssetResponse, mockEnvs, mockRpcResponse, page }: TestFnArgs) => {
await mockEnvs([
[ 'NEXT_PUBLIC_MARKETPLACE_ENABLED', 'true' ],
[ 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', MARKETPLACE_CONFIG_URL ],
......
import React from 'react';
import * as validatorsMock from 'mocks/validators/blackfort';
import { test, expect } from 'playwright/lib';
import Validators from './ValidatorsBlackfort';
const chainType = 'blackfort';
test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => {
await mockEnvs([
[ 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', chainType ],
]);
await mockApiResponse('validators_blackfort', validatorsMock.validatorsResponse);
await mockApiResponse('validators_blackfort_counters', validatorsMock.validatorsCountersResponse);
await mockTextAd();
const component = await render(<Validators/>);
await expect(component).toHaveScreenshot();
});
import { Box, Hide, HStack, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type {
ValidatorsBlackfortSorting,
ValidatorsBlackfortSortingField,
ValidatorsBlackfortSortingValue,
} from 'types/api/validators';
import config from 'configs/app';
import { generateListStub } from 'stubs/utils';
import { VALIDATOR_BLACKFORT } from 'stubs/validators';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import Sort from 'ui/shared/sort/Sort';
import { VALIDATORS_BLACKFORT_SORT_OPTIONS } from 'ui/validatorsBlackfort/utils';
import ValidatorsCounters from 'ui/validatorsBlackfort/ValidatorsCounters';
import ValidatorsList from 'ui/validatorsBlackfort/ValidatorsList';
import ValidatorsTable from 'ui/validatorsBlackfort/ValidatorsTable';
const ValidatorsBlackfort = () => {
const router = useRouter();
const [ sort, setSort ] =
React.useState<ValidatorsBlackfortSortingValue | undefined>(
getSortValueFromQuery<ValidatorsBlackfortSortingValue>(router.query, VALIDATORS_BLACKFORT_SORT_OPTIONS),
);
const { isError, isPlaceholderData, data, pagination, onSortingChange } = useQueryWithPages({
resourceName: 'validators_blackfort',
sorting: getSortParamsFromValue<ValidatorsBlackfortSortingValue, ValidatorsBlackfortSortingField, ValidatorsBlackfortSorting['order']>(sort),
options: {
enabled: config.features.validators.isEnabled,
placeholderData: generateListStub<'validators_blackfort'>(
VALIDATOR_BLACKFORT,
50,
{ next_page_params: null },
),
},
});
const handleSortChange = React.useCallback((value?: ValidatorsBlackfortSortingValue) => {
setSort(value);
onSortingChange(getSortParamsFromValue(value));
}, [ onSortingChange ]);
const sortButton = (
<Sort
name="validators_sorting"
defaultValue={ sort }
options={ VALIDATORS_BLACKFORT_SORT_OPTIONS }
onChange={ handleSortChange }
/>
);
const actionBar = (
<>
<HStack spacing={ 3 } mb={ 6 } display={{ base: 'flex', lg: 'none' }}>
{ sortButton }
</HStack>
{ pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
) }
</>
);
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
<ValidatorsList data={ data.items } isLoading={ isPlaceholderData }/>
</Show>
<Hide below="lg" ssr={ false }>
<ValidatorsTable
data={ data.items }
sort={ sort }
setSorting={ handleSortChange }
isLoading={ isPlaceholderData }
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
/>
</Hide>
</>
) : null;
return (
<Box>
<PageTitle title="Validators" withTextAd/>
<ValidatorsCounters/>
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText="There are no validators."
content={ content }
actionBar={ actionBar }
/>
</Box>
);
};
export default ValidatorsBlackfort;
import React from 'react';
import * as validatorsMock from 'mocks/validators/index';
import * as validatorsMock from 'mocks/validators/stability';
import { test, expect } from 'playwright/lib';
import Validators from './Validators';
import Validators from './ValidatorsStability';
const chainType = 'stability';
......@@ -11,8 +11,8 @@ test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd
await mockEnvs([
[ 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', chainType ],
]);
await mockApiResponse('validators', validatorsMock.validatorsResponse, { pathParams: { chainType } });
await mockApiResponse('validators_counters', validatorsMock.validatorsCountersResponse, { pathParams: { chainType } });
await mockApiResponse('validators_stability', validatorsMock.validatorsResponse);
await mockApiResponse('validators_stability_counters', validatorsMock.validatorsCountersResponse);
await mockTextAd();
const component = await render(<Validators/>);
......
......@@ -2,8 +2,12 @@ import { Box, Hide, HStack, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types';
import type { ValidatorsFilters, ValidatorsSorting, ValidatorsSortingField, ValidatorsSortingValue } from 'types/api/validators';
import type {
ValidatorsStabilityFilters,
ValidatorsStabilitySorting,
ValidatorsStabilitySortingField,
ValidatorsStabilitySortingValue,
} from 'types/api/validators';
import config from 'configs/app';
// import useDebounce from 'lib/hooks/useDebounce';
......@@ -11,7 +15,7 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import { generateListStub } from 'stubs/utils';
import { VALIDATOR } from 'stubs/validators';
import { VALIDATOR_STABILITY } from 'stubs/validators';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
// import FilterInput from 'ui/shared/filters/FilterInput';
......@@ -21,35 +25,36 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import Sort from 'ui/shared/sort/Sort';
import { SORT_OPTIONS } from 'ui/validators/utils';
import ValidatorsCounters from 'ui/validators/ValidatorsCounters';
import ValidatorsFilter from 'ui/validators/ValidatorsFilter';
import ValidatorsList from 'ui/validators/ValidatorsList';
import ValidatorsTable from 'ui/validators/ValidatorsTable';
import { VALIDATORS_STABILITY_SORT_OPTIONS } from 'ui/validatorsStability/utils';
import ValidatorsCounters from 'ui/validatorsStability/ValidatorsCounters';
import ValidatorsFilter from 'ui/validatorsStability/ValidatorsFilter';
import ValidatorsList from 'ui/validatorsStability/ValidatorsList';
import ValidatorsTable from 'ui/validatorsStability/ValidatorsTable';
const Validators = () => {
const ValidatorsStability = () => {
const router = useRouter();
// const [ searchTerm, setSearchTerm ] = React.useState(getQueryParamString(router.query.address_hash) || undefined);
const [ statusFilter, setStatusFilter ] = React.useState(getQueryParamString(router.query.state_filter) as ValidatorsFilters['state_filter'] || undefined);
const [ sort, setSort ] =
React.useState<ValidatorsSortingValue | undefined>(getSortValueFromQuery<ValidatorsSortingValue>(router.query, SORT_OPTIONS));
const [ statusFilter, setStatusFilter ] =
React.useState(getQueryParamString(router.query.state_filter) as ValidatorsStabilityFilters['state_filter'] || undefined);
const [ sort, setSort ] = React.useState<ValidatorsStabilitySortingValue | undefined>(
getSortValueFromQuery<ValidatorsStabilitySortingValue>(router.query, VALIDATORS_STABILITY_SORT_OPTIONS),
);
// const debouncedSearchTerm = useDebounce(searchTerm || '', 300);
const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination, onFilterChange, onSortingChange } = useQueryWithPages({
resourceName: 'validators',
pathParams: { chainType: getFeaturePayload(config.features.validators)?.chainType },
resourceName: 'validators_stability',
filters: {
// address_hash: debouncedSearchTerm,
state_filter: statusFilter,
},
sorting: getSortParamsFromValue<ValidatorsSortingValue, ValidatorsSortingField, ValidatorsSorting['order']>(sort),
sorting: getSortParamsFromValue<ValidatorsStabilitySortingValue, ValidatorsStabilitySortingField, ValidatorsStabilitySorting['order']>(sort),
options: {
enabled: config.features.validators.isEnabled,
placeholderData: generateListStub<'validators'>(
VALIDATOR,
placeholderData: generateListStub<'validators_stability'>(
VALIDATOR_STABILITY,
50,
{ next_page_params: null },
),
......@@ -69,7 +74,7 @@ const Validators = () => {
return;
}
const state = value === 'all' ? undefined : value as ValidatorsFilters['state_filter'];
const state = value === 'all' ? undefined : value as ValidatorsStabilityFilters['state_filter'];
onFilterChange({
// address_hash: debouncedSearchTerm,
......@@ -78,12 +83,13 @@ const Validators = () => {
setStatusFilter(state);
}, [ onFilterChange ]);
const handleSortChange = React.useCallback((value?: ValidatorsSortingValue) => {
const handleSortChange = React.useCallback((value?: ValidatorsStabilitySortingValue) => {
setSort(value);
onSortingChange(getSortParamsFromValue(value));
}, [ onSortingChange ]);
const filterMenu = <ValidatorsFilter onChange={ handleStateFilterChange } defaultValue={ statusFilter } hasActiveFilter={ Boolean(statusFilter) }/>;
const filterMenu =
<ValidatorsFilter onChange={ handleStateFilterChange } defaultValue={ statusFilter } hasActiveFilter={ Boolean(statusFilter) }/>;
// const filterInput = (
// <FilterInput
......@@ -99,7 +105,7 @@ const Validators = () => {
<Sort
name="validators_sorting"
defaultValue={ sort }
options={ SORT_OPTIONS }
options={ VALIDATORS_STABILITY_SORT_OPTIONS }
onChange={ handleSortChange }
/>
);
......@@ -141,7 +147,7 @@ const Validators = () => {
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText="There are no verified contracts."
emptyText="There are no validators."
filterProps={{
emptyFilteredText: `Couldn${ apos }t find any validator that matches your query.`,
hasActiveFilters: Boolean(
......@@ -156,4 +162,4 @@ const Validators = () => {
);
};
export default Validators;
export default ValidatorsStability;
......@@ -22,6 +22,7 @@ const CopyToClipboard = ({ text, className, isLoading, onClick, size = 5, type,
// have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107
const { isOpen, onOpen, onClose } = useDisclosure();
const iconColor = useColorModeValue('gray.400', 'gray.500');
const colorProps = colorScheme ? {} : { color: iconColor };
const iconName = icon || (type === 'link' ? 'link' : 'copy');
useEffect(() => {
......@@ -44,10 +45,10 @@ const CopyToClipboard = ({ text, className, isLoading, onClick, size = 5, type,
return (
<Tooltip label={ copied ? 'Copied' : `Copy${ type === 'link' ? ' link ' : ' ' }to clipboard` } isOpen={ isOpen || copied }>
<IconButton
{ ...colorProps }
aria-label="copy"
icon={ <IconSvg name={ iconName } boxSize={ size }/> }
boxSize={ size }
color={ iconColor }
variant={ variant }
colorScheme={ colorScheme }
display="inline-block"
......
......@@ -154,9 +154,9 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa
{ withTextAd && <TextAd order={{ base: -1, lg: 100 }} mb={{ base: 6, lg: 0 }} ml="auto" w={{ base: '100%', lg: 'auto' }}/> }
</Flex>
{ secondRow && (
<Flex alignItems="center" minH={ 10 } overflow="hidden" _empty={{ display: 'none' }}>
<Skeleton isLoaded={ !isLoading } alignItems="center" minH={ 10 } overflow="hidden" display="flex" _empty={{ display: 'none' }}>
{ secondRow }
</Flex>
</Skeleton>
) }
</Flex>
);
......
import type { TagProps } from '@chakra-ui/react';
import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { StatsInterval, StatsIntervalIds } from 'types/client/stats';
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
import { STATS_INTERVALS } from 'ui/stats/constants';
import StatsDropdownMenu from 'ui/stats/StatsDropdownMenu';
const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
title: STATS_INTERVALS[id as StatsIntervalIds].title,
})) as Array<StatsInterval>;
const intervalListShort = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
title: STATS_INTERVALS[id as StatsIntervalIds].shortTitle,
})) as Array<StatsInterval>;
type Props = {
interval: StatsIntervalIds;
onIntervalChange: (newInterval: StatsIntervalIds) => void;
isLoading?: boolean;
selectTagSize?: TagProps['size'];
}
const ChartIntervalSelect = ({ interval, onIntervalChange, isLoading, selectTagSize }: Props) => {
return (
<>
<Skeleton display={{ base: 'none', lg: 'flex' }} borderRadius="base" isLoaded={ !isLoading }>
<TagGroupSelect<StatsIntervalIds> items={ intervalListShort } onChange={ onIntervalChange } value={ interval } tagSize={ selectTagSize }/>
</Skeleton>
<Skeleton display={{ base: 'block', lg: 'none' }} borderRadius="base" isLoaded={ !isLoading }>
<StatsDropdownMenu
items={ intervalList }
selectedId={ interval }
onSelect={ onIntervalChange }
/>
</Skeleton>
</>
);
};
export default React.memo(ChartIntervalSelect);
import {
IconButton,
MenuButton,
MenuItem,
MenuList,
Skeleton,
useClipboard,
useColorModeValue,
VisuallyHidden,
} from '@chakra-ui/react';
import domToImage from 'dom-to-image';
import React from 'react';
import type { TimeChartItem } from './types';
import type { Resolution } from '@blockscout/stats-types';
import dayjs from 'lib/date/dayjs';
import isBrowser from 'lib/isBrowser';
import saveAsCSV from 'lib/saveAsCSV';
import Menu from 'ui/shared/chakra/Menu';
import IconSvg from 'ui/shared/IconSvg';
import FullscreenChartModal from './FullscreenChartModal';
export type Props = {
items?: Array<TimeChartItem>;
title: string;
description?: string;
units?: string;
isLoading: boolean;
chartRef: React.RefObject<HTMLDivElement>;
chartUrl?: string;
resolution?: Resolution;
zoomRange?: [ Date, Date ];
handleZoom: (range: [ Date, Date ]) => void;
handleZoomReset: () => void;
}
const DOWNLOAD_IMAGE_SCALE = 5;
const ChartMenu = ({
items,
title,
description,
units,
isLoading,
chartRef,
chartUrl,
resolution,
zoomRange,
handleZoom,
handleZoomReset,
}: Props) => {
const pngBackgroundColor = useColorModeValue('white', 'black');
const [ isFullscreen, setIsFullscreen ] = React.useState(false);
const { onCopy } = useClipboard(chartUrl ?? '');
const isInBrowser = isBrowser();
const showChartFullscreen = React.useCallback(() => {
setIsFullscreen(true);
}, []);
const clearFullscreenChart = React.useCallback(() => {
setIsFullscreen(false);
}, []);
const handleFileSaveClick = React.useCallback(() => {
// wait for context menu to close
setTimeout(() => {
if (chartRef.current) {
domToImage.toPng(chartRef.current,
{
quality: 100,
bgcolor: pngBackgroundColor,
width: chartRef.current.offsetWidth * DOWNLOAD_IMAGE_SCALE,
height: chartRef.current.offsetHeight * DOWNLOAD_IMAGE_SCALE,
filter: (node) => node.nodeName !== 'BUTTON',
style: {
borderColor: 'transparent',
transform: `scale(${ DOWNLOAD_IMAGE_SCALE })`,
'transform-origin': 'top left',
},
})
.then((dataUrl) => {
const link = document.createElement('a');
link.download = `${ title } (Blockscout chart).png`;
link.href = dataUrl;
link.click();
link.remove();
});
}
}, 100);
}, [ pngBackgroundColor, title, chartRef ]);
const handleSVGSavingClick = React.useCallback(() => {
if (items) {
const headerRows = [
'Date', 'Value',
];
const dataRows = items.map((item) => [
dayjs(item.date).format('YYYY-MM-DD'), String(item.value),
]);
saveAsCSV(headerRows, dataRows, `${ title } (Blockscout stats)`);
}
}, [ items, title ]);
// TS thinks window.navigator.share can't be undefined, but it can
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const hasShare = isInBrowser && (window.navigator.share as any);
const handleShare = React.useCallback(async() => {
try {
await window.navigator.share({
title: title,
text: description,
url: chartUrl,
});
} catch (error) {}
}, [ title, description, chartUrl ]);
return (
<>
<Menu>
<Skeleton isLoaded={ !isLoading } borderRadius="base">
<MenuButton
w="36px"
h="32px"
icon={ <IconSvg name="dots" boxSize={ 4 } transform="rotate(-90deg)"/> }
colorScheme="gray"
variant="simple"
as={ IconButton }
>
<VisuallyHidden>
Open chart options menu
</VisuallyHidden>
</MenuButton>
</Skeleton>
<MenuList>
{ chartUrl && (
<MenuItem
display="flex"
alignItems="center"
onClick={ hasShare ? handleShare : onCopy }
closeOnSelect={ hasShare ? false : true }
>
<IconSvg name={ hasShare ? 'share' : 'copy' } boxSize={ 5 } mr={ 3 }/>
{ hasShare ? 'Share' : 'Copy link' }
</MenuItem>
) }
<MenuItem
display="flex"
alignItems="center"
onClick={ showChartFullscreen }
>
<IconSvg name="scope" boxSize={ 5 } mr={ 3 }/>
View fullscreen
</MenuItem>
<MenuItem
display="flex"
alignItems="center"
onClick={ handleFileSaveClick }
>
<IconSvg name="files/image" boxSize={ 5 } mr={ 3 }/>
Save as PNG
</MenuItem>
<MenuItem
display="flex"
alignItems="center"
onClick={ handleSVGSavingClick }
>
<IconSvg name="files/csv" boxSize={ 5 } mr={ 3 }/>
Save as CSV
</MenuItem>
</MenuList>
</Menu>
{ items && isFullscreen && (
<FullscreenChartModal
isOpen
items={ items }
title={ title }
description={ description }
onClose={ clearFullscreenChart }
units={ units }
resolution={ resolution }
zoomRange={ zoomRange }
handleZoom={ handleZoom }
handleZoomReset={ handleZoomReset }
/>
) }
</>
);
};
export default ChartMenu;
import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { Resolution } from '@blockscout/stats-types';
import { STATS_RESOLUTIONS } from 'ui/stats/constants';
import StatsDropdownMenu from 'ui/stats/StatsDropdownMenu';
type Props = {
resolution: Resolution;
resolutions: Array<string>;
onResolutionChange: (resolution: Resolution) => void;
isLoading?: boolean;
}
const ChartResolutionSelect = ({ resolution, resolutions, onResolutionChange, isLoading }: Props) => {
return (
<Skeleton borderRadius="base" isLoaded={ !isLoading } w={{ base: 'auto', lg: '160px' }}>
<StatsDropdownMenu
items={ STATS_RESOLUTIONS.filter(r => resolutions.includes(r.id)) }
selectedId={ resolution }
onSelect={ onResolutionChange }
/>
</Skeleton>
);
};
export default React.memo(ChartResolutionSelect);
import * as d3 from 'd3';
import React from 'react';
import { Resolution } from '@blockscout/stats-types';
import type { TimeChartData } from 'ui/shared/chart/types';
import ChartTooltipBackdrop, { useRenderBackdrop } from './tooltip/ChartTooltipBackdrop';
......@@ -21,9 +22,21 @@ interface Props {
yScale: d3.ScaleLinear<number, number>;
anchorEl: SVGRectElement | null;
noAnimation?: boolean;
resolution?: Resolution;
}
const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data, anchorEl, noAnimation, ...props }: Props) => {
const ChartTooltip = ({
xScale,
yScale,
width,
tooltipWidth = 200,
height,
data,
anchorEl,
noAnimation,
resolution,
...props
}: Props) => {
const ref = React.useRef<SVGGElement>(null);
const trackerId = React.useRef<number>();
const isVisible = React.useRef(false);
......@@ -150,8 +163,8 @@ const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data,
{ data.map(({ name }) => <ChartTooltipPoint key={ name }/>) }
<ChartTooltipContent>
<ChartTooltipBackdrop/>
<ChartTooltipTitle/>
<ChartTooltipRow label="Date" lineNum={ 1 }/>
<ChartTooltipTitle resolution={ resolution }/>
<ChartTooltipRow label={ getDateLabel(resolution) } lineNum={ 1 }/>
{ data.map(({ name }, index) => <ChartTooltipRow key={ name } label={ name } lineNum={ index + 1 }/>) }
</ChartTooltipContent>
</g>
......@@ -159,3 +172,16 @@ const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data,
};
export default React.memo(ChartTooltip);
function getDateLabel(resolution?: Resolution): string {
switch (resolution) {
case Resolution.WEEK:
return 'Dates';
case Resolution.MONTH:
return 'Month';
case Resolution.YEAR:
return 'Year';
default:
return 'Date';
}
}
import type { IconProps } from '@chakra-ui/react';
import { Icon, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
// eslint-disable-next-line no-restricted-imports
import logoIcon from 'icons/networks/logo-placeholder.svg';
const ChartWatermarkIcon = (props: IconProps) => {
const watermarkColor = useColorModeValue('link', 'white');
return (
<Icon
{ ...props }
as={ logoIcon }
position="absolute"
opacity={ 0.1 }
top="50%"
left="50%"
transform="translate(-50%, -50%)"
pointerEvents="none"
viewBox="0 0 114 20"
color={ watermarkColor }
/>
);
};
export default ChartWatermarkIcon;
import {
Box,
Center,
chakra,
Flex,
IconButton, Link,
MenuButton,
MenuItem,
MenuList,
IconButton,
Skeleton,
Text,
Tooltip,
useColorModeValue,
VisuallyHidden,
} from '@chakra-ui/react';
import domToImage from 'dom-to-image';
import React, { useRef, useCallback, useState } from 'react';
import NextLink from 'next/link';
import React, { useRef } from 'react';
import type { TimeChartItem } from './types';
import dayjs from 'lib/date/dayjs';
import { apos } from 'lib/html-entities';
import saveAsCSV from 'lib/saveAsCSV';
import Menu from 'ui/shared/chakra/Menu';
import { route, type Route } from 'nextjs-routes';
import config from 'configs/app';
import IconSvg from 'ui/shared/IconSvg';
import ChartWidgetGraph from './ChartWidgetGraph';
import FullscreenChartModal from './FullscreenChartModal';
import ChartMenu from './ChartMenu';
import ChartWidgetContent from './ChartWidgetContent';
import useZoom from './useZoom';
export type Props = {
items?: Array<TimeChartItem>;
......@@ -37,236 +30,124 @@ export type Props = {
isError: boolean;
emptyText?: string;
noAnimation?: boolean;
href?: Route;
}
const DOWNLOAD_IMAGE_SCALE = 5;
const ChartWidget = ({ items, title, description, isLoading, className, isError, units, emptyText, noAnimation }: Props) => {
const ChartWidget = ({
items,
title,
description,
isLoading,
className,
isError,
units,
emptyText,
noAnimation,
href,
}: Props) => {
const ref = useRef<HTMLDivElement>(null);
const [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const { zoomRange, handleZoom, handleZoomReset } = useZoom();
const pngBackgroundColor = useColorModeValue('white', 'black');
const borderColor = useColorModeValue('gray.200', 'gray.600');
const handleZoom = useCallback(() => {
setIsZoomResetInitial(false);
}, []);
const handleZoomResetClick = useCallback(() => {
setIsZoomResetInitial(true);
}, []);
const showChartFullscreen = useCallback(() => {
setIsFullscreen(true);
}, []);
const clearFullscreenChart = useCallback(() => {
setIsFullscreen(false);
}, []);
const handleFileSaveClick = useCallback(() => {
// wait for context menu to close
setTimeout(() => {
if (ref.current) {
domToImage.toPng(ref.current,
{
quality: 100,
bgcolor: pngBackgroundColor,
width: ref.current.offsetWidth * DOWNLOAD_IMAGE_SCALE,
height: ref.current.offsetHeight * DOWNLOAD_IMAGE_SCALE,
filter: (node) => node.nodeName !== 'BUTTON',
style: {
borderColor: 'transparent',
transform: `scale(${ DOWNLOAD_IMAGE_SCALE })`,
'transform-origin': 'top left',
},
})
.then((dataUrl) => {
const link = document.createElement('a');
link.download = `${ title } (Blockscout chart).png`;
link.href = dataUrl;
link.click();
link.remove();
});
}
}, 100);
}, [ pngBackgroundColor, title ]);
const handleSVGSavingClick = useCallback(() => {
if (items) {
const headerRows = [
'Date', 'Value',
];
const dataRows = items.map((item) => [
dayjs(item.date).format('YYYY-MM-DD'), String(item.value),
]);
saveAsCSV(headerRows, dataRows, `${ title } (Blockscout stats)`);
}
}, [ items, title ]);
const hasItems = items && items.length > 2;
const content = (() => {
if (isError) {
return (
<Flex
alignItems="center"
justifyContent="center"
flexGrow={ 1 }
py={ 4 }
>
<Text
variant="secondary"
fontSize="sm"
textAlign="center"
>
{ `The data didn${ apos }t load. Please, ` }
<Link href={ window.document.location.href }>try to reload the page.</Link>
</Text>
</Flex>
);
}
if (isLoading) {
return <Skeleton flexGrow={ 1 } w="100%"/>;
}
if (!hasItems) {
return (
<Center flexGrow={ 1 }>
<Text variant="secondary" fontSize="sm">{ emptyText || 'No data' }</Text>
</Center>
);
}
return (
<Box flexGrow={ 1 } maxW="100%">
<ChartWidgetGraph
items={ items }
onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
title={ title }
units={ units }
noAnimation={ noAnimation }
/>
</Box>
);
})();
const content = (
<ChartWidgetContent
items={ items }
isError={ isError }
isLoading={ isLoading }
units={ units }
title={ title }
emptyText={ emptyText }
handleZoom={ handleZoom }
zoomRange={ zoomRange }
noAnimation={ noAnimation }
/>
);
return (
<>
<Flex
height="100%"
ref={ ref }
flexDir="column"
padding={{ base: 3, lg: 4 }}
borderRadius="md"
border="1px"
borderColor={ borderColor }
className={ className }
const chartHeader = (
<Flex
flexGrow={ 1 }
flexDir="column"
alignItems="flex-start"
cursor={ href ? 'pointer' : 'default' }
_hover={ href ? { color: 'link_hovered' } : {} }
>
<Skeleton
isLoaded={ !isLoading }
fontWeight={ 600 }
size={{ base: 'xs', lg: 'sm' }}
>
<Flex columnGap={ 6 } mb={ 1 } alignItems="flex-start">
<Flex flexGrow={ 1 } flexDir="column" alignItems="flex-start">
<Skeleton
isLoaded={ !isLoading }
fontWeight={ 600 }
size={{ base: 'xs', lg: 'sm' }}
>
{ title }
</Skeleton>
{ description && (
<Skeleton
isLoaded={ !isLoading }
color="text_secondary"
fontSize="xs"
mt={ 1 }
>
<span>{ description }</span>
</Skeleton>
) }
</Flex>
<Flex ml="auto" columnGap={ 2 }>
<Tooltip label="Reset zoom">
<IconButton
hidden={ isZoomResetInitial }
aria-label="Reset zoom"
colorScheme="blue"
w={ 9 }
h={ 8 }
size="sm"
variant="outline"
onClick={ handleZoomResetClick }
icon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
/>
</Tooltip>
<span>{ title }</span>
</Skeleton>
{ hasItems && (
<Menu>
<Skeleton isLoaded={ !isLoading } borderRadius="base">
<MenuButton
w="36px"
h="32px"
icon={ <IconSvg name="dots" boxSize={ 4 } transform="rotate(-90deg)"/> }
colorScheme="gray"
variant="ghost"
as={ IconButton }
>
<VisuallyHidden>
Open chart options menu
</VisuallyHidden>
</MenuButton>
</Skeleton>
<MenuList>
<MenuItem
display="flex"
alignItems="center"
onClick={ showChartFullscreen }
>
<IconSvg name="scope" boxSize={ 5 } mr={ 3 }/>
View fullscreen
</MenuItem>
{ description && (
<Skeleton
isLoaded={ !isLoading }
color="text_secondary"
fontSize="xs"
mt={ 1 }
<MenuItem
display="flex"
alignItems="center"
onClick={ handleFileSaveClick }
>
<IconSvg name="files/image" boxSize={ 5 } mr={ 3 }/>
Save as PNG
</MenuItem>
>
<span>{ description }</span>
</Skeleton>
) }
</Flex>
);
<MenuItem
display="flex"
alignItems="center"
onClick={ handleSVGSavingClick }
>
<IconSvg name="files/csv" boxSize={ 5 } mr={ 3 }/>
Save as CSV
</MenuItem>
</MenuList>
</Menu>
) }
</Flex>
return (
<Flex
height="100%"
ref={ ref }
flexDir="column"
padding={{ base: 3, lg: 4 }}
borderRadius="lg"
border="1px"
borderColor={ borderColor }
className={ className }
>
<Flex columnGap={ 6 } mb={ 2 } alignItems="flex-start">
{ href ? (
<NextLink href={ href } passHref legacyBehavior >
{ chartHeader }
</NextLink>
) : chartHeader }
<Flex ml="auto" columnGap={ 2 }>
<Tooltip label="Reset zoom">
<IconButton
hidden={ !zoomRange }
aria-label="Reset zoom"
colorScheme="blue"
w={ 9 }
h={ 8 }
size="sm"
variant="outline"
onClick={ handleZoomReset }
icon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
/>
</Tooltip>
{ hasItems && (
<ChartMenu
items={ items }
title={ title }
description={ description }
chartUrl={ href ? config.app.baseUrl + route(href) : undefined }
isLoading={ isLoading }
chartRef={ ref }
units={ units }
handleZoom={ handleZoom }
handleZoomReset={ handleZoomReset }
zoomRange={ zoomRange }
/>
) }
</Flex>
{ content }
</Flex>
{ hasItems && (
<FullscreenChartModal
isOpen={ isFullscreen }
items={ items }
title={ title }
description={ description }
onClose={ clearFullscreenChart }
units={ units }
/>
) }
</>
{ content }
</Flex>
);
};
......
import { Box, Center, Flex, Link, Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import type { TimeChartItem } from './types';
import type { Resolution } from '@blockscout/stats-types';
import { apos } from 'lib/html-entities';
import ChartWatermarkIcon from './ChartWatermarkIcon';
import ChartWidgetGraph from './ChartWidgetGraph';
export type Props = {
items?: Array<TimeChartItem>;
title: string;
units?: string;
isLoading?: boolean;
isError?: boolean;
emptyText?: string;
zoomRange?: [ Date, Date ];
handleZoom: (range: [ Date, Date ]) => void;
isEnlarged?: boolean;
noAnimation?: boolean;
resolution?: Resolution;
}
const ChartWidgetContent = ({
items,
title,
isLoading,
isError,
units,
emptyText,
zoomRange,
handleZoom,
isEnlarged,
noAnimation,
resolution,
}: Props) => {
const hasItems = items && items.length > 2;
if (isError) {
return (
<Flex
alignItems="center"
justifyContent="center"
flexGrow={ 1 }
py={ 4 }
>
<Text
variant="secondary"
fontSize="sm"
textAlign="center"
>
{ `The data didn${ apos }t load. Please, ` }
<Link href={ window.document.location.href }>try to reload the page.</Link>
</Text>
</Flex>
);
}
if (isLoading) {
return <Skeleton flexGrow={ 1 } w="100%"/>;
}
if (!hasItems) {
return (
<Center flexGrow={ 1 }>
<Text variant="secondary" fontSize="sm">{ emptyText || 'No data' }</Text>
</Center>
);
}
return (
<Box flexGrow={ 1 } maxW="100%" position="relative" h="100%">
<ChartWidgetGraph
items={ items }
zoomRange={ zoomRange }
onZoom={ handleZoom }
title={ title }
units={ units }
isEnlarged={ isEnlarged }
noAnimation={ noAnimation }
resolution={ resolution }
/>
<ChartWatermarkIcon w="162px" h="15%"/>
</Box>
);
};
export default React.memo(ChartWidgetContent);
......@@ -2,9 +2,9 @@ import { useToken } from '@chakra-ui/react';
import * as d3 from 'd3';
import React from 'react';
import { Resolution } from '@blockscout/stats-types';
import type { ChartMargin, TimeChartData, TimeChartItem } from 'ui/shared/chart/types';
import dayjs from 'lib/date/dayjs';
import useIsMobile from 'lib/hooks/useIsMobile';
import ChartArea from 'ui/shared/chart/ChartArea';
import ChartAxis from 'ui/shared/chart/ChartAxis';
......@@ -20,37 +20,42 @@ interface Props {
title: string;
units?: string;
items: Array<TimeChartItem>;
onZoom: () => void;
isZoomResetInitial: boolean;
zoomRange?: [ Date, Date ];
onZoom: (range: [ Date, Date ]) => void;
margin?: ChartMargin;
noAnimation?: boolean;
resolution?: Resolution;
}
// temporarily turn off the data aggregation, we need a better algorithm for that
const MAX_SHOW_ITEMS = 100_000_000_000;
const DEFAULT_CHART_MARGIN = { bottom: 20, left: 10, right: 20, top: 10 };
const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin: marginProps, units, noAnimation }: Props) => {
const ChartWidgetGraph = ({
isEnlarged,
items,
onZoom,
title,
margin: marginProps,
units,
noAnimation,
resolution,
zoomRange,
}: Props) => {
const isMobile = useIsMobile();
const color = useToken('colors', 'blue.200');
const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`;
const overlayRef = React.useRef<SVGRectElement>(null);
const [ range, setRange ] = React.useState<[ Date, Date ]>([ items[0].date, items[items.length - 1].date ]);
const range = React.useMemo(() => zoomRange || [ items[0].date, items[items.length - 1].date ], [ zoomRange, items ]);
const rangedItems = React.useMemo(() =>
items.filter((item) => item.date >= range[0] && item.date <= range[1]),
[ items, range ]);
const isGroupedValues = rangedItems.length > MAX_SHOW_ITEMS;
const displayedData = React.useMemo(() => {
if (isGroupedValues) {
return groupChartItemsByWeekNumber(rangedItems);
} else {
return rangedItems;
}
}, [ isGroupedValues, rangedItems ]);
const displayedData = React.useMemo(() =>
items
.filter((item) => item.date >= range[0] && item.date <= range[1])
.map((item) => ({
...item,
dateLabel: getDateLabel(item.date, item.date_to, resolution),
})),
[ items, range, resolution ]);
const chartData: TimeChartData = React.useMemo(() => ([ { items: displayedData, name: 'Value', color, units } ]), [ color, displayedData, units ]);
......@@ -80,17 +85,6 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
axesConfig,
});
const handleRangeSelect = React.useCallback((nextRange: [ Date, Date ]) => {
setRange([ nextRange[0], nextRange[1] ]);
onZoom();
}, [ onZoom ]);
React.useEffect(() => {
if (isZoomResetInitial) {
setRange([ items[0].date, items[items.length - 1].date ]);
}
}, [ isZoomResetInitial, items ]);
return (
<svg width="100%" height="100%" ref={ ref } cursor="pointer" id={ chartId } opacity={ rect ? 1 : 0 }>
......@@ -143,12 +137,13 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
<ChartTooltip
anchorEl={ overlayRef.current }
width={ innerWidth }
tooltipWidth={ isGroupedValues ? 280 : 200 }
tooltipWidth={ (resolution === Resolution.WEEK) ? 280 : 200 }
height={ innerHeight }
xScale={ axes.x.scale }
yScale={ axes.y.scale }
data={ chartData }
noAnimation={ noAnimation }
resolution={ resolution }
/>
<ChartSelectionX
......@@ -156,7 +151,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
height={ innerHeight }
scale={ axes.x.scale }
data={ chartData }
onSelect={ handleRangeSelect }
onSelect={ onZoom }
/>
</ChartOverlay>
</g>
......@@ -166,13 +161,15 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
export default React.memo(ChartWidgetGraph);
function groupChartItemsByWeekNumber(items: Array<TimeChartItem>): Array<TimeChartItem> {
return d3.rollups(items,
(group) => ({
date: group[0].date,
value: d3.sum(group, (d) => d.value),
dateLabel: `${ d3.timeFormat('%e %b %Y')(group[0].date) }${ d3.timeFormat('%e %b %Y')(group[group.length - 1].date) }`,
}),
(t) => `${ dayjs(t.date).week() } / ${ dayjs(t.date).year() }`,
).map(([ , v ]) => v);
function getDateLabel(date: Date, dateTo?: Date, resolution?: Resolution): string {
switch (resolution) {
case Resolution.WEEK:
return d3.timeFormat('%e %b %Y')(date) + (dateTo ? ` – ${ d3.timeFormat('%e %b %Y')(dateTo) }` : '');
case Resolution.MONTH:
return d3.timeFormat('%b %Y')(date);
case Resolution.YEAR:
return d3.timeFormat('%Y')(date);
default:
return d3.timeFormat('%e %b %Y')(date);
}
}
import { Box, Button, Grid, Heading, Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay, Text } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import React from 'react';
import type { TimeChartItem } from './types';
import type { Resolution } from '@blockscout/stats-types';
import IconSvg from 'ui/shared/IconSvg';
import ChartWidgetGraph from './ChartWidgetGraph';
import ChartWidgetContent from './ChartWidgetContent';
type Props = {
isOpen: boolean;
......@@ -14,6 +15,10 @@ type Props = {
items: Array<TimeChartItem>;
onClose: () => void;
units?: string;
resolution?: Resolution;
zoomRange?: [ Date, Date ];
handleZoom: (range: [ Date, Date ]) => void;
handleZoomReset: () => void;
}
const FullscreenChartModal = ({
......@@ -23,17 +28,11 @@ const FullscreenChartModal = ({
items,
units,
onClose,
resolution,
zoomRange,
handleZoom,
handleZoomReset,
}: Props) => {
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const handleZoom = useCallback(() => {
setIsZoomResetInitial(false);
}, []);
const handleZoomResetClick = useCallback(() => {
setIsZoomResetInitial(true);
}, []);
return (
<Modal
isOpen={ isOpen }
......@@ -69,7 +68,7 @@ const FullscreenChartModal = ({
</Text>
) }
{ !isZoomResetInitial && (
{ Boolean(zoomRange) && (
<Button
leftIcon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
colorScheme="blue"
......@@ -79,7 +78,7 @@ const FullscreenChartModal = ({
gridRow="1/3"
size="sm"
variant="outline"
onClick={ handleZoomResetClick }
onClick={ handleZoomReset }
>
Reset zoom
</Button>
......@@ -91,15 +90,16 @@ const FullscreenChartModal = ({
<ModalBody
h="100%"
margin={{ bottom: 60 }}
>
<ChartWidgetGraph
margin={{ bottom: 60 }}
<ChartWidgetContent
isEnlarged
items={ items }
units={ units }
onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
handleZoom={ handleZoom }
zoomRange={ zoomRange }
title={ title }
resolution={ resolution }
/>
</ModalBody>
</ModalContent>
......
......@@ -2,10 +2,15 @@ import { useToken } from '@chakra-ui/react';
import * as d3 from 'd3';
import React from 'react';
import { Resolution } from '@blockscout/stats-types';
import { STATS_RESOLUTIONS } from 'ui/stats/constants';
import ChartTooltipRow from './ChartTooltipRow';
const ChartTooltipTitle = () => {
const ChartTooltipTitle = ({ resolution = Resolution.DAY }: { resolution?: Resolution }) => {
const titleColor = useToken('colors', 'yellow.300');
const resolutionTitle = STATS_RESOLUTIONS.find(r => r.id === resolution)?.title || 'day';
return (
<ChartTooltipRow lineNum={ 0 }>
......@@ -16,7 +21,7 @@ const ChartTooltipTitle = () => {
opacity={ 0 }
dominantBaseline="hanging"
>
Incomplete day
{ `Incomplete ${ resolutionTitle.toLowerCase() }` }
</text>
</ChartTooltipRow>
);
......
......@@ -6,6 +6,7 @@ export interface TimeChartItemRaw {
export interface TimeChartItem {
date: Date;
date_to?: Date;
dateLabel?: string;
value: number;
isApproximate?: boolean;
......
import React from 'react';
import type { LineChart, Resolution } from '@blockscout/stats-types';
import type { StatsIntervalIds } from 'types/client/stats';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app';
import { STATS_INTERVALS } from 'ui/stats/constants';
import formatDate from './utils/formatIntervalDate';
export default function useChartQuery(id: string, resolution: Resolution, interval: StatsIntervalIds, enabled = true) {
const { apiData } = useAppContext<'/stats/[id]'>();
const selectedInterval = STATS_INTERVALS[interval];
const endDate = selectedInterval.start ? formatDate(new Date()) : undefined;
const startDate = selectedInterval.start ? formatDate(selectedInterval.start) : undefined;
const [ info, setInfo ] = React.useState<LineChart['info']>(apiData || undefined);
const lineQuery = useApiQuery('stats_line', {
pathParams: { id },
queryParams: {
from: startDate,
to: endDate,
resolution,
},
queryOptions: {
enabled: enabled,
refetchOnMount: false,
placeholderData: {
info: {
title: 'Chart title placeholder',
description: 'Chart placeholder description chart placeholder description',
resolutions: [ 'DAY', 'WEEK', 'MONTH', 'YEAR' ],
id: 'placeholder',
units: undefined,
},
chart: [],
},
},
});
React.useEffect(() => {
if (!info && lineQuery.data?.info && !lineQuery.isPlaceholderData) {
// save info to keep title and description when change query params
setInfo(lineQuery.data?.info);
}
}, [ info, lineQuery.data?.info, lineQuery.isPlaceholderData ]);
const items = React.useMemo(() => lineQuery.data?.chart?.map((item) => {
return { date: new Date(item.date), date_to: new Date(item.date_to), value: Number(item.value), isApproximate: item.is_approximate };
}), [ lineQuery ]);
return {
items,
info,
lineQuery,
};
}
import React from 'react';
export default function useZoom() {
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const [ zoomRange, setZoomRange ] = React.useState<[ Date, Date ] | undefined>();
const handleZoom = React.useCallback((range: [ Date, Date ]) => {
setZoomRange(range);
setIsZoomResetInitial(false);
}, []);
const handleZoomReset = React.useCallback(() => {
setZoomRange(undefined);
setIsZoomResetInitial(true);
}, []);
return {
isZoomResetInitial,
zoomRange,
handleZoom,
handleZoomReset,
};
}
export default function formatDate(date: Date) {
return date.toISOString().substring(0, 10);
}
......@@ -48,7 +48,7 @@ const Content = chakra((props: ContentProps) => {
const Container = EntityBase.Container;
export interface EntityProps extends EntityBase.EntityBaseProps {
number: number;
number: number | string;
hash?: string;
}
......
import React from 'react';
import type { Validator } from 'types/api/validators';
import type { ValidatorStability } from 'types/api/validators';
import StatusTag from './StatusTag';
interface Props {
state: Validator['state'];
state: ValidatorStability['state'];
isLoading?: boolean;
}
const ValidatorStatus = ({ state, isLoading }: Props) => {
const ValidatorStabilityStatus = ({ state, isLoading }: Props) => {
switch (state) {
case 'active':
return <StatusTag type="ok" text="Active" isLoading={ isLoading }/>;
......@@ -20,4 +20,4 @@ const ValidatorStatus = ({ state, isLoading }: Props) => {
}
};
export default React.memo(ValidatorStatus);
export default React.memo(ValidatorStabilityStatus);
import type { TagProps } from '@chakra-ui/react';
import { HStack, Tag } from '@chakra-ui/react';
import React from 'react';
type Props<T extends string> = {
items: Array<{ id: T; title: string }>;
tagSize?: TagProps['size'];
} & (
{
value: T;
......@@ -15,7 +17,7 @@ type Props<T extends string> = {
}
)
const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange }: Props<T>) => {
const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tagSize }: Props<T>) => {
const onItemClick = React.useCallback((event: React.SyntheticEvent) => {
const itemValue = (event.currentTarget as HTMLDivElement).getAttribute('data-id') as T;
if (isMulti) {
......@@ -44,6 +46,9 @@ const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange }: P
fontWeight={ 500 }
cursor="pointer"
onClick={ onItemClick }
size={ tagSize }
display="inline-flex"
justifyContent="center"
>
{ item.title }
</Tag>
......
import { chakra } from '@chakra-ui/react';
import React, { useEffect, useMemo } from 'react';
import React, { useEffect } from 'react';
import { Resolution } from '@blockscout/stats-types';
import type { StatsIntervalIds } from 'types/client/stats';
import useApiQuery from 'lib/api/useApiQuery';
import type { Route } from 'nextjs-routes';
import useChartQuery from 'ui/shared/chart/useChartQuery';
import ChartWidget from '../shared/chart/ChartWidget';
import { STATS_INTERVALS } from './constants';
type Props = {
id: string;
......@@ -17,50 +19,39 @@ type Props = {
onLoadingError: () => void;
isPlaceholderData: boolean;
className?: string;
href?: Route;
}
function formatDate(date: Date) {
return date.toISOString().substring(0, 10);
}
const ChartWidgetContainer = ({ id, title, description, interval, onLoadingError, units, isPlaceholderData, className }: Props) => {
const selectedInterval = STATS_INTERVALS[interval];
const endDate = selectedInterval.start ? formatDate(new Date()) : undefined;
const startDate = selectedInterval.start ? formatDate(selectedInterval.start) : undefined;
const { data, isPending, isError } = useApiQuery('stats_line', {
pathParams: { id },
queryParams: {
from: startDate,
to: endDate,
},
queryOptions: {
enabled: !isPlaceholderData,
refetchOnMount: false,
},
});
const items = useMemo(() => data?.chart?.map((item) => {
return { date: new Date(item.date), value: Number(item.value), isApproximate: item.is_approximate };
}), [ data ]);
const ChartWidgetContainer = ({
id,
title,
description,
interval,
onLoadingError,
units,
isPlaceholderData,
className,
href,
}: Props) => {
const { items, lineQuery } = useChartQuery(id, Resolution.DAY, interval, !isPlaceholderData);
useEffect(() => {
if (isError) {
if (lineQuery.isError) {
onLoadingError();
}
}, [ isError, onLoadingError ]);
}, [ lineQuery.isError, onLoadingError ]);
return (
<ChartWidget
isError={ isError }
isError={ lineQuery.isError }
items={ items }
title={ title }
units={ units }
description={ description }
isLoading={ isPending }
isLoading={ lineQuery.isPlaceholderData }
minH="230px"
className={ className }
href={ href }
/>
);
};
......
......@@ -95,6 +95,7 @@ const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, in
units={ chart.units || undefined }
isPlaceholderData={ isPlaceholderData }
onLoadingError={ handleChartLoadingError }
href={{ pathname: '/stats/[id]', query: { id: chart.id } }}
/>
)) }
</Grid>
......
......@@ -5,7 +5,7 @@ import Menu from 'ui/shared/chakra/Menu';
import IconSvg from 'ui/shared/IconSvg';
type Props<T extends string> = {
items: Array<{id: T; title: string}>;
items: ReadonlyArray<{id: T; title: string}>;
selectedId: T;
onSelect: (id: T) => void;
}
......@@ -23,7 +23,7 @@ export function StatsDropdownMenu<T extends string>({ items, selectedId, onSelec
>
<MenuButton
as={ Button }
size="md"
size="sm"
variant="outline"
colorScheme="gray"
w="100%"
......
......@@ -2,18 +2,13 @@ import { Grid, GridItem, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type * as stats from '@blockscout/stats-types';
import type { StatsInterval, StatsIntervalIds } from 'types/client/stats';
import type { StatsIntervalIds } from 'types/client/stats';
import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect';
import FilterInput from 'ui/shared/filters/FilterInput';
import { STATS_INTERVALS } from './constants';
import StatsDropdownMenu from './StatsDropdownMenu';
const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
title: STATS_INTERVALS[id as StatsIntervalIds].title,
})) as Array<StatsInterval>;
type Props = {
sections?: Array<stats.LineChartSection>;
currentSection: string;
......@@ -37,24 +32,25 @@ const StatsFilters = ({
}: Props) => {
const sectionsList = [ {
id: 'all',
title: 'All',
title: 'All stats',
}, ... (sections || []) ];
return (
<Grid
gap={ 2 }
gap={{ base: 2, lg: 6 }}
templateAreas={{
base: `"section interval"
"input input"`,
lg: `"section interval input"`,
}}
gridTemplateColumns={{ base: 'repeat(2, minmax(0, 1fr))', lg: 'auto auto 1fr' }}
alignItems="center"
>
<GridItem
w={{ base: '100%', lg: 'auto' }}
area="section"
>
{ isLoading ? <Skeleton w={{ base: '100%', lg: '76px' }} h="40px" borderRadius="base"/> : (
{ isLoading ? <Skeleton w={{ base: '100%', lg: '103px' }} h="32px" borderRadius="base"/> : (
<StatsDropdownMenu
items={ sectionsList }
selectedId={ currentSection }
......@@ -67,13 +63,7 @@ const StatsFilters = ({
w={{ base: '100%', lg: 'auto' }}
area="interval"
>
{ isLoading ? <Skeleton w={{ base: '100%', lg: '118px' }} h="40px" borderRadius="base"/> : (
<StatsDropdownMenu
items={ intervalList }
selectedId={ interval }
onSelect={ onIntervalChange }
/>
) }
<ChartIntervalSelect interval={ interval } onIntervalChange={ onIntervalChange } isLoading={ isLoading } selectTagSize="md"/>
</GridItem>
<GridItem
......@@ -86,6 +76,7 @@ const StatsFilters = ({
onChange={ onFilterInputChange }
placeholder="Find chart, metric..."
initialValue={ initialFilterValue }
size="xs"
/>
</GridItem>
</Grid>
......
import { Resolution } from '@blockscout/stats-types';
import type { StatsIntervalIds } from 'types/client/stats';
export const STATS_INTERVALS: { [key in StatsIntervalIds]: { title: string; start?: Date } } = {
export const STATS_RESOLUTIONS: Array<{id: Resolution; title: string }> = [
{
id: Resolution.DAY,
title: 'Day',
},
{
id: Resolution.WEEK,
title: 'Week',
},
{
id: Resolution.MONTH,
title: 'Month',
},
{
id: Resolution.YEAR,
title: 'Year',
},
];
export const STATS_INTERVALS: { [key in StatsIntervalIds]: { title: string; shortTitle: string; start?: Date } } = {
all: {
title: 'All time',
shortTitle: 'All time',
},
oneMonth: {
title: '1 month',
shortTitle: '1M',
start: getStartDateInPast(1),
},
threeMonths: {
title: '3 months',
shortTitle: '3M',
start: getStartDateInPast(3),
},
sixMonths: {
title: '6 months',
shortTitle: '6M',
start: getStartDateInPast(6),
},
oneYear: {
title: '1 year',
shortTitle: '1Y',
start: getStartDateInPast(12),
},
};
......
......@@ -8,7 +8,7 @@ import { protocolTagWithMeta } from 'mocks/metadata/address';
import { tokenInfoERC721a } from 'mocks/tokens/tokenInfo';
import * as tokenInstanceMock from 'mocks/tokens/tokenInstance';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect } from 'playwright/lib';
import { test, expect, devices } from 'playwright/lib';
import * as pwConfig from 'playwright/utils/config';
import { MetadataUpdateProvider } from 'ui/tokenInstance/contexts/metadataUpdate';
......@@ -42,7 +42,7 @@ test.beforeEach(async({ mockApiResponse, mockAssetResponse }) => {
await mockAssetResponse(tokenInstanceMock.unique.image_url as string, './playwright/mocks/image_md.jpg');
});
test('base view +@dark-mode +@mobile', async({ render, page }) => {
test('base view +@dark-mode', async({ render, page }) => {
const component = await render(
<MetadataUpdateProvider>
<TokenInstanceDetails data={{ ...tokenInstanceMock.unique, image_url: null }} token={ tokenInfoERC721a }/>
......@@ -61,7 +61,7 @@ test.describe('action button', () => {
await mockAssetResponse(protocolTagWithMeta?.meta?.appLogoURL as string, './playwright/mocks/image_s.jpg');
});
test('base view +@dark-mode +@mobile', async({ render, page }) => {
test('base view +@dark-mode', async({ render, page }) => {
const component = await render(
<MetadataUpdateProvider>
<TokenInstanceDetails data={ tokenInstanceMock.unique } token={ tokenInfoERC721a }/>
......@@ -73,7 +73,7 @@ test.describe('action button', () => {
});
});
test('without marketplaces +@dark-mode +@mobile', async({ render, page, mockEnvs }) => {
test('without marketplaces +@dark-mode', async({ render, page, mockEnvs }) => {
mockEnvs(ENVS_MAP.noNftMarketplaces);
const component = await render(
<MetadataUpdateProvider>
......@@ -86,3 +86,19 @@ test.describe('action button', () => {
});
});
});
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', async({ render, page }) => {
const component = await render(
<MetadataUpdateProvider>
<TokenInstanceDetails data={{ ...tokenInstanceMock.unique, image_url: null }} token={ tokenInfoERC721a }/>
</MetadataUpdateProvider>,
);
await expect(component).toHaveScreenshot({
mask: [ page.locator(pwConfig.adsBannerSelector) ],
maskColor: pwConfig.maskColor,
});
});
});
......@@ -46,7 +46,7 @@ const TxsStats = () => {
value={ Number(txsStatsQuery.data?.transactions_count_24h).toLocaleString() }
period="24h"
isLoading={ txsStatsQuery.isPlaceholderData }
href={ config.features.stats.isEnabled ? { pathname: '/stats', query: { chartId: 'newTxns' } } : undefined }
href={ config.features.stats.isEnabled ? { pathname: '/stats/[id]', query: { id: 'newTxns' } } : undefined }
/>
<StatsWidget
label="Pending transactions"
......@@ -63,7 +63,7 @@ const TxsStats = () => {
valuePostfix={ thinsp + config.chain.currency.symbol }
period="24h"
isLoading={ txsStatsQuery.isPlaceholderData }
href={ config.features.stats.isEnabled ? { pathname: '/stats', query: { chartId: 'txnsFee' } } : undefined }
href={ config.features.stats.isEnabled ? { pathname: '/stats/[id]', query: { id: 'txnsFee' } } : undefined }
/>
<StatsWidget
label="Avg. transaction fee"
......@@ -72,7 +72,7 @@ const TxsStats = () => {
valuePostfix={ txFeeAvg.usd ? undefined : thinsp + config.chain.currency.symbol }
period="24h"
isLoading={ txsStatsQuery.isPlaceholderData }
href={ config.features.stats.isEnabled ? { pathname: '/stats', query: { chartId: 'averageTxnFee' } } : undefined }
href={ config.features.stats.isEnabled ? { pathname: '/stats/[id]', query: { id: 'averageTxnFee' } } : undefined }
/>
</Box>
);
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { VALIDATORS_BLACKFORT_COUNTERS } from 'stubs/validators';
import StatsWidget from 'ui/shared/stats/StatsWidget';
const ValidatorsCounters = () => {
const countersQuery = useApiQuery('validators_blackfort_counters', {
queryOptions: {
enabled: config.features.validators.isEnabled,
placeholderData: VALIDATORS_BLACKFORT_COUNTERS,
},
});
if (!countersQuery.data) {
return null;
}
return (
<Box columnGap={ 3 } rowGap={ 3 } mb={ 6 } display="grid" gridTemplateColumns={{ base: '1fr', lg: 'repeat(2, 1fr)' }}>
<StatsWidget
label="Total validators"
value={ Number(countersQuery.data.validators_counter).toLocaleString() }
diff={ Number(countersQuery.data.new_validators_counter_24h).toLocaleString() }
isLoading={ countersQuery.isPlaceholderData }
/>
</Box>
);
};
export default React.memo(ValidatorsCounters);
import { Box } from '@chakra-ui/react';
import React from 'react';
import type { Validator } from 'types/api/validators';
import type { ValidatorBlackfort } from 'types/api/validators';
import ValidatorsListItem from './ValidatorsListItem';
const ValidatorsList = ({ data, isLoading }: { data: Array<Validator>; isLoading: boolean }) => {
const ValidatorsList = ({ data, isLoading }: { data: Array<ValidatorBlackfort>; isLoading: boolean }) => {
return (
<Box>
{ data.map((item, index) => (
......
import { Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { ValidatorBlackfort } from 'types/api/validators';
import config from 'configs/app';
import { currencyUnits } from 'lib/units';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
import TruncatedValue from 'ui/shared/TruncatedValue';
interface Props {
data: ValidatorBlackfort;
isLoading?: boolean;
}
const ValidatorsListItem = ({ data, isLoading }: Props) => {
return (
<ListItemMobileGrid.Container gridTemplateColumns="130px auto">
<ListItemMobileGrid.Label isLoading={ isLoading }>Address</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntity
isLoading={ isLoading }
address={ data.address }
truncation="constant"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Name</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Flex><TruncatedValue value={ data.name } isLoading={ isLoading }/></Flex>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Commission</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ data.commission / 100 }%` }
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Self bonded</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ BigNumber(data.self_bonded_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } ${ currencyUnits.ether }` }
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Delegated amount</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ BigNumber(data.delegated_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } ${ currencyUnits.ether }` }
</Skeleton>
</ListItemMobileGrid.Value>
</ListItemMobileGrid.Container>
);
};
export default React.memo(ValidatorsListItem);
import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react';
import type {
ValidatorBlackfort,
ValidatorsBlackfortSorting,
ValidatorsBlackfortSortingField,
ValidatorsBlackfortSortingValue,
} from 'types/api/validators';
import { currencyUnits } from 'lib/units';
import IconSvg from 'ui/shared/IconSvg';
import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { default as Thead } from 'ui/shared/TheadSticky';
import { VALIDATORS_BLACKFORT_SORT_SEQUENCE } from './utils';
import ValidatorsTableItem from './ValidatorsTableItem';
interface Props {
data: Array<ValidatorBlackfort>;
sort: ValidatorsBlackfortSortingValue | undefined;
setSorting: (val: ValidatorsBlackfortSortingValue | undefined) => void;
isLoading?: boolean;
top: number;
}
const ValidatorsTable = ({ data, sort, setSorting, isLoading, top }: Props) => {
const sortIconTransform = sort?.includes('asc' as ValidatorsBlackfortSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const onSortToggle = React.useCallback((field: ValidatorsBlackfortSortingField) => () => {
const value = getNextSortValue<ValidatorsBlackfortSortingField, ValidatorsBlackfortSortingValue>(VALIDATORS_BLACKFORT_SORT_SEQUENCE, field)(sort);
setSorting(value);
}, [ sort, setSorting ]);
return (
<Table variant="simple" size="sm">
<Thead top={ top }>
<Tr>
<Th>
<Link
display="flex"
alignItems="center"
onClick={ isLoading ? undefined : onSortToggle('address_hash') }
columnGap={ 1 }
>
{ sort?.includes('address') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> }
Validator’s address
</Link>
</Th>
<Th>Name</Th>
<Th isNumeric>Commission</Th>
<Th isNumeric>{ `Self bonded ${ currencyUnits.ether }` }</Th>
<Th isNumeric>{ `Delegated amount ${ currencyUnits.ether }` }</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item, index) => (
<ValidatorsTableItem
key={ item.address.hash + (isLoading ? index : '') }
data={ item }
isLoading={ isLoading }/>
)) }
</Tbody>
</Table>
);
};
export default React.memo(ValidatorsTable);
import { Tr, Td, Skeleton, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { ValidatorBlackfort } from 'types/api/validators';
import config from 'configs/app';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TruncatedValue from 'ui/shared/TruncatedValue';
interface Props {
data: ValidatorBlackfort;
isLoading?: boolean;
}
const ValidatorsTableItem = ({ data, isLoading }: Props) => {
return (
<Tr>
<Td verticalAlign="middle">
<AddressEntity
address={ data.address }
isLoading={ isLoading }
truncation="constant"
/>
</Td>
<Td verticalAlign="middle">
<Flex>
<TruncatedValue value={ data.name } isLoading={ isLoading }/>
</Flex>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ data.commission / 100 }%` }
</Skeleton>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ BigNumber(data.self_bonded_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
</Skeleton>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ BigNumber(data.delegated_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
</Skeleton>
</Td>
</Tr>
);
};
export default React.memo(ValidatorsTableItem);
import type {
ValidatorsBlackfortSortingValue,
ValidatorsBlackfortSortingField,
} from 'types/api/validators';
import type { TOption } from 'ui/shared/sort/Option';
export const VALIDATORS_BLACKFORT_SORT_OPTIONS: Array<TOption<ValidatorsBlackfortSortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Address descending', id: 'address_hash-desc' },
{ title: 'Address ascending', id: 'address_hash-asc' },
];
export const VALIDATORS_BLACKFORT_SORT_SEQUENCE: Record<ValidatorsBlackfortSortingField, Array<ValidatorsBlackfortSortingValue | undefined>> = {
address_hash: [ 'address_hash-desc', 'address_hash-asc', undefined ],
};
import { Box } from '@chakra-ui/react';
import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types';
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { VALIDATORS_COUNTERS } from 'stubs/validators';
import { VALIDATORS_STABILITY_COUNTERS } from 'stubs/validators';
import StatsWidget from 'ui/shared/stats/StatsWidget';
const ValidatorsCounters = () => {
const countersQuery = useApiQuery('validators_counters', {
pathParams: { chainType: getFeaturePayload(config.features.validators)?.chainType },
const countersQuery = useApiQuery('validators_stability_counters', {
queryOptions: {
enabled: config.features.validators.isEnabled,
placeholderData: VALIDATORS_COUNTERS,
placeholderData: VALIDATORS_STABILITY_COUNTERS,
},
});
......
import React from 'react';
import type { ValidatorsFilters } from 'types/api/validators';
import type { ValidatorsStabilityFilters } from 'types/api/validators';
import PopoverFilterRadio from 'ui/shared/filters/PopoverFilterRadio';
......@@ -13,7 +13,7 @@ const OPTIONS = [
interface Props {
hasActiveFilter: boolean;
defaultValue: ValidatorsFilters['state_filter'] | undefined;
defaultValue: ValidatorsStabilityFilters['state_filter'] | undefined;
onChange: (nextValue: string | Array<string>) => void;
}
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import type { ValidatorStability } from 'types/api/validators';
import ValidatorsListItem from './ValidatorsListItem';
const ValidatorsList = ({ data, isLoading }: { data: Array<ValidatorStability>; isLoading: boolean }) => {
return (
<Box>
{ data.map((item, index) => (
<ValidatorsListItem
key={ item.address.hash + (isLoading ? index : '') }
data={ item }
isLoading={ isLoading }
/>
)) }
</Box>
);
};
export default React.memo(ValidatorsList);
import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { Validator } from 'types/api/validators';
import type { ValidatorStability } from 'types/api/validators';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStatus';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStabilityStatus';
interface Props {
data: Validator;
data: ValidatorStability;
isLoading?: boolean;
}
......
import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react';
import type { Validator, ValidatorsSorting, ValidatorsSortingField, ValidatorsSortingValue } from 'types/api/validators';
import type {
ValidatorStability,
ValidatorsStabilitySorting,
ValidatorsStabilitySortingField,
ValidatorsStabilitySortingValue,
} from 'types/api/validators';
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import IconSvg from 'ui/shared/IconSvg';
import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { default as Thead } from 'ui/shared/TheadSticky';
import { SORT_SEQUENCE } from './utils';
import { VALIDATORS_STABILITY_SORT_SEQUENCE } from './utils';
import ValidatorsTableItem from './ValidatorsTableItem';
interface Props {
data: Array<Validator>;
sort: ValidatorsSortingValue | undefined;
setSorting: (val: ValidatorsSortingValue | undefined) => void;
data: Array<ValidatorStability>;
sort: ValidatorsStabilitySortingValue | undefined;
setSorting: (val: ValidatorsStabilitySortingValue | undefined) => void;
isLoading?: boolean;
}
const ValidatorsTable = ({ data, sort, setSorting, isLoading }: Props) => {
const sortIconTransform = sort?.includes('asc' as ValidatorsSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const sortIconTransform = sort?.includes('asc' as ValidatorsStabilitySorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const onSortToggle = React.useCallback((field: ValidatorsSortingField) => () => {
const value = getNextSortValue<ValidatorsSortingField, ValidatorsSortingValue>(SORT_SEQUENCE, field)(sort);
const onSortToggle = React.useCallback((field: ValidatorsStabilitySortingField) => () => {
const value = getNextSortValue<ValidatorsStabilitySortingField, ValidatorsStabilitySortingValue>(VALIDATORS_STABILITY_SORT_SEQUENCE, field)(sort);
setSorting(value);
}, [ sort, setSorting ]);
......
import { Tr, Td, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { Validator } from 'types/api/validators';
import type { ValidatorStability } from 'types/api/validators';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStatus';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStabilityStatus';
interface Props {
data: Validator;
data: ValidatorStability;
isLoading?: boolean;
}
......
import type { ValidatorsSortingValue, ValidatorsSortingField } from 'types/api/validators';
import type {
ValidatorsStabilitySortingValue,
ValidatorsStabilitySortingField,
} from 'types/api/validators';
import type { TOption } from 'ui/shared/sort/Option';
export const SORT_OPTIONS: Array<TOption<ValidatorsSortingValue>> = [
export const VALIDATORS_STABILITY_SORT_OPTIONS: Array<TOption<ValidatorsStabilitySortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Status descending', id: 'state-desc' },
{ title: 'Status ascending', id: 'state-asc' },
......@@ -10,7 +13,7 @@ export const SORT_OPTIONS: Array<TOption<ValidatorsSortingValue>> = [
{ title: 'Blocks validated ascending', id: 'blocks_validated-asc' },
];
export const SORT_SEQUENCE: Record<ValidatorsSortingField, Array<ValidatorsSortingValue | undefined>> = {
export const VALIDATORS_STABILITY_SORT_SEQUENCE: Record<ValidatorsStabilitySortingField, Array<ValidatorsStabilitySortingValue | undefined>> = {
state: [ 'state-desc', 'state-asc', undefined ],
blocks_validated: [ 'blocks_validated-desc', 'blocks_validated-asc', undefined ],
};
......@@ -25,7 +25,8 @@ const VerifiedContractsCounters = () => {
diff={ countersQuery.data.new_smart_contracts_24h }
diffFormatted={ Number(countersQuery.data.new_smart_contracts_24h).toLocaleString() }
isLoading={ countersQuery.isPlaceholderData }
href={ config.features.stats.isEnabled ? { pathname: '/stats', query: { chartId: 'contractsGrowth' } } : undefined }
// there is no stats for contracts growth for now
// href={ config.features.stats.isEnabled ? { pathname: '/stats/[id]', query: { id: 'contractsGrowth' } } : undefined }
/>
<StatsWidget
label="Verified contracts"
......@@ -33,7 +34,7 @@ const VerifiedContractsCounters = () => {
diff={ countersQuery.data.new_verified_smart_contracts_24h }
diffFormatted={ Number(countersQuery.data.new_verified_smart_contracts_24h).toLocaleString() }
isLoading={ countersQuery.isPlaceholderData }
href={ config.features.stats.isEnabled ? { pathname: '/stats', query: { chartId: 'verifiedContractsGrowth' } } : undefined }
href={ config.features.stats.isEnabled ? { pathname: '/stats/[id]', query: { id: 'verifiedContractsGrowth' } } : undefined }
/>
</Box>
);
......
......@@ -37,6 +37,14 @@
"@babel/highlight" "^7.23.4"
chalk "^2.4.2"
"@babel/code-frame@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465"
integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==
dependencies:
"@babel/highlight" "^7.24.7"
picocolors "^1.0.0"
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30"
......@@ -47,6 +55,11 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.3.tgz#cd502a6a0b6e37d7ad72ce7e71a7160a3ae36f7e"
integrity sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==
"@babel/compat-data@^7.25.2":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb"
integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==
"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.19.6":
version "7.20.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92"
......@@ -89,6 +102,27 @@
json5 "^2.2.2"
semver "^6.3.0"
"@babel/core@^7.24.5":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77"
integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.24.7"
"@babel/generator" "^7.25.0"
"@babel/helper-compilation-targets" "^7.25.2"
"@babel/helper-module-transforms" "^7.25.2"
"@babel/helpers" "^7.25.0"
"@babel/parser" "^7.25.0"
"@babel/template" "^7.25.0"
"@babel/traverse" "^7.25.2"
"@babel/types" "^7.25.2"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/generator@^7.20.2", "@babel/generator@^7.7.2":
version "7.20.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8"
......@@ -118,6 +152,16 @@
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/generator@^7.25.0", "@babel/generator@^7.25.6":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c"
integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==
dependencies:
"@babel/types" "^7.25.6"
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
......@@ -154,6 +198,17 @@
lru-cache "^5.1.1"
semver "^6.3.0"
"@babel/helper-compilation-targets@^7.25.2":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c"
integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==
dependencies:
"@babel/compat-data" "^7.25.2"
"@babel/helper-validator-option" "^7.24.8"
browserslist "^4.23.1"
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.2":
version "7.20.2"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz#3c08a5b5417c7f07b5cf3dfb6dc79cbec682e8c2"
......@@ -260,6 +315,14 @@
dependencies:
"@babel/types" "^7.21.4"
"@babel/helper-module-imports@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b"
integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==
dependencies:
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2":
version "7.20.2"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712"
......@@ -288,6 +351,16 @@
"@babel/traverse" "^7.22.1"
"@babel/types" "^7.22.0"
"@babel/helper-module-transforms@^7.25.2":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6"
integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==
dependencies:
"@babel/helper-module-imports" "^7.24.7"
"@babel/helper-simple-access" "^7.24.7"
"@babel/helper-validator-identifier" "^7.24.7"
"@babel/traverse" "^7.25.2"
"@babel/helper-optimise-call-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe"
......@@ -305,6 +378,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf"
integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==
"@babel/helper-plugin-utils@^7.24.7":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878"
integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==
"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519"
......@@ -340,6 +418,14 @@
dependencies:
"@babel/types" "^7.21.5"
"@babel/helper-simple-access@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3"
integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==
dependencies:
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-skip-transparent-expression-wrappers@^7.18.9":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684"
......@@ -376,6 +462,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83"
integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==
"@babel/helper-string-parser@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d"
integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
......@@ -386,6 +477,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-identifier@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db"
integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
"@babel/helper-validator-option@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
......@@ -396,6 +492,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==
"@babel/helper-validator-option@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d"
integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==
"@babel/helper-wrap-function@^7.18.9":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1"
......@@ -424,6 +525,14 @@
"@babel/traverse" "^7.22.1"
"@babel/types" "^7.22.3"
"@babel/helpers@^7.25.0":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60"
integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==
dependencies:
"@babel/template" "^7.25.0"
"@babel/types" "^7.25.6"
"@babel/highlight@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
......@@ -442,6 +551,16 @@
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/highlight@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d"
integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==
dependencies:
"@babel/helper-validator-identifier" "^7.24.7"
chalk "^2.4.2"
js-tokens "^4.0.0"
picocolors "^1.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.2":
version "7.20.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2"
......@@ -452,6 +571,13 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.4.tgz#03c4339d2b8971eb3beca5252bafd9b9f79db3dc"
integrity sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA==
"@babel/parser@^7.20.7", "@babel/parser@^7.25.0", "@babel/parser@^7.25.6":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f"
integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==
dependencies:
"@babel/types" "^7.25.6"
"@babel/parser@^7.21.9", "@babel/parser@^7.22.0":
version "7.22.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32"
......@@ -952,6 +1078,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-transform-react-jsx-self@^7.24.5":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz#66bff0248ea0b549972e733516ffad577477bdab"
integrity sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==
dependencies:
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-transform-react-jsx-source@^7.19.6":
version "7.19.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86"
......@@ -959,6 +1092,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.19.0"
"@babel/plugin-transform-react-jsx-source@^7.24.1":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz#1198aab2548ad19582013815c938d3ebd8291ee3"
integrity sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==
dependencies:
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-transform-react-jsx@^7.18.6":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9"
......@@ -1265,6 +1405,15 @@
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/template@^7.25.0":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a"
integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==
dependencies:
"@babel/code-frame" "^7.24.7"
"@babel/parser" "^7.25.0"
"@babel/types" "^7.25.0"
"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.22.1", "@babel/traverse@^7.7.2":
version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5"
......@@ -1281,6 +1430,19 @@
debug "^4.3.1"
globals "^11.1.0"
"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41"
integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==
dependencies:
"@babel/code-frame" "^7.24.7"
"@babel/generator" "^7.25.6"
"@babel/parser" "^7.25.6"
"@babel/template" "^7.25.0"
"@babel/types" "^7.25.6"
debug "^4.3.1"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.20.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842"
......@@ -1299,6 +1461,15 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6"
integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==
dependencies:
"@babel/helper-string-parser" "^7.24.8"
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3":
version "7.22.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.4.tgz#56a2653ae7e7591365dabf20b76295410684c071"
......@@ -1327,10 +1498,10 @@
resolved "https://registry.yarnpkg.com/@blockscout/bens-types/-/bens-types-1.4.1.tgz#9182a79d9015b7fa2339edf0bfa3cd0c32045e66"
integrity sha512-TlZ1HVdZ2Cswm/CcvNoxS+Ydiht/YGaLo//PJR/UmkmihlEFoY4HfVJvVcUnOQXi+Si7FwJ486DPii889nTJsQ==
"@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/stats-types@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@blockscout/stats-types/-/stats-types-2.0.0.tgz#3805f8379b75377cde8a9ab76306af37bb735846"
integrity sha512-icYDsOHsDACjG/7VZhlV+1QRKSJOycblpswQ5Si0dqeWdOpbtmxSqolAS/z6C77d8p+uxZUCMjNa9otUCqn18A==
"@blockscout/visualizer-types@0.2.0":
version "0.2.0"
......@@ -2184,11 +2355,6 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
"@cush/relative@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@cush/relative/-/relative-1.0.0.tgz#8cd1769bf9bde3bb27dac356b1bc94af40f6cc16"
integrity sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==
"@discoveryjs/json-ext@0.5.7":
version "0.5.7"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
......@@ -2314,115 +2480,120 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb"
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==
"@esbuild/android-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==
"@esbuild/android-arm@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682"
integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==
"@esbuild/android-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2"
integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==
"@esbuild/darwin-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1"
integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==
"@esbuild/darwin-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d"
integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==
"@esbuild/freebsd-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54"
integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==
"@esbuild/freebsd-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e"
integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==
"@esbuild/linux-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0"
integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==
"@esbuild/linux-arm@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0"
integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==
"@esbuild/linux-ia32@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7"
integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==
"@esbuild/linux-loong64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d"
integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==
"@esbuild/linux-mips64el@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231"
integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==
"@esbuild/linux-ppc64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb"
integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==
"@esbuild/linux-riscv64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6"
integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==
"@esbuild/linux-s390x@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071"
integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==
"@esbuild/linux-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338"
integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==
"@esbuild/netbsd-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1"
integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==
"@esbuild/openbsd-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae"
integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==
"@esbuild/sunos-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d"
integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==
"@esbuild/win32-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9"
integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==
"@esbuild/win32-ia32@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102"
integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==
"@esbuild/win32-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d"
integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==
"@esbuild/aix-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
"@esbuild/android-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
"@esbuild/android-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
"@esbuild/android-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
"@esbuild/darwin-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
"@esbuild/darwin-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
"@esbuild/freebsd-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
"@esbuild/freebsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
"@esbuild/linux-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
"@esbuild/linux-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
"@esbuild/linux-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
"@esbuild/linux-loong64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
"@esbuild/linux-mips64el@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
"@esbuild/linux-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
"@esbuild/linux-riscv64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
"@esbuild/linux-s390x@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
"@esbuild/linux-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
"@esbuild/netbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
"@esbuild/openbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
"@esbuild/sunos-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
"@esbuild/win32-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
"@esbuild/win32-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
"@esbuild/win32-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
"@eslint-community/eslint-utils@^4.2.0":
version "4.4.0"
......@@ -2901,21 +3072,45 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.5"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
dependencies:
"@jridgewell/set-array" "^1.2.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/set-array@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/sourcemap-codec@^1.4.14":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
"@jridgewell/trace-mapping@0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
......@@ -2932,6 +3127,14 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
version "0.3.25"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.16"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.16.tgz#a7982f16c18cae02be36274365433e5b49d7b23f"
......@@ -3323,10 +3526,10 @@
dependencies:
webpack-bundle-analyzer "4.10.1"
"@next/env@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.9.tgz#f7fed48efa51b069cfc611082ad0101756df4c6a"
integrity sha512-hnDAoDPMii31V0ivibI8p6b023jOF1XblWTVjsDUoZKwnZlaBtJFZKDwFqi22R8r9i6W08dThUWU7Bsh2Rg8Ww==
"@next/env@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.13.tgz#ba341ba9eb70db428fc1c754f49c3c516f7bab47"
integrity sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==
"@next/eslint-plugin-next@13.3.0":
version "13.3.0"
......@@ -3335,50 +3538,50 @@
dependencies:
glob "7.1.7"
"@next/swc-darwin-arm64@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.9.tgz#6d6880b580a0cb8d71be929d5399f0904d867e0a"
integrity sha512-/kfQifl3uLYi3DlwFlzCkgxe6fprJNLzzTUFknq3M5wGYicDIbdGlxUl6oHpVLJpBB/CBY3Y//gO6alz/K4NWA==
"@next/swc-darwin-x64@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.9.tgz#56af7531ed75638923cd8cba9a43b724bcfd7fea"
integrity sha512-tK/RyhCmOCiXQ9IVdFrBbZOf4/1+0RSuJkebXU2uMEsusS51TjIJO4l8ZmEijH9gZa0pJClvmApRHi7JuBqsRw==
"@next/swc-linux-arm64-gnu@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.9.tgz#d3e9a1fdf8eabd510c1139446178bfea2737c1e5"
integrity sha512-tS5eqwsp2nO7mzywRUuFYmefNZsUKM/mTG3exK2jIHv9TEVklE1SByB1KMhFkqlit1PxS9YK1tV8BOV90Wpbrw==
"@next/swc-linux-arm64-musl@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.9.tgz#82e570b25b471e9aabba70c8f62ccb7dd33e45fa"
integrity sha512-8svpeTFNAMTUMKQbEzE8qRAwl9o7mNBv7LR1bmSkQvo1oy4WrNyZbhWsldOiKrc4mZ5dfQkGYsI9T75mIFMfeA==
"@next/swc-linux-x64-gnu@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.9.tgz#277884aecc9ef7cddc9079d2dc137ecaa537ce0c"
integrity sha512-0HNulLWpKTB7H5BhHCkEhcRAnWUHeAYCftrrGw3QC18+ZywTdAoPv/zEqKy/0adqt+ks4JDdlgSQ1lNKOKjo0A==
"@next/swc-linux-x64-musl@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.9.tgz#6fd60f804c95b9cd8abf178424b76b63806c9b53"
integrity sha512-hhVFViPHLAVUJRNtwwm609p9ozWajOmRvzOZzzKXgiVGwx/CALxlMUeh+M+e0Zj6orENhWLZeilOPHpptuENsA==
"@next/swc-win32-arm64-msvc@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.9.tgz#f7d99c80d118e29a5910f8925ff11eb29fd775b3"
integrity sha512-p/v6XlOdrk06xfN9z4evLNBqftVQUWiyduQczCwSj7hNh8fWTbzdVxsEiNOcajMXJbQiaX/ZzZdFgKVmmJnnGQ==
"@next/swc-win32-ia32-msvc@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.9.tgz#e233fd707d827786a676df4b858345e8812cbae3"
integrity sha512-IcW9dynWDjMK/0M05E3zopbRen7v0/yEaMZbHFOSS1J/w+8YG3jKywOGZWNp/eCUVtUUXs0PW+7Lpz8uLu+KQA==
"@next/swc-win32-x64-msvc@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.9.tgz#c71cc74a6247ddcb4ec1b0e73f52e084c71efe9b"
integrity sha512-gcbpoXyWZdVOBgNa5BRzynrL5UR1nb2ZT38yKgnphYU9UHjeecnylMHntrQiMg/QtONDcJPFC/PmsS47xIRYoA==
"@next/swc-darwin-arm64@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.13.tgz#76f08d78360c4d27d444df7f35a56f59a48f4808"
integrity sha512-IkAmQEa2Htq+wHACBxOsslt+jMoV3msvxCn0WFSfJSkv/scy+i/EukBKNad36grRxywaXUYJc9mxEGkeIs8Bzg==
"@next/swc-darwin-x64@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.13.tgz#1d4821d54bb01dacc6a6c32408f8468a4f4af269"
integrity sha512-Dv1RBGs2TTjkwEnFMVL5XIfJEavnLqqwYSD6LXgTPdEy/u6FlSrLBSSfe1pcfqhFEXRAgVL3Wpjibe5wXJzWog==
"@next/swc-linux-arm64-gnu@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.13.tgz#79d9af8d3408df9990c8911889eca1ca6a308f19"
integrity sha512-yB1tYEFFqo4ZNWkwrJultbsw7NPAAxlPXURXioRl9SdW6aIefOLS+0TEsKrWBtbJ9moTDgU3HRILL6QBQnMevg==
"@next/swc-linux-arm64-musl@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.13.tgz#b13180645865b120591db2f1e831743ebc02ab36"
integrity sha512-v5jZ/FV/eHGoWhMKYrsAweQ7CWb8xsWGM/8m1mwwZQ/sutJjoFaXchwK4pX8NqwImILEvQmZWyb8pPTcP7htWg==
"@next/swc-linux-x64-gnu@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.13.tgz#8cb8480dfeee512648e4e08c2095aac0461b876f"
integrity sha512-aVc7m4YL7ViiRv7SOXK3RplXzOEe/qQzRA5R2vpXboHABs3w8vtFslGTz+5tKiQzWUmTmBNVW0UQdhkKRORmGA==
"@next/swc-linux-x64-musl@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.13.tgz#df5ca922fa1e1ee81b15a06a2d3d3ace0efd2bd7"
integrity sha512-4wWY7/OsSaJOOKvMsu1Teylku7vKyTuocvDLTZQq0TYv9OjiYYWt63PiE1nTuZnqQ4RPvME7Xai+9enoiN0Wrg==
"@next/swc-win32-arm64-msvc@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.13.tgz#8a7db6e71f526212587975f743b28e4d1cb829d1"
integrity sha512-uP1XkqCqV2NVH9+g2sC7qIw+w2tRbcMiXFEbMihkQ8B1+V6m28sshBwAB0SDmOe0u44ne1vFU66+gx/28RsBVQ==
"@next/swc-win32-ia32-msvc@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.13.tgz#6aa664f36f2d70c5ae6ffcbbc56784d33f24522d"
integrity sha512-V26ezyjPqQpDBV4lcWIh8B/QICQ4v+M5Bo9ykLN+sqeKKBxJVDpEc6biDVyluTXTC40f5IqCU0ttth7Es2ZuMw==
"@next/swc-win32-x64-msvc@14.2.13":
version "14.2.13"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.13.tgz#5a920eea82a58affa6146192586716cec6c87fed"
integrity sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==
"@noble/curves@1.1.0", "@noble/curves@~1.1.0":
version "1.1.0"
......@@ -4290,29 +4493,29 @@
tiny-glob "^0.2.9"
tslib "^2.4.0"
"@playwright/experimental-ct-core@1.41.1":
version "1.41.1"
resolved "https://registry.yarnpkg.com/@playwright/experimental-ct-core/-/experimental-ct-core-1.41.1.tgz#9bad5adb0a048c687a596bd832a258c8ced24ec8"
integrity sha512-d7PxESV29x6W9RYs0mhkXmxr+6FfTbg2Tm/WJZlhgbIP+OLv79uJ8hl8ERsiBBFtH88sR+WmxHBMiZRpfpa6Fw==
"@playwright/experimental-ct-core@1.47.2":
version "1.47.2"
resolved "https://registry.yarnpkg.com/@playwright/experimental-ct-core/-/experimental-ct-core-1.47.2.tgz#b04a7d3b6ea7577959e1fad4a8f4604f0a9bfbed"
integrity sha512-aTR254jpS7mpuX8Od6vt9zOOmeuZ1PPI8aaT3vxb8lOtRWoq/XsCsnlxu3eWInbDsiYyAEcxPsiGLU9PXgC4LQ==
dependencies:
playwright "1.41.1"
playwright-core "1.41.1"
vite "^4.4.12"
playwright "1.47.2"
playwright-core "1.47.2"
vite "^5.2.8"
"@playwright/experimental-ct-react@1.41.1":
version "1.41.1"
resolved "https://registry.yarnpkg.com/@playwright/experimental-ct-react/-/experimental-ct-react-1.41.1.tgz#4711881caf1ab47acbc3f7c121b87e17f165a46c"
integrity sha512-Ht04RKD/4J69EPHOR4iAWtsOkkqswxonkcEEhniTNflGn30SoPyNww72LJECDrls+7AJayflJf4qe/cK1Ao/ug==
"@playwright/experimental-ct-react@1.47.2":
version "1.47.2"
resolved "https://registry.yarnpkg.com/@playwright/experimental-ct-react/-/experimental-ct-react-1.47.2.tgz#45d99240be04db378183433608fc39470ea61799"
integrity sha512-taWSdPhbdXl6dsC2RmlfGoOy9moeBxMdV7j4n/IEXfzMWu7GshUR9kAESg3QsV03i1vU8kTX5Ko28Yt3ISnk4w==
dependencies:
"@playwright/experimental-ct-core" "1.41.1"
"@vitejs/plugin-react" "^4.0.0"
"@playwright/experimental-ct-core" "1.47.2"
"@vitejs/plugin-react" "^4.2.1"
"@playwright/test@1.41.1":
version "1.41.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.41.1.tgz#6954139ed4a67999f1b17460aa3d184f4b334f18"
integrity sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw==
"@playwright/test@1.47.2":
version "1.47.2"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.47.2.tgz#dbe7051336bfc5cc599954214f9111181dbc7475"
integrity sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==
dependencies:
playwright "1.41.1"
playwright "1.47.2"
"@polka/url@^1.0.0-next.24":
version "1.0.0-next.25"
......@@ -4538,6 +4741,86 @@
estree-walker "^2.0.2"
picomatch "^2.3.1"
"@rollup/rollup-android-arm-eabi@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz#8b613b9725e8f9479d142970b106b6ae878610d5"
integrity sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==
"@rollup/rollup-android-arm64@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz#654ca1049189132ff602bfcf8df14c18da1f15fb"
integrity sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==
"@rollup/rollup-darwin-arm64@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz#6d241d099d1518ef0c2205d96b3fa52e0fe1954b"
integrity sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==
"@rollup/rollup-darwin-x64@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz#42bd19d292a57ee11734c980c4650de26b457791"
integrity sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==
"@rollup/rollup-linux-arm-gnueabihf@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz#f23555ee3d8fe941c5c5fd458cd22b65eb1c2232"
integrity sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==
"@rollup/rollup-linux-arm-musleabihf@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz#f3bbd1ae2420f5539d40ac1fde2b38da67779baa"
integrity sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==
"@rollup/rollup-linux-arm64-gnu@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz#7abe900120113e08a1f90afb84c7c28774054d15"
integrity sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==
"@rollup/rollup-linux-arm64-musl@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz#9e655285c8175cd44f57d6a1e8e5dedfbba1d820"
integrity sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==
"@rollup/rollup-linux-powerpc64le-gnu@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz#9a79ae6c9e9d8fe83d49e2712ecf4302db5bef5e"
integrity sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==
"@rollup/rollup-linux-riscv64-gnu@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz#67ac70eca4ace8e2942fabca95164e8874ab8128"
integrity sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==
"@rollup/rollup-linux-s390x-gnu@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz#9f883a7440f51a22ed7f99e1d070bd84ea5005fc"
integrity sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==
"@rollup/rollup-linux-x64-gnu@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz#70116ae6c577fe367f58559e2cffb5641a1dd9d0"
integrity sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==
"@rollup/rollup-linux-x64-musl@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz#f473f88219feb07b0b98b53a7923be716d1d182f"
integrity sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==
"@rollup/rollup-win32-arm64-msvc@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz#4349482d17f5d1c58604d1c8900540d676f420e0"
integrity sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==
"@rollup/rollup-win32-ia32-msvc@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz#a6fc39a15db618040ec3c2a24c1e26cb5f4d7422"
integrity sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==
"@rollup/rollup-win32-x64-msvc@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz#3dd5d53e900df2a40841882c02e56f866c04d202"
integrity sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==
"@rushstack/eslint-patch@^1.1.3":
version "1.10.2"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.2.tgz#053f1540703faa81dea2966b768ee5581c66aeda"
......@@ -5420,6 +5703,17 @@
"@types/babel__template" "*"
"@types/babel__traverse" "*"
"@types/babel__core@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
dependencies:
"@babel/parser" "^7.20.7"
"@babel/types" "^7.20.7"
"@types/babel__generator" "*"
"@types/babel__template" "*"
"@types/babel__traverse" "*"
"@types/babel__generator@*":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7"
......@@ -5731,6 +6025,11 @@
resolved "https://registry.yarnpkg.com/@types/dom-to-image/-/dom-to-image-2.6.4.tgz#008411e23903cb0ee9e51a42ab8358c609541ee8"
integrity sha512-UddUdGF1qulrSDulkz3K2Ypq527MR6ixlgAzqLbxSiQ0icx0XDlIV+h4+edmjq/1dqn0KgN0xGSe1kI9t+vGuw==
"@types/estree@1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
"@types/estree@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2"
......@@ -6004,12 +6303,12 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.4.tgz#54be907698f40de8a45770b48486aa3cbd3adff7"
integrity sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow==
"@types/node@20.11.0":
version "20.11.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.0.tgz#8e0b99e70c0c1ade1a86c4a282f7b7ef87c9552f"
integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==
"@types/node@20.16.7":
version "20.16.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.7.tgz#0a245bf5805add998a22b8b5adac612ee70190bc"
integrity sha512-QkDQjAY3gkvJNcZOWwzy3BN34RweT0OQ9zJyvLCU0kSK22dO2QYh/NHGfbEAYylPYzRB1/iXcojS79wOg5gFSw==
dependencies:
undici-types "~5.26.4"
undici-types "~6.19.2"
"@types/node@>=13.7.0":
version "20.9.0"
......@@ -6401,6 +6700,17 @@
"@babel/plugin-transform-react-jsx-source" "^7.19.6"
react-refresh "^0.14.0"
"@vitejs/plugin-react@^4.2.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz#d0be6594051ded8957df555ff07a991fb618b48e"
integrity sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==
dependencies:
"@babel/core" "^7.24.5"
"@babel/plugin-transform-react-jsx-self" "^7.24.5"
"@babel/plugin-transform-react-jsx-source" "^7.24.1"
"@types/babel__core" "^7.20.5"
react-refresh "^0.14.2"
"@wagmi/connectors@5.1.10":
version "5.1.10"
resolved "https://registry.yarnpkg.com/@wagmi/connectors/-/connectors-5.1.10.tgz#316dcdbb3924c546c177b0fc287e0f591225c63f"
......@@ -7004,11 +7314,6 @@ ansi-styles@^6.1.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
anymatch@^3.0.3:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
......@@ -7502,6 +7807,16 @@ browserslist@^4.21.3, browserslist@^4.21.4:
node-releases "^2.0.6"
update-browserslist-db "^1.0.9"
browserslist@^4.23.1:
version "4.24.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4"
integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==
dependencies:
caniuse-lite "^1.0.30001663"
electron-to-chromium "^1.5.28"
node-releases "^2.0.18"
update-browserslist-db "^1.1.0"
bs-logger@0.x:
version "0.2.6"
resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
......@@ -7604,10 +7919,10 @@ caniuse-lite@^1.0.30001400:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz#5f459215192a024c99e3e3a53aac310fc7cf24e6"
integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg==
caniuse-lite@^1.0.30001579:
version "1.0.30001614"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001614.tgz#f894b4209376a0bf923d67d9c361d96b1dfebe39"
integrity sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog==
caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001663:
version "1.0.30001663"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz#1529a723505e429fdfd49532e9fc42273ba7fed7"
integrity sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==
"cbw-sdk@npm:@coinbase/wallet-sdk@3.9.3":
version "3.9.3"
......@@ -7873,11 +8188,6 @@ commander@^2.20.3:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
commander@^9.3.0:
version "9.4.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd"
......@@ -8822,6 +9132,11 @@ electron-to-chromium@^1.4.251:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.276.tgz#17837b19dafcc43aba885c4689358b298c19b520"
integrity sha512-EpuHPqu8YhonqLBXHoU6hDJCD98FCe6KDoet3/gY1qsQ6usjJoHqBH2YIVs8FXaAtHwVL8Uqa/fsYao/vq9VWQ==
electron-to-chromium@^1.5.28:
version "1.5.28"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.28.tgz#aee074e202c6ee8a0030a9c2ef0b3fe9f967d576"
integrity sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==
elliptic@^6.5.4:
version "6.5.5"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded"
......@@ -9141,39 +9456,45 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
esbuild@^0.18.10:
version "0.18.20"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6"
integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==
esbuild@^0.21.3:
version "0.21.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
optionalDependencies:
"@esbuild/android-arm" "0.18.20"
"@esbuild/android-arm64" "0.18.20"
"@esbuild/android-x64" "0.18.20"
"@esbuild/darwin-arm64" "0.18.20"
"@esbuild/darwin-x64" "0.18.20"
"@esbuild/freebsd-arm64" "0.18.20"
"@esbuild/freebsd-x64" "0.18.20"
"@esbuild/linux-arm" "0.18.20"
"@esbuild/linux-arm64" "0.18.20"
"@esbuild/linux-ia32" "0.18.20"
"@esbuild/linux-loong64" "0.18.20"
"@esbuild/linux-mips64el" "0.18.20"
"@esbuild/linux-ppc64" "0.18.20"
"@esbuild/linux-riscv64" "0.18.20"
"@esbuild/linux-s390x" "0.18.20"
"@esbuild/linux-x64" "0.18.20"
"@esbuild/netbsd-x64" "0.18.20"
"@esbuild/openbsd-x64" "0.18.20"
"@esbuild/sunos-x64" "0.18.20"
"@esbuild/win32-arm64" "0.18.20"
"@esbuild/win32-ia32" "0.18.20"
"@esbuild/win32-x64" "0.18.20"
"@esbuild/aix-ppc64" "0.21.5"
"@esbuild/android-arm" "0.21.5"
"@esbuild/android-arm64" "0.21.5"
"@esbuild/android-x64" "0.21.5"
"@esbuild/darwin-arm64" "0.21.5"
"@esbuild/darwin-x64" "0.21.5"
"@esbuild/freebsd-arm64" "0.21.5"
"@esbuild/freebsd-x64" "0.21.5"
"@esbuild/linux-arm" "0.21.5"
"@esbuild/linux-arm64" "0.21.5"
"@esbuild/linux-ia32" "0.21.5"
"@esbuild/linux-loong64" "0.21.5"
"@esbuild/linux-mips64el" "0.21.5"
"@esbuild/linux-ppc64" "0.21.5"
"@esbuild/linux-riscv64" "0.21.5"
"@esbuild/linux-s390x" "0.21.5"
"@esbuild/linux-x64" "0.21.5"
"@esbuild/netbsd-x64" "0.21.5"
"@esbuild/openbsd-x64" "0.21.5"
"@esbuild/sunos-x64" "0.21.5"
"@esbuild/win32-arm64" "0.21.5"
"@esbuild/win32-ia32" "0.21.5"
"@esbuild/win32-x64" "0.21.5"
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escalade@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
......@@ -9926,6 +10247,11 @@ fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
......@@ -10101,23 +10427,6 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
glob-regex@^0.3.0:
version "0.3.2"
resolved "https://registry.yarnpkg.com/glob-regex/-/glob-regex-0.3.2.tgz#27348f2f60648ec32a4a53137090b9fb934f3425"
integrity sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw==
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.7:
version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
......@@ -11643,7 +11952,7 @@ json5@^2.2.1:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
json5@^2.2.2:
json5@^2.2.2, json5@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
......@@ -12271,26 +12580,12 @@ multiformats@^9.4.2:
resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37"
integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==
mz@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
thenify-all "^1.0.0"
nan@^2.14.0, nan@^2.14.1, nan@^2.17.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
nanoid@^3.3.6:
version "3.3.6"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
nanoid@^3.3.7:
nanoid@^3.3.6, nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
......@@ -12315,12 +12610,12 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
next@14.2.9:
version "14.2.9"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.9.tgz#51d0e067cd9eb8a51fd5c0efd5d8f28181849729"
integrity sha512-3CzBNo6BuJnRjcQvRw+irnU1WiuJNZEp+dkzkt91y4jeIDN/Emg95F+takSYiLpJ/HkxClVQRyqiTwYce5IVqw==
next@14.2.13:
version "14.2.13"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.13.tgz#32da2ee0afbe729e2d4a467c3570def90e1c974d"
integrity sha512-BseY9YNw8QJSwLYD7hlZzl6QVDoSFHL/URN5K64kVEVpCsSOWeyjbIGK+dZUaRViHTaMQX8aqmnn0PHBbGZezg==
dependencies:
"@next/env" "14.2.9"
"@next/env" "14.2.13"
"@swc/helpers" "0.5.5"
busboy "1.6.0"
caniuse-lite "^1.0.30001579"
......@@ -12328,15 +12623,15 @@ next@14.2.9:
postcss "8.4.31"
styled-jsx "5.1.1"
optionalDependencies:
"@next/swc-darwin-arm64" "14.2.9"
"@next/swc-darwin-x64" "14.2.9"
"@next/swc-linux-arm64-gnu" "14.2.9"
"@next/swc-linux-arm64-musl" "14.2.9"
"@next/swc-linux-x64-gnu" "14.2.9"
"@next/swc-linux-x64-musl" "14.2.9"
"@next/swc-win32-arm64-msvc" "14.2.9"
"@next/swc-win32-ia32-msvc" "14.2.9"
"@next/swc-win32-x64-msvc" "14.2.9"
"@next/swc-darwin-arm64" "14.2.13"
"@next/swc-darwin-x64" "14.2.13"
"@next/swc-linux-arm64-gnu" "14.2.13"
"@next/swc-linux-arm64-musl" "14.2.13"
"@next/swc-linux-x64-gnu" "14.2.13"
"@next/swc-linux-x64-musl" "14.2.13"
"@next/swc-win32-arm64-msvc" "14.2.13"
"@next/swc-win32-ia32-msvc" "14.2.13"
"@next/swc-win32-x64-msvc" "14.2.13"
nextjs-routes@^1.0.8:
version "1.0.8"
......@@ -12443,6 +12738,11 @@ node-int64@^0.4.0:
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
node-releases@^2.0.18:
version "2.0.18"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f"
integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
......@@ -12493,7 +12793,7 @@ obj-multiplex@^1.0.0:
once "^1.4.0"
readable-stream "^2.3.3"
object-assign@^4.0.1, object-assign@^4.1.1:
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
......@@ -12917,10 +13217,10 @@ phoenix@^1.6.15:
resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.6.15.tgz#efb2088a310cde333b3762002831b79dedf76002"
integrity sha512-O6AG5jTkZOOkdd/GOSCsM4v3bzBoyRnC5bEi57KhX/Daba6FvnBRzt0nhEeRRiVQGLSxDlyb0dUe9CkYWMZd8g==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59"
integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
version "2.3.1"
......@@ -13033,7 +13333,7 @@ pino@^8.0.0:
sonic-boom "^3.1.0"
thread-stream "^2.0.0"
pirates@^4.0.1, pirates@^4.0.4:
pirates@^4.0.4:
version "4.0.5"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
......@@ -13054,17 +13354,17 @@ pkg-types@^1.0.3:
mlly "^1.2.0"
pathe "^1.1.0"
playwright-core@1.41.1:
version "1.41.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.41.1.tgz#9c152670010d9d6f970f34b68e3e935d3c487431"
integrity sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg==
playwright-core@1.47.2:
version "1.47.2"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.47.2.tgz#7858da9377fa32a08be46ba47d7523dbd9460a4e"
integrity sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==
playwright@1.41.1:
version "1.41.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.41.1.tgz#83325f34165840d019355c2a78a50f21ed9b9c85"
integrity sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ==
playwright@1.47.2:
version "1.47.2"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.47.2.tgz#155688aa06491ee21fb3e7555b748b525f86eb20"
integrity sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==
dependencies:
playwright-core "1.41.1"
playwright-core "1.47.2"
optionalDependencies:
fsevents "2.3.2"
......@@ -13165,7 +13465,7 @@ postcss@8.4.31:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.19, postcss@^8.4.27:
postcss@^8.4.19:
version "8.4.32"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9"
integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==
......@@ -13174,6 +13474,15 @@ postcss@^8.4.19, postcss@^8.4.27:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.43:
version "8.4.47"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
dependencies:
nanoid "^3.3.7"
picocolors "^1.1.0"
source-map-js "^1.2.1"
postgres-array@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
......@@ -13677,6 +13986,11 @@ react-refresh@^0.14.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
react-refresh@^0.14.2:
version "0.14.2"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==
react-remove-scroll-bar@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.3.tgz#e291f71b1bb30f5f67f023765b7435f4b2b2cd94"
......@@ -13838,17 +14152,6 @@ real-require@^0.2.0:
resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78"
integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==
recrawl-sync@^2.0.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/recrawl-sync/-/recrawl-sync-2.2.3.tgz#757adcdaae4799466dde5b8ee52122ff9636dfb1"
integrity sha512-vSaTR9t+cpxlskkdUFrsEpnf67kSmPk66yAGT1fZPrDudxQjoMzPgQhSMImQ0pAw5k0NPirefQfhopSjhdUtpQ==
dependencies:
"@cush/relative" "^1.0.0"
glob-regex "^0.3.0"
slash "^3.0.0"
sucrase "^3.20.3"
tslib "^1.9.3"
redis-errors@^1.0.0, redis-errors@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
......@@ -14157,11 +14460,29 @@ rollup-plugin-visualizer@^5.9.2:
source-map "^0.7.4"
yargs "^17.5.1"
rollup@^3.27.1:
version "3.29.4"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981"
integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==
rollup@^4.20.0:
version "4.22.4"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.22.4.tgz#4135a6446671cd2a2453e1ad42a45d5973ec3a0f"
integrity sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==
dependencies:
"@types/estree" "1.0.5"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.22.4"
"@rollup/rollup-android-arm64" "4.22.4"
"@rollup/rollup-darwin-arm64" "4.22.4"
"@rollup/rollup-darwin-x64" "4.22.4"
"@rollup/rollup-linux-arm-gnueabihf" "4.22.4"
"@rollup/rollup-linux-arm-musleabihf" "4.22.4"
"@rollup/rollup-linux-arm64-gnu" "4.22.4"
"@rollup/rollup-linux-arm64-musl" "4.22.4"
"@rollup/rollup-linux-powerpc64le-gnu" "4.22.4"
"@rollup/rollup-linux-riscv64-gnu" "4.22.4"
"@rollup/rollup-linux-s390x-gnu" "4.22.4"
"@rollup/rollup-linux-x64-gnu" "4.22.4"
"@rollup/rollup-linux-x64-musl" "4.22.4"
"@rollup/rollup-win32-arm64-msvc" "4.22.4"
"@rollup/rollup-win32-ia32-msvc" "4.22.4"
"@rollup/rollup-win32-x64-msvc" "4.22.4"
fsevents "~2.3.2"
run-parallel@^1.1.9:
......@@ -14487,11 +14808,16 @@ sonic-boom@^3.0.0, sonic-boom@^3.1.0:
dependencies:
atomic-sleep "^1.0.0"
source-map-js@^1.0.1, source-map-js@^1.0.2:
source-map-js@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map-js@^1.0.2, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
source-map-support@0.5.13:
version "0.5.13"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
......@@ -14802,18 +15128,6 @@ stylis@4.1.3:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7"
integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==
sucrase@^3.20.3:
version "3.29.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.29.0.tgz#3207c5bc1b980fdae1e539df3f8a8a518236da7d"
integrity sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A==
dependencies:
commander "^4.0.0"
glob "7.1.6"
lines-and-columns "^1.1.6"
mz "^2.7.0"
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
superstruct@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046"
......@@ -15028,20 +15342,6 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==
dependencies:
thenify ">= 3.1.0 < 4"
"thenify@>= 3.1.0 < 4":
version "3.3.1"
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
dependencies:
any-promise "^1.0.0"
thread-stream@^0.15.1:
version "0.15.2"
resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4"
......@@ -15167,11 +15467,6 @@ tree-sitter@=0.20.4:
nan "^2.17.0"
prebuild-install "^7.1.1"
ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
ts-jest@^29.0.3:
version "29.0.3"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.3.tgz#63ea93c5401ab73595440733cefdba31fcf9cb77"
......@@ -15210,6 +15505,11 @@ ts-toolbelt@^9.6.0:
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5"
integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==
tsconfck@^3.0.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.3.tgz#a8202f51dab684c426314796cdb0bbd0fe0cdf80"
integrity sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==
tsconfig-paths@^3.15.0:
version "3.15.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
......@@ -15220,21 +15520,12 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
tsconfig-paths@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz#f8ef7d467f08ae3a695335bf1ece088c5538d2c1"
integrity sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==
dependencies:
json5 "^2.2.1"
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@1.14.1, tslib@^1.8.1, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@2.4.0, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0:
tslib@2.4.0, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
......@@ -15244,6 +15535,11 @@ tslib@^2.3.0, tslib@^2.5.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
tslib@^2.4.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
tslib@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
......@@ -15411,6 +15707,11 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici-types@~6.19.2:
version "6.19.8"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
undici@^5.24.0:
version "5.28.4"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068"
......@@ -15501,6 +15802,14 @@ update-browserslist-db@^1.0.9:
escalade "^3.1.1"
picocolors "^1.0.0"
update-browserslist-db@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e"
integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==
dependencies:
escalade "^3.1.2"
picocolors "^1.0.1"
uqr@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/uqr/-/uqr-0.1.2.tgz#5c6cd5dcff9581f9bb35b982cb89e2c483a41d7d"
......@@ -15651,26 +15960,25 @@ vite-plugin-svgr@^2.2.2:
"@rollup/pluginutils" "^5.0.0"
"@svgr/core" "^6.4.0"
vite-tsconfig-paths@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-3.5.2.tgz#fd3232f93c426311d7e0d581187d8b63fff55fbc"
integrity sha512-xJMgHA2oJ28QCG2f+hXrcqzo7IttrSRK4A//Tp94CfuX5eetOx33qiwXHUdi3FwkHP2ocpxHuvE45Ix67gwEmQ==
vite-tsconfig-paths@4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9"
integrity sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==
dependencies:
debug "^4.1.1"
globrex "^0.1.2"
recrawl-sync "^2.0.3"
tsconfig-paths "^4.0.0"
tsconfck "^3.0.3"
vite@^4.4.12:
version "4.5.3"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.3.tgz#d88a4529ea58bae97294c7e2e6f0eab39a50fb1a"
integrity sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==
vite@^5.2.8:
version "5.4.8"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.8.tgz#af548ce1c211b2785478d3ba3e8da51e39a287e8"
integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==
dependencies:
esbuild "^0.18.10"
postcss "^8.4.27"
rollup "^3.27.1"
esbuild "^0.21.3"
postcss "^8.4.43"
rollup "^4.20.0"
optionalDependencies:
fsevents "~2.3.2"
fsevents "~2.3.3"
vscode-languageserver-types@^3.17.1:
version "3.17.3"
......
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