Commit 38cb449a authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into custom-select

parents 110009d3 34a1d7f5
......@@ -9,6 +9,8 @@ NEXT_PUBLIC_AUTH_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH_URL__
# network config
NEXT_PUBLIC_NETWORK_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_NAME__
NEXT_PUBLIC_NETWORK_SHORT_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_SHORT_NAME__
NEXT_PUBLIC_NETWORK_LOGO=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_LOGO__
NEXT_PUBLIC_NETWORK_SMALL_LOGO=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_SMALL_LOGO__
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME__
NEXT_PUBLIC_NETWORK_TYPE=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_TYPE__
NEXT_PUBLIC_NETWORK_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ID__
......@@ -35,6 +37,8 @@ NEXT_PUBLIC_HOMEPAGE_CHARTS=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_CHARTS__
NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT__
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER__
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME__
NEXT_PUBLIC_AD_DOMAIN_WITH_AD=__PLACEHOLDER_FOR_NEXT_PUBLIC_DOMAIN_WITH_AD__
NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FORNEXT_PUBLIC_AD_ADBUTLER_ON__
# api config
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
......
......@@ -52,6 +52,7 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS | `string` | Address of network's native token | `0x029a799563238d0e75e20be2f4bda0ea68d00172` |
| NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME | `string` *(optional)* | Network name for constructing url of token logos according to template `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/${assetsNamePath}/assets/${tokenAddress}/logo.png`. It should match network name in TrustWallet assets repo, see the full list [here](https://github.com/trustwallet/assets/tree/master/blockchains) | `ethereum` |
| NEXT_PUBLIC_NETWORK_LOGO | `string` *(optional)* | Network logo; if not provided, will fallback to logo predefined in the project; if the project doesn't have logo for such network then the common placeholder will be shown; *Note* that logo height should be 20px and width less than 120px | `https://www.fillmurray.com/240/40` |
| NEXT_PUBLIC_NETWORK_SMALL_LOGO | `string` *(optional)* | Small version of network logo; if not provided, will fallback to logo predefined in the project; if the project doesn't have logo for such network then the common placeholder will be shown; *Note* that logo should have square format (e.g 60px by 60px) | `https://www.fillmurray.com/60/60` |
| NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED | `boolean` *(optional)* | Set to true if network has account feature | `true` |
### UI configuration
......@@ -75,7 +76,8 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT | `string` *(optional)* | Gradient value for hero plate on the homepage | `radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%), radial-gradient(at 72% 57%, hsla(14,95%,76%,1) 0px, transparent 50%)` |
| NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` *(optional)* | Set to false if network doesn't have gas tracker | `true` |
| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` *(optional)* | Set to false if average block time is useless for the network | `true` |
| NEXT_PUBLIC_DOMAIN_WITH_AD | `string` *(optional)* | The domain on which we display ads | `blockscout.com` |
| NEXT_PUBLIC_AD_ADBUTLER_ON | `boolean` *(optional)* | Set to true to show Adbutler banner instead of Coinzilla banner | `false` |
### App configuration
| Variable | Type | Description | Default value
......
......@@ -13,7 +13,7 @@ const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
};
const stripTrailingSlash = (str: string) => str[str.length - 1] === '/' ? str.slice(0, -1) : str;
const env = process.env.VERCEL_ENV || process.env.NODE_ENV;
const env = process.env.NODE_ENV;
const isDev = env === 'development';
const appPort = getEnvValue(process.env.NEXT_PUBLIC_APP_PORT);
......@@ -22,7 +22,7 @@ const appHost = getEnvValue(process.env.NEXT_PUBLIC_APP_HOST);
const baseUrl = [
appSchema || 'https',
'://',
process.env.NEXT_PUBLIC_VERCEL_URL || appHost,
appHost,
appPort && ':' + appPort,
].filter(Boolean).join('');
const authUrl = getEnvValue(process.env.NEXT_PUBLIC_AUTH_URL) || baseUrl;
......@@ -54,6 +54,7 @@ const config = Object.freeze({
network: {
type: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_TYPE) as PreDefinedNetwork | undefined,
logo: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_LOGO),
smallLogo: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_SMALL_LOGO),
name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_NAME),
id: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_ID),
shortName: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_SHORT_NAME),
......@@ -84,6 +85,10 @@ const config = Object.freeze({
baseUrl,
authUrl,
logoutUrl,
ad: {
domainWithAd: getEnvValue(process.env.NEXT_PUBLIC_AD_DOMAIN_WITH_AD) || 'blockscout.com',
adButlerOn: getEnvValue(process.env.NEXT_PUBLIC_AD_ADBUTLER_ON) === 'true',
},
api: {
endpoint: apiHost ? `https://${ apiHost }` : 'https://blockscout.com',
socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com',
......
......@@ -18,5 +18,5 @@ NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_APP_LIST=[{'author': 'Blockscout','id':'token-approval-tracker','title':'Token Approval Tracker','logo':'https://approval-tracker.vercel.app/icon-192.png','categories':['security','tools'],'shortDescription':'Token Approval Tracker shows all approvals for any ERC20-compliant tokens and NFTs and lets to revoke them or adjust the approved amount.','site':'https://docs.blockscout.com/for-users/blockscout-apps/token-approval-tracker','description':'Token Approval Tracker shows all approvals for any ERC20-compliant tokens and NFTs and lets to revoke them or adjust the approved amount.','url':'https://approval-tracker.vercel.app/'},{'author': 'Revoke','id':'revoke.cash','title':'Revoke.cash','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FVBMGyUFnd6CScjfK7CYQ%252Frevoke_sing.png%3Falt%3Dmedia%26token%3D9ab94986-7ab1-41c8-bf7e-d9ce11d23182','categories':['security','tools'],'shortDescription': 'Revoke.cash comes in as a preventative tool to manage your token allowances and practice proper wallet hygiene. By regularly revoking active allowances you reduce the chances of becoming the victim of allowance exploits.','site': 'https://revoke.cash/about','description': 'Revoke.cash comes in as a preventative tool to manage your token allowances and practice proper wallet hygiene. By regularly revoking active allowances you reduce the chances of becoming the victim of allowance exploits.','url':'https://revoke.cash/'},{'author':'Aave','id': 'aave','title': 'Aave','logo': 'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FrZkUTIUCG7Zx8BW6Em34%252FAave.png%3Falt%3Dmedia%26token%3D249797a4-4c1e-4372-9cd2-3e48e05e5f30','categories':['tools'],'shortDescription':'Aave is a decentralised non-custodial liquidity market protocol where users can participate as suppliers or borrowers. Suppliers provide liquidity to the market to earn a passive income, while borrowers are able to borrow in an overcollateralised (perpetually) or undercollateralised (one-block liquidity) fashion.','site': 'https://docs.aave.com/faq/','description': 'Aave is a decentralised non-custodial liquidity market protocol where users can participate as suppliers or borrowers. Suppliers provide liquidity to the market to earn a passive income, while borrowers are able to borrow in an overcollateralised (perpetually) or undercollateralised (one-block liquidity) fashion.','url': 'https://staging.aave.com/'},{'author':'LooksRare','id':'looksrare','external':true,'title':'LooksRare','logo': 'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FeAI4gy3qPMt68mZOZHAx%252FLooksRare.png%3Falt%3Dmedia%26token%3D44c01439-ae09-40aa-b904-3a9ce5b2e002','categories':['tools'],'shortDescription': 'LooksRare is the web3 NFT Marketplace where traders and collectors have earned over $1.3 Billion in rewards.','site':'https://docs.looksrare.org/about/welcome-to-looksrare','description':'LooksRare is the web3 NFT Marketplace where traders and collectors have earned over $1.3 Billion in rewards.','url': 'https://goerli.looksrare.org/'},{'author':'zkSync Bridge','id':'zksync-bridge','external':true,'title':'zkSync Bridge','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FrtQsaAz9BjGBc35tVAnq%252FzkSync.png%3Falt%3Dmedia%26token%3D5c18171c-8ccf-4a88-8f44-680cbf238115','categories':['security','tools'],'shortDescription':'zkSync 2.0 Goerli Bridge','site':'https://v2-docs.zksync.io/dev/','description':'zkSync 2.0 Goerli Bridge','url':'https://portal.zksync.io/bridge'},{'author':'dYdX','id':'dydx','external':true,'title':'dYdX','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FCrOglR72wpi0UhEscwe4%252Fdxdy.png%3Falt%3Dmedia%26token%3D8811909e-93e3-487c-9614-dffce37223e9','categories': ['security','tools'],'shortDescription':'dYdX is a leading decentralized exchange that currently supports perpetual trading. dYdX runs on smart contracts on the Ethereum blockchain, and allows users to trade with no intermediaries.','site':'https://help.dydx.exchange/en/articles/3047379-introduction-and-overview','description':'dYdX is a leading decentralized exchange that currently supports perpetual trading. dYdX runs on smart contracts on the Ethereum blockchain, and allows users to trade with no intermediaries.','url':'https://trade.stage.dydx.exchange/portfolio/overview'},{'author':'MetalSwap','id':'metalswap','title':'MetalSwap','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252F8xqldTvxb6avrwVVc3rS%252FMetalSwap.png%3Falt%3Dmedia%26token%3D92d2db99-853a-487d-8d8c-8cdeaeaaf014','categories':['security','tools'],'shortDescription':'MetalSwap is a decentralised platform that enables hedging swaps in financial markets with the aim of providing a hedge for commodity traders and an investment opportunity for those who contribute to the shared liquidity of the project.','site':'https://docs.metalswap.finance/','description':'MetalSwap is a decentralised platform that enables hedging swaps in financial markets with the aim of providing a hedge for commodity traders and an investment opportunity for those who contribute to the shared liquidity of the project.','url':'https://demo.metalswap.finance/'},{'author':'FaucetDao','id':'faucetdao','title':'FaucetDao','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252Ffnnt3ZNZhzRwqMM5YYjD%252FPlaceholder.png%3Falt%3Dmedia%26token%3D507571bb-d76f-4d96-a35e-2b278608f7ca','categories':['tools'],'shortDescription':'FaucetDao is a decentralised community fund providing liquidity and support to early-stage well vetted blockchain projects.','site':'https://linktr.ee/faucet_dao','description':'FaucetDao is a decentralised community fund providing liquidity and support to early-stage well vetted blockchain projects.','url':'https://www.faucetdao.shop/swap?chain=goerli'},{'author':'Uniswap','id':'uniswap','title':'Uniswap','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FJc0QAyeaBmFIL97tSmGv%252FUniswap.png%3Falt%3Dmedia%26token%3D5d25d796-c273-4e22-92fa-ff85206bec76','categories':['tools'],'shortDescription':'Uniswap is a cryptocurrency exchange which uses a decentralized network protocol.','site':'https://docs.uniswap.org/','description':'Uniswap is a cryptocurrency exchange which uses a decentralized network protocol.','url':'https://app.uniswap.org/swap'}]
# api config
NEXT_PUBLIC_API_HOST=eth-goerli.blockscout.com
NEXT_PUBLIC_API_HOST=blockscout-main.test.aws-k8s.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
......@@ -5,6 +5,7 @@ NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','url':'https://blockscout
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/transaction','address':'/ethereum/poa/core/address'}}]
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cup']
NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT=radial-gradient(at 12% 37%, hsla(324,73%,67%,1) 0px, transparent 50%), radial-gradient(at 62% 14%, hsla(256,87%,73%,1) 0px, transparent 50%), radial-gradient(at 84% 80%, hsla(128,75%,73%,1) 0px, transparent 50%), radial-gradient(at 57% 46%, hsla(285,63%,72%,1) 0px, transparent 50%), radial-gradient(at 37% 30%, hsla(174,70%,61%,1) 0px, transparent 50%), radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%), radial-gradient(at 67% 57%, hsla(14,95%,76%,1) 0px, transparent 50%)
#NEXT_PUBLIC_NETWORK_SMALL_LOGO=https://placekitten.com/300/300
# network config
NEXT_PUBLIC_NETWORK_NAME=POA
......
......@@ -9,6 +9,8 @@ NEXT_PUBLIC_APP_ENV=testing
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
import type { NextjsOptions } from '@sentry/nextjs/types/utils/nextjsOptions';
const config: NextjsOptions = {
environment: process.env.VERCEL_ENV || process.env.NODE_ENV,
environment: process.env.NODE_ENV,
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
release: process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
release: process.env.NEXT_PUBLIC_GIT_COMMIT_SHA,
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
......
......@@ -2,9 +2,9 @@ import type * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
export const config: Sentry.BrowserOptions = {
environment: process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_APP_ENV || process.env.NODE_ENV,
environment: process.env.NEXT_PUBLIC_APP_ENV || process.env.NODE_ENV,
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
release: process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
release: process.env.NEXT_PUBLIC_GIT_COMMIT_SHA,
integrations: [ new BrowserTracing() ],
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
......
......@@ -454,6 +454,7 @@ frontend:
- "/tx"
- "/blocks"
- "/block"
- "/address"
resources:
limits:
memory:
......
......@@ -316,6 +316,8 @@ frontend:
- "/tx"
- "/blocks"
- "/block"
- "/login"
- "/address"
resources:
limits:
memory:
......
<svg viewBox="0 0 143 26" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.513 1a1 1 0 0 0-1-1H6.068a1 1 0 0 0-1 1v3.417a1 1 0 0 1-1 1H1.49a1 1 0 0 0-1 1V25a1 1 0 0 0 1 1h3.445a1 1 0 0 0 1-1V6.417a1 1 0 0 1 1-1h2.578a1 1 0 0 0 1-1V1Zm10.926 0a1 1 0 0 0-1-1h-3.445a1 1 0 0 0-1 1v3.417a1 1 0 0 0 1 1h2.389a1 1 0 0 1 1 1V25a1 1 0 0 0 1 1h3.444a1 1 0 0 0 1-1V6.417a1 1 0 0 0-1-1H22.44a1 1 0 0 1-1-1V1Zm-5.52 10.369a1 1 0 0 0-1-1h-3.445a1 1 0 0 0-1 1v8.524a1 1 0 0 0 1 1h3.445a1 1 0 0 0 1-1v-8.524Z"/>
<path d="M35.113 5.893h6.228c2.638 0 4.065 1.434 4.065 3.498.02.645-.15 1.28-.486 1.825a3.12 3.12 0 0 1-1.395 1.221v.067a3.548 3.548 0 0 1 1.842 1.364c.453.652.687 1.44.666 2.242 0 2.308-1.544 4.122-4.346 4.122h-6.574V5.893Zm6.27 5.827c.996 0 1.71-.761 1.71-1.92 0-1.158-.714-1.92-1.71-1.92h-3.95v3.84h3.95Zm.282 6.54c1.19 0 2.054-.94 2.054-2.306 0-1.367-.865-2.285-2.054-2.285h-4.232v4.592h4.232ZM48.93 7.599h-1.303V5.436h3.59v14.786h-2.293L48.931 7.6Zm4.409 7.324c0-3.248 2.317-5.644 5.517-5.644s5.535 2.374 5.535 5.644-2.313 5.623-5.535 5.623-5.517-2.375-5.517-5.623Zm4.911 3.495h1.236c1.406 0 2.638-1.546 2.638-3.495 0-1.948-1.236-3.52-2.638-3.52H58.25c-1.405 0-2.638 1.6-2.638 3.52s1.233 3.495 2.638 3.495Zm7.63-3.518c0-3.314 2.21-5.621 5.388-5.621 2.552 0 4.562 1.366 4.887 4.16h-2.227c-.217-1.412-1.19-2.039-2.184-2.039h-1.082c-1.384 0-2.508 1.568-2.508 3.52s1.124 3.54 2.508 3.54h1.082a2.163 2.163 0 0 0 1.494-.597c.409-.386.662-.916.711-1.487h2.227c-.308 2.755-2.335 4.189-4.942 4.189-3.17-.02-5.353-2.349-5.353-5.664Zm12.348-9.464h2.27v8.156h1.647l3.07-4.01h2.595l-3.784 4.951 3.957 5.69h-2.725l-3.308-4.704h-1.452v4.703h-2.27V5.436ZM89.02 17.029h2.185c.108.94.67 1.546 1.853 1.546h1.43c1.017 0 1.493-.538 1.493-1.255 0-.717-.39-1.142-1.32-1.28l-2.403-.32c-2.032-.246-2.94-1.5-2.94-3.136 0-2.15 1.544-3.315 4.303-3.315 2.64 0 4.24 1.098 4.324 3.54h-2.184c-.108-.92-.475-1.569-1.6-1.569h-1.3c-.973 0-1.428.538-1.428 1.232 0 .695.433 1.165 1.363 1.3l2.425.32c1.968.246 2.876 1.343 2.876 3.046 0 2.195-1.406 3.404-4.52 3.404-3.012.004-4.473-1.184-4.556-3.513ZM99.524 14.9c0-3.314 2.205-5.621 5.387-5.621 2.552 0 4.563 1.366 4.887 4.16h-2.227c-.216-1.412-1.189-2.039-2.184-2.039h-1.081c-1.387 0-2.512 1.568-2.512 3.52s1.125 3.54 2.512 3.54h1.081a2.165 2.165 0 0 0 1.495-.597 2.32 2.32 0 0 0 .711-1.487h2.227c-.309 2.755-2.336 4.189-4.943 4.189-3.166-.02-5.353-2.349-5.353-5.664Zm11.738.023c0-3.248 2.314-5.644 5.514-5.644 3.201 0 5.539 2.374 5.539 5.644s-2.314 5.623-5.539 5.623c-3.225 0-5.514-2.375-5.514-5.623Zm4.909 3.495h1.236c1.405 0 2.641-1.546 2.641-3.495 0-1.948-1.236-3.52-2.641-3.52h-1.236c-1.406 0-2.638 1.6-2.638 3.52s1.239 3.495 2.644 3.495h-.006Zm8.029-2.128V9.595h2.27v6.295c0 1.545.757 2.464 1.752 2.464h1.235c1.06 0 2.011-1.008 2.011-2.464V9.595h2.274v10.64h-2.209V18.78c-.618 1.098-1.708 1.77-3.373 1.77-2.49-.003-3.96-1.504-3.96-4.26Zm13.32 1.456v-6.08h-1.99v-2.07h1.99V6.612h2.292v2.982h2.681v2.061h-2.681v5.42c0 .695.216 1.031.951 1.031h1.817v2.128h-2.472c-1.662-.003-2.588-.899-2.588-2.49Z"/>
</svg>
<svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.822 15.052h-6.9v2.731h3.19l-.01 3.433c-.123.132-.269.24-.43.319-.21.108-.43.197-.656.267a5.653 5.653 0 0 1-1.656.247 3.012 3.012 0 0 1-2.715-1.424 7.746 7.746 0 0 1-.9-4.064v-2.6a9.44 9.44 0 0 1 .265-2.36 5.76 5.76 0 0 1 .715-1.71c.267-.427.63-.785 1.06-1.047.4-.237.858-.36 1.323-.357a2.922 2.922 0 0 1 2.191.735c.526.615.84 1.383.895 2.191h3.627a8.472 8.472 0 0 0-.615-2.458 5.315 5.315 0 0 0-1.304-1.857 5.626 5.626 0 0 0-2.047-1.164 9.016 9.016 0 0 0-2.84-.4 6.71 6.71 0 0 0-2.774.572A6.354 6.354 0 0 0 2.016 7.77a7.927 7.927 0 0 0-1.483 2.659 10.954 10.954 0 0 0-.532 3.557v2.575a11.475 11.475 0 0 0 .51 3.557c.3.97.793 1.871 1.45 2.646a6.251 6.251 0 0 0 2.264 1.65 7.406 7.406 0 0 0 2.966.573c.765.005 1.529-.07 2.278-.221a10.497 10.497 0 0 0 1.914-.58 7.636 7.636 0 0 0 1.477-.8c.362-.247.691-.54.98-.87l-.018-7.464Zm2.66 2.783c-.01.97.143 1.935.451 2.855.281.837.73 1.61 1.317 2.269a5.985 5.985 0 0 0 2.133 1.5 7.278 7.278 0 0 0 2.88.54 7.194 7.194 0 0 0 2.86-.54 5.935 5.935 0 0 0 2.118-1.5 6.602 6.602 0 0 0 1.311-2.27c.308-.92.46-1.884.45-2.854v-.274a8.673 8.673 0 0 0-.45-2.84 6.532 6.532 0 0 0-1.317-2.27 6.078 6.078 0 0 0-2.125-1.508 7.812 7.812 0 0 0-5.74 0 6.082 6.082 0 0 0-2.12 1.508 6.516 6.516 0 0 0-1.317 2.27 8.647 8.647 0 0 0-.45 2.84v.274Zm3.681-.273a7.394 7.394 0 0 1 .174-1.625c.1-.478.284-.936.541-1.352a2.633 2.633 0 0 1 2.357-1.26c.495-.016.984.1 1.418.337.387.224.712.542.947.923.253.417.435.874.535 1.352a7.4 7.4 0 0 1 .173 1.625v.273a7.613 7.613 0 0 1-.172 1.659c-.1.478-.281.935-.537 1.352a2.622 2.622 0 0 1-2.337 1.255 2.818 2.818 0 0 1-1.424-.338 2.759 2.759 0 0 1-.96-.917 4.143 4.143 0 0 1-.541-1.352 7.6 7.6 0 0 1-.174-1.659v-.274.001Zm-1.747-10.3c.086.196.212.37.37.514.167.147.36.262.57.338.472.164.985.164 1.456 0 .21-.076.404-.19.57-.338.16-.143.285-.318.37-.514a1.594 1.594 0 0 0 0-1.274 1.562 1.562 0 0 0-.37-.52 1.7 1.7 0 0 0-.57-.344 2.206 2.206 0 0 0-1.456 0 1.7 1.7 0 0 0-.57.345 1.562 1.562 0 0 0-.502 1.157c0 .219.045.436.133.637v-.001Zm6.357.013c.086.197.212.374.37.52a1.7 1.7 0 0 0 .57.345c.47.165.984.165 1.456 0a1.7 1.7 0 0 0 .57-.345c.157-.146.283-.323.37-.52.089-.205.134-.427.132-.65a1.561 1.561 0 0 0-.503-1.151 1.761 1.761 0 0 0-.57-.338 2.206 2.206 0 0 0-1.456 0c-.21.075-.403.19-.57.338a1.546 1.546 0 0 0-.503 1.151c-.002.224.043.446.134.65Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 114 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.21 0a1 1 0 0 1 1 1v2.167a1 1 0 0 1-1 1H5.687a1 1 0 0 0-1 1V19a1 1 0 0 1-1 1H1.5a1 1 0 0 1-1-1V5.167a1 1 0 0 1 1-1h1.521a1 1 0 0 0 1-1V1a1 1 0 0 1 1-1H7.21Zm8.404 0a1 1 0 0 1 1 1v2.167a1 1 0 0 0 1 1h1.376a1 1 0 0 1 1 1V19a1 1 0 0 1-1 1H16.8a1 1 0 0 1-1-1V5.167a1 1 0 0 0-1-1h-1.375a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h2.188Zm-4.246 7.976a1 1 0 0 1 1 1v6.095a1 1 0 0 1-1 1H9.18a1 1 0 0 1-1-1V8.976a1 1 0 0 1 1-1h2.188Zm26.776-2.131h1.035l-.005 9.71h1.821V4.181h-2.851v1.664ZM33.15 4.533h-4.948v11.03h5.223c2.225 0 3.452-1.396 3.452-3.17a2.825 2.825 0 0 0-.529-1.726 2.81 2.81 0 0 0-1.463-1.049v-.052a2.456 2.456 0 0 0 1.494-2.343c0-1.588-1.134-2.69-3.23-2.69Zm1.391 3.005c0 .891-.567 1.477-1.357 1.477h-3.139V6.061h3.139c.79 0 1.357.586 1.357 1.477Zm.498 4.734c0 1.05-.687 1.774-1.632 1.774h-3.362v-3.532h3.362c.945 0 1.632.707 1.632 1.758Zm7.642-.793c0-2.498 1.84-4.342 4.383-4.342s4.398 1.826 4.398 4.342c0 2.516-1.838 4.325-4.398 4.325-2.56 0-4.383-1.827-4.383-4.325Zm3.902 2.688h.982c1.117 0 2.096-1.189 2.096-2.688 0-1.5-.982-2.708-2.096-2.708h-.982c-1.116 0-2.095 1.231-2.095 2.708s.979 2.688 2.095 2.688Zm10.342-7.03c-2.525 0-4.28 1.775-4.28 4.325 0 2.55 1.735 4.342 4.253 4.357 2.071 0 3.681-1.103 3.927-3.223h-1.77a1.75 1.75 0 0 1-1.752 1.602h-.859c-1.1 0-1.993-1.22-1.993-2.722 0-1.501.893-2.707 1.993-2.707h.859c.79 0 1.563.482 1.735 1.568h1.77c-.258-2.149-1.856-3.2-3.883-3.2Zm5.53-2.956h1.803v6.274h1.308l2.44-3.084h2.06l-3.006 3.808 3.144 4.376H68.04l-2.629-3.618h-1.153v3.618h-1.804V4.181Zm10.309 8.918h-1.735c.066 1.792 1.227 2.705 3.62 2.702 2.473 0 3.59-.93 3.59-2.619 0-1.31-.722-2.154-2.285-2.343l-1.926-.246c-.74-.104-1.083-.465-1.083-1 0-.534.361-.947 1.134-.947h1.033c.894 0 1.186.5 1.272 1.206h1.735c-.067-1.878-1.338-2.722-3.436-2.722-2.192 0-3.419.896-3.419 2.55 0 1.257.722 2.222 2.337 2.412l1.909.246c.739.106 1.048.433 1.048.985 0 .551-.378.964-1.186.964h-1.136c-.94 0-1.386-.465-1.472-1.188Zm6.609-1.637c0-2.55 1.752-4.325 4.28-4.325 2.027 0 3.624 1.051 3.882 3.2h-1.77c-.171-1.086-.944-1.568-1.734-1.568h-.86c-1.101 0-1.995 1.206-1.995 2.707 0 1.502.894 2.723 1.996 2.723h.859a1.75 1.75 0 0 0 1.752-1.602h1.77c-.246 2.119-1.856 3.222-3.927 3.222-2.516-.015-4.253-1.807-4.253-4.357Zm13.706-4.325c-2.543 0-4.38 1.844-4.38 4.342 0 2.498 1.818 4.325 4.38 4.325s4.4-1.81 4.4-4.325c0-2.516-1.858-4.342-4.4-4.342Zm.5 7.03h-.976c-1.117 0-2.101-1.211-2.101-2.688s.98-2.708 2.096-2.708h.981c1.117 0 2.099 1.209 2.099 2.708s-.982 2.688-2.099 2.688Zm5.397-1.637V7.38h1.804v4.842c0 1.19.601 1.896 1.391 1.896h.982c.842 0 1.598-.776 1.598-1.896V7.381h1.806v8.184h-1.755v-1.12c-.491.844-1.357 1.361-2.68 1.361-1.978-.002-3.146-1.157-3.146-3.276Zm10.582-3.557v4.677c0 1.223.736 1.913 2.057 1.915h1.963v-1.637h-1.443c-.584 0-.756-.258-.756-.792v-4.17h2.13V7.38h-2.13V5.086h-1.821v2.295h-1.58v1.592h1.58Z" fill="#2B6CB0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.21 0a1 1 0 0 1 1 1v2.167a1 1 0 0 1-1 1H5.687a1 1 0 0 0-1 1V19a1 1 0 0 1-1 1H1.5a1 1 0 0 1-1-1V5.167a1 1 0 0 1 1-1h1.521a1 1 0 0 0 1-1V1a1 1 0 0 1 1-1H7.21Zm8.404 0a1 1 0 0 1 1 1v2.167a1 1 0 0 0 1 1h1.376a1 1 0 0 1 1 1V19a1 1 0 0 1-1 1H16.8a1 1 0 0 1-1-1V5.167a1 1 0 0 0-1-1h-1.375a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h2.188Zm-4.246 7.976a1 1 0 0 1 1 1v6.095a1 1 0 0 1-1 1H9.18a1 1 0 0 1-1-1V8.976a1 1 0 0 1 1-1h2.188Zm26.776-2.131h1.035l-.005 9.71h1.821V4.181h-2.851v1.664ZM33.15 4.533h-4.948v11.03h5.223c2.225 0 3.452-1.396 3.452-3.17a2.825 2.825 0 0 0-.529-1.726 2.81 2.81 0 0 0-1.463-1.049v-.052a2.456 2.456 0 0 0 1.494-2.343c0-1.588-1.134-2.69-3.23-2.69Zm1.391 3.005c0 .891-.567 1.477-1.357 1.477h-3.139V6.061h3.139c.79 0 1.357.586 1.357 1.477Zm.498 4.734c0 1.05-.687 1.774-1.632 1.774h-3.362v-3.532h3.362c.945 0 1.632.707 1.632 1.758Zm7.642-.793c0-2.498 1.84-4.342 4.383-4.342s4.398 1.826 4.398 4.342c0 2.516-1.838 4.325-4.398 4.325-2.56 0-4.383-1.827-4.383-4.325Zm3.902 2.688h.982c1.117 0 2.096-1.189 2.096-2.688 0-1.5-.982-2.708-2.096-2.708h-.982c-1.116 0-2.095 1.231-2.095 2.708s.979 2.688 2.095 2.688Zm10.342-7.03c-2.525 0-4.28 1.775-4.28 4.325 0 2.55 1.735 4.342 4.253 4.357 2.071 0 3.681-1.103 3.927-3.223h-1.77a1.75 1.75 0 0 1-1.752 1.602h-.859c-1.1 0-1.993-1.22-1.993-2.722 0-1.501.893-2.707 1.993-2.707h.859c.79 0 1.563.482 1.735 1.568h1.77c-.258-2.149-1.856-3.2-3.883-3.2Zm5.53-2.956h1.803v6.274h1.308l2.44-3.084h2.06l-3.006 3.808 3.144 4.376H68.04l-2.629-3.618h-1.153v3.618h-1.804V4.181Zm10.309 8.918h-1.735c.066 1.792 1.227 2.705 3.62 2.702 2.473 0 3.59-.93 3.59-2.619 0-1.31-.722-2.154-2.285-2.343l-1.926-.246c-.74-.104-1.083-.465-1.083-1 0-.534.361-.947 1.134-.947h1.033c.894 0 1.186.5 1.272 1.206h1.735c-.067-1.878-1.338-2.722-3.436-2.722-2.192 0-3.419.896-3.419 2.55 0 1.257.722 2.222 2.337 2.412l1.909.246c.739.106 1.048.433 1.048.985 0 .551-.378.964-1.186.964h-1.136c-.94 0-1.386-.465-1.472-1.188Zm6.609-1.637c0-2.55 1.752-4.325 4.28-4.325 2.027 0 3.624 1.051 3.882 3.2h-1.77c-.171-1.086-.944-1.568-1.734-1.568h-.86c-1.101 0-1.995 1.206-1.995 2.707 0 1.502.894 2.723 1.996 2.723h.859a1.75 1.75 0 0 0 1.752-1.602h1.77c-.246 2.119-1.856 3.222-3.927 3.222-2.516-.015-4.253-1.807-4.253-4.357Zm13.706-4.325c-2.543 0-4.38 1.844-4.38 4.342 0 2.498 1.818 4.325 4.38 4.325s4.4-1.81 4.4-4.325c0-2.516-1.858-4.342-4.4-4.342Zm.5 7.03h-.976c-1.117 0-2.101-1.211-2.101-2.688s.98-2.708 2.096-2.708h.981c1.117 0 2.099 1.209 2.099 2.708s-.982 2.688-2.099 2.688Zm5.397-1.637V7.38h1.804v4.842c0 1.19.601 1.896 1.391 1.896h.982c.842 0 1.598-.776 1.598-1.896V7.381h1.806v8.184h-1.755v-1.12c-.491.844-1.357 1.361-2.68 1.361-1.978-.002-3.146-1.157-3.146-3.276Zm10.582-3.557v4.677c0 1.223.736 1.913 2.057 1.915h1.963v-1.637h-1.443c-.584 0-.756-.258-.756-.792v-4.17h2.13V7.38h-2.13V5.086h-1.821v2.295h-1.58v1.592h1.58Z" fill="currentColor"/>
</svg>
......@@ -63,6 +63,9 @@ function makePolicyMap() {
'sentry.io', '*.sentry.io',
appConfig.api.socket,
// ad
'request-global.czilladx.com',
],
'script-src': [
......@@ -76,6 +79,12 @@ function makePolicyMap() {
// hash of ColorModeScript
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
// ad
'coinzillatag.com',
'servedbyadbutler.com',
'\'sha256-wMOeDjJaOTjCfNjluteV+tSqHW547T89sgxd8W6tQJM=\'',
'\'sha256-FcyIn1h7zra8TVnnRhYrwrplxJW7dpD5TV7kP2AG/kI=\'',
],
'style-src': [
......@@ -113,6 +122,9 @@ function makePolicyMap() {
// marketplace apps logos
...getMarketplaceAppsLogosOrigins().map((url) => url.host),
// ad
'servedbyadbutler.com',
],
'font-src': [
......@@ -135,7 +147,12 @@ function makePolicyMap() {
KEY_WORDS.NONE,
],
'frame-src': getMarketplaceAppsOrigins(),
'frame-src': [
...getMarketplaceAppsOrigins(),
// ad
'request-global.czilladx.com',
],
...(REPORT_URI ? {
'report-uri': [
......
import appConfig from 'configs/app/config';
export default function isSelfHosted() {
return appConfig.host?.endsWith(appConfig.ad.domainWithAd) || appConfig.host === 'localhost';
}
import type { FeaturedNetwork, PreDefinedNetwork } from 'types/networks';
import type { FeaturedNetwork } from 'types/networks';
import appConfig from 'configs/app/config';
import arbitrumIcon from 'icons/networks/icons/arbitrum.svg';
import artisIcon from 'icons/networks/icons/artis.svg';
import ethereumClassicIcon from 'icons/networks/icons/ethereum-classic.svg';
import ethereumIcon from 'icons/networks/icons/ethereum.svg';
import gnosisIcon from 'icons/networks/icons/gnosis.svg';
import optimismIcon from 'icons/networks/icons/optimism.svg';
import poaSokolIcon from 'icons/networks/icons/poa-sokol.svg';
import poaIcon from 'icons/networks/icons/poa.svg';
import rskIcon from 'icons/networks/icons/rsk.svg';
// predefined network icons
const ICONS: Partial<Record<PreDefinedNetwork, React.FunctionComponent<React.SVGAttributes<SVGElement>>>> = {
xdai_mainnet: gnosisIcon,
xdai_optimism: optimismIcon,
xdai_aox: arbitrumIcon,
eth_mainnet: ethereumIcon,
etc_mainnet: ethereumClassicIcon,
poa_core: poaIcon,
rsk_mainnet: rskIcon,
xdai_testnet: arbitrumIcon,
poa_sokol: poaSokolIcon,
artis_sigma1: artisIcon,
};
import ASSETS from 'lib/networks/networkAssets';
// for easy .env.example update
// const FEATURED_NETWORKS = JSON.stringify([
......@@ -136,7 +114,7 @@ const ICONS: Partial<Record<PreDefinedNetwork, React.FunctionComponent<React.SVG
const featuredNetworks: Array<FeaturedNetwork> = (() => {
return appConfig.featuredNetworks.map((network) => ({
...network,
icon: network.icon || (network.type ? ICONS[network.type] : undefined),
icon: network.icon || (network.type ? ASSETS[network.type]?.icon : undefined),
}));
})();
......
import type React from 'react';
import type { PreDefinedNetwork } from 'types/networks';
import arbitrumIcon from 'icons/networks/icons/arbitrum.svg';
import artisIcon from 'icons/networks/icons/artis.svg';
import ethereumClassicIcon from 'icons/networks/icons/ethereum-classic.svg';
import ethereumIcon from 'icons/networks/icons/ethereum.svg';
import gnosisIcon from 'icons/networks/icons/gnosis.svg';
import goerliIcon from 'icons/networks/icons/goerli.svg';
import optimismIcon from 'icons/networks/icons/optimism.svg';
import poaSokolIcon from 'icons/networks/icons/poa-sokol.svg';
import poaIcon from 'icons/networks/icons/poa.svg';
import rskIcon from 'icons/networks/icons/rsk.svg';
import artisLogo from 'icons/networks/logos/artis.svg';
import astarLogo from 'icons/networks/logos/astar.svg';
import etcLogo from 'icons/networks/logos/etc.svg';
import ethLogo from 'icons/networks/logos/eth.svg';
import gnosisLogo from 'icons/networks/logos/gnosis.svg';
import goerliLogo from 'icons/networks/logos/goerli.svg';
import luksoLogo from 'icons/networks/logos/lukso.svg';
import poaLogo from 'icons/networks/logos/poa.svg';
import rskLogo from 'icons/networks/logos/rsk.svg';
import shibuyaLogo from 'icons/networks/logos/shibuya.svg';
import shidenLogo from 'icons/networks/logos/shiden.svg';
import sokolLogo from 'icons/networks/logos/sokol.svg';
interface NetworkAssets {
icon?: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
logo?: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
smallLogo?: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
}
const networkAssets: Partial<Record<PreDefinedNetwork, NetworkAssets>> = {
xdai_mainnet: {
icon: gnosisIcon,
logo: gnosisLogo,
},
xdai_optimism: {
icon: optimismIcon,
},
xdai_aox: {
icon: arbitrumIcon,
},
eth_mainnet: {
icon: ethereumIcon,
logo: ethLogo,
},
etc_mainnet: {
icon: ethereumClassicIcon,
logo: etcLogo,
},
poa_core: {
icon: poaIcon,
logo: poaLogo,
},
rsk_mainnet: {
icon: rskIcon,
logo: rskLogo,
},
xdai_testnet: {
icon: arbitrumIcon,
logo: gnosisLogo,
},
poa_sokol: {
icon: poaSokolIcon,
logo: sokolLogo,
},
artis_sigma1: {
icon: artisIcon,
logo: artisLogo,
},
lukso_l14: {
logo: luksoLogo,
},
astar: {
logo: astarLogo,
},
shiden: {
logo: shidenLogo,
},
shibuya: {
logo: shibuyaLogo,
},
goerli: {
logo: goerliLogo,
icon: goerliIcon,
},
};
export default networkAssets;
......@@ -5,6 +5,8 @@ import notEmpty from 'lib/notEmpty';
import { useSocket } from './context';
const CHANNEL_REGISTRY: Record<string, Channel> = {};
interface Params {
topic: string | undefined;
params?: object;
......@@ -51,12 +53,21 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on
return;
}
const ch = socket.channel(topic, params);
ch.join().receive('ok', (message) => onJoinRef.current?.(ch, message));
let ch: Channel;
if (CHANNEL_REGISTRY[topic]) {
ch = CHANNEL_REGISTRY[topic];
onJoinRef.current?.(ch, '');
} else {
ch = socket.channel(topic);
CHANNEL_REGISTRY[topic] = ch;
ch.join().receive('ok', (message) => onJoinRef.current?.(ch, message));
}
setChannel(ch);
return () => {
ch.leave();
delete CHANNEL_REGISTRY[topic];
setChannel(undefined);
};
}, [ socket, topic, params, isDisabled ]);
......
......@@ -12,7 +12,6 @@
"dev:poa_core": "./node_modules/.bin/dotenv -e ./configs/envs/.env.poa_core -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty",
"dev:goerli": "./node_modules/.bin/dotenv -e ./configs/envs/.env.goerli -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty",
"build": "next build",
"build:vercel": "./node_modules/.bin/dotenv -e ./configs/envs/.env.poa_core -e ./configs/envs/.env.common next build",
"build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse HEAD) -t blockscout ./",
"start": "next start",
"start:docker:poa_core": "docker run -p 3000:3000 --env-file ./configs/envs/.env.common --env-file ./configs/envs/.env.poa_core --env-file ./configs/envs/.env.secrets blockscout",
......
......@@ -2,17 +2,17 @@ import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import Vercel from 'ui/pages/Vercel';
import Login from 'ui/pages/Login';
const VercelPage: NextPage = () => {
const LoginPage: NextPage = () => {
return (
<>
<Head><title>Vercel Page</title></Head>
<Vercel/>
<Head><title>Login Page</title></Head>
<Login/>
</>
);
};
export default VercelPage;
export default LoginPage;
export { getServerSideProps } from 'lib/next/getServerSideProps';
import type { test } from '@playwright/experimental-ct-react';
interface Env {
name: string;
value: string;
}
// keep in mind that all passed variables here should be present in env config files (.env.pw or .env.poa)
export default function createContextWithEnvs(envs: Array<Env>): Parameters<typeof test.extend>[0]['context'] {
return async({ browser }, use) => {
const context = await browser.newContext({
storageState: {
origins: [
{ origin: 'http://localhost:3100', localStorage: envs },
],
cookies: [],
},
});
await use(context);
await context.close();
};
}
import type { TestFixture } from '@playwright/test';
import type { TestFixture, Page } from '@playwright/test';
import type { WebSocket } from 'ws';
import { WebSocketServer } from 'ws';
......@@ -6,8 +6,6 @@ import type { AddressCoinBalancePayload } from 'lib/socket/types';
import type { NewBlockSocketResponse } from 'types/api/block';
type ReturnType = () => Promise<WebSocket>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ArgsType = any;
type Channel = [string, string, string];
......@@ -18,7 +16,7 @@ export interface SocketServerFixture {
export const PORT = 3200;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const createSocket: TestFixture<ReturnType, ArgsType> = async({ page }, use) => {
export const createSocket: TestFixture<ReturnType, { page: Page}> = async({ page }, use) => {
const socketServer = new WebSocketServer({ port: PORT });
const connectionPromise = new Promise<WebSocket>((resolve) => {
......
......@@ -27,7 +27,7 @@ do
# if there is a value, escape it and add line to target file
if [ -n "$configValue" ]; then
escapedConfigValue=$(echo $configValue | sed s/\'/\"/g);
echo "window.process.env.${configName} = '${escapedConfigValue}';" >> $targetFile;
echo "window.process.env.${configName} = localStorage.getItem('${configName}') || '${escapedConfigValue}';" >> $targetFile;
fi
done < $envFile
done
......
export default async function insertAdText() {
const ad = document.getElementsByClassName('coinzilla');
ad[0].textContent = 'coinzilla banner!';
}
......@@ -22,7 +22,7 @@ const baseStyleDialog = defineStyle((props) => {
const baseStyleDialogContainer = defineStyle({
'::-webkit-scrollbar': { display: 'none' },
'scrollbar-width': 'none',
'@supports (height: -webkit-fill-available)': { height: '100vh' },
'@supports (height: -webkit-fill-available)': { height: '-webkit-fill-available' },
});
const baseStyleHeader = defineStyle((props) => ({
......@@ -63,6 +63,7 @@ const baseStyleOverlay = defineStyle({
const baseStyle = definePartsStyle((props) => ({
dialog: runIfFn(baseStyleDialog, props),
dialogContainer: baseStyleDialogContainer,
header: runIfFn(baseStyleHeader, props),
body: baseStyleBody,
footer: baseStyleFooter,
......
......@@ -3,6 +3,7 @@ const zIndices = {
auto: 'auto',
base: 0,
docked: 10,
tooltip: 900,
dropdown: 1000,
sticky: 1100,
sticky1: 1101,
......@@ -13,7 +14,6 @@ const zIndices = {
popover: 1500,
skipLink: 1600,
toast: 1700,
tooltip: 1800,
};
export default zIndices;
......@@ -34,7 +34,7 @@ const NAME_MAX_LENGTH = 255;
const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({
mode: 'all',
mode: 'onTouched',
defaultValues: {
token: data?.api_key || '',
name: data?.name || '',
......@@ -113,7 +113,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
maxLength={ NAME_MAX_LENGTH }
/>
<FormLabel>
<InputPlaceholder text="Application name for API key (e.g Web3 project)" error={ errors.name?.message }/>
<InputPlaceholder text="Application name for API key (e.g Web3 project)" error={ errors.name }/>
</FormLabel>
</FormControl>
);
......
......@@ -42,7 +42,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
name: data?.name || '',
abi: JSON.stringify(data?.abi) || '',
},
mode: 'all',
mode: 'onTouched',
});
const queryClient = useQueryClient();
......@@ -118,7 +118,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
isInvalid={ Boolean(errors.name) }
maxLength={ NAME_MAX_LENGTH }
/>
<InputPlaceholder text="Project name" error={ errors.name?.message }/>
<InputPlaceholder text="Project name" error={ errors.name }/>
</FormControl>
);
}, [ errors, formBackgroundColor ]);
......@@ -132,7 +132,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
minH="300px"
isInvalid={ Boolean(errors.abi) }
/>
<InputPlaceholder text="Custom ABI [{...}] (JSON format)" error={ errors.abi?.message }/>
<InputPlaceholder text="Custom ABI [{...}] (JSON format)" error={ errors.abi }/>
</FormControl>
);
}, [ errors, formBackgroundColor ]);
......
......@@ -2,6 +2,7 @@ import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import * as statsMock from 'mocks/stats/index';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import Stats from './Stats';
......@@ -19,7 +20,55 @@ test('all items +@mobile +@dark-mode +@desktop-xl', async({ mount, page }) => {
<Stats/>
</TestApp>,
);
await page.waitForResponse(API_URL),
await expect(component).toHaveScreenshot();
});
test.describe('4 items', () => {
const extendedTest = test.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME', value: 'false' },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
]) as any,
});
extendedTest('default view +@mobile -@default', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
const component = await mount(
<TestApp>
<Stats/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
});
test.describe('3 items', () => {
const extendedTest = test.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME', value: 'false' },
{ name: 'NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER', value: 'false' },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
]) as any,
});
extendedTest('default view +@mobile -@default', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
const component = await mount(
<TestApp>
<Stats/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
});
import { Flex, Icon, Text, useColorModeValue, chakra } from '@chakra-ui/react';
import React from 'react';
import breakpoints from 'theme/foundations/breakpoints';
type Props = {
icon: React.FC<React.SVGAttributes<SVGElement>>;
title: string;
......@@ -8,13 +10,24 @@ type Props = {
className?: string;
}
const LARGEST_BREAKPOINT = '1240px';
const StatsItem = ({ icon, title, value, className }: Props) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sxContainer = {} as any;
sxContainer[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`] = { flexDirection: 'column' };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sxText = {} as any;
sxText[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`] = { alignItems: 'center' };
return (
<Flex
backgroundColor={ useColorModeValue('blue.50', 'blue.800') }
padding={ 3 }
borderRadius="md"
flexDirection={{ base: 'row', lg: 'column', xl: 'row' }}
flexDirection="row"
sx={ sxContainer }
alignItems="center"
columnGap={ 3 }
rowGap={ 2 }
......@@ -22,7 +35,11 @@ const StatsItem = ({ icon, title, value, className }: Props) => {
color={ useColorModeValue('black', 'white') }
>
<Icon as={ icon } boxSize={ 7 }/>
<Flex flexDirection="column" alignItems={{ base: 'start', lg: 'center', xl: 'start' }}>
<Flex
flexDirection="column"
alignItems="start"
sx={ sxText }
>
<Text variant="secondary" fontSize="xs" lineHeight="16px">{ title }</Text>
<Text fontWeight={ 500 } fontSize="md" color={ useColorModeValue('black', 'white') }>{ value }</Text>
</Flex>
......
......@@ -5,11 +5,13 @@ import React from 'react';
import type { Address } from 'types/api/address';
import { QueryKeys } from 'types/client/queries';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useFetch from 'lib/hooks/useFetch';
import AddressDetails from 'ui/address/AddressDetails';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const AddressPageContent = () => {
const router = useRouter();
......@@ -29,6 +31,14 @@ const AddressPageContent = () => {
...(addressQuery.data?.watchlist_names || []),
].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const tabs: Array<RoutedTab> = [
{ id: 'txs', title: 'Transactions', component: null },
{ id: 'token_transfers', title: 'Token transfers', component: null },
{ id: 'tokens', title: 'Tokens', component: null },
{ id: 'internal_txn', title: 'Internal txn', component: null },
{ id: 'coin_balance_history', title: 'Coin balance history', component: null },
];
return (
<Page>
<Flex alignItems="center" columnGap={ 3 }>
......@@ -40,6 +50,7 @@ const AddressPageContent = () => {
) }
</Flex>
<AddressDetails addressQuery={ addressQuery }/>
<RoutedTabs tabs={ tabs } tabListProps={{ mt: 8 }}/>
</Page>
);
};
......
......@@ -40,14 +40,17 @@ const BlockPageContent = () => {
{ id: 'txs', title: 'Transactions', component: <TxsContent query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> },
];
const isPaginatorHidden = !blockTxsQuery.isLoading && !blockTxsQuery.isError && blockTxsQuery.pagination.page === 1 && !blockTxsQuery.pagination.hasNextPage;
const hasPagination = !isMobile && router.query.tab === 'txs' && !isPaginatorHidden;
return (
<Page>
<PageTitle text={ `Block #${ router.query.id }` }/>
<RoutedTabs
tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ isMobile ? null : <Pagination { ...blockTxsQuery.pagination }/> }
stickyEnabled={ !isMobile }
rightSlot={ hasPagination ? <Pagination { ...blockTxsQuery.pagination }/> : null }
stickyEnabled={ hasPagination }
/>
</Page>
);
......
......@@ -5,6 +5,7 @@ import * as blockMock from 'mocks/blocks/block';
import * as dailyTxsMock from 'mocks/stats/daily_txs';
import * as statsMock from 'mocks/stats/index';
import * as txMock from 'mocks/txs/tx';
import insertAdText from 'playwright/scripts/insertAdText';
import TestApp from 'playwright/TestApp';
import Home from './Home';
......@@ -40,5 +41,7 @@ test('default view -@default +@desktop-xl +@mobile +@dark-mode', async({ mount,
</TestApp>,
);
await page.evaluate(insertAdText);
await expect(component).toHaveScreenshot();
});
......@@ -6,6 +6,7 @@ import ChainIndicators from 'ui/home/indicators/ChainIndicators';
import LatestBlocks from 'ui/home/LatestBlocks';
import LatestTxs from 'ui/home/LatestTxs';
import Stats from 'ui/home/Stats';
import AdBanner from 'ui/shared/ad/AdBanner';
import Page from 'ui/shared/Page/Page';
import ColorModeToggler from 'ui/snippets/header/ColorModeToggler';
import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
......@@ -47,6 +48,7 @@ const Home = () => {
</Box>
<Stats/>
<ChainIndicators/>
<AdBanner mt={{ base: 6, lg: 8 }} justifyContent="center"/>
<Flex mt={ 8 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 8 }>
<LatestBlocks/>
<LatestTxs/>
......
......@@ -10,8 +10,8 @@ import useToast from 'lib/hooks/useToast';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
{ /* will be deleted when we move to new CI */ }
const Vercel = () => {
{ /* will be deleted when we fix login in preview CI stands */ }
const Login = () => {
const toast = useToast();
const [ num, setNum ] = useGradualIncrement(0);
......@@ -86,4 +86,4 @@ const Vercel = () => {
};
export default Vercel;
export default Login;
......@@ -79,13 +79,13 @@ const PublicTagsComponent: React.FC = () => {
return (
<Page>
{ isMobile && screen === 'form' && (
{ screen === 'form' && (
<Link display="inline-flex" alignItems="center" mb={ 6 } onClick={ onGoBack }>
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/>
<Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text>
{ isMobile && <Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text> }
</Link>
) }
<PageTitle text={ header }/>
<PageTitle text={ header } display={{ base: 'block', lg: 'inline-flex' }} ml={{ base: 0, lg: 3 }}/>
{ content }
</Page>
);
......
......@@ -11,6 +11,7 @@ import { useAppContext } from 'lib/appContext';
import useFetch from 'lib/hooks/useFetch';
import isBrowser from 'lib/isBrowser';
import networkExplorers from 'lib/networks/networkExplorers';
import AdBanner from 'ui/shared/ad/AdBanner';
import ExternalLink from 'ui/shared/ExternalLink';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
......@@ -83,6 +84,7 @@ const TransactionPageContent = () => {
) }
</Flex>
<RoutedTabs tabs={ TABS }/>
<AdBanner mt={ 6 } justifyContent={{ base: 'center', lg: 'start' }}/>
</Page>
);
};
......
......@@ -35,7 +35,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const fetch = useFetch();
const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({
mode: 'all',
mode: 'onTouched',
defaultValues: {
address: data?.address_hash || '',
tag: data?.name || '',
......
......@@ -36,7 +36,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) =>
const formBackgroundColor = useColorModeValue('white', 'gray.900');
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({
mode: 'all',
mode: 'onTouched',
defaultValues: {
transaction: data?.transaction_hash || '',
tag: data?.name || '',
......
......@@ -25,7 +25,7 @@ export default function PublicTagFormComment({ control, error, size }: Props) {
isInvalid={ Boolean(error) }
/>
<FormLabel>
<InputPlaceholder text="Specify the reason for adding tags and color preference(s)" error={ error?.message }/>
<InputPlaceholder text="Specify the reason for adding tags and color preference(s)" error={ error }/>
</FormLabel>
</FormControl>
);
......
......@@ -73,7 +73,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
comment: data?.additional_comment || '',
action: data?.is_owner === undefined || data?.is_owner ? 'add' : 'report',
},
mode: 'all',
mode: 'onTouched',
});
const { fields, append, remove } = useFieldArray({
......@@ -146,11 +146,6 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
mutation.mutate(data);
}, [ mutation ]);
const changeToData = useCallback(() => {
setAlertVisible(false);
changeToDataScreen(false);
}, [ changeToDataScreen ]);
return (
<chakra.form
noValidate
......@@ -242,14 +237,6 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
>
Send request
</Button>
<Button
size="lg"
variant="outline"
onClick={ changeToData }
disabled={ mutation.isLoading }
>
Cancel
</Button>
</HStack>
</chakra.form>
);
......
......@@ -36,7 +36,7 @@ export default function PublicTagsFormInput<Inputs extends FieldValues>({
isInvalid={ Boolean(error) }
maxLength={ TEXT_INPUT_MAX_LENGTH }
/>
<InputPlaceholder text={ label } error={ error?.message }/>
<InputPlaceholder text={ label } error={ error }/>
</FormControl>
);
}, [ label, required, error, size ]);
......
......@@ -32,7 +32,7 @@ export default function AddressInput<Inputs extends FieldValues, Name extends Pa
isInvalid={ Boolean(error) }
maxLength={ ADDRESS_LENGTH }
/>
<InputPlaceholder text={ placeholder } error={ error?.message }/>
<InputPlaceholder text={ placeholder } error={ error }/>
</FormControl>
);
}
......@@ -47,7 +47,7 @@ export default function FormModal<TData>({
<ModalContent>
<ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader>
<ModalCloseButton/>
<ModalBody mb={ 0 }>
<ModalBody>
{ (isAlertVisible || text) && (
<Box marginBottom={{ base: 6, lg: 8 }}>
{ text && (
......
import { FormLabel, chakra } from '@chakra-ui/react';
import React from 'react';
import type { FieldError } from 'react-hook-form';
interface Props {
text: string;
error?: string;
error?: FieldError;
}
const InputPlaceholder = ({ text, error }: Props) => {
let errorMessage = error?.message;
if (!errorMessage && error?.type === 'pattern') {
errorMessage = 'Invalid format';
}
return (
<FormLabel>
<chakra.span>{ text }</chakra.span>
{ error && <chakra.span order={ 3 } whiteSpace="pre"> - { error }</chakra.span> }
{ errorMessage && <chakra.span order={ 3 } whiteSpace="pre"> - { errorMessage }</chakra.span> }
</FormLabel>
);
};
......
import { Heading } from '@chakra-ui/react';
import { Heading, chakra } from '@chakra-ui/react';
import React from 'react';
const PageTitle = ({ text }: {text: string}) => {
const PageTitle = ({ text, className }: {text: string; className?: string}) => {
return (
<Heading as="h1" size="lg" marginBottom={ 6 }>{ text }</Heading>
<Heading as="h1" size="lg" marginBottom={ 6 } className={ className }>{ text }</Heading>
);
};
export default PageTitle;
export default chakra(PageTitle);
......@@ -23,7 +23,7 @@ function TagInput<Inputs extends FieldValues, Name extends Path<Inputs>>({ field
isInvalid={ Boolean(error) }
maxLength={ TAG_MAX_LENGTH }
/>
<InputPlaceholder text="Private tag (max 35 characters)" error={ error?.message }/>
<InputPlaceholder text="Private tag (max 35 characters)" error={ error }/>
</FormControl>
);
}
......
......@@ -22,7 +22,7 @@ function TransactionInput<Field extends Partial<ControllerRenderProps<FieldValue
isInvalid={ Boolean(error) }
maxLength={ TRANSACTION_HASH_LENGTH }
/>
<InputPlaceholder text="Transaction hash (0x...)" error={ error?.message }/>
<InputPlaceholder text="Transaction hash (0x...)" error={ error }/>
</FormControl>
);
}
......
import { chakra } from '@chakra-ui/react';
import React from 'react';
import appConfig from 'configs/app/config';
import isSelfHosted from 'lib/isSelfHosted';
import AdbutlerBanner from './AdbutlerBanner';
import CoinzillaBanner from './CoinzillaBanner';
const AdBanner = ({ className }: { className?: string }) => {
if (!isSelfHosted()) {
return null;
}
if (appConfig.ad.adButlerOn) {
return <AdbutlerBanner className={ className }/>;
}
return <CoinzillaBanner className={ className }/>;
};
export default chakra(AdBanner);
/* eslint-disable max-len */
import { Flex, chakra } from '@chakra-ui/react';
import React from 'react';
// eslint-disable-next-line @typescript-eslint/no-var-requires
// const adbutlerHTML = require('ui/shared/ad/adbutler.html');
// didn't find a way to raw-load html that works both for webpack (app build) and vite (playwright build)
const adbutlerHTML = `
<div id="ad-banner"></div>
<script type="text/javascript">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);}());}</script>
<script type="text/javascript">
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || '';
const isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;
if (isMobile) {
var plc539876 = window.plc539876 || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_539876_'+plc539876+'"></'+'div>';
document.getElementById("ad-banner").className = "ad-container mb-3";
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 539876, [320,100], 'placement_539876_'+opt.place, opt); }, opt: { place: plc539876++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} else {
var plc523705 = window.plc523705 || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_523705_'+plc523705+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 523705, [728,90], 'placement_523705_'+opt.place, opt); }, opt: { place: plc523705++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
}
</script>
`;
const AdbutlerBanner = ({ className }: { className?: string }) => {
return (
<Flex className={ className } dangerouslySetInnerHTML={{ __html: adbutlerHTML }}/>
);
};
export default chakra(AdbutlerBanner);
import { Flex, chakra } from '@chakra-ui/react';
import React from 'react';
import isBrowser from 'lib/isBrowser';
declare global {
interface Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
coinzilla_display: any;
}
}
type CPreferences = {
zone: string;
width: string;
height: string;
}
const CoinzillaBanner = ({ className }: { className?: string }) => {
const isInBrowser = isBrowser();
if (isInBrowser) {
window.coinzilla_display = window.coinzilla_display || [];
const cDisplayPreferences = {} as CPreferences;
cDisplayPreferences.zone = '26660bf627543e46851';
cDisplayPreferences.width = '728';
cDisplayPreferences.height = '90';
window.coinzilla_display.push(cDisplayPreferences);
}
return (
<Flex className={ className }>
<script async src="https://coinzillatag.com/lib/display.js"></script>
<div className="coinzilla" data-zone="C-26660bf627543e46851"></div>
</Flex>
);
};
export default chakra(CoinzillaBanner);
<div id="ad-banner"></div>
<script type="text/javascript">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);}());}</script>
<script type="text/javascript">
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || '';
const isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;
if (isMobile) {
var plc539876 = window.plc539876 || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_539876_'+plc539876+'"></'+'div>';
document.getElementById("ad-banner").className = "ad-container mb-3";
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 539876, [320,100], 'placement_539876_'+opt.place, opt); }, opt: { place: plc539876++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} else {
var plc523705 = window.plc523705 || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_523705_'+plc523705+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 523705, [728,90], 'placement_523705_'+opt.place, opt); }, opt: { place: plc523705++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
}
</script>
\ No newline at end of file
......@@ -63,12 +63,13 @@ const NavigationDesktop = () => {
<Box
as="header"
display="flex"
justifyContent="center"
justifyContent="flex-start"
alignItems="center"
flexDirection="row"
w="100%"
px={ 3 }
px={{ lg: isExpanded ? 3 : '15px', xl: isCollapsed ? '15px' : 3 }}
h={ 10 }
{ ...getDefaultTransitionProps({ transitionProperty: 'padding' }) }
>
<NetworkLogo isCollapsed={ isCollapsed }/>
<NetworkMenu isCollapsed={ isCollapsed }/>
......
import { Icon, Box, Image, useColorModeValue } from '@chakra-ui/react';
import { Icon, Box, Image, useColorModeValue, useBreakpointValue } from '@chakra-ui/react';
import React from 'react';
import type { FunctionComponent, SVGAttributes } from 'react';
import type { PreDefinedNetwork } from 'types/networks';
import appConfig from 'configs/app/config';
import blockscoutLogo from 'icons/logo.svg';
import artisLogo from 'icons/networks/logos/artis.svg';
import astarLogo from 'icons/networks/logos/astar.svg';
import etcLogo from 'icons/networks/logos/etc.svg';
import ethLogo from 'icons/networks/logos/eth.svg';
import gnosisLogo from 'icons/networks/logos/gnosis.svg';
import goerliIcon from 'icons/networks/logos/goerli.svg';
import luksoLogo from 'icons/networks/logos/lukso.svg';
import poaLogo from 'icons/networks/logos/poa.svg';
import rskLogo from 'icons/networks/logos/rsk.svg';
import shibuyaLogo from 'icons/networks/logos/shibuya.svg';
import shidenLogo from 'icons/networks/logos/shiden.svg';
import sokolLogo from 'icons/networks/logos/sokol.svg';
import smallLogoPlaceholder from 'icons/networks/icons/placeholder.svg';
import logoPlaceholder from 'icons/networks/logos/blockscout.svg';
import link from 'lib/link/link';
import ASSETS from 'lib/networks/networkAssets';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
// predefined network logos
const LOGOS: Partial<Record<PreDefinedNetwork, React.FunctionComponent<React.SVGAttributes<SVGElement>>>> = {
xdai_mainnet: gnosisLogo,
eth_mainnet: ethLogo,
etc_mainnet: etcLogo,
poa_core: poaLogo,
rsk_mainnet: rskLogo,
xdai_testnet: gnosisLogo,
poa_sokol: sokolLogo,
artis_sigma1: artisLogo,
lukso_l14: luksoLogo,
astar: astarLogo,
shiden: shidenLogo,
shibuya: shibuyaLogo,
goerli: goerliIcon,
};
interface Props {
isCollapsed?: boolean;
onClick?: (event: React.SyntheticEvent) => void;
......@@ -46,54 +16,72 @@ interface Props {
const NetworkLogo = ({ isCollapsed, onClick }: Props) => {
const logoColor = useColorModeValue('blue.600', 'white');
const href = link('network_index');
const logo = appConfig.network.logo || (appConfig.network.type ? LOGOS[appConfig.network.type] : undefined);
const style = useColorModeValue({}, { filter: 'brightness(0) invert(1)' });
const isLg = useBreakpointValue({ base: false, lg: true, xl: false }, { ssr: true });
let logoEl;
const logoEl = (() => {
const showSmallLogo = isCollapsed || (isCollapsed !== false && isLg);
if (showSmallLogo) {
if (appConfig.network.smallLogo) {
return (
<Image
w="auto"
h="100%"
src={ appConfig.network.smallLogo }
alt={ `${ appConfig.network.name } network logo` }
/>
);
}
if (logo && typeof logo === 'string') {
logoEl = (
<Image
w="auto"
h="100%"
src={ logo }
alt={ `${ appConfig.network.name } network icon` }
/>
);
} else if (typeof logo !== 'undefined') {
logoEl = (
<Icon
as={ logo as FunctionComponent<SVGAttributes<SVGElement>> }
width="auto"
height="100%"
{ ...getDefaultTransitionProps() }
style={ style }
/>
);
} else {
logoEl = (
const smallLogo = appConfig.network.type ? ASSETS[appConfig.network.type]?.smallLogo || ASSETS[appConfig.network.type]?.icon : undefined;
return (
<Icon
as={ smallLogo || smallLogoPlaceholder }
width="auto"
height="100%"
color={ smallLogo ? undefined : logoColor }
{ ...getDefaultTransitionProps() }
style={ style }
/>
);
}
if (appConfig.network.logo) {
return (
<Image
w="auto"
h="100%"
src={ appConfig.network.logo }
alt={ `${ appConfig.network.name } network logo` }
/>
);
}
const logo = appConfig.network.type ? ASSETS[appConfig.network.type]?.logo : undefined;
return (
<Icon
as={ blockscoutLogo }
as={ logo || logoPlaceholder }
width="auto"
height="100%"
color={ logoColor }
color={ logo ? undefined : logoColor }
{ ...getDefaultTransitionProps() }
style={ style }
/>
);
}
})();
return (
// TODO switch to <NextLink href={ href } passHref> when main page for network will be ready
<Box
as="a"
href={ href }
width={{ base: 'auto', lg: isCollapsed === false ? '113px' : 0, xl: isCollapsed ? '0' : '113px' }}
height="20px"
width={{ base: 'auto', lg: isCollapsed === false ? '113px' : '30px', xl: isCollapsed ? '30px' : '113px' }}
height={{ base: '20px', lg: isCollapsed === false ? '20px' : '30px', xl: isCollapsed ? '30px' : '20px' }}
display="inline-flex"
overflow="hidden"
onClick={ onClick }
flexShrink={ 0 }
{ ...getDefaultTransitionProps({ transitionProperty: 'width' }) }
aria-label="Link to main page"
>
......
......@@ -13,7 +13,11 @@ const NetworkMenu = ({ isCollapsed }: Props) => {
{ ({ isOpen }) => (
<>
<PopoverTrigger>
<Box marginLeft={{ base: '7px', lg: isCollapsed === false ? '7px' : '0px', xl: isCollapsed ? '0px' : '7px' }}>
<Box
marginLeft="auto"
overflow="hidden"
width={{ base: 'auto', lg: isCollapsed === false ? 'auto' : '0px', xl: isCollapsed ? '0px' : 'auto' }}
>
<NetworkMenuButton isActive={ isOpen }/>
</Box>
</PopoverTrigger>
......
......@@ -80,7 +80,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
notification: data ? data.notification_methods.email : true,
notification_settings: notificationsDefault,
},
mode: 'all',
mode: 'onTouched',
});
const queryClient = useQueryClient();
......
......@@ -4,6 +4,8 @@ import React, { useCallback, useState } from 'react';
import type { TWatchlistItem } from 'types/client/account';
import useFetch from 'lib/hooks/useFetch';
import useToast from 'lib/hooks/useToast';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
......@@ -17,6 +19,7 @@ interface Props {
const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const [ notificationEnabled, setNotificationEnabled ] = useState(item.notification_methods.email);
const [ switchDisabled, setSwitchDisabled ] = useState(false);
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
......@@ -25,16 +28,49 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
const errorToast = useToast();
const fetch = useFetch();
const showErrorToast = useCallback(() => {
errorToast({
position: 'top-right',
description: 'There has been an error processing your request',
colorScheme: 'red',
status: 'error',
variant: 'subtle',
isClosable: true,
icon: null,
});
}, [ errorToast ]);
const notificationToast = useToast();
const showNotificationToast = useCallback((isOn: boolean) => {
notificationToast({
position: 'top-right',
description: isOn ? 'Email notification is ON' : 'Email notification is OFF',
colorScheme: 'green',
status: 'success',
variant: 'subtle',
title: 'Success',
isClosable: true,
icon: null,
});
}, [ notificationToast ]);
const { mutate } = useMutation(() => {
const data = { ...item, notification_methods: { email: !notificationEnabled } };
return fetch(`/node-api/account/watchlist/${ item.id }`, { method: 'PUT', body: JSON.stringify(data) });
setSwitchDisabled(true);
const body = { ...item, notification_methods: { email: !notificationEnabled } };
setNotificationEnabled(prevState => !prevState);
return fetch(`/node-api/account/watchlist/${ item.id }`, { method: 'PUT', body });
}, {
onError: () => {
// eslint-disable-next-line no-console
console.log('error');
showErrorToast();
setNotificationEnabled(prevState => !prevState);
setSwitchDisabled(false);
},
onSuccess: () => {
setNotificationEnabled(prevState => !prevState);
setSwitchDisabled(false);
showNotificationToast(!notificationEnabled);
},
});
......@@ -56,7 +92,14 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
<Flex alignItems="center" justifyContent="space-between" mt={ 6 } w="100%">
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Email notification</Text>
<Switch colorScheme="blue" size="md" isChecked={ notificationEnabled } onChange={ onSwitch } aria-label="Email notification"/>
<Switch
colorScheme="blue"
size="md"
isChecked={ notificationEnabled }
onChange={ onSwitch }
aria-label="Email notification"
isDisabled={ switchDisabled }
/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Flex>
......
......@@ -33,11 +33,11 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
const toast = useToast();
const errorToast = useToast();
const fetch = useFetch();
const showToast = useCallback(() => {
toast({
const showErrorToast = useCallback(() => {
errorToast({
position: 'top-right',
description: 'There has been an error processing your request',
colorScheme: 'red',
......@@ -46,7 +46,21 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
isClosable: true,
icon: null,
});
}, [ toast ]);
}, [ errorToast ]);
const notificationToast = useToast();
const showNotificationToast = useCallback((isOn: boolean) => {
notificationToast({
position: 'top-right',
description: isOn ? 'Email notification is ON' : 'Email notification is OFF',
colorScheme: 'green',
status: 'success',
variant: 'subtle',
title: 'Success',
isClosable: true,
icon: null,
});
}, [ notificationToast ]);
const { mutate } = useMutation(() => {
setSwitchDisabled(true);
......@@ -55,12 +69,13 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return fetch(`/node-api/account/watchlist/${ item.id }`, { method: 'PUT', body });
}, {
onError: () => {
showToast();
showErrorToast();
setNotificationEnabled(prevState => !prevState);
setSwitchDisabled(false);
},
onSuccess: () => {
setSwitchDisabled(false);
showNotificationToast(!notificationEnabled);
},
});
......
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