Commit 89fac78e authored by Max Alekseenko's avatar Max Alekseenko

Merge branch 'main' into dapp-rating

parents 3d295216 51e0ca1c
...@@ -5,9 +5,19 @@ const RESTRICTED_MODULES = { ...@@ -5,9 +5,19 @@ const RESTRICTED_MODULES = {
{ name: '@metamask/providers', message: 'Please lazy-load @metamask/providers or use useProvider hook instead' }, { name: '@metamask/providers', message: 'Please lazy-load @metamask/providers or use useProvider hook instead' },
{ name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' }, { name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' },
{ name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' }, { name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' },
{
name: '@chakra-ui/react',
importNames: [ 'Popover', 'Menu', 'useToast' ],
message: 'Please use corresponding component or hook from ui/shared/chakra component instead',
},
{
name: 'lodash',
message: 'Please use `import [package] from \'lodash/[package]\'` instead.',
},
], ],
patterns: [ patterns: [
'icons/*', 'icons/*',
'!lodash/*',
], ],
}; };
......
...@@ -12,6 +12,7 @@ on: ...@@ -12,6 +12,7 @@ on:
- none - none
- arbitrum - arbitrum
- base - base
- celo_alfajores
- gnosis - gnosis
- eth - eth
- eth_sepolia - eth_sepolia
......
...@@ -339,7 +339,9 @@ ...@@ -339,7 +339,9 @@
"main", "main",
"main.L2", "main.L2",
"localhost", "localhost",
"arbitrum",
"base", "base",
"celo_alfajores",
"gnosis", "gnosis",
"eth", "eth",
"eth_goerli", "eth_goerli",
......
...@@ -15,6 +15,7 @@ const chain = Object.freeze({ ...@@ -15,6 +15,7 @@ const chain = Object.freeze({
secondaryCoin: { secondaryCoin: {
symbol: getEnvValue('NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL'), symbol: getEnvValue('NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL'),
}, },
hasMultipleGasCurrencies: getEnvValue('NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES') === 'true',
tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC', tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC',
rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'), rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'),
isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true', isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true',
......
# Set of ENVs for Celo Alfajjores network explorer
# https://celo-alfajores.blockscout.com/
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
# blockchain parameters
NEXT_PUBLIC_NETWORK_NAME=Celo Alfajores
NEXT_PUBLIC_NETWORK_SHORT_NAME=Alfajores
NEXT_PUBLIC_NETWORK_ID=44787
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Celo
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=CELO
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES=true
NEXT_PUBLIC_NETWORK_RPC_URL=https://alfajores-forno.celo-testnet.org
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_IS_TESTNET=true
# api configuration
NEXT_PUBLIC_API_HOST=celo-alfajores.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(0,0,0,1)
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(252,255,82,1)
## sidebar
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-light.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-dark.svg
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/celo-icon-light.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/celo-icon-dark.svg
## footer
##views
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
## views
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_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
# app features
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_GAS_TRACKER_ENABLED=false
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
# NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.k8s-dev.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs-test.k8s-dev.blockscout.com
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/celo.png
...@@ -43,7 +43,7 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com ...@@ -43,7 +43,7 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_AD_BANNER_PROVIDER=hype NEXT_PUBLIC_AD_BANNER_PROVIDER=slise
NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global
NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
......
...@@ -91,11 +91,11 @@ brace-expansion@^1.1.7: ...@@ -91,11 +91,11 @@ brace-expansion@^1.1.7:
concat-map "0.0.1" concat-map "0.0.1"
braces@^3.0.2: braces@^3.0.2:
version "3.0.2" version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies: dependencies:
fill-range "^7.0.1" fill-range "^7.1.1"
color-name@^1.1.4: color-name@^1.1.4:
version "1.1.4" version "1.1.4"
...@@ -276,10 +276,10 @@ filing-cabinet@^4.1.6: ...@@ -276,10 +276,10 @@ filing-cabinet@^4.1.6:
tsconfig-paths "^4.2.0" tsconfig-paths "^4.2.0"
typescript "^5.0.4" typescript "^5.0.4"
fill-range@^7.0.1: fill-range@^7.1.1:
version "7.0.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies: dependencies:
to-regex-range "^5.0.1" to-regex-range "^5.0.1"
......
...@@ -273,7 +273,7 @@ const rollupSchema = yup ...@@ -273,7 +273,7 @@ const rollupSchema = yup
.when('NEXT_PUBLIC_ROLLUP_TYPE', { .when('NEXT_PUBLIC_ROLLUP_TYPE', {
is: (value: string) => value === 'optimistic', is: (value: string) => value === 'optimistic',
then: (schema) => schema.test(urlTest).required(), then: (schema) => schema.test(urlTest).required(),
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined'), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL can be used only if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' '),
}), }),
}); });
...@@ -507,6 +507,7 @@ const schema = yup ...@@ -507,6 +507,7 @@ const schema = yup
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(), NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(),
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(), NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(),
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL: yup.string(), NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL: yup.string(),
NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES: yup.boolean(),
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup.string<NetworkVerificationType>().oneOf([ 'validation', 'mining' ]), NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup.string<NetworkVerificationType>().oneOf([ 'validation', 'mining' ]),
NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME: yup.string(), NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME: yup.string(),
NEXT_PUBLIC_IS_TESTNET: yup.boolean(), NEXT_PUBLIC_IS_TESTNET: yup.boolean(),
......
...@@ -47,6 +47,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether ...@@ -47,6 +47,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Explorer','baseUrl':'https://example.com/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Explorer','baseUrl':'https://example.com/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=GNO NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=GNO
NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES=true
NEXT_PUBLIC_NETWORK_ICON=https://example.com/icon.png NEXT_PUBLIC_NETWORK_ICON=https://example.com/icon.png
NEXT_PUBLIC_NETWORK_ICON_DARK=https://example.com/icon.png NEXT_PUBLIC_NETWORK_ICON_DARK=https://example.com/icon.png
NEXT_PUBLIC_NETWORK_LOGO=https://example.com/logo.png NEXT_PUBLIC_NETWORK_LOGO=https://example.com/logo.png
......
...@@ -262,11 +262,11 @@ ansi-styles@^4.1.0: ...@@ -262,11 +262,11 @@ ansi-styles@^4.1.0:
color-convert "^2.0.1" color-convert "^2.0.1"
braces@^3.0.2: braces@^3.0.2:
version "3.0.2" version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies: dependencies:
fill-range "^7.0.1" fill-range "^7.1.1"
browserslist@^4.14.5: browserslist@^4.14.5:
version "4.21.9" version "4.21.9"
...@@ -439,10 +439,10 @@ fastest-levenshtein@^1.0.12: ...@@ -439,10 +439,10 @@ fastest-levenshtein@^1.0.12:
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
fill-range@^7.0.1: fill-range@^7.1.1:
version "7.0.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies: dependencies:
to-regex-range "^5.0.1" to-regex-range "^5.0.1"
......
...@@ -68,7 +68,6 @@ frontend: ...@@ -68,7 +68,6 @@ frontend:
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']" NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']"
NEXT_PUBLIC_NETWORK_RPC_URL: https://eth-sepolia.public.blastapi.io NEXT_PUBLIC_NETWORK_RPC_URL: https://eth-sepolia.public.blastapi.io
NEXT_PUBLIC_NETWORK_ID: '11155111'
NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]"
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_FEATURED_APP: zkbob-wallet NEXT_PUBLIC_MARKETPLACE_FEATURED_APP: zkbob-wallet
...@@ -77,22 +76,15 @@ frontend: ...@@ -77,22 +76,15 @@ frontend:
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS: "['top_accounts']"
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED: true NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED: true
NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: "['value','fee_currency','gas_price','gas_fees','burnt_fees']"
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS: "['fee_per_gas']"
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]" NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
NEXT_PUBLIC_HAS_USER_OPS: true NEXT_PUBLIC_HAS_USER_OPS: true
NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: blockscout
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true
NEXT_PUBLIC_AD_BANNER_PROVIDER: getit NEXT_PUBLIC_AD_BANNER_PROVIDER: slise
NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: adbutler
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP: "{ \"id\": \"632019\", \"width\": \"728\", \"height\": \"90\" }"
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: "{ \"id\": \"632018\", \"width\": \"320\", \"height\": \"100\" }"
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: true
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']" NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']"
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
......
...@@ -91,6 +91,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -91,6 +91,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | v1.29.0+ | | NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | v1.29.0+ |
| NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES | `boolean` | Set to `true` for networks where users can pay transaction fees in either the native coin or ERC-20 tokens. | - | `false` | `true` | v1.33.0+ |
| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` or `mining` | Verification type in the network | - | `mining` | `validation` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` or `mining` | Verification type in the network | - | `mining` | `validation` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME | `string` | Name of the standard for creating tokens | - | `ERC` | `BEP` | v1.31.0+ | | NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME | `string` | Name of the standard for creating tokens | - | `ERC` | `BEP` | v1.31.0+ |
| NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` | v1.0.x+ | | NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` | v1.0.x+ |
...@@ -359,6 +360,7 @@ This feature is **enabled by default**. To switch it off pass `NEXT_PUBLIC_GAS_T ...@@ -359,6 +360,7 @@ This feature is **enabled by default**. To switch it off pass `NEXT_PUBLIC_GAS_T
### Banner ads ### Banner ads
This feature is **enabled by default** with the `slise` ads provider. To switch it off pass `NEXT_PUBLIC_AD_BANNER_PROVIDER=none`. This feature is **enabled by default** with the `slise` ads provider. To switch it off pass `NEXT_PUBLIC_AD_BANNER_PROVIDER=none`.
*Note* that the `getit` ad provider is temporary disabled.
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
...@@ -402,8 +404,8 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi ...@@ -402,8 +404,8 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ | | NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ |
| NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0+ | | NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0+ |
| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals | Required only for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ | | NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals (Optimistic stack only) | Required for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ |
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (OP stack only) | - | - | `true` | v1.31.0+ | | NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ |
&nbsp; &nbsp;
......
<svg viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 11a7.5 7.5 0 1 1 15 0 7.5 7.5 0 0 1-15 0ZM11 1C5.477 1 1 5.477 1 11s4.477 10 10 10 10-4.477 10-10S16.523 1 11 1Zm1.25 5a1.25 1.25 0 1 0-2.5 0v5c0 .69.56 1.25 1.25 1.25h5a1.25 1.25 0 1 0 0-2.5h-3.75V6Z" fill="currentColor" stroke="transparent" stroke-width=".6" stroke-linecap="round" stroke-linejoin="round"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M10 .3C4.643.3.3 4.643.3 10c0 5.357 4.343 9.7 9.7 9.7 5.357 0 9.7-4.343 9.7-9.7 0-5.357-4.343-9.7-9.7-9.7ZM2.2 10a7.8 7.8 0 1 1 15.6 0 7.8 7.8 0 0 1-15.6 0ZM10 4.05a.95.95 0 0 0-.95.95v5c0 .525.425.95.95.95h5a.95.95 0 1 0 0-1.9h-3.75a.3.3 0 0 1-.3-.3V5a.95.95 0 0 0-.95-.95Z" fill="currentColor"/>
</svg> </svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.392.45C3.644.163 3.984 0 4.34 0h8.038a.63.63 0 0 1 .474.225L17.54 5.61a.83.83 0 0 1 .196.544v12.308c0 .408-.141.799-.393 1.087-.25.289-.592.451-.947.451H4.34c-.356 0-.696-.162-.948-.45A1.661 1.661 0 0 1 3 18.461V1.538c0-.408.141-.799.392-1.087Zm.948 1.088h6.87v4.497c0 .388.315.702.702.702h4.485v11.725H4.34V1.538Zm8.274.59 2.791 3.205h-2.791V2.128Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.233 8.8a2.293 2.293 0 0 0-1.613.644l-.006.006-.595.591a.564.564 0 1 0 .795.8l.592-.589a1.166 1.166 0 0 1 1.649 1.648l-1.034 1.034a1.168 1.168 0 0 1-1.758-.126.564.564 0 1 0-.903.676 2.293 2.293 0 0 0 3.458.247l1.038-1.037.007-.007a2.294 2.294 0 0 0-1.63-3.887Zm-2.259 2.403a2.294 2.294 0 0 0-1.786.666l-1.037 1.037-.007.008a2.293 2.293 0 0 0 3.243 3.242l.006-.007.592-.591a.564.564 0 0 0-.797-.797l-.588.587A1.166 1.166 0 0 1 7.952 13.7l1.034-1.034a1.167 1.167 0 0 1 1.758.126.564.564 0 0 0 .903-.675 2.293 2.293 0 0 0-1.673-.914Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5 23.75h1.25v2.5H2.5v-2.5h1.25V5A1.25 1.25 0 0 1 5 3.75h11.25A1.25 1.25 0 0 1 17.5 5v10H20a2.5 2.5 0 0 1 2.5 2.5v5a1.25 1.25 0 0 0 2.5 0v-8.75h-2.5a1.25 1.25 0 0 1-1.25-1.25V8.018l-2.071-2.072 1.767-1.767 6.188 6.187a1.244 1.244 0 0 1 .366.884V22.5a3.75 3.75 0 0 1-7.5 0v-5h-2.5v6.25Zm-11.25 0H15v-7.5H6.25v7.5Zm0-17.5v7.5H15v-7.5H6.25Z" fill="currentColor"/> <path d="M11.666 15.833h.834V17.5H1.666v-1.667H2.5v-12.5a.833.833 0 0 1 .833-.833h7.5a.833.833 0 0 1 .833.833V10h1.667A1.667 1.667 0 0 1 15 11.667V15a.833.833 0 0 0 1.666 0V9.167H15a.833.833 0 0 1-.834-.834V5.345l-1.38-1.38 1.178-1.18 4.125 4.126a.831.831 0 0 1 .244.589V15a2.5 2.5 0 0 1-5 0v-3.333h-1.667v4.166Zm-7.5 0H10v-5H4.167v5Zm0-11.666v5H10v-5H4.167Z" fill="currentColor"/>
</svg> </svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.16 3A2.16 2.16 0 0 0 3 5.16v10.18a2.16 2.16 0 0 0 2.16 2.16h10.18a2.16 2.16 0 0 0 2.16-2.16v-3.272h-1.614v3.114c0 .389-.315.704-.704.704H5.318a.705.705 0 0 1-.704-.704V5.318c0-.389.315-.704.704-.704h3.114V3H5.159Zm6.135 0a.41.41 0 0 0-.41.41v.793c0 .226.184.409.41.409h3.453l-4.244 4.245a.409.409 0 0 0 0 .578l.56.561c.16.16.42.16.58 0l4.244-4.245v3.454c0 .226.183.41.41.41h.793a.41.41 0 0 0 .409-.41V3h-6.205Z" fill="currentColor"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M5.16 3A2.16 2.16 0 0 0 3 5.16v10.18a2.16 2.16 0 0 0 2.16 2.16h10.18a2.16 2.16 0 0 0 2.16-2.16v-3.272h-1.614v3.114a.704.704 0 0 1-.704.704H5.318a.705.705 0 0 1-.704-.704V5.318c0-.389.315-.704.704-.704h3.114V3H5.159Zm6.135 0a.41.41 0 0 0-.41.41v.793a.41.41 0 0 0 .41.409h3.453l-4.244 4.245a.409.409 0 0 0 0 .578l.56.561c.16.16.42.16.58 0l4.244-4.245v3.454c0 .226.183.41.41.41h.793a.41.41 0 0 0 .409-.41V3h-6.205Z" fill="currentColor"/>
</svg> </svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 31 30"> <svg viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor" clip-path="url(#wallet_svg__a)"> <path d="M17.139 20H4.83a3.847 3.847 0 0 1-3.846-3.845V5.39a.769.769 0 0 1 .77-.769h15.384a3.847 3.847 0 0 1 3.846 3.845v7.69A3.844 3.844 0 0 1 17.139 20ZM2.524 6.16v9.996a2.307 2.307 0 0 0 2.307 2.307H17.14a2.308 2.308 0 0 0 2.308-2.307v-7.69A2.306 2.306 0 0 0 17.14 6.16H2.524Z" fill="currentColor"/>
<path d="M22.75 27.188h-15A4.688 4.688 0 0 1 3.062 22.5V9.375A.937.937 0 0 1 4 8.437h18.75a4.688 4.688 0 0 1 4.688 4.688V22.5a4.688 4.688 0 0 1-4.688 4.688ZM4.937 10.313V22.5a2.812 2.812 0 0 0 2.813 2.813h15a2.812 2.812 0 0 0 2.813-2.813v-9.375a2.812 2.812 0 0 0-2.813-2.813H4.937Z"/> <path d="M18.677 6.159a.77.77 0 0 1-.769-.77V3.276a1.79 1.79 0 0 0-.6-1.438 1.493 1.493 0 0 0-1.284-.246l-12.9 2.93a.77.77 0 0 0-.6.769.769.769 0 0 1-1.539 0A2.306 2.306 0 0 1 2.778 3.02L15.685.091a3 3 0 0 1 2.585.546 3.338 3.338 0 0 1 1.177 2.638V5.39a.77.77 0 0 1-.77.769Zm1.539 9.227H14.83a3.077 3.077 0 0 1-2.175-5.25 3.074 3.074 0 0 1 2.175-.902h5.385a.77.77 0 0 1 .769.77v4.613a.77.77 0 0 1-.77.769Zm-5.385-4.614a1.539 1.539 0 1 0 0 3.076h4.616v-3.076H14.83Z" fill="currentColor"/>
<path d="M24.625 10.312a.937.937 0 0 1-.937-.937V6.797a2.184 2.184 0 0 0-.732-1.754 1.82 1.82 0 0 0-1.565-.3L5.669 8.315a.938.938 0 0 0-.731.938.938.938 0 0 1-1.875 0 2.813 2.813 0 0 1 2.184-2.766l15.731-3.572a3.656 3.656 0 0 1 3.15.666 4.069 4.069 0 0 1 1.435 3.216v2.578a.938.938 0 0 1-.938.937ZM26.5 21.563h-6.563a3.75 3.75 0 1 1 0-7.5H26.5a.938.938 0 0 1 .938.937v5.625a.938.938 0 0 1-.938.938Zm-6.563-5.625a1.875 1.875 0 1 0 0 3.75h5.625v-3.75h-5.625Z"/>
</g>
<defs>
<clipPath id="wallet_svg__a">
<path fill="#fff" d="M.25 0h30v30h-30z"/>
</clipPath>
</defs>
</svg> </svg>
...@@ -60,9 +60,9 @@ export function AddressHighlightProvider({ children }: AddressHighlightProviderP ...@@ -60,9 +60,9 @@ export function AddressHighlightProvider({ children }: AddressHighlightProviderP
); );
} }
export function useAddressHighlightContext() { export function useAddressHighlightContext(disabled?: boolean) {
const context = React.useContext(AddressHighlightContext); const context = React.useContext(AddressHighlightContext);
if (context === undefined) { if (context === undefined || disabled) {
return null; return null;
} }
return context; return context;
......
...@@ -17,7 +17,7 @@ const TEST_URLS: Record<AdBannerProviders, string> = { ...@@ -17,7 +17,7 @@ const TEST_URLS: Record<AdBannerProviders, string> = {
adbutler: 'https://servedbyadbutler.com/app.js', adbutler: 'https://servedbyadbutler.com/app.js',
hype: 'https://api.hypelab.com/v1/scripts/hp-sdk.js', hype: 'https://api.hypelab.com/v1/scripts/hp-sdk.js',
// I don't have an url for getit to test // I don't have an url for getit to test
getit: DEFAULT_URL, // getit: DEFAULT_URL,
none: DEFAULT_URL, none: DEFAULT_URL,
}; };
......
...@@ -2,7 +2,7 @@ import type { UseToastOptions, ToastProps } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ import type { UseToastOptions, ToastProps } from '@chakra-ui/react';
import { createToastFn, useChakra } from '@chakra-ui/react'; import { createToastFn, useChakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Toast from 'ui/shared/Toast'; import Toast from 'ui/shared/chakra/Toast';
// there is no toastComponent prop in UseToastOptions type // there is no toastComponent prop in UseToastOptions type
// but these options will be passed to createRenderToast under the hood // but these options will be passed to createRenderToast under the hood
...@@ -14,6 +14,7 @@ const defaultOptions: UseToastOptions & { toastComponent?: React.FC<ToastProps> ...@@ -14,6 +14,7 @@ const defaultOptions: UseToastOptions & { toastComponent?: React.FC<ToastProps>
containerStyle: { containerStyle: {
margin: 8, margin: 8,
}, },
variant: 'subtle',
}; };
export default function useToastModified() { export default function useToastModified() {
......
import { uniq } from 'lodash'; import _uniq from 'lodash/uniq';
import isBrowser from './isBrowser'; import isBrowser from './isBrowser';
...@@ -27,7 +27,7 @@ export function saveToRecentKeywords(value: string) { ...@@ -27,7 +27,7 @@ export function saveToRecentKeywords(value: string) {
} }
const keywordsArr = getRecentSearchKeywords(); const keywordsArr = getRecentSearchKeywords();
const result = uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1); const result = _uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1);
window.localStorage.setItem(RECENT_KEYWORDS_LS_KEY, JSON.stringify(result)); window.localStorage.setItem(RECENT_KEYWORDS_LS_KEY, JSON.stringify(result));
} }
......
export const multiple = [
{ address: '0xA84d24bD8ACE4d349C5f8c5DeeDd8bc071Ce5e2b', name: null },
{ address: '0xc9e91eDeA9DC16604022e4E5b437Df9c64EdB05A', name: 'Diamond' },
{ address: '0x2041832c62C0F89426b48B5868146C0b1fcd23E7', name: null },
{ address: '0x5f7DC6ECcF05594429671F83cc0e42EE18bC0974', name: 'VariablePriceFacet' },
{ address: '0x7abC92E242e88e4B0d6c5Beb4Df80e94D2c8A78c', name: null },
{ address: '0x84178a0c58A860eCCFB7E3aeA64a09543062A356', name: 'MultiSaleFacet' },
{ address: '0x33aD95537e63e9f09d96dE201e10715Ed40D9400', name: 'SVGTemplatesFacet' },
{ address: '0xfd86Aa7f902185a8Df9859c25E4BF52D3DaDd9FA', name: 'ERC721AReceiverFacet' },
{ address: '0x6945a35df18e59Ce09fec4B6cD3C4F9cFE6369de', name: null },
];
...@@ -299,7 +299,7 @@ export const stabilityTx: Transaction = { ...@@ -299,7 +299,7 @@ export const stabilityTx: Transaction = {
decimals: '18', decimals: '18',
exchange_rate: '123.567', exchange_rate: '123.567',
holders: '92', holders: '92',
icon_url: null, icon_url: 'https://example.com/icon.png',
name: 'Stability Gas', name: 'Stability Gas',
symbol: 'GAS', symbol: 'GAS',
total_supply: '10000000000000000000000000', total_supply: '10000000000000000000000000',
...@@ -321,6 +321,24 @@ export const stabilityTx: Transaction = { ...@@ -321,6 +321,24 @@ export const stabilityTx: Transaction = {
}, },
}; };
export const celoTxn: Transaction = {
...base,
celo: {
gas_token: {
address: '0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1',
circulating_market_cap: null,
decimals: '18',
exchange_rate: '0.42',
holders: '205738',
icon_url: 'https://example.com/icon.png',
name: 'Celo Dollar',
symbol: 'cUSD',
total_supply: '7145754483836626799435133',
type: 'ERC-20',
},
},
};
export const base2 = { export const base2 = {
...base, ...base,
hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193',
......
...@@ -39,7 +39,8 @@ export function ad(): CspDev.DirectiveDescriptor { ...@@ -39,7 +39,8 @@ export function ad(): CspDev.DirectiveDescriptor {
// adbutler // adbutler
'servedbyadbutler.com', 'servedbyadbutler.com',
`'sha256-${ Base64.stringify(sha256(connectAdbutler)) }'`, `'sha256-${ Base64.stringify(sha256(connectAdbutler)) }'`,
`'sha256-${ Base64.stringify(sha256(placeAd ?? '')) }'`, `'sha256-${ Base64.stringify(sha256(placeAd(undefined) ?? '')) }'`,
`'sha256-${ Base64.stringify(sha256(placeAd('mobile') ?? '')) }'`,
// slise // slise
'*.slise.xyz', '*.slise.xyz',
......
...@@ -70,13 +70,13 @@ ...@@ -70,13 +70,13 @@
"chakra-react-select": "^4.4.3", "chakra-react-select": "^4.4.3",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"d3": "^7.6.1", "d3": "^7.6.1",
"dappscout-iframe": "0.2.1", "dappscout-iframe": "0.2.2",
"dayjs": "^1.11.5", "dayjs": "^1.11.5",
"dom-to-image": "^2.6.0", "dom-to-image": "^2.6.0",
"focus-visible": "^5.2.0", "focus-visible": "^5.2.0",
"framer-motion": "^6.5.1", "framer-motion": "^6.5.1",
"getit-sdk": "^1.0.4", "getit-sdk": "^1.0.4",
"gradient-avatar": "^1.0.2", "gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git",
"graphiql": "^2.2.0", "graphiql": "^2.2.0",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"graphql-ws": "^5.11.3", "graphql-ws": "^5.11.3",
......
import type { TestFixture, Page } from '@playwright/test';
import _isEqual from 'lodash/isEqual';
import type { PublicRpcSchema } from 'viem';
import { getEnvValue } from 'configs/app/utils';
type Params = PublicRpcSchema[number];
export type MockRpcResponseFixture = (params: Params) => Promise<void>;
// WIP
const fixture: TestFixture<MockRpcResponseFixture, { page: Page }> = async({ page }, use) => {
await use(async({ Method, ReturnType }) => {
const rpcUrl = getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL');
if (!rpcUrl) {
return;
}
await page.route(rpcUrl, (route) => {
const method = route.request().method();
if (method !== 'POST') {
route.continue();
return;
}
const json = route.request().postDataJSON();
const id = json?.id;
const payload = {
id,
jsonrpc: '2.0',
method: Method,
// TODO: add params to match actual payload
};
if (_isEqual(json, payload) && id !== undefined) {
return route.fulfill({
status: 200,
body: JSON.stringify({
id,
jsonrpc: '2.0',
result: ReturnType,
}),
});
}
});
});
};
export default fixture;
...@@ -8,6 +8,7 @@ import * as mockConfigResponse from './fixtures/mockConfigResponse'; ...@@ -8,6 +8,7 @@ import * as mockConfigResponse from './fixtures/mockConfigResponse';
import * as mockContractReadResponse from './fixtures/mockContractReadResponse'; import * as mockContractReadResponse from './fixtures/mockContractReadResponse';
import * as mockEnvs from './fixtures/mockEnvs'; import * as mockEnvs from './fixtures/mockEnvs';
import * as mockFeatures from './fixtures/mockFeatures'; import * as mockFeatures from './fixtures/mockFeatures';
import * as mockRpcResponse from './fixtures/mockRpcResponse';
import * as mockTextAd from './fixtures/mockTextAd'; import * as mockTextAd from './fixtures/mockTextAd';
import * as render from './fixtures/render'; import * as render from './fixtures/render';
import * as socketServer from './fixtures/socketServer'; import * as socketServer from './fixtures/socketServer';
...@@ -20,6 +21,7 @@ interface Fixtures { ...@@ -20,6 +21,7 @@ interface Fixtures {
mockContractReadResponse: mockContractReadResponse.MockContractReadResponseFixture; mockContractReadResponse: mockContractReadResponse.MockContractReadResponseFixture;
mockEnvs: mockEnvs.MockEnvsFixture; mockEnvs: mockEnvs.MockEnvsFixture;
mockFeatures: mockFeatures.MockFeaturesFixture; mockFeatures: mockFeatures.MockFeaturesFixture;
mockRpcResponse: mockRpcResponse.MockRpcResponseFixture;
createSocket: socketServer.CreateSocketFixture; createSocket: socketServer.CreateSocketFixture;
injectMetaMaskProvider: injectMetaMaskProvider.InjectMetaMaskProvider; injectMetaMaskProvider: injectMetaMaskProvider.InjectMetaMaskProvider;
mockTextAd: mockTextAd.MockTextAdFixture; mockTextAd: mockTextAd.MockTextAdFixture;
...@@ -33,6 +35,7 @@ const test = base.extend<Fixtures>({ ...@@ -33,6 +35,7 @@ const test = base.extend<Fixtures>({
mockContractReadResponse: mockContractReadResponse.default, mockContractReadResponse: mockContractReadResponse.default,
mockEnvs: mockEnvs.default, mockEnvs: mockEnvs.default,
mockFeatures: mockFeatures.default, mockFeatures: mockFeatures.default,
mockRpcResponse: mockRpcResponse.default,
// FIXME: for some reason Playwright does not intercept requests to text ad provider when running multiple tests in parallel // FIXME: for some reason Playwright does not intercept requests to text ad provider when running multiple tests in parallel
// even if we have a global request interceptor (maybe it is related to service worker issue, maybe not) // even if we have a global request interceptor (maybe it is related to service worker issue, maybe not)
// so we have to inject mockTextAd fixture in each test and mock the response where it is needed // so we have to inject mockTextAd fixture in each test and mock the response where it is needed
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
| "clock" | "clock"
| "coins/bitcoin" | "coins/bitcoin"
| "collection" | "collection"
| "contracts/proxy"
| "contracts/regular_many" | "contracts/regular_many"
| "contracts/regular" | "contracts/regular"
| "contracts/verified_many" | "contracts/verified_many"
......
...@@ -17,7 +17,6 @@ const sizes = { ...@@ -17,7 +17,6 @@ const sizes = {
xl: defineStyle({ xl: defineStyle({
fontSize: '40px', fontSize: '40px',
lineHeight: '48px', lineHeight: '48px',
letterSpacing: '-1px',
}), }),
lg: defineStyle({ lg: defineStyle({
fontSize: '32px', fontSize: '32px',
......
...@@ -19,6 +19,10 @@ const semanticTokens = { ...@@ -19,6 +19,10 @@ const semanticTokens = {
link_hovered: { link_hovered: {
'default': 'blue.400', 'default': 'blue.400',
}, },
icon_info: {
'default': 'gray.400',
_dark: 'gray.500',
},
error: { error: {
'default': 'red.500', 'default': 'red.500',
_dark: 'red.500', _dark: 'red.500',
......
...@@ -24,9 +24,7 @@ export interface UserTags { ...@@ -24,9 +24,7 @@ export interface UserTags {
export type AddressParamBasic = { export type AddressParamBasic = {
hash: string; hash: string;
// API doesn't return hash in this model yet implementations: Array<AddressImplementation> | null;
// will be fixed in the future releases
implementations: Array<Omit<AddressImplementation, 'address'>> | null;
name: string | null; name: string | null;
is_contract: boolean; is_contract: boolean;
is_verified: boolean | null; is_verified: boolean | null;
......
...@@ -77,6 +77,10 @@ export type Transaction = { ...@@ -77,6 +77,10 @@ export type Transaction = {
validator_address: AddressParam; validator_address: AddressParam;
validator_fee: string; validator_fee: string;
}; };
// Celo fields
celo?: {
gas_token: TokenInfo<'ERC-20'> | null;
};
// zkEvm fields // zkEvm fields
zkevm_verify_hash?: string; zkevm_verify_hash?: string;
zkevm_batch_number?: number; zkevm_batch_number?: number;
......
import type { ArrayElement } from 'types/utils'; import type { ArrayElement } from 'types/utils';
export const SUPPORTED_AD_BANNER_PROVIDERS = [ 'slise', 'adbutler', 'coinzilla', 'hype', 'getit', 'none' ] as const; export const SUPPORTED_AD_BANNER_PROVIDERS = [
'slise',
'adbutler',
'coinzilla',
'hype',
// 'getit', // temporary disabled
'none',
] as const;
export type AdBannerProviders = ArrayElement<typeof SUPPORTED_AD_BANNER_PROVIDERS>; export type AdBannerProviders = ArrayElement<typeof SUPPORTED_AD_BANNER_PROVIDERS>;
export const SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS = [ 'adbutler' ] as const; export const SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS = [ 'adbutler' ] as const;
......
import { Box, Text, Icon, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react'; import { Box, Text, Icon, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
// This icon doesn't work properly when it is in the sprite // This icon doesn't work properly when it is in the sprite
...@@ -7,6 +7,7 @@ import React from 'react'; ...@@ -7,6 +7,7 @@ import React from 'react';
import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import solidityScanIcon from 'icons/brands/solidity_scan.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { SOLIDITYSCAN_REPORT } from 'stubs/contract'; import { SOLIDITYSCAN_REPORT } from 'stubs/contract';
import Popover from 'ui/shared/chakra/Popover';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton';
import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails';
......
...@@ -2,7 +2,6 @@ import { ...@@ -2,7 +2,6 @@ import {
Flex, Flex,
Button, Button,
chakra, chakra,
Popover,
PopoverTrigger, PopoverTrigger,
PopoverBody, PopoverBody,
PopoverContent, PopoverContent,
...@@ -14,6 +13,7 @@ import { ...@@ -14,6 +13,7 @@ import {
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
......
...@@ -7,7 +7,6 @@ import { ...@@ -7,7 +7,6 @@ import {
Modal, Modal,
ModalCloseButton, ModalCloseButton,
ModalContent, ModalContent,
Popover,
PopoverBody, PopoverBody,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
...@@ -22,6 +21,7 @@ import type { SmartContractExternalLibrary } from 'types/api/contract'; ...@@ -22,6 +21,7 @@ import type { SmartContractExternalLibrary } from 'types/api/contract';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import Popover from 'ui/shared/chakra/Popover';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
......
import { import {
chakra, chakra,
Popover,
PopoverBody, PopoverBody,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
...@@ -14,6 +13,7 @@ import { ...@@ -14,6 +13,7 @@ import {
import React from 'react'; import React from 'react';
import { times } from 'lib/html-entities'; import { times } from 'lib/html-entities';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
interface Props { interface Props {
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Flex, Flex,
Grid, Grid,
Hide, Hide,
Popover,
PopoverBody, PopoverBody,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
...@@ -23,6 +22,7 @@ import { route } from 'nextjs-routes'; ...@@ -23,6 +22,7 @@ import { route } from 'nextjs-routes';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import Popover from 'ui/shared/chakra/Popover';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity'; import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
......
import { Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react'; import { PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { FormattedData } from './types'; import type { FormattedData } from './types';
import Popover from 'ui/shared/chakra/Popover';
import TokenSelectButton from './TokenSelectButton'; import TokenSelectButton from './TokenSelectButton';
import TokenSelectMenu from './TokenSelectMenu'; import TokenSelectMenu from './TokenSelectMenu';
import useTokenSelect from './useTokenSelect'; import useTokenSelect from './useTokenSelect';
......
...@@ -53,7 +53,7 @@ const TokenBalances = () => { ...@@ -53,7 +53,7 @@ const TokenBalances = () => {
name="Net Worth" name="Net Worth"
value={ addressData?.exchange_rate ? `${ prefix }$${ totalUsd.toFormat(2) }` : 'N/A' } value={ addressData?.exchange_rate ? `${ prefix }$${ totalUsd.toFormat(2) }` : 'N/A' }
isLoading={ addressQuery.isPending || tokenQuery.isPending } isLoading={ addressQuery.isPending || tokenQuery.isPending }
icon={ <IconSvg name="wallet" boxSize="24px" flexShrink={ 0 } color="text_secondary"/> } icon={ <IconSvg name="wallet" boxSize="20px" flexShrink={ 0 } color="text_secondary"/> }
/> />
<TokenBalancesItem <TokenBalancesItem
name={ `${ currencyUnits.ether } Balance` } name={ `${ currencyUnits.ether } Balance` }
......
...@@ -86,33 +86,33 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => { ...@@ -86,33 +86,33 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => {
</Skeleton> </Skeleton>
) : data.tx_count } ) : data.tx_count }
</Td> </Td>
<Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ BigNumber(data.gas_used || 0).toFormat() }</Skeleton>
<Flex mt={ 2 }>
<Tooltip label={ isLoading ? undefined : 'Gas Used %' }>
<Box>
<Utilization
colorScheme="gray"
value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() }
isLoading={ isLoading }
/>
</Box>
</Tooltip>
{ data.gas_target_percentage && (
<>
<TextSeparator color={ separatorColor } mx={ 1 }/>
<GasUsedToTargetRatio value={ data.gas_target_percentage } isLoading={ isLoading }/>
</>
) }
</Flex>
</Td>
{ !isRollup && !config.UI.views.block.hiddenFields?.total_reward && ( { !isRollup && !config.UI.views.block.hiddenFields?.total_reward && (
<Td fontSize="sm"> <Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ BigNumber(data.gas_used || 0).toFormat() }</Skeleton> <Skeleton isLoaded={ !isLoading } display="inline-block">
<Flex mt={ 2 }> { totalReward.toFixed(8) }
<Tooltip label={ isLoading ? undefined : 'Gas Used %' }> </Skeleton>
<Box>
<Utilization
colorScheme="gray"
value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() }
isLoading={ isLoading }
/>
</Box>
</Tooltip>
{ data.gas_target_percentage && (
<>
<TextSeparator color={ separatorColor } mx={ 1 }/>
<GasUsedToTargetRatio value={ data.gas_target_percentage } isLoading={ isLoading }/>
</>
) }
</Flex>
</Td> </Td>
) } ) }
<Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ totalReward.toFixed(8) }
</Skeleton>
</Td>
{ !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees && ( { !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees && (
<Td fontSize="sm"> <Td fontSize="sm">
<Flex alignItems="center" columnGap={ 2 }> <Flex alignItems="center" columnGap={ 2 }>
......
import { import {
Link, Link,
chakra, chakra,
Popover,
PopoverTrigger, PopoverTrigger,
Portal, Portal,
PopoverContent, PopoverContent,
...@@ -21,6 +20,7 @@ import type { FormFields } from '../types'; ...@@ -21,6 +20,7 @@ import type { FormFields } from '../types';
import type { SmartContractVerificationConfig, SmartContractVerificationMethod } from 'types/api/contract'; import type { SmartContractVerificationConfig, SmartContractVerificationMethod } from 'types/api/contract';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import Popover from 'ui/shared/chakra/Popover';
import FancySelect from 'ui/shared/FancySelect/FancySelect'; import FancySelect from 'ui/shared/FancySelect/FancySelect';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -105,7 +105,7 @@ const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props ...@@ -105,7 +105,7 @@ const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props
<Popover trigger="hover" isLazy placement={ isMobile ? 'bottom-end' : 'right-start' } offset={ [ -8, 8 ] }> <Popover trigger="hover" isLazy placement={ isMobile ? 'bottom-end' : 'right-start' } offset={ [ -8, 8 ] }>
<PopoverTrigger> <PopoverTrigger>
<chakra.span display="inline-block" ml={ 1 } cursor="pointer" verticalAlign="middle" h="22px"> <chakra.span display="inline-block" ml={ 1 } cursor="pointer" verticalAlign="middle" h="22px">
<IconSvg name="info" boxSize={ 5 } color="link" _hover={{ color: 'link_hovered' }}/> <IconSvg name="info" boxSize={ 5 } color="icon_info" _hover={{ color: 'link_hovered' }}/>
</chakra.span> </chakra.span>
</PopoverTrigger> </PopoverTrigger>
<Portal> <Portal>
......
...@@ -67,7 +67,7 @@ const OptimisticDepositsListItem = ({ item, isLoading }: Props) => { ...@@ -67,7 +67,7 @@ const OptimisticDepositsListItem = ({ item, isLoading }: Props) => {
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 txn origin</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>L1 txn origin</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value> <ListItemMobileGrid.Value>
<AddressEntityL1 <AddressEntityL1
address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, ens_domain_name: null }} address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, ens_domain_name: null, implementations: null }}
isLoading={ isLoading } isLoading={ isLoading }
noCopy noCopy
truncation="constant" truncation="constant"
......
...@@ -59,7 +59,7 @@ const OptimisticDepositsTableItem = ({ item, isLoading }: Props) => { ...@@ -59,7 +59,7 @@ const OptimisticDepositsTableItem = ({ item, isLoading }: Props) => {
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<AddressEntityL1 <AddressEntityL1
address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, ens_domain_name: null }} address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, ens_domain_name: null, implementations: null }}
isLoading={ isLoading } isLoading={ isLoading }
truncation="constant" truncation="constant"
noCopy noCopy
......
...@@ -17,7 +17,7 @@ import { currencyUnits } from 'lib/units'; ...@@ -17,7 +17,7 @@ import { currencyUnits } from 'lib/units';
import AddressFromTo from 'ui/shared/address/AddressFromTo'; import AddressFromTo from 'ui/shared/address/AddressFromTo';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFee from 'ui/shared/tx/TxFee';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags'; import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo'; import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxType from 'ui/txs/TxType'; import TxType from 'ui/txs/TxType';
...@@ -96,13 +96,7 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -96,13 +96,7 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
{ !config.UI.views.tx.hiddenFields?.tx_fee && ( { !config.UI.views.tx.hiddenFields?.tx_fee && (
<Skeleton isLoaded={ !isLoading } display="flex" whiteSpace="pre" my="3px"> <Skeleton isLoaded={ !isLoading } display="flex" whiteSpace="pre" my="3px">
<Text as="span">Fee </Text> <Text as="span">Fee </Text>
{ tx.stability_fee ? ( <TxFee tx={ tx } accuracy={ 5 } color="text_secondary"/>
<TxFeeStability data={ tx.stability_fee } accuracy={ 5 } color="text_secondary" hideUsd/>
) : (
<Text as="span" variant="secondary">
{ tx.fee.value ? `${ getValueWithUnit(tx.fee.value).dp(5).toFormat() } ${ currencyUnits.ether }` : '-' }
</Text>
) }
</Skeleton> </Skeleton>
) } ) }
</Flex> </Flex>
......
...@@ -16,7 +16,7 @@ import { currencyUnits } from 'lib/units'; ...@@ -16,7 +16,7 @@ import { currencyUnits } from 'lib/units';
import AddressFromTo from 'ui/shared/address/AddressFromTo'; import AddressFromTo from 'ui/shared/address/AddressFromTo';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFee from 'ui/shared/tx/TxFee';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags'; import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo'; import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxType from 'ui/txs/TxType'; import TxType from 'ui/txs/TxType';
...@@ -76,18 +76,14 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -76,18 +76,14 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
/> />
{ !config.UI.views.tx.hiddenFields?.value && ( { !config.UI.views.tx.hiddenFields?.value && (
<Skeleton isLoaded={ !isLoading } mb={ 2 } fontSize="sm" w="fit-content"> <Skeleton isLoaded={ !isLoading } mb={ 2 } fontSize="sm" w="fit-content">
<Text as="span">Value { currencyUnits.ether } </Text> <Text as="span">Value </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text> <Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether }</Text>
</Skeleton> </Skeleton>
) } ) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && ( { !config.UI.views.tx.hiddenFields?.tx_fee && (
<Skeleton isLoaded={ !isLoading } fontSize="sm" w="fit-content" display="flex" whiteSpace="pre"> <Skeleton isLoaded={ !isLoading } fontSize="sm" w="fit-content" display="flex" whiteSpace="pre">
<Text as="span">Fee { !config.UI.views.tx.hiddenFields?.fee_currency ? `${ currencyUnits.ether } ` : '' }</Text> <Text as="span">Fee </Text>
{ tx.stability_fee ? ( <TxFee tx={ tx } accuracy={ 5 } color="text_secondary"/>
<TxFeeStability data={ tx.stability_fee } accuracy={ 5 } color="text_secondary" hideUsd/>
) : (
<Text as="span" variant="secondary">{ tx.fee.value ? getValueWithUnit(tx.fee.value).dp(5).toFormat() : '-' }</Text>
) }
</Skeleton> </Skeleton>
) } ) }
</Box> </Box>
......
...@@ -3,7 +3,6 @@ import React from 'react'; ...@@ -3,7 +3,6 @@ import React from 'react';
import * as statsMock from 'mocks/stats/index'; import * as statsMock from 'mocks/stats/index';
import { test, expect } from 'playwright/lib'; import { test, expect } from 'playwright/lib';
import * as pwConfig from 'playwright/utils/config';
import Stats from './Stats'; import Stats from './Stats';
...@@ -18,14 +17,6 @@ test.describe('all items', () => { ...@@ -18,14 +17,6 @@ test.describe('all items', () => {
test('+@mobile +@dark-mode', async() => { test('+@mobile +@dark-mode', async() => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test.describe('screen xl', () => {
test.use({ viewport: pwConfig.viewport.xl });
test('', async() => {
await expect(component).toHaveScreenshot();
});
});
}); });
test('4 items default view +@mobile -@default', async({ render, mockApiResponse, mockEnvs }) => { test('4 items default view +@mobile -@default', async({ render, mockApiResponse, mockEnvs }) => {
......
...@@ -2,8 +2,6 @@ import { Grid } from '@chakra-ui/react'; ...@@ -2,8 +2,6 @@ import { Grid } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import { route } from 'nextjs-routes';
import config from 'configs/app'; import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { WEI } from 'lib/consts'; import { WEI } from 'lib/consts';
...@@ -11,8 +9,7 @@ import { HOMEPAGE_STATS } from 'stubs/stats'; ...@@ -11,8 +9,7 @@ import { HOMEPAGE_STATS } from 'stubs/stats';
import GasInfoTooltip from 'ui/shared/gas/GasInfoTooltip'; import GasInfoTooltip from 'ui/shared/gas/GasInfoTooltip';
import GasPrice from 'ui/shared/gas/GasPrice'; import GasPrice from 'ui/shared/gas/GasPrice';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import StatsWidget from 'ui/shared/stats/StatsWidget';
import StatsItem from './StatsItem';
const hasAvgBlockTime = config.UI.homepage.showAvgBlockTime; const hasAvgBlockTime = config.UI.homepage.showAvgBlockTime;
const rollupFeature = config.features.rollup; const rollupFeature = config.features.rollup;
...@@ -58,7 +55,7 @@ const Stats = () => { ...@@ -58,7 +55,7 @@ const Stats = () => {
let content; let content;
const lastItemTouchStyle = { gridColumn: { base: 'span 2', lg: 'unset' } }; const lastItemStyle = { gridColumn: 'span 2' };
let itemsCount = 5; let itemsCount = 5;
!hasGasTracker && itemsCount--; !hasGasTracker && itemsCount--;
...@@ -75,12 +72,10 @@ const Stats = () => { ...@@ -75,12 +72,10 @@ const Stats = () => {
isLoading={ isLoading } isLoading={ isLoading }
name="info" name="info"
boxSize={ 5 } boxSize={ 5 }
display="block" flexShrink={ 0 }
cursor="pointer" cursor="pointer"
color="icon_info"
_hover={{ color: 'link_hovered' }} _hover={{ color: 'link_hovered' }}
position="absolute"
top={{ base: 'calc(50% - 12px)', lg: '10px', xl: 'calc(50% - 12px)' }}
right="10px"
/> />
</GasInfoTooltip> </GasInfoTooltip>
) : null; ) : null;
...@@ -88,80 +83,80 @@ const Stats = () => { ...@@ -88,80 +83,80 @@ const Stats = () => {
content = ( content = (
<> <>
{ rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && ( { rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && (
<StatsItem <StatsWidget
icon="txn_batches" icon="txn_batches_slim"
title="Latest batch" label="Latest batch"
value={ (zkEvmLatestBatchQuery.data || 0).toLocaleString() } value={ (zkEvmLatestBatchQuery.data || 0).toLocaleString() }
url={ route({ pathname: '/batches' }) } href={{ pathname: '/batches' }}
isLoading={ isLoading } isLoading={ isLoading }
/> />
) } ) }
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && ( { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && (
<StatsItem <StatsWidget
icon="txn_batches" icon="txn_batches_slim"
title="Latest batch" label="Latest batch"
value={ (zkSyncLatestBatchQuery.data || 0).toLocaleString() } value={ (zkSyncLatestBatchQuery.data || 0).toLocaleString() }
url={ route({ pathname: '/batches' }) } href={{ pathname: '/batches' }}
isLoading={ isLoading } isLoading={ isLoading }
/> />
) } ) }
{ !(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync')) && ( { !(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync')) && (
<StatsItem <StatsWidget
icon="block" icon="block_slim"
title="Total blocks" label="Total blocks"
value={ Number(data.total_blocks).toLocaleString() } value={ Number(data.total_blocks).toLocaleString() }
url={ route({ pathname: '/blocks' }) } href={{ pathname: '/blocks' }}
isLoading={ isLoading } isLoading={ isLoading }
/> />
) } ) }
{ hasAvgBlockTime && ( { hasAvgBlockTime && (
<StatsItem <StatsWidget
icon="clock-light" icon="clock"
title="Average block time" label="Average block time"
value={ `${ (data.average_block_time / 1000).toFixed(1) }s` } value={ `${ (data.average_block_time / 1000).toFixed(1) }s` }
isLoading={ isLoading } isLoading={ isLoading }
/> />
) } ) }
<StatsItem <StatsWidget
icon="transactions" icon="transactions_slim"
title="Total transactions" label="Total transactions"
value={ Number(data.total_transactions).toLocaleString() } value={ Number(data.total_transactions).toLocaleString() }
url={ route({ pathname: '/txs' }) } href={{ pathname: '/txs' }}
isLoading={ isLoading } isLoading={ isLoading }
/> />
{ rollupFeature.isEnabled && data.last_output_root_size && ( { rollupFeature.isEnabled && data.last_output_root_size && (
<StatsItem <StatsWidget
icon="txn_batches" icon="txn_batches_slim"
title="Latest L1 state batch" label="Latest L1 state batch"
value={ data.last_output_root_size } value={ data.last_output_root_size }
url={ route({ pathname: '/batches' }) } href={{ pathname: '/batches' }}
isLoading={ isLoading } isLoading={ isLoading }
/> />
) } ) }
<StatsItem <StatsWidget
icon="wallet" icon="wallet"
title="Wallet addresses" label="Wallet addresses"
value={ Number(data.total_addresses).toLocaleString() } value={ Number(data.total_addresses).toLocaleString() }
_last={ isOdd ? lastItemTouchStyle : undefined }
isLoading={ isLoading } isLoading={ isLoading }
_last={ isOdd ? lastItemStyle : undefined }
/> />
{ hasGasTracker && data.gas_prices && ( { hasGasTracker && data.gas_prices && (
<StatsItem <StatsWidget
icon="gas" icon="gas"
title="Gas tracker" label="Gas tracker"
value={ data.gas_prices.average ? <GasPrice data={ data.gas_prices.average }/> : 'N/A' } value={ data.gas_prices.average ? <GasPrice data={ data.gas_prices.average }/> : 'N/A' }
_last={ isOdd ? lastItemTouchStyle : undefined } hint={ gasInfoTooltip }
tooltip={ gasInfoTooltip }
isLoading={ isLoading } isLoading={ isLoading }
_last={ isOdd ? lastItemStyle : undefined }
/> />
) } ) }
{ data.rootstock_locked_btc && ( { data.rootstock_locked_btc && (
<StatsItem <StatsWidget
icon="coins/bitcoin" icon="coins/bitcoin"
title="BTC Locked in 2WP" label="BTC Locked in 2WP"
value={ `${ BigNumber(data.rootstock_locked_btc).div(WEI).dp(0).toFormat() } RBTC` } value={ `${ BigNumber(data.rootstock_locked_btc).div(WEI).dp(0).toFormat() } RBTC` }
_last={ isOdd ? lastItemTouchStyle : undefined }
isLoading={ isLoading } isLoading={ isLoading }
_last={ isOdd ? lastItemStyle : undefined }
/> />
) } ) }
</> </>
...@@ -170,10 +165,10 @@ const Stats = () => { ...@@ -170,10 +165,10 @@ const Stats = () => {
return ( return (
<Grid <Grid
gridTemplateColumns={{ lg: `repeat(${ itemsCount }, 1fr)`, base: '1fr 1fr' }} gridTemplateColumns="1fr 1fr"
gridTemplateRows={{ lg: 'none', base: undefined }}
gridGap={{ base: 1, lg: 2 }} gridGap={{ base: 1, lg: 2 }}
marginTop={ 3 } flexBasis="50%"
flexGrow={ 1 }
> >
{ content } { content }
</Grid> </Grid>
......
import type { SystemStyleObject } from '@chakra-ui/react';
import { Skeleton, Flex, useColorModeValue, chakra } from '@chakra-ui/react';
import React from 'react';
import breakpoints from 'theme/foundations/breakpoints';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg';
type Props = {
icon: IconName;
title: string;
value: string | React.ReactNode;
className?: string;
tooltip?: React.ReactNode;
url?: string;
isLoading?: boolean;
}
const LARGEST_BREAKPOINT = '1240px';
const StatsItem = ({ icon, title, value, className, tooltip, url, isLoading }: Props) => {
const sxContainer: SystemStyleObject = {
[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`]: { flexDirection: 'column' },
};
const sxText: SystemStyleObject = {
[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`]: { alignItems: 'center' },
};
const bgColor = useColorModeValue('blue.50', 'whiteAlpha.100');
const loadingBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return (
<Flex
backgroundColor={ isLoading ? loadingBgColor : bgColor }
padding={ 3 }
borderRadius="md"
flexDirection="row"
sx={ sxContainer }
alignItems="center"
columnGap={ 3 }
rowGap={ 2 }
className={ className }
color={ useColorModeValue('black', 'white') }
position="relative"
{ ...(url && !isLoading ? {
as: 'a',
href: url,
} : {}) }
>
<IconSvg name={ icon } boxSize={ 7 } isLoading={ isLoading } borderRadius="base"/>
<Flex
flexDirection="column"
alignItems="start"
sx={ sxText }
>
<Skeleton isLoaded={ !isLoading } color="text_secondary" fontSize="xs" lineHeight="16px" borderRadius="base">
<span>{ title }</span>
</Skeleton>
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 } fontSize="md" color={ useColorModeValue('black', 'white') } borderRadius="base">
{ typeof value === 'string' ? <span>{ value }</span> : value }
</Skeleton>
</Flex>
{ tooltip }
</Flex>
);
};
export default chakra(StatsItem);
import { Flex } from '@chakra-ui/react'; import { chakra, Flex, Box } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
...@@ -15,21 +15,25 @@ const ChainIndicatorChartContainer = ({ data, isError, isPending }: Props) => { ...@@ -15,21 +15,25 @@ const ChainIndicatorChartContainer = ({ data, isError, isPending }: Props) => {
const content = (() => { const content = (() => {
if (isPending) { if (isPending) {
return <ContentLoader mt="auto"/>; return <ContentLoader mt="auto" fontSize="xs"/>;
} }
if (isError) { if (isError) {
return <DataFetchAlert/>; return <DataFetchAlert fontSize="xs" p={ 3 }/>;
} }
if (data[0].items.length === 0) { if (data[0].items.length === 0) {
return <span>no data</span>; return <chakra.span fontSize="xs">no data</chakra.span>;
} }
return <ChainIndicatorChart data={ data }/>; return (
<Box mx="-10px" my="-5px" h="calc(100% + 10px)" w="calc(100% + 20px)">
<ChainIndicatorChart data={ data }/>
</Box>
);
})(); })();
return <Flex h={{ base: '150px', lg: 'auto' }} minH="150px" alignItems="flex-start" flexGrow={ 1 }>{ content }</Flex>; return <Flex h={{ base: '80px', lg: '110px' }} alignItems="flex-start" flexGrow={ 1 }>{ content }</Flex>;
}; };
export default React.memo(ChainIndicatorChartContainer); export default React.memo(ChainIndicatorChartContainer);
...@@ -6,8 +6,6 @@ import type { HomeStats } from 'types/api/stats'; ...@@ -6,8 +6,6 @@ import type { HomeStats } from 'types/api/stats';
import type { ChainIndicatorId } from 'types/homepage'; import type { ChainIndicatorId } from 'types/homepage';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import useIsMobile from 'lib/hooks/useIsMobile';
import IconSvg from 'ui/shared/IconSvg';
interface Props { interface Props {
id: ChainIndicatorId; id: ChainIndicatorId;
...@@ -21,42 +19,27 @@ interface Props { ...@@ -21,42 +19,27 @@ interface Props {
} }
const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onClick, stats }: Props) => { const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onClick, stats }: Props) => {
const isMobile = useIsMobile(); const activeColor = useColorModeValue('gray.500', 'gray.400');
const activeBgColor = useColorModeValue('white', 'black');
const activeBgColorDesktop = useColorModeValue('white', 'gray.900');
const activeBgColorMobile = useColorModeValue('white', 'black');
const activeBgColor = isMobile ? activeBgColorMobile : activeBgColorDesktop;
const handleClick = React.useCallback(() => { const handleClick = React.useCallback(() => {
onClick(id); onClick(id);
}, [ id, onClick ]); }, [ id, onClick ]);
const valueContent = (() => { const valueContent = (() => {
if (isMobile) {
return null;
}
if (stats.isPlaceholderData) {
return (
<Skeleton
h={ 3 }
w="70px"
my={ 1.5 }
// ssr: isMobile = undefined, isLoading = true
display={{ base: 'none', lg: 'block' }}
/>
);
}
if (!stats.data) { if (!stats.data) {
return <Text variant="secondary" fontWeight={ 400 }>no data</Text>; return <Text variant="secondary" fontWeight={ 400 }>no data</Text>;
} }
return <Text variant="secondary" fontWeight={ 600 }>{ value(stats.data) }</Text>; return (
<Skeleton isLoaded={ !stats.isPlaceholderData } variant="secondary" fontWeight={ 600 } minW="30px">
{ value(stats.data) }
</Skeleton>
);
})(); })();
const valueDiffContent = (() => { const valueDiffContent = (() => {
if (isMobile || !valueDiff) { if (!valueDiff) {
return null; return null;
} }
const diff = valueDiff(stats.data); const diff = valueDiff(stats.data);
...@@ -67,9 +50,9 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC ...@@ -67,9 +50,9 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC
const diffColor = diff >= 0 ? 'green.500' : 'red.500'; const diffColor = diff >= 0 ? 'green.500' : 'red.500';
return ( return (
<Skeleton isLoaded={ !stats.isPlaceholderData } ml={ 3 } display="flex" alignItems="center" color={ diffColor }> <Skeleton isLoaded={ !stats.isPlaceholderData } ml={ 1 } display="flex" alignItems="center" color={ diffColor }>
<IconSvg name="arrows/up-head" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/> <span>{ diff >= 0 ? '+' : '-' }</span>
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text> <Text color={ diffColor } fontWeight={ 600 }>{ Math.abs(diff) }%</Text>
</Skeleton> </Skeleton>
); );
})(); })();
...@@ -77,25 +60,28 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC ...@@ -77,25 +60,28 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC
return ( return (
<Flex <Flex
alignItems="center" alignItems="center"
columnGap={ 3 } columnGap={ 2 }
px={ 4 } flexGrow={{ base: 0, lg: 1 }}
py={ 2 } px={{ base: '6px', lg: 2 }}
py="6px"
as="li" as="li"
borderRadius="md" borderRadius="base"
cursor="pointer" cursor="pointer"
color={ isSelected ? activeColor : 'link' }
bgColor={ isSelected ? activeBgColor : undefined }
onClick={ handleClick } onClick={ handleClick }
bgColor={ isSelected ? activeBgColor : 'inherit' } fontSize="xs"
boxShadow={ isSelected ? 'lg' : 'none' } fontWeight={ 500 }
zIndex={ isSelected ? 1 : 'initial' }
_hover={{ _hover={{
activeBgColor, bgColor: activeBgColor,
color: isSelected ? activeColor : 'link_hovered',
zIndex: 1, zIndex: 1,
}} }}
> >
{ icon } { icon }
<Box> <Box display={{ base: 'none', lg: 'block' }}>
<Text fontFamily="heading" fontWeight={ 500 }>{ title }</Text> <span>{ title }</span>
<Flex alignItems="center"> <Flex alignItems="center" color="text">
{ valueContent } { valueContent }
{ valueDiffContent } { valueDiffContent }
</Flex> </Flex>
......
...@@ -22,7 +22,10 @@ test.describe('daily txs chart', () => { ...@@ -22,7 +22,10 @@ test.describe('daily txs chart', () => {
await mockApiResponse('stats_charts_txs', dailyTxsMock.base); await mockApiResponse('stats_charts_txs', dailyTxsMock.base);
await mockAssetResponse(statsMock.withSecondaryCoin.coin_image as string, './playwright/mocks/image_svg.svg'); await mockAssetResponse(statsMock.withSecondaryCoin.coin_image as string, './playwright/mocks/image_svg.svg');
component = await render(<ChainIndicators/>); component = await render(<ChainIndicators/>);
await page.hover('.ChartOverlay', { position: { x: 100, y: 100 } }); await page.waitForFunction(() => {
return document.querySelector('path[data-name="gradient-chart-area"]')?.getAttribute('opacity') === '1';
});
await page.hover('.ChartOverlay', { position: { x: 50, y: 50 } });
}); });
test('+@mobile', async() => { test('+@mobile', async() => {
...@@ -38,9 +41,11 @@ test.describe('daily txs chart', () => { ...@@ -38,9 +41,11 @@ test.describe('daily txs chart', () => {
}); });
}); });
test('partial data', async({ page, mockApiResponse, render }) => { test('partial data', async({ page, mockApiResponse, mockAssetResponse, render }) => {
await mockApiResponse('stats', statsMock.base); await mockApiResponse('stats', statsMock.base);
await mockApiResponse('stats_charts_txs', dailyTxsMock.partialData); await mockApiResponse('stats_charts_txs', dailyTxsMock.partialData);
await mockAssetResponse(statsMock.base.coin_image as string, './playwright/mocks/image_s.jpg');
const component = await render(<ChainIndicators/>); const component = await render(<ChainIndicators/>);
await page.waitForFunction(() => { await page.waitForFunction(() => {
return document.querySelector('path[data-name="gradient-chart-area"]')?.getAttribute('opacity') === '1'; return document.querySelector('path[data-name="gradient-chart-area"]')?.getAttribute('opacity') === '1';
...@@ -48,9 +53,11 @@ test('partial data', async({ page, mockApiResponse, render }) => { ...@@ -48,9 +53,11 @@ test('partial data', async({ page, mockApiResponse, render }) => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('no data', async({ mockApiResponse, render }) => { test('no data', async({ mockApiResponse, mockAssetResponse, render }) => {
await mockApiResponse('stats', statsMock.noChartData); await mockApiResponse('stats', statsMock.noChartData);
await mockApiResponse('stats_charts_txs', dailyTxsMock.noData); await mockApiResponse('stats_charts_txs', dailyTxsMock.noData);
await mockAssetResponse(statsMock.base.coin_image as string, './playwright/mocks/image_s.jpg');
const component = await render(<ChainIndicators/>); const component = await render(<ChainIndicators/>);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -38,10 +38,7 @@ const ChainIndicators = () => { ...@@ -38,10 +38,7 @@ const ChainIndicators = () => {
}, },
}); });
const bgColorDesktop = useColorModeValue('white', 'gray.900'); const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100');
const bgColorMobile = useColorModeValue('white', 'black');
const listBgColorDesktop = useColorModeValue('gray.50', 'black');
const listBgColorMobile = useColorModeValue('gray.50', 'gray.900');
if (indicators.length === 0) { if (indicators.length === 0) {
return null; return null;
...@@ -49,15 +46,15 @@ const ChainIndicators = () => { ...@@ -49,15 +46,15 @@ const ChainIndicators = () => {
const valueTitle = (() => { const valueTitle = (() => {
if (statsQueryResult.isPlaceholderData) { if (statsQueryResult.isPlaceholderData) {
return <Skeleton h="48px" w="215px" mt={ 3 } mb={ 4 }/>; return <Skeleton h="36px" w="215px"/>;
} }
if (!statsQueryResult.data) { if (!statsQueryResult.data) {
return <Text mt={ 3 } mb={ 4 }>There is no data</Text>; return <Text fontSize="xs">There is no data</Text>;
} }
return ( return (
<Text fontWeight={ 600 } fontFamily="heading" fontSize="48px" lineHeight="48px" mt={ 3 }> <Text fontWeight={ 700 } fontSize="30px" lineHeight="36px">
{ indicator?.value(statsQueryResult.data) } { indicator?.value(statsQueryResult.data) }
</Text> </Text>
); );
...@@ -85,23 +82,22 @@ const ChainIndicators = () => { ...@@ -85,23 +82,22 @@ const ChainIndicators = () => {
return ( return (
<Flex <Flex
p={{ base: 0, lg: 8 }} px={{ base: 3, lg: 4 }}
borderRadius={{ base: 'none', lg: 'lg' }} py={ 3 }
boxShadow={{ base: 'none', lg: 'xl' }} borderRadius="base"
bgColor={{ base: bgColorMobile, lg: bgColorDesktop }} bgColor={ bgColor }
columnGap={ 6 } columnGap={{ base: 3, lg: 4 }}
rowGap={ 0 } rowGap={ 0 }
flexDir={{ base: 'column', lg: 'row' }} flexBasis="50%"
w="100%" flexGrow={ 1 }
alignItems="stretch" alignItems="stretch"
mt={{ base: 1, lg: 3 }}
> >
<Flex flexGrow={ 1 } flexDir="column" order={{ base: 2, lg: 1 }} p={{ base: 6, lg: 0 }}> <Flex flexGrow={ 1 } flexDir="column">
<Flex alignItems="center"> <Flex alignItems="center">
<Text fontWeight={ 500 } fontFamily="heading" fontSize="lg">{ indicator?.title }</Text> <Text fontWeight={ 500 }>{ indicator?.title }</Text>
{ indicator?.hint && <Hint label={ indicator.hint } ml={ 1 }/> } { indicator?.hint && <Hint label={ indicator.hint } ml={ 1 }/> }
</Flex> </Flex>
<Flex mb={ 4 } alignItems="end"> <Flex mb={{ base: 0, lg: 2 }} mt={ 1 } alignItems="end">
{ valueTitle } { valueTitle }
{ valueDiff } { valueDiff }
</Flex> </Flex>
...@@ -112,11 +108,9 @@ const ChainIndicators = () => { ...@@ -112,11 +108,9 @@ const ChainIndicators = () => {
flexShrink={ 0 } flexShrink={ 0 }
flexDir="column" flexDir="column"
as="ul" as="ul"
p={ 3 }
borderRadius="lg" borderRadius="lg"
bgColor={{ base: listBgColorMobile, lg: listBgColorDesktop }} rowGap="6px"
rowGap={ 3 } m={{ base: 'auto 0', lg: 0 }}
order={{ base: 1, lg: 2 }}
> >
{ indicators.map((indicator) => ( { indicators.map((indicator) => (
<ChainIndicatorItem <ChainIndicatorItem
......
...@@ -7,6 +7,7 @@ import config from 'configs/app'; ...@@ -7,6 +7,7 @@ import config from 'configs/app';
import { sortByDateDesc } from 'ui/shared/chart/utils/sorts'; import { sortByDateDesc } from 'ui/shared/chart/utils/sorts';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import NativeTokenIcon from 'ui/shared/NativeTokenIcon'; import NativeTokenIcon from 'ui/shared/NativeTokenIcon';
import TokenLogoPlaceholder from 'ui/shared/TokenLogoPlaceholder';
const nonNullTailReducer = (result: Array<TimeChartItemRaw>, item: TimeChartItemRaw) => { const nonNullTailReducer = (result: Array<TimeChartItemRaw>, item: TimeChartItemRaw) => {
if (item.value === null && result.length === 0) { if (item.value === null && result.length === 0) {
...@@ -70,7 +71,7 @@ const secondaryCoinPriceIndicator: TChainIndicator<'stats_charts_secondary_coin_ ...@@ -70,7 +71,7 @@ const secondaryCoinPriceIndicator: TChainIndicator<'stats_charts_secondary_coin_
'$N/A' : '$N/A' :
'$' + Number(stats.secondary_coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }), '$' + Number(stats.secondary_coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }),
valueDiff: () => null, valueDiff: () => null,
icon: <NativeTokenIcon boxSize={ 6 }/>, icon: <TokenLogoPlaceholder boxSize={ 6 }/>,
hint: `${ config.chain.secondaryCoin.symbol } token daily price in USD.`, hint: `${ config.chain.secondaryCoin.symbol } token daily price in USD.`,
api: { api: {
resourceName: 'stats_charts_secondary_coin_price', resourceName: 'stats_charts_secondary_coin_price',
......
import { Box, Text, Link, Popover, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, chakra, Flex, Divider, Icon } from '@chakra-ui/react'; import { Box, Text, Link, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, chakra, Flex, Divider, Icon } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { MarketplaceAppSecurityReport } from 'types/client/marketplace'; import type { MarketplaceAppSecurityReport } from 'types/client/marketplace';
...@@ -11,6 +11,7 @@ import config from 'configs/app'; ...@@ -11,6 +11,7 @@ import config from 'configs/app';
import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import solidityScanIcon from 'icons/brands/solidity_scan.svg';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton';
import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails';
......
import { Box, Text, Popover, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Icon } from '@chakra-ui/react'; import { Box, Text, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Icon } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SolidityscanReport } from 'types/api/contract'; import type { SolidityscanReport } from 'types/api/contract';
...@@ -9,6 +9,7 @@ import config from 'configs/app'; ...@@ -9,6 +9,7 @@ import config from 'configs/app';
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import solidityScanIcon from 'icons/brands/solidity_scan.svg';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import Popover from 'ui/shared/chakra/Popover';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton';
import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails';
......
...@@ -208,7 +208,7 @@ const MarketplaceAppCard = ({ ...@@ -208,7 +208,7 @@ const MarketplaceAppCard = ({
showContractList={ showContractList } showContractList={ showContractList }
isLoading={ isLoading } isLoading={ isLoading }
source="Discovery view" source="Discovery view"
popoverPlacement={ isMobile ? 'bottom-end' : 'bottom-start' } popoverPlacement={ isMobile ? 'bottom-end' : 'left' }
position="absolute" position="absolute"
right={{ base: 3, md: 5 }} right={{ base: 3, md: 5 }}
top={{ base: '10px', md: 5 }} top={{ base: '10px', md: 5 }}
......
import { import {
Popover, PopoverTrigger, PopoverContent, PopoverBody, PopoverTrigger, PopoverContent, PopoverBody,
Modal, ModalContent, ModalCloseButton, useDisclosure, Modal, ModalContent, ModalCloseButton, useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -7,6 +7,7 @@ import React from 'react'; ...@@ -7,6 +7,7 @@ import React from 'react';
import type { MarketplaceAppOverview } from 'types/client/marketplace'; import type { MarketplaceAppOverview } from 'types/client/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import Popover from 'ui/shared/chakra/Popover';
import Content from './MarketplaceAppInfo/Content'; import Content from './MarketplaceAppInfo/Content';
import TriggerButton from './MarketplaceAppInfo/TriggerButton'; import TriggerButton from './MarketplaceAppInfo/TriggerButton';
......
...@@ -53,7 +53,7 @@ const NameDomainsActionBar = ({ ...@@ -53,7 +53,7 @@ const NameDomainsActionBar = ({
minW={{ base: 'auto', lg: '250px' }} minW={{ base: 'auto', lg: '250px' }}
size="xs" size="xs"
onChange={ onSearchChange } onChange={ onSearchChange }
placeholder="Search by name" placeholder="Search by name or address"
initialValue={ searchTerm } initialValue={ searchTerm }
isLoading={ isInitialLoading } isLoading={ isInitialLoading }
/> />
......
...@@ -272,7 +272,7 @@ const AddressPageContent = () => { ...@@ -272,7 +272,7 @@ const AddressPageContent = () => {
/> />
) } ) }
<AddressEntity <AddressEntity
address={{ ...addressQuery.data, hash, name: '', ens_domain_name: '' }} address={{ ...addressQuery.data, hash, name: '', ens_domain_name: '', implementations: null }}
isLoading={ isLoading } isLoading={ isLoading }
fontFamily="heading" fontFamily="heading"
fontSize="lg" fontSize="lg"
......
...@@ -64,21 +64,12 @@ const GasTracker = () => { ...@@ -64,21 +64,12 @@ const GasTracker = () => {
</Flex> </Flex>
); );
const content = (() => { const snippets = (() => {
if (!isPlaceholderData && data?.gas_prices?.slow === null && data?.gas_prices.average === null && data.gas_prices.fast === null) { if (!isPlaceholderData && data?.gas_prices?.slow === null && data?.gas_prices.average === null && data.gas_prices.fast === null) {
return <Alert status="warning">No data available yet</Alert>; return <Alert status="warning">No recent data available</Alert>;
} }
return ( return data?.gas_prices ? <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> : null;
<>
{ data?.gas_prices && <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> }
{ config.features.stats.isEnabled && (
<Box mt={ 12 }>
<GasTrackerChart/>
</Box>
) }
</>
);
})(); })();
return ( return (
...@@ -88,7 +79,12 @@ const GasTracker = () => { ...@@ -88,7 +79,12 @@ const GasTracker = () => {
secondRow={ titleSecondRow } secondRow={ titleSecondRow }
withTextAd withTextAd
/> />
{ content } { snippets }
{ config.features.stats.isEnabled && (
<Box mt={ 12 }>
<GasTrackerChart/>
</Box>
) }
</> </>
); );
}; };
......
...@@ -2,6 +2,7 @@ import { Box, Flex, Heading } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Box, Flex, Heading } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import ChainIndicators from 'ui/home/indicators/ChainIndicators'; import ChainIndicators from 'ui/home/indicators/ChainIndicators';
import LatestBlocks from 'ui/home/LatestBlocks'; import LatestBlocks from 'ui/home/LatestBlocks';
import LatestZkEvmL2Batches from 'ui/home/LatestZkEvmL2Batches'; import LatestZkEvmL2Batches from 'ui/home/LatestZkEvmL2Batches';
...@@ -15,44 +16,51 @@ import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; ...@@ -15,44 +16,51 @@ import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop';
const rollupFeature = config.features.rollup; const rollupFeature = config.features.rollup;
const Home = () => { const Home = () => {
const isMobile = useIsMobile();
return ( return (
<Box as="main"> <Box as="main">
<Box <Flex
w="100%" w="100%"
background={ config.UI.homepage.plate.background } background={ config.UI.homepage.plate.background }
borderRadius={{ base: 'md', lg: 'xl' }} borderRadius="md"
px={{ base: 4, lg: 10 }} p={{ base: 4, lg: 8 }}
py={{ base: 3, lg: 8 }} columnGap={ 8 }
minW={{ base: 'unset', lg: '900px' }} alignItems="center"
data-label="hero plate" data-label="hero plate"
> >
<Flex mb={{ base: 2, lg: 6 }} justifyContent="space-between" alignItems="center"> <Box flexGrow={ 1 }>
<Heading <Flex mb={{ base: 2, lg: 3 }} justifyContent="space-between" alignItems="center" columnGap={ 2 }>
as="h1" <Heading
fontSize={{ base: '18px', lg: '40px' }} as="h1"
lineHeight={{ base: '24px', lg: '48px' }} fontSize={{ base: '18px', lg: '30px' }}
fontWeight={ 600 } lineHeight={{ base: '24px', lg: '36px' }}
color={ config.UI.homepage.plate.textColor } fontWeight={{ base: 500, lg: 700 }}
> color={ config.UI.homepage.plate.textColor }
{ >
config.meta.seo.enhancedDataEnabled ? {
`${ config.chain.name } blockchain explorer` : config.meta.seo.enhancedDataEnabled ?
`${ config.chain.name } explorer` `${ config.chain.name } blockchain explorer` :
} `${ config.chain.name } explorer`
</Heading> }
{ config.UI.navigation.layout === 'vertical' && ( </Heading>
<Box display={{ base: 'none', lg: 'flex' }}> { config.UI.navigation.layout === 'vertical' && (
{ config.features.account.isEnabled && <ProfileMenuDesktop isHomePage/> } <Box display={{ base: 'none', lg: 'flex' }}>
{ config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop isHomePage/> } { config.features.account.isEnabled && <ProfileMenuDesktop isHomePage/> }
</Box> { config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop isHomePage/> }
) } </Box>
</Flex> ) }
<SearchBar isHomepage/> </Flex>
</Box> <SearchBar isHomepage/>
<Stats/> </Box>
<ChainIndicators/> { !isMobile && <AdBanner platform="mobile" w="fit-content" flexShrink={ 0 } borderRadius="md" overflow="hidden"/> }
<AdBanner mt={ 6 } mx="auto" display="flex" justifyContent="center"/> </Flex>
<Flex mt={ 6 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 6 }> <Flex flexDir={{ base: 'column', lg: 'row' }} columnGap={ 2 } rowGap={ 1 } mt={ 3 } _empty={{ mt: 0 }}>
<Stats/>
<ChainIndicators/>
</Flex>
{ isMobile && <AdBanner mt={ 6 } mx="auto" display="flex" justifyContent="center"/> }
<Flex mt={ 8 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 6 }>
{ rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' ? <LatestZkEvmL2Batches/> : <LatestBlocks/> } { rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' ? <LatestZkEvmL2Batches/> : <LatestBlocks/> }
<Box flexGrow={ 1 }> <Box flexGrow={ 1 }>
<Transactions/> <Transactions/>
......
import { Box, Menu, MenuButton, MenuItem, MenuList, Flex, IconButton } from '@chakra-ui/react'; import { Box, MenuButton, MenuItem, MenuList, Flex, IconButton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
...@@ -14,6 +14,7 @@ import MarketplaceAppModal from 'ui/marketplace/MarketplaceAppModal'; ...@@ -14,6 +14,7 @@ import MarketplaceAppModal from 'ui/marketplace/MarketplaceAppModal';
import MarketplaceDisclaimerModal from 'ui/marketplace/MarketplaceDisclaimerModal'; import MarketplaceDisclaimerModal from 'ui/marketplace/MarketplaceDisclaimerModal';
import MarketplaceList from 'ui/marketplace/MarketplaceList'; import MarketplaceList from 'ui/marketplace/MarketplaceList';
import { SORT_OPTIONS } from 'ui/marketplace/utils'; import { SORT_OPTIONS } from 'ui/marketplace/utils';
import Menu from 'ui/shared/chakra/Menu';
import FilterInput from 'ui/shared/filters/FilterInput'; import FilterInput from 'ui/shared/filters/FilterInput';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import type { IconName } from 'ui/shared/IconSvg'; import type { IconName } from 'ui/shared/IconSvg';
......
...@@ -33,9 +33,9 @@ const testFn: Parameters<typeof test>[1] = async({ render, mockConfigResponse, m ...@@ -33,9 +33,9 @@ const testFn: Parameters<typeof test>[1] = async({ render, mockConfigResponse, m
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}; };
test('base view +@dark-mode', testFn); test.fixme('base view +@dark-mode', testFn);
test.describe('mobile', () => { test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport }); test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', testFn); test.fixme('base view', testFn);
}); });
import { inRange } from 'lodash'; import _inRange from 'lodash/inRange';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -43,7 +43,11 @@ const UserOp = () => { ...@@ -43,7 +43,11 @@ const UserOp = () => {
if (!userOpQuery.data) { if (!userOpQuery.data) {
return true; return true;
} else { } else {
if (inRange(Number(tt.log_index), userOpQuery.data?.user_logs_start_index, userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count)) { if (_inRange(
Number(tt.log_index),
userOpQuery.data?.user_logs_start_index,
userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count,
)) {
return true; return true;
} }
return false; return false;
...@@ -54,7 +58,7 @@ const UserOp = () => { ...@@ -54,7 +58,7 @@ const UserOp = () => {
if (!userOpQuery.data) { if (!userOpQuery.data) {
return true; return true;
} else { } else {
if (inRange(log.index, userOpQuery.data?.user_logs_start_index, userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count)) { if (_inRange(log.index, userOpQuery.data?.user_logs_start_index, userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count)) {
return true; return true;
} }
return false; return false;
......
import { Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react'; import { PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react';
import _debounce from 'lodash/debounce'; import _debounce from 'lodash/debounce';
import type { FormEvent, FocusEvent } from 'react'; import type { FormEvent, FocusEvent } from 'react';
import React from 'react'; import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { getRecentSearchKeywords } from 'lib/recentSearchKeywords'; import { getRecentSearchKeywords } from 'lib/recentSearchKeywords';
import Popover from 'ui/shared/chakra/Popover';
import SearchBarBackdrop from 'ui/snippets/searchBar/SearchBarBackdrop'; import SearchBarBackdrop from 'ui/snippets/searchBar/SearchBarBackdrop';
import SearchBarInput from 'ui/snippets/searchBar/SearchBarInput'; import SearchBarInput from 'ui/snippets/searchBar/SearchBarInput';
import SearchBarRecentKeywords from 'ui/snippets/searchBar/SearchBarRecentKeywords'; import SearchBarRecentKeywords from 'ui/snippets/searchBar/SearchBarRecentKeywords';
...@@ -76,7 +77,7 @@ const SearchResultsInput = ({ searchTerm, handleSubmit, handleSearchTermChange } ...@@ -76,7 +77,7 @@ const SearchResultsInput = ({ searchTerm, handleSubmit, handleSearchTermChange }
autoFocus={ false } autoFocus={ false }
onClose={ onClose } onClose={ onClose }
placement="bottom-start" placement="bottom-start"
offset={ isMobile ? [ 16, -12 ] : undefined } offset={ isMobile ? [ 16, -12 ] : [ 0, 8 ] }
isLazy isLazy
> >
<PopoverTrigger> <PopoverTrigger>
......
import { Box, IconButton, Menu, MenuButton, MenuList, Skeleton, chakra } from '@chakra-ui/react'; import { Box, IconButton, MenuButton, MenuList, Skeleton, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -9,6 +9,7 @@ import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; ...@@ -9,6 +9,7 @@ import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import useIsAccountActionAllowed from 'lib/hooks/useIsAccountActionAllowed'; import useIsAccountActionAllowed from 'lib/hooks/useIsAccountActionAllowed';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import Menu from 'ui/shared/chakra/Menu';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import MetadataUpdateMenuItem from './items/MetadataUpdateMenuItem'; import MetadataUpdateMenuItem from './items/MetadataUpdateMenuItem';
......
...@@ -42,7 +42,7 @@ const AdditionalInfoButton = ({ isOpen, onClick, className, isLoading }: Props, ...@@ -42,7 +42,7 @@ const AdditionalInfoButton = ({ isOpen, onClick, className, isLoading }: Props,
<IconSvg <IconSvg
name="info" name="info"
boxSize={ 5 } boxSize={ 5 }
color="link" color={ isOpen ? 'link_hovered' : 'icon_info' }
_hover={{ color: 'link_hovered' }} _hover={{ color: 'link_hovered' }}
/> />
</Button> </Button>
......
import { Box, Text, chakra, Skeleton } from '@chakra-ui/react'; import { chakra, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
...@@ -23,20 +23,20 @@ const CurrencyValue = ({ value, currency = '', decimals, exchangeRate, className ...@@ -23,20 +23,20 @@ const CurrencyValue = ({ value, currency = '', decimals, exchangeRate, className
if (value === undefined || value === null) { if (value === undefined || value === null) {
return ( return (
<Box as="span" className={ className }> <chakra.span className={ className }>
<Text>N/A</Text> -
</Box> </chakra.span>
); );
} }
const { valueStr: valueResult, usd: usdResult } = getCurrencyValue({ value, accuracy, accuracyUsd, exchangeRate, decimals }); const { valueStr: valueResult, usd: usdResult } = getCurrencyValue({ value, accuracy, accuracyUsd, exchangeRate, decimals });
return ( return (
<Box as="span" className={ className } display="inline-flex" rowGap={ 3 } columnGap={ 1 }> <chakra.span className={ className } display="inline-flex" rowGap={ 3 } columnGap={ 1 }>
<Text display="inline-block"> <chakra.span display="inline-block">
{ valueResult }{ currency ? ` ${ currency }` : '' } { valueResult }{ currency ? ` ${ currency }` : '' }
</Text> </chakra.span>
{ usdResult && <Text as="span" variant="secondary" fontWeight={ 400 }>(${ usdResult })</Text> } { usdResult && <chakra.span color="text_secondary" fontWeight={ 400 }>(${ usdResult })</chakra.span> }
</Box> </chakra.span>
); );
}; };
......
import { chakra, Image, Flex, Popover, PopoverArrow, PopoverBody, PopoverContent, PopoverTrigger, useColorModeValue, DarkMode } from '@chakra-ui/react'; import { chakra, Image, Flex, PopoverArrow, PopoverBody, PopoverContent, PopoverTrigger, useColorModeValue, DarkMode } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { EntityTag } from './types'; import type { EntityTag } from './types';
import makePrettyLink from 'lib/makePrettyLink'; import makePrettyLink from 'lib/makePrettyLink';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import Popover from 'ui/shared/chakra/Popover';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
interface Props { interface Props {
...@@ -35,7 +36,7 @@ const EntityTagPopover = ({ data, children }: Props) => { ...@@ -35,7 +36,7 @@ const EntityTagPopover = ({ data, children }: Props) => {
} }
return ( return (
<Popover trigger="hover" isLazy> <Popover trigger="hover" isLazy gutter={ 8 }>
<PopoverTrigger> <PopoverTrigger>
{ children } { children }
</PopoverTrigger> </PopoverTrigger>
......
import { Box, Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger, chakra } from '@chakra-ui/react'; import { Box, Flex, PopoverBody, PopoverContent, PopoverTrigger, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { EntityTag as TEntityTag } from './types'; import type { EntityTag as TEntityTag } from './types';
import config from 'configs/app'; import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import Popover from 'ui/shared/chakra/Popover';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import EntityTag from './EntityTag'; import EntityTag from './EntityTag';
......
...@@ -35,7 +35,7 @@ const Hint = ({ label, className, tooltipProps, isLoading }: Props) => { ...@@ -35,7 +35,7 @@ const Hint = ({ label, className, tooltipProps, isLoading }: Props) => {
<IconButton <IconButton
colorScheme="none" colorScheme="none"
aria-label="hint" aria-label="hint"
icon={ <IconSvg name="info" w="100%" h="100%"/> } icon={ <IconSvg name="info" w="100%" h="100%" color="icon_info" _hover={{ color: 'link_hovered' }}/> }
boxSize={ 5 } boxSize={ 5 }
variant="simple" variant="simple"
display="inline-block" display="inline-block"
......
import { import {
Image, Image,
Button, Button,
Popover,
PopoverTrigger, PopoverTrigger,
PopoverBody, PopoverBody,
PopoverContent, PopoverContent,
...@@ -18,6 +17,7 @@ import type { NetworkExplorer as TNetworkExplorer } from 'types/networks'; ...@@ -18,6 +17,7 @@ import type { NetworkExplorer as TNetworkExplorer } from 'types/networks';
import config from 'configs/app'; import config from 'configs/app';
import stripTrailingSlash from 'lib/stripTrailingSlash'; import stripTrailingSlash from 'lib/stripTrailingSlash';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
import PopoverTriggerTooltip from 'ui/shared/PopoverTriggerTooltip'; import PopoverTriggerTooltip from 'ui/shared/PopoverTriggerTooltip';
......
import type { import type {
ButtonProps } from '@chakra-ui/react'; ButtonProps } from '@chakra-ui/react';
import { Popover, import {
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
...@@ -12,6 +12,8 @@ import React from 'react'; ...@@ -12,6 +12,8 @@ import React from 'react';
import type { MenuButton, TabItem } from './types'; import type { MenuButton, TabItem } from './types';
import Popover from 'ui/shared/chakra/Popover';
import TabCounter from './TabCounter'; import TabCounter from './TabCounter';
import { menuButton } from './utils'; import { menuButton } from './utils';
......
import { chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { BannerPlatform } from './types';
import config from 'configs/app'; import config from 'configs/app';
import { useAppContext } from 'lib/contexts/app'; import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
...@@ -9,7 +11,13 @@ import AdBannerContent from './AdBannerContent'; ...@@ -9,7 +11,13 @@ import AdBannerContent from './AdBannerContent';
const feature = config.features.adsBanner; const feature = config.features.adsBanner;
const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: boolean }) => { interface Props {
className?: string;
isLoading?: boolean;
platform?: BannerPlatform;
}
const AdBanner = ({ className, isLoading, platform }: Props) => {
const provider = useAppContext().adBannerProvider; const provider = useAppContext().adBannerProvider;
const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies); const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies);
...@@ -23,6 +31,7 @@ const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: bo ...@@ -23,6 +31,7 @@ const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: bo
className={ className } className={ className }
isLoading={ isLoading } isLoading={ isLoading }
provider={ provider } provider={ provider }
platform={ platform }
/> />
); );
}; };
......
import { chakra, Skeleton } from '@chakra-ui/react'; import { chakra, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { BannerPlatform } from './types';
import type { AdBannerProviders } from 'types/client/adProviders'; import type { AdBannerProviders } from 'types/client/adProviders';
import config from 'configs/app'; import config from 'configs/app';
import AdbutlerBanner from './AdbutlerBanner'; import AdbutlerBanner from './AdbutlerBanner';
import CoinzillaBanner from './CoinzillaBanner'; import CoinzillaBanner from './CoinzillaBanner';
import GetitBanner from './GetitBanner'; // import GetitBanner from './GetitBanner';
import HypeBanner from './HypeBanner'; import HypeBanner from './HypeBanner';
import SliseBanner from './SliseBanner'; import SliseBanner from './SliseBanner';
const feature = config.features.adsBanner; const feature = config.features.adsBanner;
const AdBannerContent = ({ className, isLoading, provider }: { className?: string; isLoading?: boolean; provider: AdBannerProviders }) => { interface Props {
className?: string;
isLoading?: boolean;
platform?: BannerPlatform;
provider: AdBannerProviders;
}
const AdBannerContent = ({ className, isLoading, provider, platform }: Props) => {
const content = (() => { const content = (() => {
switch (provider) { switch (provider) {
case 'adbutler': case 'adbutler':
return <AdbutlerBanner/>; return <AdbutlerBanner platform={ platform }/>;
case 'coinzilla': case 'coinzilla':
return <CoinzillaBanner/>; return <CoinzillaBanner platform={ platform }/>;
case 'getit': // case 'getit':
return <GetitBanner/>; // return <GetitBanner platform={ platform }/>;
case 'hype': case 'hype':
return <HypeBanner/>; return <HypeBanner platform={ platform }/>;
case 'slise': case 'slise':
return <SliseBanner/>; return <SliseBanner platform={ platform }/>;
} }
})(); })();
......
...@@ -3,6 +3,8 @@ import { useRouter } from 'next/navigation'; ...@@ -3,6 +3,8 @@ import { useRouter } from 'next/navigation';
import Script from 'next/script'; import Script from 'next/script';
import React from 'react'; import React from 'react';
import type { BannerProps } from './types';
import config from 'configs/app'; import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
...@@ -10,9 +12,10 @@ import { connectAdbutler, placeAd, ADBUTLER_ACCOUNT } from 'ui/shared/ad/adbutle ...@@ -10,9 +12,10 @@ import { connectAdbutler, placeAd, ADBUTLER_ACCOUNT } from 'ui/shared/ad/adbutle
const feature = config.features.adsBanner; const feature = config.features.adsBanner;
const AdbutlerBanner = ({ className }: { className?: string }) => { const AdbutlerBanner = ({ className, platform }: BannerProps) => {
const router = useRouter(); const router = useRouter();
const isMobile = useIsMobile(); const isMobileViewport = useIsMobile();
const isMobile = platform === 'mobile' || isMobileViewport;
React.useEffect(() => { React.useEffect(() => {
if (!('adButler' in feature)) { if (!('adButler' in feature)) {
...@@ -24,10 +27,10 @@ const AdbutlerBanner = ({ className }: { className?: string }) => { ...@@ -24,10 +27,10 @@ const AdbutlerBanner = ({ className }: { className?: string }) => {
if (!window.AdButler.ads) { if (!window.AdButler.ads) {
window.AdButler.ads = []; window.AdButler.ads = [];
} }
const adButlerConfig = isMobile ? feature.adButler.config.mobile : feature.adButler.config.desktop;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: // @ts-ignore:
let plc = window[`plc${ feature.adButler.config.mobile.id }`] || 0; let plc = window[`plc${ adButlerConfig.id }`] || 0;
const adButlerConfig = isMobile ? feature.adButler.config.mobile : feature.adButler.config.desktop;
const banner = document.getElementById('ad-banner'); const banner = document.getElementById('ad-banner');
if (banner) { if (banner) {
banner.innerHTML = '<' + 'div id="placement_' + adButlerConfig?.id + '_' + plc + '"></' + 'div>'; banner.innerHTML = '<' + 'div id="placement_' + adButlerConfig?.id + '_' + plc + '"></' + 'div>';
...@@ -46,10 +49,34 @@ const AdbutlerBanner = ({ className }: { className?: string }) => { ...@@ -46,10 +49,34 @@ const AdbutlerBanner = ({ className }: { className?: string }) => {
} }
}, [ router, isMobile ]); }, [ router, isMobile ]);
if (!('adButler' in feature)) {
return null;
}
const { width, height } = (() => {
switch (platform) {
case 'desktop':
return { width: `${ feature.adButler.config.desktop.width }px`, height: `${ feature.adButler.config.desktop.height }px` };
case 'mobile':
return { width: `${ feature.adButler.config.mobile.width }px`, height: `${ feature.adButler.config.mobile.height }px` };
default:
return {
width: {
base: `${ feature.adButler.config.mobile.width }px`,
lg: `${ feature.adButler.config.desktop.width }px`,
},
height: {
base: `${ feature.adButler.config.mobile.height }px`,
lg: `${ feature.adButler.config.desktop.height }px`,
},
};
}
})() ?? { width: '0', height: '0' };
return ( return (
<Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}> <Flex className={ className } id="adBanner" h={ height } w={ width }>
<Script strategy="lazyOnload" id="ad-butler-1">{ connectAdbutler }</Script> <Script strategy="lazyOnload" id="ad-butler-1">{ connectAdbutler }</Script>
<Script strategy="lazyOnload" id="ad-butler-2">{ placeAd }</Script> <Script strategy="lazyOnload" id="ad-butler-2">{ placeAd(platform) }</Script>
<div id="ad-banner"></div> <div id="ad-banner"></div>
</Flex> </Flex>
); );
......
...@@ -2,25 +2,44 @@ import { Flex, chakra } from '@chakra-ui/react'; ...@@ -2,25 +2,44 @@ import { Flex, chakra } from '@chakra-ui/react';
import Script from 'next/script'; import Script from 'next/script';
import React from 'react'; import React from 'react';
import type { BannerProps } from './types';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
const CoinzillaBanner = ({ className }: { className?: string }) => { const CoinzillaBanner = ({ className, platform }: BannerProps) => {
const isInBrowser = isBrowser(); const isInBrowser = isBrowser();
const { width, height } = (() => {
switch (platform) {
case 'desktop':
return { width: 728, height: 90 };
case 'mobile':
return { width: 320, height: 100 };
default:
return { width: undefined, height: undefined };
}
})();
React.useEffect(() => { React.useEffect(() => {
if (isInBrowser) { if (isInBrowser) {
window.coinzilla_display = window.coinzilla_display || []; window.coinzilla_display = window.coinzilla_display || [];
const cDisplayPreferences = { const cDisplayPreferences = {
zone: '26660bf627543e46851', zone: '26660bf627543e46851',
width: '728', width: width ? String(width) : '728',
height: '90', height: height ? String(height) : '90',
}; };
window.coinzilla_display.push(cDisplayPreferences); window.coinzilla_display.push(cDisplayPreferences);
} }
}, [ isInBrowser ]); }, [ height, isInBrowser, width ]);
return ( return (
<Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}> <Flex
className={ className }
id={ 'adBanner' + (platform ? `_${ platform }` : '') }
h={ height ? `${ height }px` : { base: '100px', lg: '90px' } }
w={ width ? `${ width }px` : undefined }
>
<Script strategy="lazyOnload" src="https://coinzillatag.com/lib/display.js"/> <Script strategy="lazyOnload" src="https://coinzillatag.com/lib/display.js"/>
<div className="coinzilla" data-zone="C-26660bf627543e46851"></div> <div className="coinzilla" data-zone="C-26660bf627543e46851"></div>
</Flex> </Flex>
......
...@@ -2,6 +2,8 @@ import { Flex, chakra } from '@chakra-ui/react'; ...@@ -2,6 +2,8 @@ import { Flex, chakra } from '@chakra-ui/react';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import React from 'react'; import React from 'react';
import type { BannerProps } from './types';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useAccount from 'lib/web3/useAccount'; import useAccount from 'lib/web3/useAccount';
...@@ -9,17 +11,17 @@ const GetitAdPlugin = dynamic(() => import('getit-sdk').then(module => module.Ge ...@@ -9,17 +11,17 @@ const GetitAdPlugin = dynamic(() => import('getit-sdk').then(module => module.Ge
const GETIT_API_KEY = 'ZmGXVvwYUAW4yXL8RzWQHNKmpSyQmt3TDXsXUxqFqXPdoaiSSFyca3BOyunDcWdyOwTkX3UVVQel28qbjoOoWPxYVpPdNzbUNkAHyFyJX7Lk9TVcPDZKTQmwHlSMzO3a'; const GETIT_API_KEY = 'ZmGXVvwYUAW4yXL8RzWQHNKmpSyQmt3TDXsXUxqFqXPdoaiSSFyca3BOyunDcWdyOwTkX3UVVQel28qbjoOoWPxYVpPdNzbUNkAHyFyJX7Lk9TVcPDZKTQmwHlSMzO3a';
const GetitBanner = ({ className }: { className?: string }) => { const GetitBanner = ({ className, platform }: BannerProps) => {
const isMobile = Boolean(useIsMobile()); const isMobile = Boolean(useIsMobile());
const { address } = useAccount(); const { address } = useAccount();
return ( return (
<Flex className={ className } h="90px"> <Flex className={ className } h="90px" w={{ base: '270px', lg: platform === 'mobile' ? '270px' : undefined }}>
<GetitAdPlugin <GetitAdPlugin
key={ isMobile.toString() } key={ isMobile.toString() }
apiKey={ GETIT_API_KEY } apiKey={ GETIT_API_KEY }
walletConnected={ address ? address : '' } walletConnected={ address ? address : '' }
isMobile={ isMobile } isMobile={ platform === 'mobile' || isMobile }
slotId="0" slotId="0"
/> />
</Flex> </Flex>
......
...@@ -3,6 +3,8 @@ import { Banner, setWalletAddresses } from '@hypelab/sdk-react'; ...@@ -3,6 +3,8 @@ import { Banner, setWalletAddresses } from '@hypelab/sdk-react';
import Script from 'next/script'; import Script from 'next/script';
import React from 'react'; import React from 'react';
import type { BannerProps } from './types';
import useAccount from 'lib/web3/useAccount'; import useAccount from 'lib/web3/useAccount';
import { hypeInit } from './hypeBannerScript'; import { hypeInit } from './hypeBannerScript';
...@@ -10,7 +12,7 @@ import { hypeInit } from './hypeBannerScript'; ...@@ -10,7 +12,7 @@ import { hypeInit } from './hypeBannerScript';
const DESKTOP_BANNER_SLUG = 'b1559fc3e7'; const DESKTOP_BANNER_SLUG = 'b1559fc3e7';
const MOBILE_BANNER_SLUG = '668ed80a9e'; const MOBILE_BANNER_SLUG = '668ed80a9e';
const HypeBanner = ({ className }: { className?: string }) => { const HypeBanner = ({ className, platform }: BannerProps) => {
const { address } = useAccount(); const { address } = useAccount();
React.useEffect(() => { React.useEffect(() => {
...@@ -19,18 +21,46 @@ const HypeBanner = ({ className }: { className?: string }) => { ...@@ -19,18 +21,46 @@ const HypeBanner = ({ className }: { className?: string }) => {
} }
}, [ address ]); }, [ address ]);
const banner = (() => {
switch (platform) {
case 'desktop': {
return (
<Flex className={ className } w="728px" h="90px">
<Banner placement={ DESKTOP_BANNER_SLUG }/>
</Flex>
);
}
case 'mobile': {
return (
<Flex className={ className } w="320px" h="50px">
<Banner placement={ MOBILE_BANNER_SLUG }/>
</Flex>
);
}
default: {
return (
<>
<Flex className={ className } w="728px" h="90px" display={{ base: 'none', lg: 'flex' }}>
<Banner placement={ DESKTOP_BANNER_SLUG }/>
</Flex>
<Flex className={ className } w="320px" h="50px" display={{ base: 'flex', lg: 'none' }}>
<Banner placement={ MOBILE_BANNER_SLUG }/>
</Flex>
</>
);
}
}
})();
return ( return (
<> <>
<Script <Script
id="hypelab" id="hypelab"
strategy="afterInteractive" strategy="afterInteractive"
>{ hypeInit }</Script> >
<Flex className={ className } h="90px" display={{ base: 'none', lg: 'flex' }}> { hypeInit }
<Banner placement={ DESKTOP_BANNER_SLUG }/> </Script>
</Flex> { banner }
<Flex className={ className } h="50px" display={{ base: 'flex', lg: 'none' }}>
<Banner placement={ MOBILE_BANNER_SLUG }/>
</Flex>
</> </>
); );
}; };
......
...@@ -2,9 +2,35 @@ import { Flex, chakra } from '@chakra-ui/react'; ...@@ -2,9 +2,35 @@ import { Flex, chakra } from '@chakra-ui/react';
import { SliseAd } from '@slise/embed-react'; import { SliseAd } from '@slise/embed-react';
import React from 'react'; import React from 'react';
import type { BannerProps } from './types';
import config from 'configs/app'; import config from 'configs/app';
const SliseBanner = ({ className }: { className?: string }) => { const SliseBanner = ({ className, platform }: BannerProps) => {
if (platform === 'desktop') {
return (
<Flex className={ className } h="90px">
<SliseAd
slotId={ config.chain.name || '' }
pub="pub-10"
format="728x90"
style={{ width: '728px', height: '90px' }}/>
</Flex>
);
}
if (platform === 'mobile') {
return (
<Flex className={ className } h="90px">
<SliseAd
slotId={ config.chain.name || '' }
pub="pub-10"
format="270x90"
style={{ width: '270px', height: '90px' }}/>
</Flex>
);
}
return ( return (
<> <>
......
/* eslint-disable max-len */ /* eslint-disable max-len */
import type { BannerPlatform } from './types';
import config from 'configs/app'; import config from 'configs/app';
export const ADBUTLER_ACCOUNT = 182226; export const ADBUTLER_ACCOUNT = 182226;
export const connectAdbutler = `if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}`; export const connectAdbutler = `if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}`;
export const placeAd = (() => { export const placeAd = ((platform: BannerPlatform | undefined) => {
const feature = config.features.adsBanner; const feature = config.features.adsBanner;
if (!('adButler' in feature)) { if (!('adButler' in feature)) {
return; return;
} }
if (platform === 'mobile') {
return `
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || '';
var plc${ feature.adButler.config.mobile.id } = window.plc${ feature.adButler.config.mobile.id } || 0;
document.getElementById('ad-banner').innerHTML = '<'+'div id="placement_${ feature.adButler.config.mobile.id }_'+plc${ feature.adButler.config.mobile.id }+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ feature.adButler.config.mobile.id }, [${ feature.adButler.config.mobile.width },${ feature.adButler.config.mobile.height }], 'placement_${ feature.adButler.config.mobile.id }_'+opt.place, opt); }, opt: { place: plc${ feature.adButler.config.mobile.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
`;
}
return ` return `
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || []; var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || ''; var abkw = window.abkw || '';
...@@ -26,4 +38,4 @@ export const placeAd = (() => { ...@@ -26,4 +38,4 @@ export const placeAd = (() => {
AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ feature.adButler.config.desktop.id }, [${ feature.adButler.config.desktop.width },${ feature.adButler.config.desktop.height }], 'placement_${ feature.adButler.config.desktop.id }_'+opt.place, opt); }, opt: { place: plc${ feature.adButler.config.desktop.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }}); AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ feature.adButler.config.desktop.id }, [${ feature.adButler.config.desktop.width },${ feature.adButler.config.desktop.height }], 'placement_${ feature.adButler.config.desktop.id }_'+opt.place, opt); }, opt: { place: plc${ feature.adButler.config.desktop.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} }
`; `;
})(); });
export type BannerPlatform = 'mobile' | 'desktop';
export interface BannerProps {
className?: string;
platform?: BannerPlatform;
}
import type { MenuProps } from '@chakra-ui/react';
// eslint-disable-next-line no-restricted-imports
import { Menu as MenuBase } from '@chakra-ui/react';
import React from 'react';
const Menu = (props: MenuProps) => {
return <MenuBase gutter={ 4 } { ...props }/>;
};
export default React.memo(Menu);
import type { PopoverProps } from '@chakra-ui/react';
// eslint-disable-next-line no-restricted-imports
import { Popover as PopoverBase } from '@chakra-ui/react';
import React from 'react';
const Popover = (props: PopoverProps) => {
return <PopoverBase gutter={ 4 } { ...props }/>;
};
export default React.memo(Popover);
...@@ -20,7 +20,7 @@ function getBgColor(status?: AlertStatus) { ...@@ -20,7 +20,7 @@ function getBgColor(status?: AlertStatus) {
} }
} }
const Toast = ({ onClose, title, description, id, isClosable, status }: ToastProps) => { const Toast = ({ onClose, title, description, id, isClosable, status, icon }: ToastProps) => {
const ids = id ? const ids = id ?
{ {
...@@ -48,7 +48,7 @@ const Toast = ({ onClose, title, description, id, isClosable, status }: ToastPro ...@@ -48,7 +48,7 @@ const Toast = ({ onClose, title, description, id, isClosable, status }: ToastPro
maxWidth="400px" maxWidth="400px"
> >
<chakra.div flex="1" maxWidth="100%"> <chakra.div flex="1" maxWidth="100%">
{ title && <AlertTitle id={ ids?.title }>{ title }</AlertTitle> } { title && <AlertTitle id={ ids?.title } display="flex" alignItems="center">{ icon }{ title }</AlertTitle> }
{ description && ( { description && (
<AlertDescription id={ ids?.description } display="block"> <AlertDescription id={ ids?.description } display="block">
{ description } { description }
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
chakra, chakra,
Flex, Flex,
IconButton, Link, IconButton, Link,
Menu,
MenuButton, MenuButton,
MenuItem, MenuItem,
MenuList, MenuList,
...@@ -22,6 +21,7 @@ import type { TimeChartItem } from './types'; ...@@ -22,6 +21,7 @@ import type { TimeChartItem } from './types';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import saveAsCSV from 'lib/saveAsCSV'; import saveAsCSV from 'lib/saveAsCSV';
import Menu from 'ui/shared/chakra/Menu';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import ChartWidgetGraph from './ChartWidgetGraph'; import ChartWidgetGraph from './ChartWidgetGraph';
......
...@@ -3,6 +3,7 @@ import React from 'react'; ...@@ -3,6 +3,7 @@ import React from 'react';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight'; import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import * as addressMock from 'mocks/address/address'; import * as addressMock from 'mocks/address/address';
import * as implementationsMock from 'mocks/address/implementations';
import { test, expect } from 'playwright/lib'; import { test, expect } from 'playwright/lib';
import AddressEntity from './AddressEntity'; import AddressEntity from './AddressEntity';
...@@ -30,7 +31,7 @@ test.describe('contract', () => { ...@@ -30,7 +31,7 @@ test.describe('contract', () => {
test('unverified', async({ render, page }) => { test('unverified', async({ render, page }) => {
const component = await render( const component = await render(
<AddressEntity <AddressEntity
address={{ ...addressMock.contract, is_verified: false }} address={{ ...addressMock.contract, is_verified: false, implementations: null }}
/>, />,
); );
...@@ -41,7 +42,7 @@ test.describe('contract', () => { ...@@ -41,7 +42,7 @@ test.describe('contract', () => {
test('verified', async({ render }) => { test('verified', async({ render }) => {
const component = await render( const component = await render(
<AddressEntity <AddressEntity
address={{ ...addressMock.contract, is_verified: true }} address={{ ...addressMock.contract, is_verified: true, implementations: null }}
/>, />,
); );
...@@ -49,6 +50,58 @@ test.describe('contract', () => { ...@@ -49,6 +50,58 @@ test.describe('contract', () => {
}); });
}); });
test.describe('proxy contract', () => {
test.use({ viewport: { width: 500, height: 300 } });
test('with implementation name', async({ render, page }) => {
const component = await render(
<AddressEntity
address={ addressMock.contract }
/>,
);
await component.getByText(/home/i).hover();
await expect(page.getByText('Proxy contract')).toBeVisible();
await expect(page).toHaveScreenshot();
});
test('without implementation name', async({ render, page }) => {
const component = await render(
<AddressEntity
address={{ ...addressMock.contract, implementations: [ { address: addressMock.contract.implementations?.[0].address } ] }}
/>,
);
await component.getByText(/eternal/i).hover();
await expect(page.getByText('Proxy contract')).toBeVisible();
await expect(page).toHaveScreenshot();
});
test('without any name', async({ render, page }) => {
const component = await render(
<AddressEntity
address={{ ...addressMock.contract, name: undefined, implementations: [ { address: addressMock.contract.implementations?.[0].address } ] }}
/>,
);
await component.getByText(addressMock.contract.hash.slice(0, 4)).hover();
await expect(page.getByText('Proxy contract')).toBeVisible();
await expect(page).toHaveScreenshot();
});
test('with multiple implementations', async({ render, page }) => {
const component = await render(
<AddressEntity
address={{ ...addressMock.contract, implementations: implementationsMock.multiple }}
/>,
);
await component.getByText(/eternal/i).hover();
await expect(page.getByText('Proxy contract')).toBeVisible();
await expect(page).toHaveScreenshot();
});
});
test.describe('loading', () => { test.describe('loading', () => {
test('without alias', async({ render }) => { test('without alias', async({ render }) => {
const component = await render( const component = await render(
......
...@@ -11,6 +11,7 @@ import { useAddressHighlightContext } from 'lib/contexts/addressHighlight'; ...@@ -11,6 +11,7 @@ import { useAddressHighlightContext } from 'lib/contexts/addressHighlight';
import * as EntityBase from 'ui/shared/entities/base/components'; import * as EntityBase from 'ui/shared/entities/base/components';
import { getIconProps } from '../base/utils'; import { getIconProps } from '../base/utils';
import AddressEntityContentProxy from './AddressEntityContentProxy';
import AddressIdenticon from './AddressIdenticon'; import AddressIdenticon from './AddressIdenticon';
type LinkProps = EntityBase.LinkBaseProps & Pick<EntityProps, 'address'>; type LinkProps = EntityBase.LinkBaseProps & Pick<EntityProps, 'address'>;
...@@ -57,27 +58,18 @@ const Icon = (props: IconProps) => { ...@@ -57,27 +58,18 @@ const Icon = (props: IconProps) => {
); );
} }
if (props.address.is_verified) { const isProxy = Boolean(props.address.implementations?.length);
return ( const isVerified = isProxy ? props.address.is_verified && props.address.implementations?.every(({ name }) => Boolean(name)) : props.address.is_verified;
<Tooltip label="Verified contract"> const contractIconName: EntityBase.IconBaseProps['name'] = props.address.is_verified ? 'contracts/verified' : 'contracts/regular';
<span> const label = (isVerified ? 'verified ' : '') + (isProxy ? 'proxy contract' : 'contract');
<EntityBase.Icon
{ ...props }
name="contracts/verified"
color="green.500"
borderRadius={ 0 }
/>
</span>
</Tooltip>
);
}
return ( return (
<Tooltip label="Contract"> <Tooltip label={ label.slice(0, 1).toUpperCase() + label.slice(1) }>
<span> <span>
<EntityBase.Icon <EntityBase.Icon
{ ...props } { ...props }
name="contracts/regular" name={ isProxy ? 'contracts/proxy' : contractIconName }
color={ isVerified ? 'green.500' : undefined }
borderRadius={ 0 } borderRadius={ 0 }
/> />
</span> </span>
...@@ -95,12 +87,18 @@ const Icon = (props: IconProps) => { ...@@ -95,12 +87,18 @@ const Icon = (props: IconProps) => {
); );
}; };
type ContentProps = Omit<EntityBase.ContentBaseProps, 'text'> & Pick<EntityProps, 'address'>; export type ContentProps = Omit<EntityBase.ContentBaseProps, 'text'> & Pick<EntityProps, 'address'>;
const Content = chakra((props: ContentProps) => { const Content = chakra((props: ContentProps) => {
const nameTag = props.address.metadata?.tags.find(tag => tag.tagType === 'name')?.name; const nameTag = props.address.metadata?.tags.find(tag => tag.tagType === 'name')?.name;
const nameText = nameTag || props.address.ens_domain_name || props.address.name; const nameText = nameTag || props.address.ens_domain_name || props.address.name;
const isProxy = props.address.implementations && props.address.implementations.length > 0;
if (isProxy) {
return <AddressEntityContentProxy { ...props }/>;
}
if (nameText) { if (nameText) {
const label = ( const label = (
<VStack gap={ 0 } py={ 1 } color="inherit"> <VStack gap={ 0 } py={ 1 } color="inherit">
...@@ -140,15 +138,18 @@ const Copy = (props: CopyProps) => { ...@@ -140,15 +138,18 @@ const Copy = (props: CopyProps) => {
const Container = EntityBase.Container; const Container = EntityBase.Container;
export interface EntityProps extends EntityBase.EntityBaseProps { export interface EntityProps extends EntityBase.EntityBaseProps {
address: Pick<AddressParam, 'hash' | 'name' | 'is_contract' | 'is_verified' | 'ens_domain_name' | 'metadata'>; address: Pick<AddressParam,
'hash' | 'name' | 'is_contract' | 'is_verified' | 'implementations' | 'ens_domain_name' | 'metadata'
>;
isSafeAddress?: boolean; isSafeAddress?: boolean;
noHighlight?: boolean;
} }
const AddressEntry = (props: EntityProps) => { const AddressEntry = (props: EntityProps) => {
const linkProps = _omit(props, [ 'className' ]); const linkProps = _omit(props, [ 'className' ]);
const partsProps = _omit(props, [ 'className', 'onClick' ]); const partsProps = _omit(props, [ 'className', 'onClick' ]);
const context = useAddressHighlightContext(); const context = useAddressHighlightContext(props.noHighlight);
return ( return (
<Container <Container
......
import { Box, DarkMode, PopoverBody, PopoverContent, PopoverTrigger, Portal, useColorModeValue, Flex, PopoverArrow } from '@chakra-ui/react';
import React from 'react';
import Popover from 'ui/shared/chakra/Popover';
import * as EntityBase from 'ui/shared/entities/base/components';
import type { ContentProps } from './AddressEntity';
import AddressEntity from './AddressEntity';
const AddressEntityContentProxy = (props: ContentProps) => {
const bgColor = useColorModeValue('gray.700', 'gray.900');
const implementations = props.address.implementations;
const handleClick = React.useCallback((event: React.MouseEvent) => {
event.stopPropagation();
}, []);
if (!implementations || implementations.length === 0) {
return null;
}
const colNum = Math.min(implementations.length, 3);
const implementationName = implementations.length === 1 && implementations[0].name ? implementations[0].name : undefined;
return (
<Popover trigger="hover" isLazy gutter={ 8 }>
<PopoverTrigger>
<Box display="inline-flex" w="100%">
<EntityBase.Content
{ ...props }
truncation={ implementationName || props.address.name ? 'tail' : 'dynamic' }
text={ implementationName || props.address.name || props.address.hash }
/>
</Box>
</PopoverTrigger>
<Portal>
<DarkMode>
<PopoverContent bgColor={ bgColor } w="fit-content" borderRadius="sm" maxW={{ base: '100vw', lg: '410px' }} onClick={ handleClick }>
<PopoverArrow bgColor={ bgColor }/>
<PopoverBody color="white" p={ 2 } fontSize="sm" lineHeight={ 5 } textAlign="center">
<Box fontWeight={ 600 }>
Proxy contract
{ props.address.name ? ` (${ props.address.name })` : '' }
</Box>
<AddressEntity address={{ hash: props.address.hash }} noLink noIcon noHighlight justifyContent="center"/>
<Box fontWeight={ 600 } mt={ 2 }>
Implementation{ implementations.length > 1 ? 's' : '' }
{ implementationName ? ` (${ implementationName })` : '' }
</Box>
<Flex flexWrap="wrap" columnGap={ 3 }>
{ implementations.map((item) => (
<AddressEntity
key={ item.address }
address={{ hash: item.address }}
noLink
noIcon
noHighlight
minW={ `calc((100% - ${ colNum - 1 } * 12px) / ${ colNum })` }
flex={ 1 }
justifyContent={ colNum === 1 ? 'center' : undefined }
/>
)) }
</Flex>
</PopoverBody>
</PopoverContent>
</DarkMode>
</Portal>
</Popover>
);
};
export default React.memo(AddressEntityContentProxy);
...@@ -54,7 +54,7 @@ const Icon = dynamic( ...@@ -54,7 +54,7 @@ const Icon = dynamic(
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
return (props: IconProps) => { return (props: IconProps) => {
const svg = GradientAvatar(props.hash, props.size); const svg = GradientAvatar(props.hash, props.size, 'circle');
return <div dangerouslySetInnerHTML={{ __html: svg }}/>; return <div dangerouslySetInnerHTML={{ __html: svg }}/>;
}; };
} }
......
...@@ -142,6 +142,7 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna ...@@ -142,6 +142,7 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna
tailLength={ tailLength } tailLength={ tailLength }
/> />
); );
case 'tail':
case 'none': case 'none':
return <chakra.span as={ asProp }>{ text }</chakra.span>; return <chakra.span as={ asProp }>{ text }</chakra.span>;
} }
...@@ -153,6 +154,7 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna ...@@ -153,6 +154,7 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna
isLoaded={ !isLoading } isLoaded={ !isLoading }
overflow="hidden" overflow="hidden"
whiteSpace="nowrap" whiteSpace="nowrap"
textOverflow={ truncation === 'tail' ? 'ellipsis' : undefined }
> >
{ children } { children }
</Skeleton> </Skeleton>
......
import { Box, chakra, Flex, Image, Popover, PopoverBody, PopoverContent, PopoverTrigger, Portal, Skeleton, Text } from '@chakra-ui/react'; import { Box, chakra, Flex, Image, PopoverBody, PopoverContent, PopoverTrigger, Portal, Skeleton, Text } from '@chakra-ui/react';
import _omit from 'lodash/omit'; import _omit from 'lodash/omit';
import React from 'react'; import React from 'react';
...@@ -6,6 +6,7 @@ import type * as bens from '@blockscout/bens-types'; ...@@ -6,6 +6,7 @@ import type * as bens from '@blockscout/bens-types';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import Popover from 'ui/shared/chakra/Popover';
import * as EntityBase from 'ui/shared/entities/base/components'; import * as EntityBase from 'ui/shared/entities/base/components';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
......
import type { PopoverContentProps } from '@chakra-ui/react'; import type { PopoverContentProps } from '@chakra-ui/react';
import { import {
Popover,
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
...@@ -8,6 +7,7 @@ import { ...@@ -8,6 +7,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Popover from 'ui/shared/chakra/Popover';
import FilterButton from 'ui/shared/filters/FilterButton'; import FilterButton from 'ui/shared/filters/FilterButton';
interface Props { interface Props {
......
import { import {
Popover,
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
...@@ -11,6 +10,7 @@ import { ...@@ -11,6 +10,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Popover from 'ui/shared/chakra/Popover';
import FilterButton from 'ui/shared/filters/FilterButton'; import FilterButton from 'ui/shared/filters/FilterButton';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
......
...@@ -5,7 +5,6 @@ import { ...@@ -5,7 +5,6 @@ import {
DarkMode, DarkMode,
Flex, Flex,
Grid, Grid,
Popover,
PopoverBody, PopoverBody,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
...@@ -20,6 +19,7 @@ import { route } from 'nextjs-routes'; ...@@ -20,6 +19,7 @@ import { route } from 'nextjs-routes';
import config from 'configs/app'; import config from 'configs/app';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import Popover from 'ui/shared/chakra/Popover';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
import GasInfoTooltipRow from './GasInfoTooltipRow'; import GasInfoTooltipRow from './GasInfoTooltipRow';
...@@ -33,7 +33,6 @@ interface Props { ...@@ -33,7 +33,6 @@ interface Props {
placement?: PlacementWithLogical; placement?: PlacementWithLogical;
} }
const POPOVER_OFFSET: [ number, number ] = [ 0, 10 ];
const feature = config.features.gasTracker; const feature = config.features.gasTracker;
const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen, placement }: Props) => { const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen, placement }: Props) => {
...@@ -50,7 +49,7 @@ const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen, placement }: Pr ...@@ -50,7 +49,7 @@ const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen, placement }: Pr
3 : 2; 3 : 2;
return ( return (
<Popover trigger="hover" isLazy offset={ POPOVER_OFFSET } isOpen={ isOpen } placement={ placement }> <Popover trigger="hover" isLazy isOpen={ isOpen } placement={ placement }>
<PopoverTrigger> <PopoverTrigger>
{ children } { children }
</PopoverTrigger> </PopoverTrigger>
......
...@@ -34,7 +34,7 @@ if (config.features.dataAvailability.isEnabled) { ...@@ -34,7 +34,7 @@ if (config.features.dataAvailability.isEnabled) {
} }
if (config.features.nameService.isEnabled) { if (config.features.nameService.isEnabled) {
searchCategories.push({ id: 'domain', title: 'Names' }); searchCategories.unshift({ id: 'domain', title: 'Names' });
} }
export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleShort: string }> = { export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleShort: string }> = {
......
import { import {
Popover,
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
...@@ -10,6 +9,7 @@ import { ...@@ -10,6 +9,7 @@ import {
import React from 'react'; import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import Popover from 'ui/shared/chakra/Popover';
import SortButtonDesktop from './ButtonDesktop'; import SortButtonDesktop from './ButtonDesktop';
import SortButtonMobile from './ButtonMobile'; import SortButtonMobile from './ButtonMobile';
......
...@@ -5,20 +5,23 @@ import React from 'react'; ...@@ -5,20 +5,23 @@ import React from 'react';
import type { Route } from 'nextjs-routes'; import type { Route } from 'nextjs-routes';
import Hint from 'ui/shared/Hint'; import Hint from 'ui/shared/Hint';
import IconSvg, { type IconName } from 'ui/shared/IconSvg';
import TruncatedValue from 'ui/shared/TruncatedValue'; import TruncatedValue from 'ui/shared/TruncatedValue';
type Props = { type Props = {
className?: string;
label: string; label: string;
value: string; value: string | React.ReactNode;
valuePrefix?: string; valuePrefix?: string;
valuePostfix?: string; valuePostfix?: string;
hint?: string; hint?: string | React.ReactNode;
isLoading?: boolean; isLoading?: boolean;
diff?: string | number; diff?: string | number;
diffFormatted?: string; diffFormatted?: string;
diffPeriod?: '24h'; diffPeriod?: '24h';
period?: '1h' | '24h'; period?: '1h' | '24h';
href?: Route; href?: Route;
icon?: IconName;
} }
const Container = ({ href, children }: { href?: Route; children: JSX.Element }) => { const Container = ({ href, children }: { href?: Route; children: JSX.Element }) => {
...@@ -33,31 +36,57 @@ const Container = ({ href, children }: { href?: Route; children: JSX.Element }) ...@@ -33,31 +36,57 @@ const Container = ({ href, children }: { href?: Route; children: JSX.Element })
return children; return children;
}; };
const StatsWidget = ({ label, value, valuePrefix, valuePostfix, isLoading, hint, diff, diffPeriod = '24h', diffFormatted, period, href }: Props) => { const StatsWidget = ({
const bgColor = useColorModeValue('blue.50', 'whiteAlpha.100'); className,
icon,
label,
value,
valuePrefix,
valuePostfix,
isLoading,
hint,
diff,
diffPeriod = '24h',
diffFormatted,
period,
href,
}: Props) => {
const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100');
const skeletonBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const skeletonBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const hintColor = useColorModeValue('gray.600', 'gray.400'); const hintColor = useColorModeValue('gray.600', 'gray.400');
return ( return (
<Container href={ !isLoading ? href : undefined }> <Container href={ !isLoading ? href : undefined }>
<Flex <Flex
alignItems="flex-start" className={ className }
alignItems="center"
bgColor={ isLoading ? skeletonBgColor : bgColor } bgColor={ isLoading ? skeletonBgColor : bgColor }
px={ 3 } p={ 3 }
py={{ base: 2, lg: 3 }} borderRadius="base"
borderRadius="md"
justifyContent="space-between" justifyContent="space-between"
columnGap={ 3 } columnGap={ 2 }
{ ...(href && !isLoading ? { { ...(href && !isLoading ? {
as: 'a', as: 'a',
href, href,
} : {}) } } : {}) }
> >
{ icon && (
<IconSvg
name={ icon }
p={ 2 }
boxSize="40px"
isLoading={ isLoading }
borderRadius="base"
display={{ base: 'none', lg: 'block' }}
flexShrink={ 0 }
/>
) }
<Box w="100%"> <Box w="100%">
<Skeleton <Skeleton
isLoaded={ !isLoading } isLoaded={ !isLoading }
color="text_secondary" color="text_secondary"
fontSize="xs" fontSize="xs"
lineHeight="16px"
w="fit-content" w="fit-content"
> >
<span>{ label }</span> <span>{ label }</span>
...@@ -66,30 +95,36 @@ const StatsWidget = ({ label, value, valuePrefix, valuePostfix, isLoading, hint, ...@@ -66,30 +95,36 @@ const StatsWidget = ({ label, value, valuePrefix, valuePostfix, isLoading, hint,
isLoaded={ !isLoading } isLoaded={ !isLoading }
display="flex" display="flex"
alignItems="baseline" alignItems="baseline"
mt={ 1 } fontWeight={ 500 }
fontSize="lg"
lineHeight={ 6 }
> >
{ valuePrefix && <chakra.span fontWeight={ 500 } fontSize="lg" lineHeight={ 6 } whiteSpace="pre">{ valuePrefix }</chakra.span> } { valuePrefix && <chakra.span whiteSpace="pre">{ valuePrefix }</chakra.span> }
<TruncatedValue isLoading={ isLoading } fontWeight={ 500 } fontSize="lg" lineHeight={ 6 } value={ value }/> { typeof value === 'string' ? (
{ valuePostfix && <chakra.span fontWeight={ 500 } fontSize="lg" lineHeight={ 6 } whiteSpace="pre">{ valuePostfix }</chakra.span> } <TruncatedValue isLoading={ isLoading } value={ value }/>
) : (
value
) }
{ valuePostfix && <chakra.span whiteSpace="pre">{ valuePostfix }</chakra.span> }
{ diff && Number(diff) > 0 && ( { diff && Number(diff) > 0 && (
<> <>
<Text fontWeight={ 500 } ml={ 2 } mr={ 1 } fontSize="lg" lineHeight={ 6 } color="green.500"> <Text ml={ 2 } mr={ 1 } color="green.500">
+{ diffFormatted || Number(diff).toLocaleString() } +{ diffFormatted || Number(diff).toLocaleString() }
</Text> </Text>
<Text variant="secondary" fontSize="sm">({ diffPeriod })</Text> <Text variant="secondary" fontSize="sm">({ diffPeriod })</Text>
</> </>
) } ) }
{ period && <Text variant="secondary" fontSize="xs" ml={ 1 }>({ period })</Text> } { period && <Text variant="secondary" fontSize="xs" fontWeight={ 400 } ml={ 1 }>({ period })</Text> }
</Skeleton> </Skeleton>
</Box> </Box>
{ hint && ( { typeof hint === 'string' ? (
<Skeleton isLoaded={ !isLoading } alignSelf="center" borderRadius="base"> <Skeleton isLoaded={ !isLoading } alignSelf="center" borderRadius="base">
<Hint label={ hint } boxSize={ 6 } color={ hintColor }/> <Hint label={ hint } boxSize={ 6 } color={ hintColor }/>
</Skeleton> </Skeleton>
) } ) : hint }
</Flex> </Flex>
</Container> </Container>
); );
}; };
export default StatsWidget; export default chakra(StatsWidget);
import React from 'react';
import * as txMock from 'mocks/txs/tx';
import { test, expect } from 'playwright/lib';
import TxFee from './TxFee';
test.use({ viewport: { width: 300, height: 100 } });
test('base view', async({ render }) => {
const component = await render(<TxFee tx={ txMock.base } withUsd/>);
await expect(component).toHaveScreenshot();
});
test('no usd value', async({ render }) => {
const component = await render(<TxFee tx={ txMock.base } accuracy={ 3 }/>);
await expect(component).toHaveScreenshot();
});
test('celo gas token', async({ render, mockAssetResponse }) => {
await mockAssetResponse(txMock.celoTxn.celo?.gas_token?.icon_url as string, './playwright/mocks/image_svg.svg');
const component = await render(<TxFee tx={ txMock.celoTxn } withUsd accuracyUsd={ 3 }/>);
await expect(component).toHaveScreenshot();
});
test('stability token', async({ render, mockAssetResponse }) => {
await mockAssetResponse(txMock.stabilityTx.stability_fee?.token.icon_url as string, './playwright/mocks/image_svg.svg');
const component = await render(<TxFee tx={ txMock.stabilityTx } withUsd accuracyUsd={ 3 }/>);
await expect(component).toHaveScreenshot();
});
import { chakra, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import config from 'configs/app';
import getCurrencyValue from 'lib/getCurrencyValue';
import { currencyUnits } from 'lib/units';
import CurrencyValue from 'ui/shared/CurrencyValue';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
interface Props {
className?: string;
isLoading?: boolean;
tx: Transaction;
withCurrency?: boolean;
withUsd?: boolean;
accuracy?: number;
accuracyUsd?: number;
}
const TxFee = ({ className, tx, accuracy, accuracyUsd, isLoading, withCurrency = true, withUsd }: Props) => {
if (tx.celo?.gas_token) {
const token = tx.celo.gas_token;
const { valueStr, usd } = getCurrencyValue({
value: tx.fee.value || '0',
exchangeRate: token.exchange_rate,
decimals: token.decimals,
accuracy,
accuracyUsd,
});
return (
<Skeleton whiteSpace="pre-wrap" wordBreak="break-word" isLoaded={ !isLoading } display="flex" flexWrap="wrap" className={ className }>
<span>{ valueStr } </span>
<TokenEntity token={ token } noCopy onlySymbol w="auto" ml={ 1 }/>
{ usd && withUsd && <chakra.span color="text_secondary"> (${ usd })</chakra.span> }
</Skeleton>
);
}
if (tx.stability_fee) {
const token = tx.stability_fee.token;
const { valueStr, usd } = getCurrencyValue({
value: tx.stability_fee.total_fee,
exchangeRate: token.exchange_rate,
decimals: token.decimals,
accuracy,
accuracyUsd,
});
return (
<Skeleton whiteSpace="pre" isLoaded={ !isLoading } display="flex" className={ className }>
<span>{ valueStr } </span>
{ valueStr !== '0' && <TokenEntity token={ token } noCopy onlySymbol w="auto" ml={ 1 }/> }
{ usd && withUsd && <chakra.span color="text_secondary"> (${ usd })</chakra.span> }
</Skeleton>
);
}
const showCurrency = withCurrency && !config.UI.views.tx.hiddenFields?.fee_currency;
return (
<CurrencyValue
value={ tx.fee.value }
currency={ showCurrency ? currencyUnits.ether : '' }
exchangeRate={ withUsd ? tx.exchange_rate : null }
accuracy={ accuracy }
accuracyUsd={ accuracyUsd }
flexWrap="wrap"
className={ className }
isLoading={ isLoading }
/>
);
};
export default React.memo(chakra(TxFee));
import { Skeleton, chakra } from '@chakra-ui/react';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import type { ExcludeUndefined } from 'types/utils';
import getCurrencyValue from 'lib/getCurrencyValue';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
interface Props {
data: ExcludeUndefined<Transaction['stability_fee']>;
isLoading?: boolean;
hideUsd?: boolean;
accuracy?: number;
className?: string;
}
const TxFeeStability = ({ data, isLoading, hideUsd, accuracy, className }: Props) => {
const { valueStr, usd } = getCurrencyValue({
value: data.total_fee,
exchangeRate: data.token.exchange_rate,
decimals: data.token.decimals,
accuracy,
});
return (
<Skeleton whiteSpace="pre" isLoaded={ !isLoading } display="flex" className={ className }>
<span>{ valueStr } </span>
{ valueStr !== '0' && <TokenEntity token={ data.token } noCopy onlySymbol w="auto" ml={ 1 }/> }
{ usd && !hideUsd && <chakra.span color="text_secondary"> (${ usd })</chakra.span> }
</Skeleton>
);
};
export default React.memo(chakra(TxFeeStability));
import { IconButton, Popover, PopoverTrigger, PopoverContent, PopoverBody, Flex, Text, useColorModeValue } from '@chakra-ui/react'; import { IconButton, PopoverTrigger, PopoverContent, PopoverBody, Flex, Text, useColorModeValue } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
...@@ -9,6 +9,7 @@ import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; ...@@ -9,6 +9,7 @@ import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import { apos, nbsp, ndash } from 'lib/html-entities'; import { apos, nbsp, ndash } from 'lib/html-entities';
import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
const IntTxsIndexingStatus = () => { const IntTxsIndexingStatus = () => {
......
import { HStack, Popover, PopoverBody, PopoverContent, PopoverTrigger, chakra, StackDivider } from '@chakra-ui/react'; import { HStack, PopoverBody, PopoverContent, PopoverTrigger, chakra, StackDivider } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { NavGroupItem } from 'types/client/navigation'; import type { NavGroupItem } from 'types/client/navigation';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LightningLabel from '../LightningLabel'; import LightningLabel from '../LightningLabel';
...@@ -27,6 +28,7 @@ const NavLinkGroup = ({ item }: Props) => { ...@@ -27,6 +28,7 @@ const NavLinkGroup = ({ item }: Props) => {
trigger="hover" trigger="hover"
placement="bottom-start" placement="bottom-start"
isLazy isLazy
gutter={ 8 }
> >
{ ({ isOpen }) => ( { ({ isOpen }) => (
<> <>
......
...@@ -3,7 +3,6 @@ import { ...@@ -3,7 +3,6 @@ import {
HStack, HStack,
Box, Box,
Link, Link,
Popover,
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
...@@ -13,6 +12,7 @@ import React from 'react'; ...@@ -13,6 +12,7 @@ import React from 'react';
import type { NavGroupItem } from 'types/client/navigation'; import type { NavGroupItem } from 'types/client/navigation';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LightningLabel from '../LightningLabel'; import LightningLabel from '../LightningLabel';
...@@ -39,6 +39,7 @@ const NavLinkGroup = ({ item, isCollapsed }: Props) => { ...@@ -39,6 +39,7 @@ const NavLinkGroup = ({ item, isCollapsed }: Props) => {
trigger="hover" trigger="hover"
placement="right-start" placement="right-start"
isLazy isLazy
gutter={ 8 }
> >
<PopoverTrigger> <PopoverTrigger>
<Link <Link
......
import { Popover, PopoverTrigger } from '@chakra-ui/react'; import { PopoverTrigger } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Popover from 'ui/shared/chakra/Popover';
import NetworkMenuButton from './NetworkMenuButton'; import NetworkMenuButton from './NetworkMenuButton';
import NetworkMenuContentDesktop from './NetworkMenuContentDesktop'; import NetworkMenuContentDesktop from './NetworkMenuContentDesktop';
import useNetworkMenu from './useNetworkMenu'; import useNetworkMenu from './useNetworkMenu';
...@@ -13,7 +15,7 @@ const NetworkMenu = ({ isCollapsed }: Props) => { ...@@ -13,7 +15,7 @@ const NetworkMenu = ({ isCollapsed }: Props) => {
const menu = useNetworkMenu(); const menu = useNetworkMenu();
return ( return (
<Popover openDelay={ 300 } placement="right-start" gutter={ 8 } isLazy isOpen={ menu.isOpen } onClose={ menu.onClose }> <Popover openDelay={ 300 } placement="right-start" isLazy isOpen={ menu.isOpen } onClose={ menu.onClose }>
<PopoverTrigger> <PopoverTrigger>
<NetworkMenuButton <NetworkMenuButton
marginLeft="auto" marginLeft="auto"
......
import type { IconButtonProps } from '@chakra-ui/react'; import type { IconButtonProps } from '@chakra-ui/react';
import { Popover, PopoverContent, PopoverBody, PopoverTrigger, IconButton, Tooltip, Box, chakra } from '@chakra-ui/react'; import { PopoverContent, PopoverBody, PopoverTrigger, IconButton, Tooltip, Box, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import useLoginUrl from 'lib/hooks/useLoginUrl'; import useLoginUrl from 'lib/hooks/useLoginUrl';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import Popover from 'ui/shared/chakra/Popover';
import UserAvatar from 'ui/shared/UserAvatar'; import UserAvatar from 'ui/shared/UserAvatar';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent'; import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
......
import { import {
Box, Box,
Portal, Portal,
Popover,
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
...@@ -20,6 +19,7 @@ import { route } from 'nextjs-routes'; ...@@ -20,6 +19,7 @@ import { route } from 'nextjs-routes';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import { getRecentSearchKeywords, saveToRecentKeywords } from 'lib/recentSearchKeywords'; import { getRecentSearchKeywords, saveToRecentKeywords } from 'lib/recentSearchKeywords';
import Popover from 'ui/shared/chakra/Popover';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
import SearchBarBackdrop from './SearchBarBackdrop'; import SearchBarBackdrop from './SearchBarBackdrop';
...@@ -123,7 +123,7 @@ const SearchBar = ({ isHomepage }: Props) => { ...@@ -123,7 +123,7 @@ const SearchBar = ({ isHomepage }: Props) => {
autoFocus={ false } autoFocus={ false }
onClose={ onClose } onClose={ onClose }
placement="bottom-start" placement="bottom-start"
offset={ isMobile && !isHomepage ? [ 12, -4 ] : undefined } offset={ isMobile && !isHomepage ? [ 12, -4 ] : [ 0, 8 ] }
isLazy isLazy
> >
<PopoverTrigger> <PopoverTrigger>
......
...@@ -27,6 +27,7 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => { ...@@ -27,6 +27,7 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
name: '', name: '',
is_verified: data.is_smart_contract_verified, is_verified: data.is_smart_contract_verified,
ens_domain_name: null, ens_domain_name: null,
implementations: null,
}} }}
/> />
); );
......
import { Button, Box, Flex, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, chakra } from '@chakra-ui/react'; import { Button, Box, Flex, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -7,6 +7,7 @@ import { route } from 'nextjs-routes'; ...@@ -7,6 +7,7 @@ import { route } from 'nextjs-routes';
import config from 'configs/app'; import config from 'configs/app';
import getPageType from 'lib/mixpanel/getPageType'; import getPageType from 'lib/mixpanel/getPageType';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import DeFiDropdownItem from './DeFiDropdownItem'; import DeFiDropdownItem from './DeFiDropdownItem';
......
import { IconButton, Popover, PopoverTrigger } from '@chakra-ui/react'; import { IconButton, PopoverTrigger } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import NetworkMenuContentDesktop from 'ui/snippets/networkMenu/NetworkMenuContentDesktop'; import NetworkMenuContentDesktop from 'ui/snippets/networkMenu/NetworkMenuContentDesktop';
import useNetworkMenu from 'ui/snippets/networkMenu/useNetworkMenu'; import useNetworkMenu from 'ui/snippets/networkMenu/useNetworkMenu';
......
import { Box, IconButton, Popover, PopoverBody, PopoverContent, PopoverTrigger, useDisclosure } from '@chakra-ui/react'; import { Box, IconButton, PopoverBody, PopoverContent, PopoverTrigger, useDisclosure } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import SettingsColorTheme from './SettingsColorTheme'; import SettingsColorTheme from './SettingsColorTheme';
......
import type { ButtonProps } from '@chakra-ui/react'; import type { ButtonProps } from '@chakra-ui/react';
import { Popover, PopoverContent, PopoverBody, PopoverTrigger, Button, Box, useBoolean, chakra, useColorModeValue } from '@chakra-ui/react'; import { PopoverContent, PopoverBody, PopoverTrigger, Button, Box, useBoolean, chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { useMarketplaceContext } from 'lib/contexts/marketplace'; import { useMarketplaceContext } from 'lib/contexts/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import Popover from 'ui/shared/chakra/Popover';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
import IconSvg from 'ui/shared/IconSvg';
import useWallet from 'ui/snippets/walletMenu/useWallet'; import useWallet from 'ui/snippets/walletMenu/useWallet';
import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent'; import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent';
...@@ -65,7 +67,6 @@ const WalletMenuDesktop = ({ isHomePage, className, size = 'md' }: Props) => { ...@@ -65,7 +67,6 @@ const WalletMenuDesktop = ({ isHomePage, className, size = 'md' }: Props) => {
<Popover <Popover
openDelay={ 300 } openDelay={ 300 }
placement="bottom-end" placement="bottom-end"
gutter={ 10 }
isLazy isLazy
isOpen={ isPopoverOpen } isOpen={ isPopoverOpen }
onClose={ setIsPopoverOpen.off } onClose={ setIsPopoverOpen.off }
...@@ -87,6 +88,7 @@ const WalletMenuDesktop = ({ isHomePage, className, size = 'md' }: Props) => { ...@@ -87,6 +88,7 @@ const WalletMenuDesktop = ({ isHomePage, className, size = 'md' }: Props) => {
onClick={ isWalletConnected ? openPopover : connect } onClick={ isWalletConnected ? openPopover : connect }
fontSize="sm" fontSize="sm"
size={ size } size={ size }
px={{ lg: isHomePage ? 2 : 4, xl: 4 }}
{ ...buttonStyles } { ...buttonStyles }
> >
{ isWalletConnected ? ( { isWalletConnected ? (
...@@ -94,7 +96,12 @@ const WalletMenuDesktop = ({ isHomePage, className, size = 'md' }: Props) => { ...@@ -94,7 +96,12 @@ const WalletMenuDesktop = ({ isHomePage, className, size = 'md' }: Props) => {
<WalletIdenticon address={ address } isAutoConnectDisabled={ isAutoConnectDisabled } mr={ 2 }/> <WalletIdenticon address={ address } isAutoConnectDisabled={ isAutoConnectDisabled } mr={ 2 }/>
<HashStringShorten hash={ address } isTooltipDisabled/> <HashStringShorten hash={ address } isTooltipDisabled/>
</> </>
) : 'Connect wallet' } ) : (
<>
<IconSvg display={{ base: isHomePage ? 'inline' : 'none', xl: 'none' }} name="wallet" boxSize={ 6 } p={ 0.5 }/>
<chakra.span display={{ base: isHomePage ? 'none' : 'inline', xl: 'inline' }}>Connect wallet</chakra.span>
</>
) }
</Button> </Button>
</WalletTooltip> </WalletTooltip>
</PopoverTrigger> </PopoverTrigger>
......
...@@ -38,7 +38,7 @@ const WalletMenuMobile = () => { ...@@ -38,7 +38,7 @@ const WalletMenuMobile = () => {
aria-label="wallet menu" aria-label="wallet menu"
icon={ isWalletConnected ? icon={ isWalletConnected ?
<WalletIdenticon address={ address } isAutoConnectDisabled={ isAutoConnectDisabled }/> : <WalletIdenticon address={ address } isAutoConnectDisabled={ isAutoConnectDisabled }/> :
<IconSvg name="wallet" boxSize={ 6 }/> <IconSvg name="wallet" boxSize={ 6 } p={ 0.5 }/>
} }
variant={ isWalletConnected ? 'subtle' : 'outline' } variant={ isWalletConnected ? 'subtle' : 'outline' }
colorScheme="gray" colorScheme="gray"
......
...@@ -76,7 +76,7 @@ const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, in ...@@ -76,7 +76,7 @@ const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, in
</Heading> </Heading>
{ section.id === 'gas' && homeStatsQuery.data && homeStatsQuery.data.gas_prices && ( { section.id === 'gas' && homeStatsQuery.data && homeStatsQuery.data.gas_prices && (
<GasInfoTooltip data={ homeStatsQuery.data } dataUpdatedAt={ homeStatsQuery.dataUpdatedAt }> <GasInfoTooltip data={ homeStatsQuery.data } dataUpdatedAt={ homeStatsQuery.dataUpdatedAt }>
<IconSvg name="info" boxSize={ 5 } display="block" cursor="pointer" _hover={{ color: 'link_hovered' }}/> <IconSvg name="info" boxSize={ 5 } display="block" cursor="pointer" color="icon_info" _hover={{ color: 'link_hovered' }}/>
</GasInfoTooltip> </GasInfoTooltip>
) } ) }
</Skeleton> </Skeleton>
......
import { Box, Button, Menu, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, chakra } from '@chakra-ui/react'; import { Box, Button, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, chakra } from '@chakra-ui/react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import Menu from 'ui/shared/chakra/Menu';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
type Props<T extends string> = { type Props<T extends string> = {
......
import { import {
Popover, PopoverTrigger, PopoverContent, PopoverBody, PopoverTrigger, PopoverContent, PopoverBody,
Modal, ModalContent, ModalCloseButton, Modal, ModalContent, ModalCloseButton,
useDisclosure, useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
...@@ -8,6 +8,7 @@ import React from 'react'; ...@@ -8,6 +8,7 @@ import React from 'react';
import type { TokenVerifiedInfo } from 'types/api/token'; import type { TokenVerifiedInfo } from 'types/api/token';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import Popover from 'ui/shared/chakra/Popover';
import Content, { hasContent } from './TokenProjectInfo/Content'; import Content, { hasContent } from './TokenProjectInfo/Content';
import TriggerButton from './TokenProjectInfo/TriggerButton'; import TriggerButton from './TokenProjectInfo/TriggerButton';
......
import { chakra, Alert, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import type { ToastId } from '@chakra-ui/react';
import { chakra, Alert, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, Spinner } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import ReCaptcha from 'react-google-recaptcha'; import ReCaptcha from 'react-google-recaptcha';
...@@ -9,7 +10,7 @@ import type { TokenInstance } from 'types/api/token'; ...@@ -9,7 +10,7 @@ import type { TokenInstance } from 'types/api/token';
import config from 'configs/app'; import config from 'configs/app';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import { MINUTE } from 'lib/consts'; import { MINUTE, SECOND } from 'lib/consts';
import useToast from 'lib/hooks/useToast'; import useToast from 'lib/hooks/useToast';
import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
...@@ -23,6 +24,7 @@ interface Props { ...@@ -23,6 +24,7 @@ interface Props {
const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
const timeoutId = React.useRef<number>(); const timeoutId = React.useRef<number>();
const toastId = React.useRef<ToastId>();
const { status, setStatus } = useMetadataUpdateContext() || {}; const { status, setStatus } = useMetadataUpdateContext() || {};
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
...@@ -31,12 +33,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -31,12 +33,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
const handleRefreshError = React.useCallback(() => { const handleRefreshError = React.useCallback(() => {
setStatus?.('ERROR'); setStatus?.('ERROR');
toast.closeAll(); toastId.current && toast.update(toastId.current, {
toast({
title: 'Error', title: 'Error',
description: 'The refreshing process has failed. Please try again.', description: 'The refreshing process has failed. Please try again.',
status: 'warning', status: 'warning',
variant: 'subtle', duration: 5 * SECOND,
isClosable: true,
}); });
}, [ setStatus, toast ]); }, [ setStatus, toast ]);
...@@ -49,13 +51,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -49,13 +51,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
}, },
}) })
.then(() => { .then(() => {
toast({ setStatus?.('WAITING_FOR_RESPONSE');
toastId.current = toast({
title: 'Please wait', title: 'Please wait',
description: 'Refetching metadata request sent', description: 'Refetching metadata request sent',
icon: <Spinner size="sm" mr={ 2 }/>,
status: 'warning', status: 'warning',
variant: 'subtle', duration: null,
isClosable: false,
}); });
setStatus?.('WAITING_FOR_RESPONSE');
timeoutId.current = window.setTimeout(handleRefreshError, 2 * MINUTE); timeoutId.current = window.setTimeout(handleRefreshError, 2 * MINUTE);
}) })
.catch(() => { .catch(() => {
...@@ -63,7 +67,6 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -63,7 +67,6 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
title: 'Error', title: 'Error',
description: 'Unable to initialize metadata update', description: 'Unable to initialize metadata update',
status: 'warning', status: 'warning',
variant: 'subtle',
}); });
setStatus?.('ERROR'); setStatus?.('ERROR');
}); });
...@@ -112,12 +115,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -112,12 +115,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
}; };
}); });
toast.closeAll(); toastId.current && toast.update(toastId.current, {
toast({
title: 'Success!', title: 'Success!',
description: 'Metadata has been refreshed', description: 'Metadata has been refreshed',
status: 'success', status: 'success',
variant: 'subtle', duration: 5 * SECOND,
isClosable: true,
}); });
setStatus?.('SUCCESS'); setStatus?.('SUCCESS');
...@@ -138,6 +141,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -138,6 +141,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
handler: handleSocketMessage, handler: handleSocketMessage,
}); });
React.useEffect(() => {
return () => {
timeoutId.current && window.clearTimeout(timeoutId.current);
toastId.current && toast.close(toastId.current);
};
// run only on mount/unmount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return ( return (
<Modal isOpen={ status === 'MODAL_OPENED' } onClose={ handleModalClose } size={{ base: 'full', lg: 'sm' }}> <Modal isOpen={ status === 'MODAL_OPENED' } onClose={ handleModalClose } size={{ base: 'full', lg: 'sm' }}>
<ModalOverlay/> <ModalOverlay/>
......
...@@ -49,6 +49,7 @@ const TokensTableItem = ({ ...@@ -49,6 +49,7 @@ const TokensTableItem = ({
is_contract: true, is_contract: true,
is_verified: false, is_verified: false,
ens_domain_name: null, ens_domain_name: null,
implementations: null,
}; };
return ( return (
......
import { Table, Tbody, Tr, Th, Box, Skeleton, Text, Show, Hide } from '@chakra-ui/react'; import { Table, Tbody, Tr, Th, Box, Skeleton, Text, Show, Hide } from '@chakra-ui/react';
import _ from 'lodash'; import _chunk from 'lodash/chunk';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import type { PaginationParams } from 'ui/shared/pagination/types'; import type { PaginationParams } from 'ui/shared/pagination/types';
...@@ -33,7 +33,7 @@ export default function TxAssetFlows(props: FlowViewProps) { ...@@ -33,7 +33,7 @@ export default function TxAssetFlows(props: FlowViewProps) {
const [ page, setPage ] = useState<number>(1); const [ page, setPage ] = useState<number>(1);
const ViewData = useMemo(() => (queryData ? generateFlowViewData(queryData) : []), [ queryData ]); const ViewData = useMemo(() => (queryData ? generateFlowViewData(queryData) : []), [ queryData ]);
const chunkedViewData = _.chunk(ViewData, 50); const chunkedViewData = _chunk(ViewData, 50);
const paginationProps: PaginationParams = useMemo(() => ({ const paginationProps: PaginationParams = useMemo(() => ({
onNextPageClick: () => setPage(page + 1), onNextPageClick: () => setPage(page + 1),
......
...@@ -14,6 +14,7 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity'; ...@@ -14,6 +14,7 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData'; import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData';
import RawInputData from 'ui/shared/RawInputData'; import RawInputData from 'ui/shared/RawInputData';
import TxFee from 'ui/shared/tx/TxFee';
import TxDetailsGasPrice from 'ui/tx/details/TxDetailsGasPrice'; import TxDetailsGasPrice from 'ui/tx/details/TxDetailsGasPrice';
import TxDetailsOther from 'ui/tx/details/TxDetailsOther'; import TxDetailsOther from 'ui/tx/details/TxDetailsOther';
...@@ -80,11 +81,7 @@ const TxDetailsWrapped = ({ data }: Props) => { ...@@ -80,11 +81,7 @@ const TxDetailsWrapped = ({ data }: Props) => {
Transaction fee Transaction fee
</DetailsInfoItem.Label> </DetailsInfoItem.Label>
<DetailsInfoItem.Value> <DetailsInfoItem.Value>
<CurrencyValue <TxFee tx={ data } withUsd/>
value={ data.fee.value }
currency={ currencyUnits.ether }
flexWrap="wrap"
/>
</DetailsInfoItem.Value> </DetailsInfoItem.Value>
</> </>
) } ) }
......
import { Box, Hide, Popover, PopoverArrow, PopoverContent, PopoverTrigger, Show, Skeleton, Text, useColorModeValue } from '@chakra-ui/react'; import { Box, Hide, PopoverArrow, PopoverContent, PopoverTrigger, Show, Skeleton, Text, useColorModeValue } from '@chakra-ui/react';
import type { FC } from 'react'; import type { FC } from 'react';
import React from 'react'; import React from 'react';
import { HEX_REGEXP } from 'lib/regexp'; import { HEX_REGEXP } from 'lib/regexp';
import Popover from 'ui/shared/chakra/Popover';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
......
import _ from 'lodash'; import _findIndex from 'lodash/findIndex';
import type { NovesNft, NovesResponseData, NovesSentReceived, NovesToken } from 'types/api/noves'; import type { NovesNft, NovesResponseData, NovesSentReceived, NovesToken } from 'types/api/noves';
...@@ -27,7 +27,7 @@ export function generateFlowViewData(data: NovesResponseData): Array<NovesFlowVi ...@@ -27,7 +27,7 @@ export function generateFlowViewData(data: NovesResponseData): Array<NovesFlowVi
const txItems = [ ...sent, ...received ]; const txItems = [ ...sent, ...received ];
const paidGasIndex = _.findIndex(txItems, (item) => item.action === 'paidGas'); const paidGasIndex = _findIndex(txItems, (item) => item.action === 'paidGas');
if (paidGasIndex >= 0) { if (paidGasIndex >= 0) {
const element = txItems.splice(paidGasIndex, 1)[0]; const element = txItems.splice(paidGasIndex, 1)[0];
element.to.name = 'Validators'; element.to.name = 'Validators';
......
import _ from 'lodash'; import _groupBy from 'lodash/groupBy';
import _keysIn from 'lodash/keysIn';
import _mapValues from 'lodash/mapValues';
import type { NovesResponseData } from 'types/api/noves'; import type { NovesResponseData } from 'types/api/noves';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
...@@ -47,28 +49,28 @@ export function getTokensData(data: NovesResponseData): TokensData { ...@@ -47,28 +49,28 @@ export function getTokensData(data: NovesResponseData): TokensData {
}); });
// Group tokens by property into arrays // Group tokens by property into arrays
const tokensGroupByname = _.groupBy(tokens, 'name'); const tokensGroupByname = _groupBy(tokens, 'name');
const tokensGroupBySymbol = _.groupBy(tokens, 'symbol'); const tokensGroupBySymbol = _groupBy(tokens, 'symbol');
const tokensGroupById = _.groupBy(tokens, 'id'); const tokensGroupById = _groupBy(tokens, 'id');
// Map properties to an object and remove duplicates // Map properties to an object and remove duplicates
const mappedNames = _.mapValues(tokensGroupByname, (i) => { const mappedNames = _mapValues(tokensGroupByname, (i) => {
return i[0]; return i[0];
}); });
const mappedSymbols = _.mapValues(tokensGroupBySymbol, (i) => { const mappedSymbols = _mapValues(tokensGroupBySymbol, (i) => {
return i[0]; return i[0];
}); });
const mappedIds = _.mapValues(tokensGroupById, (i) => { const mappedIds = _mapValues(tokensGroupById, (i) => {
return i[0]; return i[0];
}); });
const filters = [ 'undefined', 'null' ]; const filters = [ 'undefined', 'null' ];
// Array of keys to match in string // Array of keys to match in string
const nameList = _.keysIn(mappedNames).filter(i => !filters.includes(i)); const nameList = _keysIn(mappedNames).filter(i => !filters.includes(i));
const symbolList = _.keysIn(mappedSymbols).filter(i => !filters.includes(i)); const symbolList = _keysIn(mappedSymbols).filter(i => !filters.includes(i));
const idList = _.keysIn(mappedIds).filter(i => !filters.includes(i)); const idList = _keysIn(mappedIds).filter(i => !filters.includes(i));
return { return {
nameList, nameList,
......
...@@ -2,21 +2,47 @@ import { Skeleton } from '@chakra-ui/react'; ...@@ -2,21 +2,47 @@ import { Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { TokenInfo } from 'types/api/token';
import config from 'configs/app'; import config from 'configs/app';
import { WEI, WEI_IN_GWEI } from 'lib/consts'; import { WEI, WEI_IN_GWEI } from 'lib/consts';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
interface Props { interface Props {
gasToken?: TokenInfo<'ERC-20'> | null;
gasPrice: string | null; gasPrice: string | null;
isLoading?: boolean; isLoading?: boolean;
} }
const TxDetailsGasPrice = ({ gasPrice, isLoading }: Props) => { const TxDetailsGasPrice = ({ gasPrice, gasToken, isLoading }: Props) => {
if (config.UI.views.tx.hiddenFields?.gas_price || !gasPrice) { if (config.UI.views.tx.hiddenFields?.gas_price || !gasPrice) {
return null; return null;
} }
const content = (() => {
if (gasToken) {
return (
<Skeleton isLoaded={ !isLoading } display="flex">
<span>{ BigNumber(gasPrice).dividedBy(WEI).toFixed() }</span>
<TokenEntity token={ gasToken } noCopy onlySymbol w="auto" ml={ 1 }/>
</Skeleton>
);
}
return (
<>
<Skeleton isLoaded={ !isLoading } mr={ 1 }>
{ BigNumber(gasPrice).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>({ BigNumber(gasPrice).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</span>
</Skeleton>
</>
);
})();
return ( return (
<> <>
<DetailsInfoItem.Label <DetailsInfoItem.Label
...@@ -26,12 +52,7 @@ const TxDetailsGasPrice = ({ gasPrice, isLoading }: Props) => { ...@@ -26,12 +52,7 @@ const TxDetailsGasPrice = ({ gasPrice, isLoading }: Props) => {
Gas price Gas price
</DetailsInfoItem.Label> </DetailsInfoItem.Label>
<DetailsInfoItem.Value> <DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } mr={ 1 }> { content }
{ BigNumber(gasPrice).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>({ BigNumber(gasPrice).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</span>
</Skeleton>
</DetailsInfoItem.Value> </DetailsInfoItem.Value>
</> </>
); );
......
...@@ -46,7 +46,7 @@ import RawInputData from 'ui/shared/RawInputData'; ...@@ -46,7 +46,7 @@ import RawInputData from 'ui/shared/RawInputData';
import StatusTag from 'ui/shared/statusTag/StatusTag'; import StatusTag from 'ui/shared/statusTag/StatusTag';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import TextSeparator from 'ui/shared/TextSeparator'; import TextSeparator from 'ui/shared/TextSeparator';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFee from 'ui/shared/tx/TxFee';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps';
import TxDetailsActions from 'ui/tx/details/txDetailsActions/TxDetailsActions'; import TxDetailsActions from 'ui/tx/details/txDetailsActions/TxDetailsActions';
...@@ -555,17 +555,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { ...@@ -555,17 +555,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
Transaction fee Transaction fee
</DetailsInfoItem.Label> </DetailsInfoItem.Label>
<DetailsInfoItem.Value> <DetailsInfoItem.Value>
{ data.stability_fee ? ( <TxFee tx={ data } isLoading={ isLoading } withUsd/>
<TxFeeStability data={ data.stability_fee } isLoading={ isLoading }/>
) : (
<CurrencyValue
value={ data.fee.value }
currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : currencyUnits.ether }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
isLoading={ isLoading }
/>
) }
</DetailsInfoItem.Value> </DetailsInfoItem.Value>
</> </>
) } ) }
...@@ -606,7 +596,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { ...@@ -606,7 +596,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
</> </>
) } ) }
<TxDetailsGasPrice gasPrice={ data.gas_price } isLoading={ isLoading }/> <TxDetailsGasPrice gasPrice={ data.gas_price } gasToken={ data.celo?.gas_token } isLoading={ isLoading }/>
<TxDetailsFeePerGas txFee={ data.fee.value } gasUsed={ data.gas_used } isLoading={ isLoading }/> <TxDetailsFeePerGas txFee={ data.fee.value } gasUsed={ data.gas_used } isLoading={ isLoading }/>
......
...@@ -3,7 +3,6 @@ import { ...@@ -3,7 +3,6 @@ import {
Modal, Modal,
ModalContent, ModalContent,
ModalCloseButton, ModalCloseButton,
Popover,
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
...@@ -14,6 +13,7 @@ import React from 'react'; ...@@ -14,6 +13,7 @@ import React from 'react';
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
import AdditionalInfoButton from 'ui/shared/AdditionalInfoButton'; import AdditionalInfoButton from 'ui/shared/AdditionalInfoButton';
import Popover from 'ui/shared/chakra/Popover';
import TxAdditionalInfoContainer from './TxAdditionalInfoContainer'; import TxAdditionalInfoContainer from './TxAdditionalInfoContainer';
import TxAdditionalInfoContent from './TxAdditionalInfoContent'; import TxAdditionalInfoContent from './TxAdditionalInfoContent';
......
...@@ -9,11 +9,10 @@ import { route } from 'nextjs-routes'; ...@@ -9,11 +9,10 @@ import { route } from 'nextjs-routes';
import config from 'configs/app'; import config from 'configs/app';
import getValueWithUnit from 'lib/getValueWithUnit'; import getValueWithUnit from 'lib/getValueWithUnit';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import CurrencyValue from 'ui/shared/CurrencyValue';
import BlobEntity from 'ui/shared/entities/blob/BlobEntity'; import BlobEntity from 'ui/shared/entities/blob/BlobEntity';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
import TextSeparator from 'ui/shared/TextSeparator'; import TextSeparator from 'ui/shared/TextSeparator';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFee from 'ui/shared/tx/TxFee';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => { const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => {
...@@ -60,20 +59,7 @@ const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => { ...@@ -60,20 +59,7 @@ const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => {
{ (tx.stability_fee !== undefined || tx.fee.value !== null) && ( { (tx.stability_fee !== undefined || tx.fee.value !== null) && (
<> <>
<Text { ...sectionTitleProps }>Transaction fee</Text> <Text { ...sectionTitleProps }>Transaction fee</Text>
{ tx.stability_fee ? ( <TxFee tx={ tx } withUsd accuracyUsd={ 2 } rowGap={ 0 }/>
<TxFeeStability data={ tx.stability_fee }/>
) : (
<Flex>
<CurrencyValue
value={ tx.fee.value }
currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : currencyUnits.ether }
exchangeRate={ tx.exchange_rate }
accuracyUsd={ 2 }
flexWrap="wrap"
rowGap={ 0 }
/>
</Flex>
) }
</> </>
) } ) }
</Box> </Box>
......
...@@ -17,7 +17,7 @@ import BlockEntity from 'ui/shared/entities/block/BlockEntity'; ...@@ -17,7 +17,7 @@ import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFee from 'ui/shared/tx/TxFee';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags'; import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo'; import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxType from 'ui/txs/TxType'; import TxType from 'ui/txs/TxType';
...@@ -111,14 +111,7 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI ...@@ -111,14 +111,7 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI
{ (tx.stability_fee !== undefined || tx.fee.value !== null) && ( { (tx.stability_fee !== undefined || tx.fee.value !== null) && (
<> <>
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Fee</Skeleton> <Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Fee</Skeleton>
{ tx.stability_fee ? ( <TxFee tx={ tx } isLoading={ isLoading }/>
<TxFeeStability data={ tx.stability_fee } isLoading={ isLoading } hideUsd/>
) : (
<Skeleton isLoaded={ !isLoading } display="inline-block" variant="text_secondary" whiteSpace="pre">
{ getValueWithUnit(tx.fee.value || 0).toFormat() }
{ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ currencyUnits.ether }` }
</Skeleton>
) }
</> </>
) } ) }
</Flex> </Flex>
......
...@@ -43,6 +43,10 @@ const TxsTable = ({ ...@@ -43,6 +43,10 @@ const TxsTable = ({
}: Props) => { }: Props) => {
const { cutRef, renderedItemsNum } = useLazyRenderedList(txs, !isLoading); const { cutRef, renderedItemsNum } = useLazyRenderedList(txs, !isLoading);
const feeCurrency = config.UI.views.tx.hiddenFields?.fee_currency || config.chain.hasMultipleGasCurrencies ?
'' :
' ' + currencyUnits.ether;
return ( return (
<AddressHighlightProvider> <AddressHighlightProvider>
<Table variant="simple" minWidth="950px" size="xs"> <Table variant="simple" minWidth="950px" size="xs">
...@@ -68,7 +72,7 @@ const TxsTable = ({ ...@@ -68,7 +72,7 @@ const TxsTable = ({
<Link onClick={ sort('fee') } display="flex" justifyContent="end"> <Link onClick={ sort('fee') } display="flex" justifyContent="end">
{ sorting === 'fee-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> } { sorting === 'fee-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> }
{ sorting === 'fee-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> } { sorting === 'fee-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> }
{ `Fee${ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ currencyUnits.ether }` }` } { `Fee${ feeCurrency }` }
</Link> </Link>
</Th> </Th>
) } ) }
......
...@@ -17,7 +17,7 @@ import CurrencyValue from 'ui/shared/CurrencyValue'; ...@@ -17,7 +17,7 @@ import CurrencyValue from 'ui/shared/CurrencyValue';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFee from 'ui/shared/tx/TxFee';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags'; import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo'; import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
...@@ -109,12 +109,13 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, ...@@ -109,12 +109,13 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
) } ) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && ( { !config.UI.views.tx.hiddenFields?.tx_fee && (
<Td isNumeric> <Td isNumeric>
{ /* eslint-disable-next-line no-nested-ternary */ } <TxFee
{ tx.stability_fee ? ( tx={ tx }
<TxFeeStability data={ tx.stability_fee } isLoading={ isLoading } accuracy={ 8 } justifyContent="end" hideUsd/> accuracy={ 8 }
) : ( isLoading={ isLoading }
tx.fee.value ? <CurrencyValue value={ tx.fee.value } accuracy={ 8 } isLoading={ isLoading }/> : '-' withCurrency={ Boolean(tx.celo || tx.stability_fee) }
) } justifyContent="end"
/>
</Td> </Td>
) } ) }
</Tr> </Tr>
......
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import _ from 'lodash'; import _chunk from 'lodash/chunk';
import _uniq from 'lodash/uniq';
import React from 'react'; import React from 'react';
import type { NovesDescribeTxsResponse } from 'types/api/noves'; import type { NovesDescribeTxsResponse } from 'types/api/noves';
...@@ -15,8 +16,8 @@ const translateEnabled = feature.isEnabled && feature.provider === 'noves'; ...@@ -15,8 +16,8 @@ const translateEnabled = feature.isEnabled && feature.provider === 'noves';
export default function useDescribeTxs(items: Array<Transaction> | undefined, viewAsAccountAddress: string | undefined, isPlaceholderData: boolean) { export default function useDescribeTxs(items: Array<Transaction> | undefined, viewAsAccountAddress: string | undefined, isPlaceholderData: boolean) {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const txsHash = _.uniq(items?.map(i => i.hash)); const txsHash = _uniq(items?.map(i => i.hash));
const txChunks = _.chunk(txsHash, 10); const txChunks = _chunk(txsHash, 10);
const queryKey = { const queryKey = {
viewAsAccountAddress, viewAsAccountAddress,
......
...@@ -2552,21 +2552,21 @@ ...@@ -2552,21 +2552,21 @@
dom-mutator "^0.6.0" dom-mutator "^0.6.0"
"@grpc/grpc-js@^1.7.1": "@grpc/grpc-js@^1.7.1":
version "1.9.9" version "1.10.10"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.9.tgz#ce3a05439b1c957ec64c2ecdc6f1e4f54e8af797" resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.10.10.tgz#476d315feeb9dbb0f2d6560008c92688c30f13e0"
integrity sha512-vQ1qwi/Kiyprt+uhb1+rHMpyk4CVRMTGNUGGPRGS7pLNfWkdCHrGEnT6T3/JyC2VZgoOX/X1KwdoU0WYQAeYcQ== integrity sha512-HPa/K5NX6ahMoeBv15njAc/sfF4/jmiXLar9UlC2UfHFKZzsCVLc3wbe7+7qua7w9VPh2/L6EBxyAV7/E8Wftg==
dependencies: dependencies:
"@grpc/proto-loader" "^0.7.8" "@grpc/proto-loader" "^0.7.13"
"@types/node" ">=12.12.47" "@js-sdsl/ordered-map" "^4.4.2"
"@grpc/proto-loader@^0.7.8": "@grpc/proto-loader@^0.7.13":
version "0.7.10" version "0.7.13"
resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.10.tgz#6bf26742b1b54d0a473067743da5d3189d06d720" resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.13.tgz#f6a44b2b7c9f7b609f5748c6eac2d420e37670cf"
integrity sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ== integrity sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==
dependencies: dependencies:
lodash.camelcase "^4.3.0" lodash.camelcase "^4.3.0"
long "^5.0.0" long "^5.0.0"
protobufjs "^7.2.4" protobufjs "^7.2.5"
yargs "^17.7.2" yargs "^17.7.2"
"@hapi/b64@5.x.x": "@hapi/b64@5.x.x":
...@@ -2945,6 +2945,11 @@ ...@@ -2945,6 +2945,11 @@
"@jridgewell/resolve-uri" "3.1.0" "@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14" "@jridgewell/sourcemap-codec" "1.4.14"
"@js-sdsl/ordered-map@^4.4.2":
version "4.4.2"
resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c"
integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==
"@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0":
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz#64df34e2f12e68e78ac57e571d25ec07fa460ca9" resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz#64df34e2f12e68e78ac57e571d25ec07fa460ca9"
...@@ -6217,7 +6222,7 @@ ...@@ -6217,7 +6222,7 @@
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~5.26.4"
"@types/node@>=12.12.47", "@types/node@>=13.7.0": "@types/node@>=13.7.0":
version "20.9.0" version "20.9.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298"
integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw== integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==
...@@ -8626,10 +8631,10 @@ damerau-levenshtein@^1.0.8: ...@@ -8626,10 +8631,10 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
dappscout-iframe@0.2.1: dappscout-iframe@0.2.2:
version "0.2.1" version "0.2.2"
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.1.tgz#b4718515ee4f00022af3912fac6ca1a321c156f9" resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.2.tgz#de3df6abccad68a27c9304300b92d86ec0ab1c59"
integrity sha512-EsiAAEk2I6hN+/E8o45WUn4BFd7aN8UvBwsIcOH79WOly0GOOHkPEO/puPkBCV0EcdxBsZIfssx3X0fSWVz5Bw== integrity sha512-ASOimgBRG61pSYQLdYGWePdiO3IsfTEgWZ6CHpZ4XQjJRmj1+WiWF56vFTeLIo5aucp+2+6oRCJ8KgKHGVDj0A==
dependencies: dependencies:
react "^18.2.0" react "^18.2.0"
react-dom "^18.2.0" react-dom "^18.2.0"
...@@ -10470,10 +10475,9 @@ graceful-fs@^4.2.4, graceful-fs@^4.2.9: ...@@ -10470,10 +10475,9 @@ graceful-fs@^4.2.4, graceful-fs@^4.2.9:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
gradient-avatar@^1.0.2: "gradient-avatar@git+https://github.com/blockscout/gradient-avatar.git":
version "1.0.2" version "1.0.3"
resolved "https://registry.yarnpkg.com/gradient-avatar/-/gradient-avatar-1.0.2.tgz#42bb408e402b1f21aafba3878858721055515224" resolved "git+https://github.com/blockscout/gradient-avatar.git#86810368c2581d2dc3aca089dc648336c8e17045"
integrity sha512-Od9KI2YImV60wnsvU/u6GEyBm2fiHUUHgiLySE243GYl/T/tiJMJ5QYey8o7tepugmlnUGQRaCItHv19UnUjUg==
dependencies: dependencies:
hsl-rgb "^1.0.0" hsl-rgb "^1.0.0"
hsl-triad "^1.0.0" hsl-triad "^1.0.0"
...@@ -13627,7 +13631,7 @@ property-information@^5.0.0: ...@@ -13627,7 +13631,7 @@ property-information@^5.0.0:
dependencies: dependencies:
xtend "^4.0.0" xtend "^4.0.0"
protobufjs@^7.2.3, protobufjs@^7.2.4: protobufjs@^7.2.3:
version "7.2.5" version "7.2.5"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d"
integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==
...@@ -13645,6 +13649,24 @@ protobufjs@^7.2.3, protobufjs@^7.2.4: ...@@ -13645,6 +13649,24 @@ protobufjs@^7.2.3, protobufjs@^7.2.4:
"@types/node" ">=13.7.0" "@types/node" ">=13.7.0"
long "^5.0.0" long "^5.0.0"
protobufjs@^7.2.5:
version "7.3.2"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.3.2.tgz#60f3b7624968868f6f739430cfbc8c9370e26df4"
integrity sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==
dependencies:
"@protobufjs/aspromise" "^1.1.2"
"@protobufjs/base64" "^1.1.2"
"@protobufjs/codegen" "^2.0.4"
"@protobufjs/eventemitter" "^1.1.0"
"@protobufjs/fetch" "^1.1.0"
"@protobufjs/float" "^1.0.2"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/path" "^1.1.2"
"@protobufjs/pool" "^1.1.0"
"@protobufjs/utf8" "^1.1.0"
"@types/node" ">=13.7.0"
long "^5.0.0"
proxy-compare@2.5.1: proxy-compare@2.5.1:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.5.1.tgz#17818e33d1653fbac8c2ec31406bce8a2966f600" resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.5.1.tgz#17818e33d1653fbac8c2ec31406bce8a2966f600"
...@@ -14877,7 +14899,16 @@ string-template@~0.2.1: ...@@ -14877,7 +14899,16 @@ string-template@~0.2.1:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: "string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
...@@ -15005,7 +15036,14 @@ string_decoder@~1.1.1: ...@@ -15005,7 +15036,14 @@ string_decoder@~1.1.1:
dependencies: dependencies:
safe-buffer "~5.1.0" safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: "strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
...@@ -15952,9 +15990,9 @@ vite-tsconfig-paths@^3.5.2: ...@@ -15952,9 +15990,9 @@ vite-tsconfig-paths@^3.5.2:
tsconfig-paths "^4.0.0" tsconfig-paths "^4.0.0"
vite@^4.4.12: vite@^4.4.12:
version "4.5.2" version "4.5.3"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.2.tgz#d6ea8610e099851dad8c7371599969e0f8b97e82" resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.3.tgz#d88a4529ea58bae97294c7e2e6f0eab39a50fb1a"
integrity sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w== integrity sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==
dependencies: dependencies:
esbuild "^0.18.10" esbuild "^0.18.10"
postcss "^8.4.27" postcss "^8.4.27"
...@@ -16165,7 +16203,7 @@ word-wrap@^1.2.5, word-wrap@~1.2.3: ...@@ -16165,7 +16203,7 @@ word-wrap@^1.2.5, word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
...@@ -16183,6 +16221,15 @@ wrap-ansi@^6.2.0: ...@@ -16183,6 +16221,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0" string-width "^4.1.0"
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0: wrap-ansi@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
...@@ -16216,14 +16263,14 @@ ws@8.13.0: ...@@ -16216,14 +16263,14 @@ ws@8.13.0:
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
ws@^7.3.1, ws@^7.5.1: ws@^7.3.1, ws@^7.5.1:
version "7.5.9" version "7.5.10"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
ws@^8.17.1, ws@^8.9.0: ws@^8.17.1, ws@^8.9.0:
version "8.17.1" version "8.18.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
ws@~8.11.0: ws@~8.11.0:
version "8.11.0" version "8.11.0"
......
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