Commit 18c8581d authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into charts-fixes

parents 8dc1eca9 80bda612
NEXT_PUBLIC_SENTRY_DSN=xxx NEXT_PUBLIC_SENTRY_DSN=xxx
SENTRY_CSP_REPORT_URI=xxx SENTRY_CSP_REPORT_URI=xxx
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
\ No newline at end of file NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X
\ No newline at end of file
...@@ -56,6 +56,7 @@ NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__ ...@@ -56,6 +56,7 @@ NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
NEXT_PUBLIC_AUTH0_CLIENT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH0_CLIENT_ID__ NEXT_PUBLIC_AUTH0_CLIENT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH0_CLIENT_ID__
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID__ NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID__
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=__PLACEHOLDER_FOR_NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY__ NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=__PLACEHOLDER_FOR_NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY__
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID__
# l2 config # l2 config
NEXT_PUBLIC_IS_L2_NETWORK=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_L2_NETWORKL__ NEXT_PUBLIC_IS_L2_NETWORK=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_L2_NETWORKL__
......
...@@ -30,6 +30,7 @@ module.exports = { ...@@ -30,6 +30,7 @@ module.exports = {
'jsx-a11y', 'jsx-a11y',
'eslint-plugin-import-helpers', 'eslint-plugin-import-helpers',
'jest', 'jest',
'eslint-plugin-no-cyrillic-string',
], ],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
...@@ -117,7 +118,7 @@ module.exports = { ...@@ -117,7 +118,7 @@ module.exports = {
'@typescript-eslint/type-annotation-spacing': 'error', '@typescript-eslint/type-annotation-spacing': 'error',
'@typescript-eslint/no-explicit-any': [ 'error', { ignoreRestArgs: true } ], '@typescript-eslint/no-explicit-any': [ 'error', { ignoreRestArgs: true } ],
// отключены в пользу @typescript-eslint // disabled in favor of @typescript-eslint
'brace-style': 'off', 'brace-style': 'off',
camelcase: 'off', camelcase: 'off',
indent: 'off', indent: 'off',
...@@ -269,7 +270,7 @@ module.exports = { ...@@ -269,7 +270,7 @@ module.exports = {
'regexp/no-empty-capturing-group': 'error', 'regexp/no-empty-capturing-group': 'error',
'regexp/no-lazy-ends': 'error', 'regexp/no-lazy-ends': 'error',
'regexp/no-obscure-range': [ 'error', { 'regexp/no-obscure-range': [ 'error', {
allowed: [ 'alphanumeric', 'А-Я', 'а-я' ], allowed: [ 'alphanumeric' ],
} ], } ],
'regexp/no-optional-assertion': 'error', 'regexp/no-optional-assertion': 'error',
'regexp/no-unused-capturing-group': [ 'error', { 'regexp/no-unused-capturing-group': [ 'error', {
...@@ -277,6 +278,8 @@ module.exports = { ...@@ -277,6 +278,8 @@ module.exports = {
} ], } ],
'regexp/no-useless-character-class': 'error', 'regexp/no-useless-character-class': 'error',
'regexp/no-useless-dollar-replacements': 'error', 'regexp/no-useless-dollar-replacements': 'error',
'no-cyrillic-string/no-cyrillic-string': 'error',
}, },
overrides: [ overrides: [
{ {
......
...@@ -36,7 +36,7 @@ To perform testing locally you need to install docker and run `yarn test:pw:dock ...@@ -36,7 +36,7 @@ To perform testing locally you need to install docker and run `yarn test:pw:dock
The app instance could be customized by passing following variables to NodeJS environment at runtime. The app instance could be customized by passing following variables to NodeJS environment at runtime.
**IMPORTANT NOTE!** For _production_ build purposes all json-like values should be single-quoted **IMPORTANT NOTE!** For _production_ build purposes all json-like values should be single-quoted. If it contains a hash (`#`) or a dollar-sign (`$`) the whole value should be wrapped in single quotes as well (see `dotenv` [readme](https://github.com/bkeepers/dotenv#variable-substitution))
### Network configuration ### Network configuration
...@@ -130,6 +130,7 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -130,6 +130,7 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` | Client id for [Auth0](https://auth0.com/) provider | `<secret>` | | NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` | Client id for [Auth0](https://auth0.com/) provider | `<secret>` |
| NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID | `string` | Project id for [WalletConnect](https://docs.walletconnect.com/2.0/web3modal/react/installation#obtain-project-id) integration | `<secret>` | | NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID | `string` | Project id for [WalletConnect](https://docs.walletconnect.com/2.0/web3modal/react/installation#obtain-project-id) integration | `<secret>` |
| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | Site key for [reCAPTCHA](https://developers.google.com/recaptcha) service | `<secret>` | | NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | Site key for [reCAPTCHA](https://developers.google.com/recaptcha) service | `<secret>` |
| NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID | `string` | Property ID for [Google Analytics](https://analytics.google.com/) service | `UA-XXXXXX-X` |
### L2 configuration ### L2 configuration
| Variable | Type | Description | Default value | Variable | Type | Description | Default value
......
...@@ -6,7 +6,7 @@ import type { ChainIndicatorId } from 'ui/home/indicators/types'; ...@@ -6,7 +6,7 @@ import type { ChainIndicatorId } from 'ui/home/indicators/types';
const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"'); const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"');
const parseEnvJson = <DataType>(env: string | undefined): DataType | null => { const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
try { try {
return JSON.parse(env || 'null'); return JSON.parse(env || 'null') as DataType | null;
} catch (error) { } catch (error) {
return null; return null;
} }
...@@ -133,6 +133,9 @@ const config = Object.freeze({ ...@@ -133,6 +133,9 @@ const config = Object.freeze({
reCaptcha: { reCaptcha: {
siteKey: getEnvValue(process.env.NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY) || '', siteKey: getEnvValue(process.env.NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY) || '',
}, },
googleAnalytics: {
propertyId: getEnvValue(process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID),
},
}); });
export default config; export default config;
...@@ -5,20 +5,14 @@ blockscout: ...@@ -5,20 +5,14 @@ blockscout:
app: blockscout app: blockscout
enabled: true enabled: true
image: image:
_default: &image blockscout/blockscout-optimism-l2-advanced:5.1.0-prerelease-3097e10c _default: &image blockscout/blockscout-optimism-l2-advanced:5.1.0-prerelease-7a8c745e
replicas: replicas:
app: 1 app: 1
docker:
port: 80
targetPort: 4000
# init container # init container
init: init:
enabled: true enabled: true
image: image:
_default: *image _default: *image
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress # enable ingress
ingress: ingress:
enabled: true enabled: true
...@@ -64,17 +58,6 @@ blockscout: ...@@ -64,17 +58,6 @@ blockscout:
_default: "1Gi" _default: "1Gi"
cpu: cpu:
_default: "1" _default: "1"
# enable service to connect to RDS
rds:
enabled: false
endpoint:
_default: <endpoint>.<region>.rds.amazonaws.com
# node label
nodeSelector:
enabled: true
labels:
_default:
app: blockscout
# Blockscout environment variables # Blockscout environment variables
environment: environment:
ENV: ENV:
...@@ -149,6 +132,12 @@ blockscout: ...@@ -149,6 +132,12 @@ blockscout:
_default: '8299683' _default: '8299683'
INDEXER_OPTIMISM_OUTPUT_ORACLE_L1: INDEXER_OPTIMISM_OUTPUT_ORACLE_L1:
_default: 0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0 _default: 0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0
INDEXER_OPTIMISM_BATCH_START_BLOCK_L1:
_default: '8381594'
INDEXER_OPTIMISM_BATCH_INBOX:
_default: 0xff00000000000000000000000000000000000420
INDEXER_OPTIMISM_BATCH_SUBMITTER:
_default: 0x7431310e026b69bfc676c0013e12a1a11411eec9
postgres: postgres:
enabled: true enabled: true
...@@ -188,30 +177,6 @@ scVerifier: ...@@ -188,30 +177,6 @@ scVerifier:
enabled: true enabled: true
image: image:
_default: ghcr.io/blockscout/smart-contract-verifier:main _default: ghcr.io/blockscout/smart-contract-verifier:main
replicas:
app: 1
ports:
http:
number: 8050
protocol: TCP
host: 'http.'
path: "/"
pathType: Prefix
grpc:
number: 8051
protocol: TCP
host: 'grpc.'
path: "/"
pathType: Prefix
metrics:
number: 6060
protocol: TCP
host: "metrics."
path: "/metrics"
pathType: Exact
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress # enable ingress
ingress: ingress:
enabled: true enabled: true
...@@ -231,24 +196,6 @@ scVerifier: ...@@ -231,24 +196,6 @@ scVerifier:
_default: "0.5Gi" _default: "0.5Gi"
cpu: cpu:
_default: "0.25" _default: "0.25"
# node label
nodeSelector:
enabled: true
labels:
app: blockscout
# probes
livenessProbe:
enabled: true
# path: /health
readinessProbe:
enabled: true
# path: /health
# enable Horizontal Pod Autoscaler
hpa:
enabled: true
minReplicas: 1
maxReplicas: 10
cpuTarget: 90
environment: environment:
SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR: SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR:
_default: 0.0.0.0:8050 _default: 0.0.0.0:8050
...@@ -292,17 +239,6 @@ stats: ...@@ -292,17 +239,6 @@ stats:
image: image:
_default: ghcr.io/blockscout/stats:main _default: ghcr.io/blockscout/stats:main
replicas:
app: 1
docker:
port: 80
targetPort: 8050
metricsPort: 6060
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress # enable ingress
ingress: ingress:
enabled: true enabled: true
...@@ -329,11 +265,6 @@ stats: ...@@ -329,11 +265,6 @@ stats:
cpu: cpu:
_default: "0.25" _default: "0.25"
# node label
nodeSelector:
enabled: true
app: blockscout
environment: environment:
RUST_LOG: RUST_LOG:
_default: info _default: info
...@@ -349,11 +280,6 @@ frontend: ...@@ -349,11 +280,6 @@ frontend:
enabled: true enabled: true
image: image:
_default: ghcr.io/blockscout/frontend:main _default: ghcr.io/blockscout/frontend:main
replicas:
app: 1
docker:
port: 80
targetPort: 3000
ingress: ingress:
enabled: true enabled: true
# annotations: # annotations:
...@@ -394,11 +320,6 @@ frontend: ...@@ -394,11 +320,6 @@ frontend:
_default: "0.3Gi" _default: "0.3Gi"
cpu: cpu:
_default: "0.2" _default: "0.2"
# node label
nodeSelector:
enabled: true
labels:
app: blockscout
environment: environment:
NEXT_PUBLIC_BLOCKSCOUT_VERSION: NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v5.1.0-beta _default: v5.1.0-beta
......
...@@ -258,126 +258,12 @@ geth: ...@@ -258,126 +258,12 @@ geth:
enabled: false enabled: false
files: files:
enabled: true enabled: true
# geth:
# enabled: true
# image:
# _default: ethereum/client-go:stable
# replicas:
# app: 1
# portHttp: 8545
# portWs: 8546
# portAuth: 8551
# command:
# - /bin/sh
# - -c
# - |-
# /root/init.sh --fakepow --dev --dev.period=1 --datadir=/root/.ethereum/devnet --keystore=/root/.ethereum/devnet/keystore --password=/root/password.txt --unlock=0 --unlock=1 --mine --miner.threads=1 --miner.etherbase=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 --ipcpath=/root/geth.ipc --http --http.vhosts=* --http.addr=0.0.0.0 --http.port=8545 --http.api=eth,net,web3,debug,txpool --ws --ws.origins=* --ws.addr=0.0.0.0 --ws.port=8546 --ws.api=eth,net,web3,debug,txpool --graphql --graphql.corsdomain=* --allow-insecure-unlock --rpc.allow-unprotected-txs --http.corsdomain=* --vmdebug --networkid=1337 --rpc.txfeecap=0
# # command: '["geth"]'
# # args: '["--goerli", "--datadir=/.ethereum", "--gcmode=archive", "--http", "--http.vhosts=*", "--http.addr=0.0.0.0", "--http.port=8545", "--http.api=eth,net,web3,debug,txpool", "--ws", "--ws.origins=*", "--ws.addr=0.0.0.0", "--ws.port=8546", "--ws.api=eth,net,web3,debug,txpool", "--allow-insecure-unlock", "--rpc.allow-unprotected-txs", "--http.corsdomain=*", "--vmdebug", "--rpc.txfeecap=0"]'
# environment: {}
# persistence:
# enabled: true
# mountPath: /geth
# storageClass: gp3-new
# storage: 1000Gi
# resources:
# limits:
# memory:
# _default: "8Gi"
# cpu:
# _default: "2"
# requests:
# memory:
# _default: "8Gi"
# cpu:
# _default: "2"
# # node label
# nodeSelector:
# enabled: true
# app: blockscout
# service:
# # ClusterIP, NodePort or LoadBalancer
# type: ClusterIP
# # enable ingress
# ingress:
# enabled: true
# host:
# _default: node.test.blockscout.aws-k8s.blockscout.com
# # enable https
# tls:
# enabled: false
# jwt:
# enabled: true
# mountPath: /geth/geth/jwtsecret
# files:
# enabled: false
# # enable client deploy (Prysm, lighthouse, nimbus, etc.)
# client:
# enabled: true
# image:
# _default: gcr.io/prysmaticlabs/prysm/beacon-chain:stable
# # command: '["sh","./root/init.sh"]'
# args: '["--goerli", "--datadir=/data", "--jwt-secret=/geth/geth/jwtsecret", "--rpc-host=0.0.0.0", "--grpc-gateway-host=0.0.0.0", "--monitoring-host=0.0.0.0", "--execution-endpoint=http://geth-svc:8545", "--checkpoint-sync-url=https://goerli.checkpoint-sync.ethdevops.io", "--genesis-beacon-api-url=https://goerli.checkpoint-sync.ethdevops.io"]'
# ports:
# port-tcp:
# number: 13000
# protocol: TCP
# port-udp:
# number: 12000
# protocol: UDP
# port-rpc:
# number: 4000
# protocol: TCP
# port-rpc-gtw:
# number: 3500
# protocol: TCP
# port-monitoring:
# number: 8080
# protocol: TCP
# persistence:
# enabled: false
# mountPath: path
# storageClass: gp3-new
# storage: 100Gi
# files:
# enabled: false
# list: {}
# service:
# # ClusterIP, NodePort or LoadBalancer
# type: ClusterIP
# # enable ingress
# ingress:
# enabled: false
# host:
# # enable https
# tls:
# enabled: false
# environment: {}
# resources:
# limits:
# memory:
# _default: "6Gi"
# cpu:
# _default: "3"
# requests:
# memory:
# _default: "6Gi"
# cpu:
# _default: "3"
# enable Smart-contract-verifier deploy # enable Smart-contract-verifier deploy
scVerifier: scVerifier:
enabled: true enabled: true
image: image:
_default: ghcr.io/blockscout/smart-contract-verifier:main _default: ghcr.io/blockscout/smart-contract-verifier:main
replicas:
app: 1
# docker:
# port: 80
# targetPort: 8043
# metricsPort: 6060
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress # enable ingress
ingress: ingress:
enabled: true enabled: true
...@@ -397,23 +283,6 @@ scVerifier: ...@@ -397,23 +283,6 @@ scVerifier:
_default: "0.05Gi" _default: "0.05Gi"
cpu: cpu:
_default: "0.01" _default: "0.01"
# node label
nodeSelector:
enabled: true
app: blockscout
# probes
livenessProbe:
enabled: true
# path: /health
readinessProbe:
enabled: true
# path: /health
# enable Horizontal Pod Autoscaler
hpa:
enabled: true
minReplicas: 1
maxReplicas: 10
cpuTarget: 90
environment: environment:
SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR: SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR:
_default: 0.0.0.0:8050 _default: 0.0.0.0:8050
...@@ -505,12 +374,6 @@ frontend: ...@@ -505,12 +374,6 @@ frontend:
_default: "0.1Gi" _default: "0.1Gi"
cpu: cpu:
_default: "0.1" _default: "0.1"
# node label
nodeSelector:
enabled: true
labels:
_default:
app: blockscout
environment: environment:
# ui config # ui config
NEXT_PUBLIC_FEATURED_NETWORKS: NEXT_PUBLIC_FEATURED_NETWORKS:
...@@ -573,7 +436,7 @@ frontend: ...@@ -573,7 +436,7 @@ frontend:
_default: https://rpc.ankr.com/eth_goerli _default: https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_HOMEPAGE_CHARTS: NEXT_PUBLIC_HOMEPAGE_CHARTS:
_default: "['daily_txs','coin_price','market_cup']" _default: "['daily_txs','coin_price','market_cup']"
NEXT_PUBLIC_API_SPEC_URL: NEXT_PUBLIC_API_SPEC_URL:
_default: https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml _default: https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_IS_TESTNET: NEXT_PUBLIC_IS_TESTNET:
_default: true _default: true
...@@ -5,15 +5,8 @@ frontend: ...@@ -5,15 +5,8 @@ frontend:
enabled: true enabled: true
image: image:
_default: ghcr.io/blockscout/frontend:main _default: ghcr.io/blockscout/frontend:main
replicas:
app: 1
docker:
port: 80
targetPort: 3000
ingress: ingress:
enabled: true enabled: true
# annotations:
# - 'nginx.ingress.kubernetes.io/use-regex: "true"'
host: host:
_default: frontend.test.blockscout.aws-k8s.blockscout.com _default: frontend.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
...@@ -57,10 +50,6 @@ frontend: ...@@ -57,10 +50,6 @@ frontend:
_default: "0.1Gi" _default: "0.1Gi"
cpu: cpu:
_default: "0.1" _default: "0.1"
# node label
nodeSelector:
enabled: true
app: blockscout
environment: environment:
NEXT_PUBLIC_BLOCKSCOUT_VERSION: NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v5.1.0-beta _default: v5.1.0-beta
......
global: global:
env: review env: review
# enable Blockscout deploy
blockscout:
app: blockscout
enabled: false
image:
_default: blockscout/blockscout:latest
replicas:
app: 1
docker:
port: 80
targetPort: 4000
# init container
init:
enabled: true
image:
_default: blockscout/blockscout:latest
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress
ingress:
enabled: true
annotations: {}
# - 'nginx.ingress.kubernetes.io/rewrite-target: /$2'
host:
_default: blockscout.test.blockscout.aws-k8s.blockscout.com
# enable https
#
tls:
enabled: true
path:
prefix:
# - "/poa/sokol(/|$)(.*)"
- "/"
# probes
livenessProbe:
enabled: true
path: /
readinessProbe:
enabled: true
path: /
resources:
limits:
memory:
_default: "1Gi"
cpu:
_default: "2"
requests:
memory:
_default: "1Gi"
cpu:
_default: "2"
# enable service to connect to RDS
rds:
enabled: false
endpoint:
_default: <endpoint>.<region>.rds.amazonaws.com
# node label
nodeSelector:
enabled: true
app: blockscout
# Blockscout environment variables
environment:
ENV:
_default: test
RESOURCE_MODE:
_default: account
PUBLIC:
_default: 'false'
PORT:
_default: 4000
PORT_PG:
_default: 5432
PORT_NETWORK_HTTP:
_default: 8545
PORT_NETWORK_WS:
_default: 8546
ETHEREUM_JSONRPC_VARIANT:
_default: geth
ETHEREUM_JSONRPC_TRACE_URL:
_default: http://geth-svc:8545
ETHEREUM_JSONRPC_HTTP_URL:
_default: http://geth-svc:8545
ETHEREUM_JSONRPC_WS_URL:
_default: ws://geth-svc:8546
COIN:
_default: DAI
MIX_ENV:
_default: prod
ECTO_USE_SSL:
_default: 'false'
RUST_VERIFICATION_SERVICE_URL:
_default: http://sc-verifier-svc:8043
ACCOUNT_ENABLED:
_default: 'true'
DISABLE_REALTIME_INDEXER:
_default: 'false'
SOCKET_ROOT:
_default: "/"
NETWORK_PATH:
_default: "/"
API_PATH:
_default: "/"
ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES:
_default: 'true'
API_BASE_PATH:
_default: "/"
APPS_MENU:
_default: 'true'
EXTERNAL_APPS:
_default: '[{"title": "Marketplace", "url": "/apps"}]'
JSON_RPC:
_default: https://sokol.poa.network
API_V2_ENABLED:
_default: 'true'
postgres:
enabled: false
image: postgres:13.8
port: 5432
command: '["docker-entrypoint.sh", "-c"]'
args: '["max_connections=300"]'
# persistence: true
resources:
limits:
memory:
_default: "4Gi"
cpu:
_default: "3"
requests:
memory:
_default: "4Gi"
cpu:
_default: "3"
environment:
POSTGRES_USER:
_default: 'postgres'
POSTGRES_HOST_AUTH_METHOD:
_default: 'trust'
# enable geth deploy
geth:
enabled: false
image:
_default: ethereum/client-go:stable
replicas:
app: 1
ports:
http:
number: 8545
protocol: TCP
ws:
number: 8546
protocol: TCP
auth:
number: 8551
protocol: TCP
command:
- /bin/sh
- -c
- |-
/root/init.sh --fakepow --dev --dev.period=1 --datadir=/root/.ethereum/devnet --keystore=/root/.ethereum/devnet/keystore --password=/root/password.txt --unlock=0 --unlock=1 --mine --miner.threads=1 --miner.etherbase=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 --ipcpath=/root/geth.ipc --http --http.vhosts=* --http.addr=0.0.0.0 --http.port=8545 --http.api=eth,net,web3,debug,txpool --ws --ws.origins=* --ws.addr=0.0.0.0 --ws.port=8546 --ws.api=eth,net,web3,debug,txpool --graphql --graphql.corsdomain=* --allow-insecure-unlock --rpc.allow-unprotected-txs --http.corsdomain=* --vmdebug --networkid=1337 --rpc.txfeecap=0
environment: {}
persistence:
enabled: false
resources:
limits:
memory:
_default: "2Gi"
cpu:
_default: "0.2"
requests:
memory:
_default: "2Gi"
cpu:
_default: "0.2"
# node label
nodeSelector:
enabled: true
app: blockscout
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress
ingress:
enabled: true
host:
_default: node.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: false
jwt:
enabled: false
files:
enabled: true
# enable Smart-contract-verifier deploy
scVerifier:
enabled: false
image:
_default: ghcr.io/blockscout/smart-contract-verifier:main
replicas:
app: 1
# docker:
# port: 80
# targetPort: 8043
# metricsPort: 6060
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress
ingress:
enabled: true
host:
_default: verifier.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: true
resources:
limits:
memory:
_default: "0.5Gi"
cpu:
_default: "0.25"
requests:
memory:
_default: "0.5Gi"
cpu:
_default: "0.25"
# node label
nodeSelector:
enabled: true
app: blockscout
# probes
livenessProbe:
enabled: true
# path: /health
readinessProbe:
enabled: true
# path: /health
# enable Horizontal Pod Autoscaler
hpa:
enabled: true
minReplicas: 1
maxReplicas: 10
cpuTarget: 90
environment:
SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR:
_default: 0.0.0.0:8050
SMART_CONTRACT_VERIFIER__SERVER__GRPC__ADDR:
_default: 0.0.0.0:8051
# SMART_CONTRACT_VERIFIER__SOLIDITY__ENABLED:
# _default: 'true'
SMART_CONTRACT_VERIFIER__SOLIDITY__COMPILERS_DIR:
_default: /tmp/solidity-compilers
SMART_CONTRACT_VERIFIER__SOLIDITY__REFRESH_VERSIONS_SCHEDULE:
_default: 0 0 * * * * *
# It depends on the OS you are running the service on
# SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL:
# _default: https://solc-bin.ethereum.org/linux-amd64/list.json
#SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://solc-bin.ethereum.org/macosx-amd64/list.json
#SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://solc-bin.ethereum.org/windows-amd64/list.json
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__REGION:
_default: ""
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ENDPOINT:
_default: https://storage.googleapis.com
SMART_CONTRACT_VERIFIER__SOURCIFY__ENABLED:
_default: 'true'
SMART_CONTRACT_VERIFIER__SOURCIFY__API_URL:
_default: https://sourcify.dev/server/
SMART_CONTRACT_VERIFIER__SOURCIFY__VERIFICATION_ATTEMPTS:
_default: 3
SMART_CONTRACT_VERIFIER__SOURCIFY__REQUEST_TIMEOUT:
_default: 10
SMART_CONTRACT_VERIFIER__METRICS__ENABLED:
_default: 'true'
SMART_CONTRACT_VERIFIER__METRICS__ADDR:
_default: 0.0.0.0:6060
SMART_CONTRACT_VERIFIER__METRICS__ROUTE:
_default: /metrics
SMART_CONTRACT_VERIFIER__JAEGER__ENABLED:
_default: 'false'
frontend: frontend:
app: blockscout app: blockscout
enabled: true enabled: true
image: image:
_default: ghcr.io/blockscout/frontend:main _default: ghcr.io/blockscout/frontend:main
replicas:
app: 1
docker:
port: 80
targetPort: 3000
ingress: ingress:
enabled: true enabled: true
# annotations:
# - 'nginx.ingress.kubernetes.io/use-regex: "true"'
host: host:
_default: frontend.test.blockscout.aws-k8s.blockscout.com _default: frontend.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
...@@ -341,10 +50,6 @@ frontend: ...@@ -341,10 +50,6 @@ frontend:
_default: "0.1Gi" _default: "0.1Gi"
cpu: cpu:
_default: "0.1" _default: "0.1"
# node label
nodeSelector:
enabled: true
app: blockscout
environment: environment:
NEXT_PUBLIC_BLOCKSCOUT_VERSION: NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v4.1.8-beta _default: v4.1.8-beta
...@@ -410,7 +115,7 @@ frontend: ...@@ -410,7 +115,7 @@ frontend:
_default: https://rpc.ankr.com/eth_goerli _default: https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_NETWORK_EXPLORERS: NEXT_PUBLIC_NETWORK_EXPLORERS:
_default: "[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/ethereum/goerli/transaction','address':'/ethereum/ethereum/goerli/address'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address'}}]" _default: "[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/ethereum/goerli/transaction','address':'/ethereum/ethereum/goerli/address'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address'}}]"
NEXT_PUBLIC_API_SPEC_URL: NEXT_PUBLIC_API_SPEC_URL:
_default: https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml _default: https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_IS_TESTNET: NEXT_PUBLIC_IS_TESTNET:
_default: true _default: true
\ No newline at end of file
import type { MetaMaskInpageProvider } from '@metamask/providers'; import type { MetaMaskInpageProvider } from '@metamask/providers';
type CPreferences = {
zone: string;
width: string;
height: string;
}
declare global { declare global {
interface Window { interface Window {
ethereum: MetaMaskInpageProvider; ethereum: MetaMaskInpageProvider;
coinzilla_display: Array<CPreferences>;
} }
} }
...@@ -6,7 +6,7 @@ import type { Params as ApiFetchParams } from './useApiFetch'; ...@@ -6,7 +6,7 @@ import type { Params as ApiFetchParams } from './useApiFetch';
import useApiFetch from './useApiFetch'; import useApiFetch from './useApiFetch';
export interface Params<R extends ResourceName, E = unknown> extends ApiFetchParams<R> { export interface Params<R extends ResourceName, E = unknown> extends ApiFetchParams<R> {
queryOptions?: Omit<UseQueryOptions<unknown, ResourceError<E>, ResourcePayload<R>>, 'queryKey' | 'queryFn'>; queryOptions?: Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, ResourcePayload<R>>, 'queryKey' | 'queryFn'>;
} }
export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) { export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) {
...@@ -23,9 +23,12 @@ export default function useApiQuery<R extends ResourceName, E = unknown>( ...@@ -23,9 +23,12 @@ export default function useApiQuery<R extends ResourceName, E = unknown>(
) { ) {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
return useQuery<unknown, ResourceError<E>, ResourcePayload<R>>( return useQuery<ResourcePayload<R>, ResourceError<E>, ResourcePayload<R>>(
getResourceKey(resource, { pathParams, queryParams }), getResourceKey(resource, { pathParams, queryParams }),
async() => { async() => {
return apiFetch<R, ResourcePayload<R>, ResourceError>(resource, { pathParams, queryParams, fetchParams }); // all errors and error typing is handled by react-query
// so error response will never go to the data
// that's why we are safe here to do type conversion "as Promise<ResourcePayload<R>>"
return apiFetch(resource, { pathParams, queryParams, fetchParams }) as Promise<ResourcePayload<R>>;
}, queryOptions); }, queryOptions);
} }
...@@ -12,7 +12,11 @@ const KEY_WORDS = { ...@@ -12,7 +12,11 @@ const KEY_WORDS = {
UNSAFE_EVAL: '\'unsafe-eval\'', UNSAFE_EVAL: '\'unsafe-eval\'',
}; };
const MAIN_DOMAINS = [ `*.${ appConfig.host }`, appConfig.host ]; const MAIN_DOMAINS = [
`*.${ appConfig.host }`,
appConfig.host,
appConfig.visualizeApi.endpoint,
].filter(Boolean);
// eslint-disable-next-line no-restricted-properties // eslint-disable-next-line no-restricted-properties
const REPORT_URI = process.env.SENTRY_CSP_REPORT_URI; const REPORT_URI = process.env.SENTRY_CSP_REPORT_URI;
...@@ -49,7 +53,9 @@ function makePolicyMap() { ...@@ -49,7 +53,9 @@ function makePolicyMap() {
return { return {
'default-src': [ 'default-src': [
KEY_WORDS.NONE, // KEY_WORDS.NONE,
// temporarily, see if warnings for "/_next/static/chunks/8861-ad3efb7f624b7bc1.js" go away
...MAIN_DOMAINS,
], ],
'connect-src': [ 'connect-src': [
...@@ -78,8 +84,16 @@ function makePolicyMap() { ...@@ -78,8 +84,16 @@ function makePolicyMap() {
'wss://*.bridge.walletconnect.org', 'wss://*.bridge.walletconnect.org',
'wss://www.walletlink.org', 'wss://www.walletlink.org',
// RPC providers
'https://infragrid.v.network',
// github (spec for api-docs page) // github (spec for api-docs page)
'raw.githubusercontent.com', 'raw.githubusercontent.com',
// google analytics
'https://www.googletagmanager.com',
'https://www.google-analytics.com',
'https://stats.g.doubleclick.net',
], ],
'script-src': [ 'script-src': [
...@@ -103,7 +117,13 @@ function makePolicyMap() { ...@@ -103,7 +117,13 @@ function makePolicyMap() {
// reCAPTCHA from google // reCAPTCHA from google
'https://www.google.com/recaptcha/api.js', 'https://www.google.com/recaptcha/api.js',
'https://www.gstatic.com', 'https://www.gstatic.com',
'https://translate.google.com',
'\'sha256-FDyPg8CqqIpPAfGVKx1YeKduyLs0ghNYWII21wL+7HM=\'', '\'sha256-FDyPg8CqqIpPAfGVKx1YeKduyLs0ghNYWII21wL+7HM=\'',
// google analytics
'\'sha256-NTmEg2dBnojQfTYrYJEmp3nG7V66756qPbQMCIBrctk=\'',
'https://www.googletagmanager.com',
'https://www.google-analytics.com',
], ],
'style-src': [ 'style-src': [
...@@ -113,6 +133,9 @@ function makePolicyMap() { ...@@ -113,6 +133,9 @@ function makePolicyMap() {
// google fonts // google fonts
'fonts.googleapis.com', 'fonts.googleapis.com',
// reCAPTCHA from google
'https://www.gstatic.com',
// yes, it is unsafe as it stands, but // yes, it is unsafe as it stands, but
// - we cannot use hashes because all styles are generated dynamically // - we cannot use hashes because all styles are generated dynamically
// - we cannot use nonces since we are not following along SSR path // - we cannot use nonces since we are not following along SSR path
...@@ -151,6 +174,13 @@ function makePolicyMap() { ...@@ -151,6 +174,13 @@ function makePolicyMap() {
// token's media // token's media
'ipfs.io', 'ipfs.io',
// reCAPTCHA from google
'https://translate.google.com',
'https://www.gstatic.com',
// google analytics
'https://www.google-analytics.com',
], ],
'font-src': [ 'font-src': [
......
export default function getErrorStatusCode(error: Error | undefined): number | undefined {
return (
error && 'cause' in error &&
typeof error.cause === 'object' && error.cause !== null &&
'status' in error.cause && typeof error.cause.status === 'number' &&
error.cause.status
) ||
undefined;
}
export default function formatNumberToMetricPrefix(number: number) {
return Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 3,
}).format(number);
}
export function shortenNumberWithLetter(
x: number,
params?: {
unitSeparator: string;
},
_options?: Intl.NumberFormatOptions,
) {
const options = _options || { maximumFractionDigits: 2 };
const unitSeparator = params?.unitSeparator || '';
if (x > 1_000_000_000) {
return (x / 1_000_000_000).toLocaleString('en', options) + unitSeparator + 'B';
}
if (x > 1_000_000) {
return (x / 1_000_000).toLocaleString('en', options) + unitSeparator + 'M';
}
if (x > 1_000) {
return (x / 1_000).toLocaleString('en', options) + unitSeparator + 'K';
}
return x.toLocaleString('en', options);
}
export default function getFilterValue<FilterType>(filterValues: ReadonlyArray<FilterType>, val: string | Array<string> | undefined) { export default function getFilterValue<FilterType>(filterValues: ReadonlyArray<FilterType>, val: string | Array<string> | undefined): FilterType | undefined {
if (typeof val === 'string' && filterValues.includes(val as unknown as FilterType)) { if (typeof val === 'string' && filterValues.includes(val as FilterType)) {
return val as unknown as FilterType; return val as FilterType;
} }
} }
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import type { Unit } from 'types/unit';
import { WEI, GWEI } from 'lib/consts'; import { WEI, GWEI } from 'lib/consts';
export default function getValueWithUnit(value: string | number, unit: 'wei' | 'gwei' | 'ether' = 'wei') { export default function getValueWithUnit(value: string | number, unit: Unit = 'wei') {
let unitBn: BigNumber.Value; let unitBn: BigNumber.Value;
switch (unit) { switch (unit) {
case 'wei': case 'wei':
......
...@@ -2,7 +2,6 @@ import React from 'react'; ...@@ -2,7 +2,6 @@ import React from 'react';
import type { Address } from 'types/api/address'; import type { Address } from 'types/api/address';
import notEmpty from 'lib/notEmpty';
import ContractCode from 'ui/address/contract/ContractCode'; import ContractCode from 'ui/address/contract/ContractCode';
import ContractRead from 'ui/address/contract/ContractRead'; import ContractRead from 'ui/address/contract/ContractRead';
import ContractWrite from 'ui/address/contract/ContractWrite'; import ContractWrite from 'ui/address/contract/ContractWrite';
...@@ -33,6 +32,6 @@ export default function useContractTabs(data: Address | undefined) { ...@@ -33,6 +32,6 @@ export default function useContractTabs(data: Address | undefined) {
data?.has_custom_methods_write ? data?.has_custom_methods_write ?
{ id: 'write_custom_methods', title: 'Write custom', component: <ContractWrite addressHash={ data?.hash } isCustomAbi/> } : { id: 'write_custom_methods', title: 'Write custom', component: <ContractWrite addressHash={ data?.hash } isCustomAbi/> } :
undefined, undefined,
].filter(notEmpty); ].filter(Boolean);
}, [ data ]); }, [ data ]);
} }
...@@ -20,10 +20,9 @@ import topAccountsIcon from 'icons/top-accounts.svg'; ...@@ -20,10 +20,9 @@ import topAccountsIcon from 'icons/top-accounts.svg';
import transactionsIcon from 'icons/transactions.svg'; import transactionsIcon from 'icons/transactions.svg';
// import depositsIcon from 'icons/arrows/south-east.svg'; // import depositsIcon from 'icons/arrows/south-east.svg';
// import txnBatchIcon from 'icons/txn_batches.svg'; // import txnBatchIcon from 'icons/txn_batches.svg';
// import verifiedIcon from 'icons/verified.svg'; import verifiedIcon from 'icons/verified.svg';
import watchlistIcon from 'icons/watchlist.svg'; import watchlistIcon from 'icons/watchlist.svg';
// import { rightLineArrow } from 'lib/html-entities'; // import { rightLineArrow } from 'lib/html-entities';
import notEmpty from 'lib/notEmpty';
export interface NavItem { export interface NavItem {
text: string; text: string;
...@@ -61,9 +60,9 @@ export default function useNavItems(): ReturnType { ...@@ -61,9 +60,9 @@ export default function useNavItems(): ReturnType {
{ text: 'Top accounts', nextRoute: { pathname: '/accounts' as const }, icon: topAccountsIcon, isActive: pathname === '/accounts', isNewUi: true }; { text: 'Top accounts', nextRoute: { pathname: '/accounts' as const }, icon: topAccountsIcon, isActive: pathname === '/accounts', isNewUi: true };
const blocks = { text: 'Blocks', nextRoute: { pathname: '/blocks' as const }, icon: blocksIcon, isActive: pathname.startsWith('/block'), isNewUi: true }; const blocks = { text: 'Blocks', nextRoute: { pathname: '/blocks' as const }, icon: blocksIcon, isActive: pathname.startsWith('/block'), isNewUi: true };
const txs = { text: 'Transactions', nextRoute: { pathname: '/txs' as const }, icon: transactionsIcon, isActive: pathname.startsWith('/tx'), isNewUi: true }; const txs = { text: 'Transactions', nextRoute: { pathname: '/txs' as const }, icon: transactionsIcon, isActive: pathname.startsWith('/tx'), isNewUi: true };
// const verifiedContracts = const verifiedContracts =
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
// { text: 'Verified contracts', nextRoute: { pathname: '/verified_contracts' as const }, icon: verifiedIcon, isActive: pathname === '/verified_contracts', isNewUi: false }, { text: 'Verified contracts', nextRoute: { pathname: '/verified-contracts' as const }, icon: verifiedIcon, isActive: false, isNewUi: false };
if (appConfig.L2.isL2Network) { if (appConfig.L2.isL2Network) {
blockchainNavItems = [ blockchainNavItems = [
...@@ -83,17 +82,15 @@ export default function useNavItems(): ReturnType { ...@@ -83,17 +82,15 @@ export default function useNavItems(): ReturnType {
], ],
[ [
topAccounts, topAccounts,
verifiedContracts,
], ],
// [
// verifiedContracts
// ],
]; ];
} else { } else {
blockchainNavItems = [ blockchainNavItems = [
txs, txs,
blocks, blocks,
topAccounts, topAccounts,
// verifiedContracts, verifiedContracts,
]; ];
} }
...@@ -106,7 +103,9 @@ export default function useNavItems(): ReturnType { ...@@ -106,7 +103,9 @@ export default function useNavItems(): ReturnType {
isActive: pathname === '/api-docs', isActive: pathname === '/api-docs',
isNewUi: true, isNewUi: true,
} : null, } : null,
].filter(notEmpty); // FIXME: need icon for this item
{ text: 'GraphQL', nextRoute: { pathname: '/graphiql' as const }, icon: topAccountsIcon, isActive: false, isNewUi: false },
].filter(Boolean);
const mainNavItems = [ const mainNavItems = [
{ {
...@@ -125,7 +124,7 @@ export default function useNavItems(): ReturnType { ...@@ -125,7 +124,7 @@ export default function useNavItems(): ReturnType {
// at this stage custom menu items is under development, we will implement it later // at this stage custom menu items is under development, we will implement it later
otherNavItems.length > 0 ? otherNavItems.length > 0 ?
{ text: 'Other', icon: gearIcon, isActive: otherNavItems.some(item => item.isActive), subItems: otherNavItems } : null, { text: 'Other', icon: gearIcon, isActive: otherNavItems.some(item => item.isActive), subItems: otherNavItems } : null,
].filter(notEmpty) as Array<NavItem | NavGroupItem>; ].filter(Boolean);
const accountNavItems = [ const accountNavItems = [
{ {
......
// https://unicode-table.com // https://unicode-table.com
export const asymp = String.fromCharCode(8776); // приблизительно export const asymp = String.fromCharCode(8776); // ~
export const hellip = String.fromCharCode(8230); // многоточие export const hellip = String.fromCharCode(8230); //
export const nbsp = String.fromCharCode(160); // неразрывный пробел export const nbsp = String.fromCharCode(160); // no-break Space
export const thinsp = String.fromCharCode(8201); // короткий пробел export const thinsp = String.fromCharCode(8201); // thin Space
export const space = String.fromCharCode(32); // обычный пробел export const space = String.fromCharCode(32); // space
export const nbdash = String.fromCharCode(8209); // неразрывное тире export const nbdash = String.fromCharCode(8209); // non-breaking hyphen
export const mdash = String.fromCharCode(8212); // длинное тире export const mdash = String.fromCharCode(8212); // em dash
export const ndash = String.fromCharCode(8211); // среднее тире export const ndash = String.fromCharCode(8211); // en dash
export const laquo = String.fromCharCode(171); // кавычки-ёлочки (левые) export const laquo = String.fromCharCode(171); // «
export const raquo = String.fromCharCode(187); // кавычки-ёлочки (правые) export const raquo = String.fromCharCode(187); // »
export const middot = String.fromCharCode(183); // точка по центру строки (в вертикальном смысле) export const middot = String.fromCharCode(183); // ·
export const blackCircle = String.fromCharCode(9679); // жирная точка по центру строки (в вертикальном смысле) export const blackCircle = String.fromCharCode(9679); //
export const blackRightwardsArrowhead = String.fromCharCode(10148); // ➤ export const blackRightwardsArrowhead = String.fromCharCode(10148); // ➤
export const degree = String.fromCharCode(176); // градус ° export const degree = String.fromCharCode(176); // °
export const times = String.fromCharCode(215); // мультипликатор × export const times = String.fromCharCode(215); // ×
export const disk = String.fromCharCode(8226); // диск export const disk = String.fromCharCode(8226); // •
export const minus = String.fromCharCode(8722); // минус export const minus = String.fromCharCode(8722); // −
export const leftLineArrow = String.fromCharCode(8592); // стрелка export const leftLineArrow = String.fromCharCode(8592); // ←
export const rightLineArrow = String.fromCharCode(8594); // стрелка export const rightLineArrow = String.fromCharCode(8594); // →
export const apos = String.fromCharCode(39); // апостроф ' export const apos = String.fromCharCode(39); // apostrophe '
export default function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
return value !== null && value !== undefined;
}
import type { Channel } from 'phoenix'; import type { Channel } from 'phoenix';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import notEmpty from 'lib/notEmpty';
import { useSocket } from './context'; import { useSocket } from './context';
const CHANNEL_REGISTRY: Record<string, Channel> = {}; const CHANNEL_REGISTRY: Record<string, Channel> = {};
...@@ -27,7 +25,7 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on ...@@ -27,7 +25,7 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on
useEffect(() => { useEffect(() => {
const cleanUpRefs = () => { const cleanUpRefs = () => {
const refs = [ onCloseRef.current, onErrorRef.current ].filter(notEmpty); const refs = [ onCloseRef.current, onErrorRef.current ].filter(Boolean);
refs.length > 0 && socket?.off(refs); refs.length > 0 && socket?.off(refs);
}; };
......
...@@ -49,7 +49,7 @@ export const base: Block = { ...@@ -49,7 +49,7 @@ export const base: Block = {
uncles_hashes: [], uncles_hashes: [],
}; };
export const genesis = { export const genesis: Block = {
base_fee_per_gas: null, base_fee_per_gas: null,
burnt_fees: null, burnt_fees: null,
burnt_fees_percentage: null, burnt_fees_percentage: null,
......
...@@ -78,6 +78,7 @@ ...@@ -78,6 +78,7 @@
"@playwright/experimental-ct-react": "1.31.0", "@playwright/experimental-ct-react": "1.31.0",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@total-typescript/ts-reset": "^0.3.7",
"@types/d3": "^7.4.0", "@types/d3": "^7.4.0",
"@types/dom-to-image": "^2.6.4", "@types/dom-to-image": "^2.6.4",
"@types/jest": "^29.2.0", "@types/jest": "^29.2.0",
...@@ -87,16 +88,17 @@ ...@@ -87,16 +88,17 @@
"@types/qrcode": "^1.5.0", "@types/qrcode": "^1.5.0",
"@types/react": "18.0.9", "@types/react": "18.0.9",
"@types/react-dom": "18.0.5", "@types/react-dom": "18.0.5",
"@types/swagger-ui-react": "^4.11.0",
"@types/react-google-recaptcha": "^2.1.5", "@types/react-google-recaptcha": "^2.1.5",
"@types/swagger-ui-react": "^4.11.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.27.0", "@typescript-eslint/eslint-plugin": "^5.53.0",
"dotenv-cli": "^6.0.0", "dotenv-cli": "^6.0.0",
"eslint": "^8.32.0", "eslint": "^8.32.0",
"eslint-config-next": "^12.3.0", "eslint-config-next": "^12.3.0",
"eslint-plugin-es5": "^1.5.0", "eslint-plugin-es5": "^1.5.0",
"eslint-plugin-import-helpers": "^1.2.1", "eslint-plugin-import-helpers": "^1.2.1",
"eslint-plugin-jest": "^27.1.6", "eslint-plugin-jest": "^27.1.6",
"eslint-plugin-no-cyrillic-string": "^1.0.5",
"eslint-plugin-playwright": "^0.11.2", "eslint-plugin-playwright": "^0.11.2",
"eslint-plugin-regexp": "^1.7.0", "eslint-plugin-regexp": "^1.7.0",
"husky": "^8.0.0", "husky": "^8.0.0",
...@@ -109,7 +111,7 @@ ...@@ -109,7 +111,7 @@
"svgo": "^2.8.0", "svgo": "^2.8.0",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "4.8.2", "typescript": "4.9.5",
"vite-plugin-svgr": "^2.2.2", "vite-plugin-svgr": "^2.2.2",
"vite-tsconfig-paths": "^3.5.2", "vite-tsconfig-paths": "^3.5.2",
"ws": "^8.11.0" "ws": "^8.11.0"
......
...@@ -9,11 +9,13 @@ import type { ResourceError } from 'lib/api/resources'; ...@@ -9,11 +9,13 @@ import type { ResourceError } from 'lib/api/resources';
import { AppContextProvider } from 'lib/appContext'; import { AppContextProvider } from 'lib/appContext';
import { Chakra } from 'lib/Chakra'; import { Chakra } from 'lib/Chakra';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import getErrorStatusCode from 'lib/errors/getErrorStatusCode';
import useConfigSentry from 'lib/hooks/useConfigSentry'; import useConfigSentry from 'lib/hooks/useConfigSentry';
import { SocketProvider } from 'lib/socket/context'; import { SocketProvider } from 'lib/socket/context';
import theme from 'theme'; import theme from 'theme';
import AppError from 'ui/shared/AppError/AppError'; import AppError from 'ui/shared/AppError/AppError';
import ErrorBoundary from 'ui/shared/ErrorBoundary'; import ErrorBoundary from 'ui/shared/ErrorBoundary';
import GoogleAnalytics from 'ui/shared/GoogleAnalytics';
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
useConfigSentry(); useConfigSentry();
...@@ -36,12 +38,11 @@ function MyApp({ Component, pageProps }: AppProps) { ...@@ -36,12 +38,11 @@ function MyApp({ Component, pageProps }: AppProps) {
})); }));
const renderErrorScreen = React.useCallback((error?: Error) => { const renderErrorScreen = React.useCallback((error?: Error) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const statusCode = getErrorStatusCode(error);
const statusCode = (error?.cause as any)?.status || 500;
return ( return (
<AppError <AppError
statusCode={ statusCode } statusCode={ statusCode || 500 }
height="100vh" height="100vh"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
...@@ -68,6 +69,7 @@ function MyApp({ Component, pageProps }: AppProps) { ...@@ -68,6 +69,7 @@ function MyApp({ Component, pageProps }: AppProps) {
</SocketProvider> </SocketProvider>
</ScrollDirectionProvider> </ScrollDirectionProvider>
<ReactQueryDevtools/> <ReactQueryDevtools/>
<GoogleAnalytics/>
</QueryClientProvider> </QueryClientProvider>
</AppContextProvider> </AppContextProvider>
</ErrorBoundary> </ErrorBoundary>
......
import type { NextPage } from 'next';
const GraphQLPage: NextPage = () => {
return null;
};
export default GraphQLPage;
export async function getServerSideProps() {
return {
notFound: true,
};
}
import type { NextPage } from 'next';
const VerifiedContractsPage: NextPage = () => {
return null;
};
export default VerifiedContractsPage;
export async function getServerSideProps() {
return {
notFound: true,
};
}
...@@ -35,7 +35,7 @@ export const joinChannel = async(socket: WebSocket, channelName: string) => { ...@@ -35,7 +35,7 @@ export const joinChannel = async(socket: WebSocket, channelName: string) => {
return new Promise<[string, string, string]>((resolve, reject) => { return new Promise<[string, string, string]>((resolve, reject) => {
socket.on('message', (msg) => { socket.on('message', (msg) => {
try { try {
const payload: Array<string> = JSON.parse(msg.toString()); const payload = JSON.parse(msg.toString()) as Array<string>;
if (channelName === payload[2] && payload[3] === 'phx_join') { if (channelName === payload[2] && payload[3] === 'phx_join') {
socket.send(JSON.stringify([ socket.send(JSON.stringify([
......
import '@total-typescript/ts-reset';
import type { AddressParam } from './addressParams';
export type TransactionReward = {
types: Array<string>;
emission_reward: string;
block_hash: string;
from: AddressParam;
to: AddressParam;
}
...@@ -25,6 +25,7 @@ declare module "nextjs-routes" { ...@@ -25,6 +25,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/blocks"> | StaticRoute<"/blocks">
| StaticRoute<"/csv-export"> | StaticRoute<"/csv-export">
| StaticRoute<"/graph"> | StaticRoute<"/graph">
| StaticRoute<"/graphiql">
| StaticRoute<"/"> | StaticRoute<"/">
| StaticRoute<"/login"> | StaticRoute<"/login">
| StaticRoute<"/search-results"> | StaticRoute<"/search-results">
...@@ -34,6 +35,7 @@ declare module "nextjs-routes" { ...@@ -34,6 +35,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/tokens"> | StaticRoute<"/tokens">
| DynamicRoute<"/tx/[hash]", { "hash": string }> | DynamicRoute<"/tx/[hash]", { "hash": string }>
| StaticRoute<"/txs"> | StaticRoute<"/txs">
| StaticRoute<"/verified-contracts">
| StaticRoute<"/visualize/sol2uml">; | StaticRoute<"/visualize/sol2uml">;
interface StaticRoute<Pathname> { interface StaticRoute<Pathname> {
......
...@@ -137,7 +137,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit, ...@@ -137,7 +137,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
{ isWrite ? 'Write' : 'Query' } { isWrite ? 'Write' : 'Query' }
</Button> </Button>
</chakra.form> </chakra.form>
{ 'outputs' in data && data.outputs.length > 0 && ( { 'outputs' in data && !isWrite && data.outputs.length > 0 && (
<Flex mt={ 3 }> <Flex mt={ 3 }>
<Icon as={ arrowIcon } boxSize={ 5 } mr={ 1 }/> <Icon as={ arrowIcon } boxSize={ 5 } mr={ 1 }/>
<Text>{ data.outputs.map(({ type }) => type).join(', ') }</Text> <Text>{ data.outputs.map(({ type }) => type).join(', ') }</Text>
......
...@@ -7,7 +7,9 @@ import type { SmartContractMethodOutput } from 'types/api/contract'; ...@@ -7,7 +7,9 @@ import type { SmartContractMethodOutput } from 'types/api/contract';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import { WEI } from 'lib/consts'; import { WEI } from 'lib/consts';
import Address from 'ui/shared/address/Address';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
interface Props { interface Props {
data: SmartContractMethodOutput; data: SmartContractMethodOutput;
...@@ -34,7 +36,12 @@ const ContractMethodStatic = ({ data }: Props) => { ...@@ -34,7 +36,12 @@ const ContractMethodStatic = ({ data }: Props) => {
const content = (() => { const content = (() => {
if (data.type === 'address' && data.value) { if (data.type === 'address' && data.value) {
return <AddressLink type="address" hash={ data.value }/>; return (
<Address>
<AddressLink type="address" hash={ data.value }/>
<CopyToClipboard text={ data.value }/>
</Address>
);
} }
return <chakra.span wordBreak="break-all">({ data.type }): { value }</chakra.span>; return <chakra.span wordBreak="break-all">({ data.type }): { value }</chakra.span>;
......
...@@ -7,7 +7,6 @@ import type { AppItemPreview } from 'types/client/apps'; ...@@ -7,7 +7,6 @@ import type { AppItemPreview } from 'types/client/apps';
import northEastIcon from 'icons/arrows/north-east.svg'; import northEastIcon from 'icons/arrows/north-east.svg';
import starFilledIcon from 'icons/star_filled.svg'; import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg'; import starOutlineIcon from 'icons/star_outline.svg';
import notEmpty from 'lib/notEmpty';
import AppCardLink from './AppCardLink'; import AppCardLink from './AppCardLink';
import { APP_CATEGORIES } from './constants'; import { APP_CATEGORIES } from './constants';
...@@ -30,7 +29,7 @@ const AppCard = ({ ...@@ -30,7 +29,7 @@ const AppCard = ({
isFavorite, isFavorite,
onFavoriteClick, onFavoriteClick,
}: Props) => { }: Props) => {
const categoriesLabel = categories.map(c => APP_CATEGORIES[c]).filter(notEmpty).join(', '); const categoriesLabel = categories.map(c => APP_CATEGORIES[c]).filter(Boolean).join(', ');
const handleInfoClick = useCallback((event: MouseEvent) => { const handleInfoClick = useCallback((event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
......
...@@ -15,7 +15,6 @@ import starFilledIcon from 'icons/star_filled.svg'; ...@@ -15,7 +15,6 @@ import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg'; import starOutlineIcon from 'icons/star_outline.svg';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import notEmpty from 'lib/notEmpty';
import AppModalLink from './AppModalLink'; import AppModalLink from './AppModalLink';
import { APP_CATEGORIES } from './constants'; import { APP_CATEGORIES } from './constants';
...@@ -60,7 +59,7 @@ const AppModal = ({ ...@@ -60,7 +59,7 @@ const AppModal = ({
icon: ghIcon, icon: ghIcon,
url: github, url: github,
} : null, } : null,
].filter(notEmpty); ].filter(Boolean);
const handleFavoriteClick = useCallback(() => { const handleFavoriteClick = useCallback(() => {
onFavoriteClick(id, isFavorite); onFavoriteClick(id, isFavorite);
......
...@@ -9,7 +9,7 @@ const favoriteAppsLocalStorageKey = 'favoriteApps'; ...@@ -9,7 +9,7 @@ const favoriteAppsLocalStorageKey = 'favoriteApps';
function getFavoriteApps() { function getFavoriteApps() {
try { try {
return JSON.parse(localStorage.getItem(favoriteAppsLocalStorageKey) || '[]'); return JSON.parse(localStorage.getItem(favoriteAppsLocalStorageKey) || '[]') as Array<string>;
} catch (e) { } catch (e) {
return []; return [];
} }
......
import { test, expect } from '@playwright/experimental-ct-react'; import { test, expect } from '@playwright/experimental-ct-react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { Block } from 'types/api/block';
import type { ResourceError } from 'lib/api/resources';
import * as blockMock from 'mocks/blocks/block'; import * as blockMock from 'mocks/blocks/block';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import BlockDetails from './BlockDetails'; import BlockDetails from './BlockDetails';
const API_URL = buildApiUrl('block', { height: '1' });
const hooksConfig = { const hooksConfig = {
router: { router: {
query: { height: '1' }, query: { height: '1' },
...@@ -15,14 +17,14 @@ const hooksConfig = { ...@@ -15,14 +17,14 @@ const hooksConfig = {
}; };
test('regular block +@mobile +@dark-mode', async({ mount, page }) => { test('regular block +@mobile +@dark-mode', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({ const query = {
status: 200, data: blockMock.base,
body: JSON.stringify(blockMock.base), isLoading: false,
})); } as UseQueryResult<Block, ResourceError>;
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<BlockDetails/> <BlockDetails query={ query }/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
...@@ -33,14 +35,14 @@ test('regular block +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -33,14 +35,14 @@ test('regular block +@mobile +@dark-mode', async({ mount, page }) => {
}); });
test('genesis block', async({ mount, page }) => { test('genesis block', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({ const query = {
status: 200, data: blockMock.genesis,
body: JSON.stringify(blockMock.genesis), isLoading: false,
})); } as UseQueryResult<Block, ResourceError>;
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<BlockDetails/> <BlockDetails query={ query }/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
......
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react'; import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
...@@ -6,10 +7,12 @@ import { route } from 'nextjs-routes'; ...@@ -6,10 +7,12 @@ import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import { scroller, Element } from 'react-scroll'; import { scroller, Element } from 'react-scroll';
import type { Block } from 'types/api/block';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import clockIcon from 'icons/clock.svg'; import clockIcon from 'icons/clock.svg';
import flameIcon from 'icons/flame.svg'; import flameIcon from 'icons/flame.svg';
import useApiQuery from 'lib/api/useApiQuery'; import type { ResourceError } from 'lib/api/resources';
import getBlockReward from 'lib/block/getBlockReward'; import getBlockReward from 'lib/block/getBlockReward';
import { WEI, WEI_IN_GWEI, ZERO } from 'lib/consts'; import { WEI, WEI_IN_GWEI, ZERO } from 'lib/consts';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
...@@ -28,15 +31,16 @@ import PrevNext from 'ui/shared/PrevNext'; ...@@ -28,15 +31,16 @@ import PrevNext from 'ui/shared/PrevNext';
import TextSeparator from 'ui/shared/TextSeparator'; import TextSeparator from 'ui/shared/TextSeparator';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
const BlockDetails = () => { interface Props {
query: UseQueryResult<Block, ResourceError>;
}
const BlockDetails = ({ query }: Props) => {
const [ isExpanded, setIsExpanded ] = React.useState(false); const [ isExpanded, setIsExpanded ] = React.useState(false);
const router = useRouter(); const router = useRouter();
const height = getQueryParamString(router.query.height); const heightOrHash = getQueryParamString(router.query.height);
const { data, isLoading, isError, error } = useApiQuery('block', { const { data, isLoading, isError, error } = query;
pathParams: { height },
queryOptions: { enabled: Boolean(height) },
});
const handleCutClick = React.useCallback(() => { const handleCutClick = React.useCallback(() => {
setIsExpanded((flag) => !flag); setIsExpanded((flag) => !flag);
...@@ -47,11 +51,15 @@ const BlockDetails = () => { ...@@ -47,11 +51,15 @@ const BlockDetails = () => {
}, []); }, []);
const handlePrevNextClick = React.useCallback((direction: 'prev' | 'next') => { const handlePrevNextClick = React.useCallback((direction: 'prev' | 'next') => {
if (!data) {
return;
}
const increment = direction === 'next' ? +1 : -1; const increment = direction === 'next' ? +1 : -1;
const nextId = String(Number(height) + increment); const nextId = String(data.height + increment);
router.push({ pathname: '/block/[height]', query: { height: nextId } }, undefined); router.push({ pathname: '/block/[height]', query: { height: nextId } }, undefined);
}, [ height, router ]); }, [ data, router ]);
if (isLoading) { if (isLoading) {
return <BlockDetailsSkeleton/>; return <BlockDetailsSkeleton/>;
...@@ -85,7 +93,7 @@ const BlockDetails = () => { ...@@ -85,7 +93,7 @@ const BlockDetails = () => {
return ( return (
<Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"> <Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden">
<DetailsInfoItem <DetailsInfoItem
title="Block height" title={ `${ data.type === 'reorg' ? 'Reorg' : 'Block' } height` }
hint="The block height of a particular block is defined as the number of blocks preceding it in the blockchain" hint="The block height of a particular block is defined as the number of blocks preceding it in the blockchain"
> >
{ data.height } { data.height }
...@@ -117,7 +125,7 @@ const BlockDetails = () => { ...@@ -117,7 +125,7 @@ const BlockDetails = () => {
title="Transactions" title="Transactions"
hint="The number of transactions in the block" hint="The number of transactions in the block"
> >
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height, tab: 'txs' } }) }> <LinkInternal href={ route({ pathname: '/block/[height]', query: { height: heightOrHash, tab: 'txs' } }) }>
{ data.tx_count } transaction{ data.tx_count === 1 ? '' : 's' } { data.tx_count } transaction{ data.tx_count === 1 ? '' : 's' }
</LinkInternal> </LinkInternal>
</DetailsInfoItem> </DetailsInfoItem>
......
...@@ -36,7 +36,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => { ...@@ -36,7 +36,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
{ isPending && <Spinner size="sm"/> } { isPending && <Spinner size="sm"/> }
<LinkInternal <LinkInternal
fontWeight={ 600 } fontWeight={ 600 }
href={ route({ pathname: '/block/[height]', query: { height: String(data.height) } }) } href={ route({ pathname: '/block/[height]', query: { height: data.type === 'reorg' ? String(data.hash) : String(data.height) } }) }
> >
{ data.height } { data.height }
</LinkInternal> </LinkInternal>
......
...@@ -41,7 +41,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => { ...@@ -41,7 +41,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
<Tooltip isDisabled={ data.type !== 'reorg' } label="Chain reorganizations"> <Tooltip isDisabled={ data.type !== 'reorg' } label="Chain reorganizations">
<LinkInternal <LinkInternal
fontWeight={ 600 } fontWeight={ 600 }
href={ route({ pathname: '/block/[height]', query: { height: String(data.height) } }) } href={ route({ pathname: '/block/[height]', query: { height: data.type === 'reorg' ? String(data.hash) : String(data.height) } }) }
> >
{ data.height } { data.height }
</LinkInternal> </LinkInternal>
......
...@@ -84,10 +84,9 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -84,10 +84,9 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
status: 'success', status: 'success',
variant: 'subtle', variant: 'subtle',
isClosable: true, isClosable: true,
onCloseComplete: () => {
router.push({ pathname: '/address/[hash]', query: { hash, tab: 'contract' } }, undefined, { shallow: true });
},
}); });
router.push({ pathname: '/address/[hash]', query: { hash, tab: 'contract' } }, undefined, { shallow: false });
}, [ hash, router, setError, toast ]); }, [ hash, router, setError, toast ]);
const handleSocketError = React.useCallback(() => { const handleSocketError = React.useCallback(() => {
......
...@@ -12,7 +12,7 @@ import ContractVerificationFieldOptimization from '../fields/ContractVerificatio ...@@ -12,7 +12,7 @@ import ContractVerificationFieldOptimization from '../fields/ContractVerificatio
const ContractVerificationFlattenSourceCode = () => { const ContractVerificationFlattenSourceCode = () => {
return ( return (
<ContractVerificationMethod title="Contract verification via Solidity (fattened source code)"> <ContractVerificationMethod title="Contract verification via Solidity (flattened source code)">
<ContractVerificationFieldName/> <ContractVerificationFieldName/>
<ContractVerificationFieldIsYul/> <ContractVerificationFieldIsYul/>
<ContractVerificationFieldCompiler/> <ContractVerificationFieldCompiler/>
......
...@@ -101,7 +101,7 @@ export const DEFAULT_VALUES = { ...@@ -101,7 +101,7 @@ export const DEFAULT_VALUES = {
}; };
export function isValidVerificationMethod(method?: string): method is SmartContractVerificationMethod { export function isValidVerificationMethod(method?: string): method is SmartContractVerificationMethod {
return method && SUPPORTED_VERIFICATION_METHODS.includes(method as SmartContractVerificationMethod) ? true : false; return method && SUPPORTED_VERIFICATION_METHODS.includes(method) ? true : false;
} }
export function sortVerificationMethods(methodA: SmartContractVerificationMethod, methodB: SmartContractVerificationMethod) { export function sortVerificationMethods(methodA: SmartContractVerificationMethod, methodB: SmartContractVerificationMethod) {
......
import type { TooltipProps } from '@chakra-ui/react'; import type { SystemStyleObject, TooltipProps } from '@chakra-ui/react';
import { Flex, Icon, Text, useColorModeValue, chakra, LightMode } from '@chakra-ui/react'; import { Flex, Icon, Text, useColorModeValue, chakra, LightMode } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -25,13 +25,13 @@ const TOOLTIP_PROPS: Partial<TooltipProps> = { ...@@ -25,13 +25,13 @@ const TOOLTIP_PROPS: Partial<TooltipProps> = {
}; };
const StatsItem = ({ icon, title, value, className, tooltipLabel, url }: Props) => { const StatsItem = ({ icon, title, value, className, tooltipLabel, url }: Props) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const sxContainer: SystemStyleObject = {
const sxContainer = {} as any; [`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`]: { flexDirection: 'column' },
sxContainer[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`] = { flexDirection: 'column' }; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any const sxText: SystemStyleObject = {
const sxText = {} as any; [`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`]: { alignItems: 'center' },
sxText[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`] = { alignItems: 'center' }; };
const infoColor = useColorModeValue('gray.600', 'gray.400'); const infoColor = useColorModeValue('gray.600', 'gray.400');
......
...@@ -6,14 +6,13 @@ import type { TChainIndicator } from '../types'; ...@@ -6,14 +6,13 @@ import type { TChainIndicator } from '../types';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import globeIcon from 'icons/globe.svg'; import globeIcon from 'icons/globe.svg';
import txIcon from 'icons/transactions.svg'; import txIcon from 'icons/transactions.svg';
import { shortenNumberWithLetter } from 'lib/formatters';
import { sortByDateDesc } from 'ui/shared/chart/utils/sorts'; import { sortByDateDesc } from 'ui/shared/chart/utils/sorts';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = { const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = {
id: 'daily_txs', id: 'daily_txs',
title: 'Daily transactions', title: 'Daily transactions',
value: (stats) => shortenNumberWithLetter(Number(stats.transactions_today), undefined, { maximumFractionDigits: 2 }), value: (stats) => Number(stats.transactions_today).toLocaleString('en', { maximumFractionDigits: 2, notation: 'compact' }),
icon: <Icon as={ txIcon } boxSize={ 6 } bgColor="#56ACD1" borderRadius="base" color="white"/>, icon: <Icon as={ txIcon } boxSize={ 6 } bgColor="#56ACD1" borderRadius="base" color="white"/>,
hint: `The total daily number of transactions on the blockchain for the last month.`, hint: `The total daily number of transactions on the blockchain for the last month.`,
api: { api: {
...@@ -23,7 +22,7 @@ const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = { ...@@ -23,7 +22,7 @@ const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = {
.map((item) => ({ date: new Date(item.date), value: item.tx_count })) .map((item) => ({ date: new Date(item.date), value: item.tx_count }))
.sort(sortByDateDesc), .sort(sortByDateDesc),
name: 'Tx/day', name: 'Tx/day',
valueFormatter: (x: number) => shortenNumberWithLetter(x, undefined, { maximumFractionDigits: 2 }), valueFormatter: (x: number) => x.toLocaleString('en', { maximumFractionDigits: 2, notation: 'compact' }),
} ]), } ]),
}, },
}; };
...@@ -49,7 +48,7 @@ const coinPriceIndicator: TChainIndicator<'homepage_chart_market'> = { ...@@ -49,7 +48,7 @@ const coinPriceIndicator: TChainIndicator<'homepage_chart_market'> = {
const marketPriceIndicator: TChainIndicator<'homepage_chart_market'> = { const marketPriceIndicator: TChainIndicator<'homepage_chart_market'> = {
id: 'market_cup', id: 'market_cup',
title: 'Market cap', title: 'Market cap',
value: (stats) => '$' + shortenNumberWithLetter(Number(stats.market_cap), undefined, { maximumFractionDigits: 0 }), value: (stats) => '$' + Number(stats.market_cap).toLocaleString('en', { maximumFractionDigits: 0, notation: 'compact' }),
icon: <Icon as={ globeIcon } boxSize={ 6 } bgColor="#6A5DCC" borderRadius="base" color="white"/>, icon: <Icon as={ globeIcon } boxSize={ 6 } bgColor="#6A5DCC" borderRadius="base" color="white"/>,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
hint: 'The total market value of a cryptocurrency\'s circulating supply. It is analogous to the free-float capitalization in the stock market. Market Cap = Current Price x Circulating Supply.', hint: 'The total market value of a cryptocurrency\'s circulating supply. It is analogous to the free-float capitalization in the stock market. Market Cap = Current Price x Circulating Supply.',
...@@ -60,7 +59,7 @@ const marketPriceIndicator: TChainIndicator<'homepage_chart_market'> = { ...@@ -60,7 +59,7 @@ const marketPriceIndicator: TChainIndicator<'homepage_chart_market'> = {
.map((item) => ({ date: new Date(item.date), value: Number(item.closing_price) * Number(response.available_supply) })) .map((item) => ({ date: new Date(item.date), value: Number(item.closing_price) * Number(response.available_supply) }))
.sort(sortByDateDesc), .sort(sortByDateDesc),
name: 'Market cap', name: 'Market cap',
valueFormatter: (x: number) => '$' + shortenNumberWithLetter(x, undefined, { maximumFractionDigits: 0 }), valueFormatter: (x: number) => '$' + x.toLocaleString('en', { maximumFractionDigits: 0 }),
} ]), } ]),
}, },
}; };
......
...@@ -9,7 +9,6 @@ import iconSuccess from 'icons/status/success.svg'; ...@@ -9,7 +9,6 @@ import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useContractTabs from 'lib/hooks/useContractTabs'; import useContractTabs from 'lib/hooks/useContractTabs';
import notEmpty from 'lib/notEmpty';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated'; import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
import AddressCoinBalance from 'ui/address/AddressCoinBalance'; import AddressCoinBalance from 'ui/address/AddressCoinBalance';
...@@ -57,7 +56,7 @@ const AddressPageContent = () => { ...@@ -57,7 +56,7 @@ const AddressPageContent = () => {
...(addressQuery.data?.public_tags || []), ...(addressQuery.data?.public_tags || []),
...(addressQuery.data?.watchlist_names || []), ...(addressQuery.data?.watchlist_names || []),
] ]
.filter(notEmpty) .filter(Boolean)
.map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>); .map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const contractTabs = useContractTabs(addressQuery.data); const contractTabs = useContractTabs(addressQuery.data);
...@@ -92,7 +91,7 @@ const AddressPageContent = () => { ...@@ -92,7 +91,7 @@ const AddressPageContent = () => {
component: <AddressContract tabs={ contractTabs } addressHash={ hash }/>, component: <AddressContract tabs={ contractTabs } addressHash={ hash }/>,
subTabs: contractTabs.map(tab => tab.id), subTabs: contractTabs.map(tab => tab.id),
} : undefined, } : undefined,
].filter(notEmpty); ].filter(Boolean);
}, [ addressQuery.data, contractTabs, hash ]); }, [ addressQuery.data, contractTabs, hash ]);
const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null; const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null;
......
import { Skeleton } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
...@@ -28,6 +30,11 @@ const BlockPageContent = () => { ...@@ -28,6 +30,11 @@ const BlockPageContent = () => {
const height = getQueryParamString(router.query.height); const height = getQueryParamString(router.query.height);
const tab = getQueryParamString(router.query.tab); const tab = getQueryParamString(router.query.tab);
const blockQuery = useApiQuery('block', {
pathParams: { height },
queryOptions: { enabled: Boolean(height) },
});
const blockTxsQuery = useQueryWithPages({ const blockTxsQuery = useQueryWithPages({
resourceName: 'block_txs', resourceName: 'block_txs',
pathParams: { height }, pathParams: { height },
...@@ -40,10 +47,10 @@ const BlockPageContent = () => { ...@@ -40,10 +47,10 @@ const BlockPageContent = () => {
throw new Error('Block not found', { cause: { status: 404 } }); throw new Error('Block not found', { cause: { status: 404 } });
} }
const tabs: Array<RoutedTab> = [ const tabs: Array<RoutedTab> = React.useMemo(() => ([
{ id: 'index', title: 'Details', component: <BlockDetails/> }, { id: 'index', title: 'Details', component: <BlockDetails query={ blockQuery }/> },
{ id: 'txs', title: 'Transactions', component: <TxsContent query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> }, { id: 'txs', title: 'Transactions', component: <TxsContent query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> },
]; ]), [ blockQuery, blockTxsQuery ]);
const hasPagination = !isMobile && tab === 'txs' && blockTxsQuery.isPaginationVisible; const hasPagination = !isMobile && tab === 'txs' && blockTxsQuery.isPaginationVisible;
...@@ -51,12 +58,16 @@ const BlockPageContent = () => { ...@@ -51,12 +58,16 @@ const BlockPageContent = () => {
return ( return (
<Page> <Page>
<TextAd mb={ 6 }/> { blockQuery.isLoading ? <Skeleton h={{ base: 12, lg: 6 }} mb={ 6 } w="100%" maxW="680px"/> : <TextAd mb={ 6 }/> }
<PageTitle { blockQuery.isLoading ? (
text={ `Block #${ height }` } <Skeleton h={ 10 } w="300px" mb={ 6 }/>
backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined } ) : (
backLinkLabel="Back to blocks list" <PageTitle
/> text={ `Block #${ blockQuery.data?.height }` }
backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
backLinkLabel="Back to blocks list"
/>
) }
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS } tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
......
...@@ -2,7 +2,7 @@ import { Text } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ import { Text } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { SmartContractVerificationConfigRaw, SmartContractVerificationMethod } from 'types/api/contract'; import type { SmartContractVerificationMethod } from 'types/api/contract';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
...@@ -39,11 +39,10 @@ const ContractVerification = () => { ...@@ -39,11 +39,10 @@ const ContractVerification = () => {
const configQuery = useApiQuery('contract_verification_config', { const configQuery = useApiQuery('contract_verification_config', {
queryOptions: { queryOptions: {
select: (data: unknown) => { select: (data) => {
const _data = data as SmartContractVerificationConfigRaw;
return { return {
..._data, ...data,
verification_options: _data.verification_options.filter(isValidVerificationMethod).sort(sortVerificationMethods), verification_options: data.verification_options.filter(isValidVerificationMethod).sort(sortVerificationMethods),
}; };
}, },
enabled: Boolean(hash), enabled: Boolean(hash),
......
...@@ -37,6 +37,7 @@ const Home = () => { ...@@ -37,6 +37,7 @@ const Home = () => {
alignItems="center" alignItems="center"
display={{ base: 'none', lg: 'flex' }} display={{ base: 'none', lg: 'flex' }}
columnGap={ 12 } columnGap={ 12 }
pl={ 4 }
> >
<ColorModeToggler trackBg="whiteAlpha.500"/> <ColorModeToggler trackBg="whiteAlpha.500"/>
<ProfileMenuDesktop/> <ProfileMenuDesktop/>
......
...@@ -10,7 +10,6 @@ import { useAppContext } from 'lib/appContext'; ...@@ -10,7 +10,6 @@ import { useAppContext } from 'lib/appContext';
import useContractTabs from 'lib/hooks/useContractTabs'; import useContractTabs from 'lib/hooks/useContractTabs';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import notEmpty from 'lib/notEmpty';
import trimTokenSymbol from 'lib/token/trimTokenSymbol'; import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AddressContract from 'ui/address/AddressContract'; import AddressContract from 'ui/address/AddressContract';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
...@@ -118,7 +117,7 @@ const TokenPageContent = () => { ...@@ -118,7 +117,7 @@ const TokenPageContent = () => {
component: <AddressContract tabs={ contractTabs } addressHash={ hashString }/>, component: <AddressContract tabs={ contractTabs } addressHash={ hashString }/>,
subTabs: contractTabs.map(tab => tab.id), subTabs: contractTabs.map(tab => tab.id),
} : undefined, } : undefined,
].filter(notEmpty); ].filter(Boolean);
let hasPagination; let hasPagination;
let pagination; let pagination;
......
import Script from 'next/script';
import React from 'react';
import appConfig from 'configs/app/config';
const GoogleAnalytics = () => {
if (!appConfig.googleAnalytics.propertyId) {
return null;
}
const id = appConfig.googleAnalytics.propertyId;
return (
<>
<Script src={ `https://www.googletagmanager.com/gtag/js?id=${ id }` }/>
<Script id="google-analytics">
{ `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${ id }');
` }
</Script>
</>
);
};
export default React.memo(GoogleAnalytics);
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import getErrorStatusCode from 'lib/errors/getErrorStatusCode';
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
import AppError from 'ui/shared/AppError/AppError'; import AppError from 'ui/shared/AppError/AppError';
import ErrorBoundary from 'ui/shared/ErrorBoundary'; import ErrorBoundary from 'ui/shared/ErrorBoundary';
...@@ -26,8 +27,7 @@ const Page = ({ ...@@ -26,8 +27,7 @@ const Page = ({
useGetCsrfToken(); useGetCsrfToken();
const renderErrorScreen = React.useCallback((error?: Error) => { const renderErrorScreen = React.useCallback((error?: Error) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const statusCode = getErrorStatusCode(error) || 500;
const statusCode = (error?.cause as any)?.status || 500;
const isInvalidTxHash = error?.message.includes('Invalid tx hash'); const isInvalidTxHash = error?.message.includes('Invalid tx hash');
if (wrapChildren) { if (wrapChildren) {
......
...@@ -4,29 +4,17 @@ import React from 'react'; ...@@ -4,29 +4,17 @@ import React from 'react';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
declare global {
interface Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
coinzilla_display: any;
}
}
type CPreferences = {
zone: string;
width: string;
height: string;
}
const CoinzillaBanner = ({ className }: { className?: string }) => { const CoinzillaBanner = ({ className }: { className?: string }) => {
const isInBrowser = isBrowser(); const isInBrowser = isBrowser();
React.useEffect(() => { React.useEffect(() => {
if (isInBrowser) { if (isInBrowser) {
window.coinzilla_display = window.coinzilla_display || []; window.coinzilla_display = window.coinzilla_display || [];
const cDisplayPreferences = {} as CPreferences; const cDisplayPreferences = {
cDisplayPreferences.zone = '26660bf627543e46851'; zone: '26660bf627543e46851',
cDisplayPreferences.width = '728'; width: '728',
cDisplayPreferences.height = '90'; height: '90',
};
window.coinzilla_display.push(cDisplayPreferences); window.coinzilla_display.push(cDisplayPreferences);
} }
}, [ isInBrowser ]); }, [ isInBrowser ]);
......
...@@ -31,7 +31,8 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => { ...@@ -31,7 +31,8 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => {
useEffect(() => { useEffect(() => {
fetch('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242') fetch('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242')
.then(res => res.status === 200 ? res.json() : null) .then(res => res.status === 200 ? res.json() : null)
.then((data) => { .then((_data) => {
const data = _data as AdData;
setAdData(data); setAdData(data);
if (data?.ad?.impressionUrl) { if (data?.ad?.impressionUrl) {
fetch(data.ad.impressionUrl); fetch(data.ad.impressionUrl);
......
...@@ -27,7 +27,7 @@ const AddressContractIcon = ({ className }: Props) => { ...@@ -27,7 +27,7 @@ const AddressContractIcon = ({ className }: Props) => {
transitionDuration="normal" transitionDuration="normal"
transitionTimingFunction="ease" transitionTimingFunction="ease"
> >
С C
</Box> </Box>
</Tooltip> </Tooltip>
); );
......
...@@ -4,7 +4,6 @@ import { useMemo } from 'react'; ...@@ -4,7 +4,6 @@ import { useMemo } from 'react';
import type { TimeChartData } from 'ui/shared/chart/types'; import type { TimeChartData } from 'ui/shared/chart/types';
import { WEEK, MONTH, YEAR } from 'lib/consts'; import { WEEK, MONTH, YEAR } from 'lib/consts';
import formatNumberToMetricPrefix from 'lib/formatNumberToMetricPrefix';
interface Props { interface Props {
data: TimeChartData; data: TimeChartData;
...@@ -73,7 +72,7 @@ export default function useTimeChartController({ data, width, height }: Props) { ...@@ -73,7 +72,7 @@ export default function useTimeChartController({ data, width, height }: Props) {
return format(d as Date); return format(d as Date);
}; };
const yTickFormat = () => (d: d3.AxisDomain) => formatNumberToMetricPrefix(Number(d)); const yTickFormat = () => (d: d3.AxisDomain) => Number(d).toLocaleString('en', { maximumFractionDigits: 3, notation: 'compact' });
return { return {
xTickFormat, xTickFormat,
......
...@@ -7,7 +7,6 @@ import placeholderIcon from 'icons/files/placeholder.svg'; ...@@ -7,7 +7,6 @@ import placeholderIcon from 'icons/files/placeholder.svg';
import solIcon from 'icons/files/sol.svg'; import solIcon from 'icons/files/sol.svg';
import yulIcon from 'icons/files/yul.svg'; import yulIcon from 'icons/files/yul.svg';
import infoIcon from 'icons/info.svg'; import infoIcon from 'icons/info.svg';
import { shortenNumberWithLetter } from 'lib/formatters';
const FILE_ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = { const FILE_ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = {
'.json': jsonIcon, '.json': jsonIcon,
...@@ -101,7 +100,9 @@ const FileSnippet = ({ file, className, index, onRemove, isDisabled, error }: Pr ...@@ -101,7 +100,9 @@ const FileSnippet = ({ file, className, index, onRemove, isDisabled, error }: Pr
alignSelf="flex-start" alignSelf="flex-start"
/> />
</Flex> </Flex>
<Text variant="secondary" mt={ 1 }>{ shortenNumberWithLetter(file.size) }B</Text> <Text variant="secondary" mt={ 1 }>
{ file.size.toLocaleString('en', { notation: 'compact', maximumFractionDigits: 2, unit: 'byte', unitDisplay: 'narrow', style: 'unit' }) }
</Text>
</Box> </Box>
</Flex> </Flex>
); );
......
...@@ -6,7 +6,6 @@ import type { Log } from 'types/api/log'; ...@@ -6,7 +6,6 @@ import type { Log } from 'types/api/log';
// import searchIcon from 'icons/search.svg'; // import searchIcon from 'icons/search.svg';
import { space } from 'lib/html-entities'; import { space } from 'lib/html-entities';
import notEmpty from 'lib/notEmpty';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -83,7 +82,7 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash ...@@ -83,7 +82,7 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash
) } ) }
<RowHeader>Topics</RowHeader> <RowHeader>Topics</RowHeader>
<GridItem> <GridItem>
{ topics.filter(notEmpty).map((item, index) => ( { topics.filter(Boolean).map((item, index) => (
<LogTopic <LogTopic
key={ index } key={ index }
hex={ item } hex={ item }
......
...@@ -25,7 +25,7 @@ test('base view', async({ mount, page }) => { ...@@ -25,7 +25,7 @@ test('base view', async({ mount, page }) => {
); );
await component.locator('svg[aria-label="Menu button"]').click(); await component.locator('svg[aria-label="Menu button"]').click();
await expect(page).toHaveScreenshot(); await expect(page.locator('.chakra-modal__content-container')).toHaveScreenshot();
await page.locator('button[aria-label="Network menu"]').click(); await page.locator('button[aria-label="Network menu"]').click();
await expect(page).toHaveScreenshot(); await expect(page).toHaveScreenshot();
...@@ -50,6 +50,19 @@ test.describe('dark mode', () => { ...@@ -50,6 +50,19 @@ test.describe('dark mode', () => {
}); });
}); });
test('submenu', async({ mount, page }) => {
const component = await mount(
<TestApp>
<Burger/>
</TestApp>,
{ hooksConfig },
);
await component.locator('svg[aria-label="Menu button"]').click();
await page.locator('div[aria-label="Blockchain link group"]').click();
await expect(page).toHaveScreenshot();
});
test.describe('auth', () => { test.describe('auth', () => {
const extendedTest = test.extend({ const extendedTest = test.extend({
context: ({ context }, use) => { context: ({ context }, use) => {
...@@ -58,19 +71,9 @@ test.describe('auth', () => { ...@@ -58,19 +71,9 @@ test.describe('auth', () => {
}, },
}); });
extendedTest('base view', async({ mount, page }) => { extendedTest.use({ viewport: { width: devices['iPhone 13 Pro'].viewport.width, height: 1000 } });
const component = await mount(
<TestApp>
<Burger/>
</TestApp>,
{ hooksConfig },
);
await component.locator('svg[aria-label="Menu button"]').click();
await expect(page).toHaveScreenshot();
});
extendedTest('submenu', async({ mount, page }) => { extendedTest('base view', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<Burger/> <Burger/>
...@@ -79,7 +82,6 @@ test.describe('auth', () => { ...@@ -79,7 +82,6 @@ test.describe('auth', () => {
); );
await component.locator('svg[aria-label="Menu button"]').click(); await component.locator('svg[aria-label="Menu button"]').click();
await page.locator('div[aria-label="Blockchain link group"]').click();
await expect(page).toHaveScreenshot(); await expect(page).toHaveScreenshot();
}); });
}); });
...@@ -70,8 +70,9 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr ...@@ -70,8 +70,9 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr
{ text } { text }
</Text> </Text>
<VStack spacing={ 1 } alignItems="start"> <VStack spacing={ 1 } alignItems="start">
{ subItems.map(item => Array.isArray(item) ? ( { subItems.map((item, index) => Array.isArray(item) ? (
<Box <Box
key={ index }
w="100%" w="100%"
as="ul" as="ul"
_notLast={{ _notLast={{
......
...@@ -93,8 +93,9 @@ const NavigationMobile = () => { ...@@ -93,8 +93,9 @@ const NavigationMobile = () => {
as="ul" as="ul"
> >
{ isGroupItem(openedItem) && openedItem.subItems?.map( { isGroupItem(openedItem) && openedItem.subItems?.map(
item => Array.isArray(item) ? ( (item, index) => Array.isArray(item) ? (
<Box <Box
key={ index }
w="100%" w="100%"
as="ul" as="ul"
_notLast={{ _notLast={{
......
...@@ -2,7 +2,6 @@ import { Grid } from '@chakra-ui/react'; ...@@ -2,7 +2,6 @@ import { Grid } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import formatNumberToMetricPrefix from 'lib/formatNumberToMetricPrefix';
import DataFetchAlert from '../shared/DataFetchAlert'; import DataFetchAlert from '../shared/DataFetchAlert';
import NumberWidget from './NumberWidget'; import NumberWidget from './NumberWidget';
...@@ -32,7 +31,7 @@ const NumberWidgetsList = () => { ...@@ -32,7 +31,7 @@ const NumberWidgetsList = () => {
<NumberWidget <NumberWidget
key={ id } key={ id }
label={ title } label={ title }
value={ `${ formatNumberToMetricPrefix(Number(value)) } ${ units ? units : '' }` } value={ `${ Number(value).toLocaleString('en', { maximumFractionDigits: 3, notation: 'compact' }) } ${ units ? units : '' }` }
/> />
); );
}) } }) }
......
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
Flex, Flex,
Tooltip, Tooltip,
chakra, chakra,
useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
...@@ -59,6 +60,7 @@ const TxDetails = () => { ...@@ -59,6 +60,7 @@ const TxDetails = () => {
smooth: true, smooth: true,
}); });
}, []); }, []);
const executionSuccessIconColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800');
if (isLoading) { if (isLoading) {
return <TxDetailsSkeleton/>; return <TxDetailsSkeleton/>;
...@@ -94,7 +96,7 @@ const TxDetails = () => { ...@@ -94,7 +96,7 @@ const TxDetails = () => {
const executionSuccessBadge = toAddress.is_contract && data.result === 'success' ? ( const executionSuccessBadge = toAddress.is_contract && data.result === 'success' ? (
<Tooltip label="Contract execution completed"> <Tooltip label="Contract execution completed">
<chakra.span display="inline-flex" ml={ 2 } mr={ 1 }> <chakra.span display="inline-flex" ml={ 2 } mr={ 1 }>
<Icon as={ successIcon } boxSize={ 4 } color="green.500" cursor="pointer"/> <Icon as={ successIcon } boxSize={ 4 } color={ executionSuccessIconColor } cursor="pointer"/>
</chakra.span> </chakra.span>
</Tooltip> </Tooltip>
) : null; ) : null;
......
...@@ -37,8 +37,9 @@ type Props = { ...@@ -37,8 +37,9 @@ type Props = {
} }
const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }: Props) => { const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }: Props) => {
const dataTo = tx.to ? tx.to : tx.created_contract;
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash); const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
const isIn = Boolean(currentAddress && currentAddress === tx.to?.hash); const isIn = Boolean(currentAddress && currentAddress === dataTo.hash);
const timeAgo = useTimeAgoIncrement(tx.timestamp, enableTimeIncrement); const timeAgo = useTimeAgoIncrement(tx.timestamp, enableTimeIncrement);
...@@ -49,8 +50,6 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement } ...@@ -49,8 +50,6 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
</Address> </Address>
); );
const dataTo = tx.to ? tx.to : tx.created_contract;
const addressTo = ( const addressTo = (
<Address> <Address>
<AddressIcon address={ dataTo }/> <AddressIcon address={ dataTo }/>
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment