Commit 21bb0fc5 authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #824 from blockscout/footer

footer
parents f2e4419e 578e2a5d
...@@ -6,6 +6,10 @@ NEXT_PUBLIC_APP_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_HOST__ ...@@ -6,6 +6,10 @@ NEXT_PUBLIC_APP_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_HOST__
NEXT_PUBLIC_APP_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PORT__ NEXT_PUBLIC_APP_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PORT__
NEXT_PUBLIC_AUTH_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH_URL__ NEXT_PUBLIC_AUTH_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH_URL__
# version config
NEXT_PUBLIC_GIT_COMMIT_SHA=__PLACEHOLDER_FOR_NEXT_PUBLIC_GIT_COMMIT_SHA__
NEXT_PUBLIC_GIT_TAG = __PLACEHOLDER_FOR_NEXT_PUBLIC_GIT_TAG__
# network config # network config
NEXT_PUBLIC_NETWORK_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_NAME__ 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_SHORT_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_SHORT_NAME__
...@@ -26,11 +30,8 @@ NEXT_PUBLIC_IS_TESTNET=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_TESTNET__ ...@@ -26,11 +30,8 @@ NEXT_PUBLIC_IS_TESTNET=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_TESTNET__
# ui config # ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=__PLACEHOLDER_FOR_NEXT_PUBLIC_BLOCKSCOUT_VERSION__ NEXT_PUBLIC_BLOCKSCOUT_VERSION=__PLACEHOLDER_FOR_NEXT_PUBLIC_BLOCKSCOUT_VERSION__
NEXT_PUBLIC_FOOTER_GITHUB_LINK=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_GITHUB_LINK__
NEXT_PUBLIC_FOOTER_TWITTER_LINK=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_TWITTER_LINK__
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_TELEGRAM_LINK__
NEXT_PUBLIC_FOOTER_STAKING_LINK=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_STAKING_LINK__
NEXT_PUBLIC_FEATURED_NETWORKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_FEATURED_NETWORKS__ NEXT_PUBLIC_FEATURED_NETWORKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_FEATURED_NETWORKS__
NEXT_PUBLIC_FOOTER_LINKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_LINKS__
NEXT_PUBLIC_NETWORK_EXPLORERS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_EXPLORERS__ NEXT_PUBLIC_NETWORK_EXPLORERS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_EXPLORERS__
NEXT_PUBLIC_OTHER_LINKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_OTHER_LINKS__ NEXT_PUBLIC_OTHER_LINKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_OTHER_LINKS__
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_CONFIG_URL__ NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_CONFIG_URL__
......
...@@ -35,6 +35,8 @@ WORKDIR /app ...@@ -35,6 +35,8 @@ WORKDIR /app
# pass commit sha to the app (uses by sentry.io as release version) # pass commit sha to the app (uses by sentry.io as release version)
ARG GIT_COMMIT_SHA ARG GIT_COMMIT_SHA
ENV NEXT_PUBLIC_GIT_COMMIT_SHA=$GIT_COMMIT_SHA ENV NEXT_PUBLIC_GIT_COMMIT_SHA=$GIT_COMMIT_SHA
# pass git tag to the app (for the footer link)
ARG GIT_TAG ARG GIT_TAG
ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG
......
...@@ -96,15 +96,12 @@ const config = Object.freeze({ ...@@ -96,15 +96,12 @@ const config = Object.freeze({
rpcUrl: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_RPC_URL), rpcUrl: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_RPC_URL),
isTestnet: getEnvValue(process.env.NEXT_PUBLIC_IS_TESTNET) === 'true', isTestnet: getEnvValue(process.env.NEXT_PUBLIC_IS_TESTNET) === 'true',
}, },
footerLinks: {
github: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK),
twitter: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_TWITTER_LINK),
telegram: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_TELEGRAM_LINK),
staking: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_STAKING_LINK),
},
otherLinks: parseEnvJson<Array<NavItemExternal>>(getEnvValue(process.env.NEXT_PUBLIC_OTHER_LINKS)) || [], otherLinks: parseEnvJson<Array<NavItemExternal>>(getEnvValue(process.env.NEXT_PUBLIC_OTHER_LINKS)) || [],
featuredNetworks: getEnvValue(process.env.NEXT_PUBLIC_FEATURED_NETWORKS), featuredNetworks: getEnvValue(process.env.NEXT_PUBLIC_FEATURED_NETWORKS),
footerLinks: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_LINKS),
blockScoutVersion: getEnvValue(process.env.NEXT_PUBLIC_BLOCKSCOUT_VERSION), blockScoutVersion: getEnvValue(process.env.NEXT_PUBLIC_BLOCKSCOUT_VERSION),
frontendVersion: getEnvValue(process.env.NEXT_PUBLIC_GIT_TAG),
frontendCommit: getEnvValue(process.env.NEXT_PUBLIC_GIT_COMMIT_SHA),
isAccountSupported: getEnvValue(process.env.NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED) === 'true', isAccountSupported: getEnvValue(process.env.NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED) === 'true',
marketplaceConfigUrl: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_CONFIG_URL), marketplaceConfigUrl: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_CONFIG_URL),
marketplaceSubmitForm: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM), marketplaceSubmitForm: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM),
......
...@@ -7,8 +7,6 @@ NEXT_PUBLIC_APP_ENV=development ...@@ -7,8 +7,6 @@ NEXT_PUBLIC_APP_ENV=development
# ui config # ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta 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_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
# api config # api config
......
...@@ -5,8 +5,6 @@ NEXT_PUBLIC_APP_ENV=testing ...@@ -5,8 +5,6 @@ NEXT_PUBLIC_APP_ENV=testing
# ui config # ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta 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_IS_TESTNET=true NEXT_PUBLIC_IS_TESTNET=true
# api config # api config
......
...@@ -7,10 +7,6 @@ NEXT_PUBLIC_APP_ENV=development ...@@ -7,10 +7,6 @@ NEXT_PUBLIC_APP_ENV=development
# ui config # ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/transaction','address':'/ethereum/poa/core/address'}}] 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_cap'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
......
# ui config # ui config
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/transaction','address':'/ethereum/poa/core/address','block':'/ethereum/poa/core/block'}}] NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/transaction','address':'/ethereum/poa/core/address','block':'/ethereum/poa/core/block'}}]
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
......
...@@ -7,16 +7,14 @@ NEXT_PUBLIC_APP_ENV=testing ...@@ -7,16 +7,14 @@ NEXT_PUBLIC_APP_ENV=testing
# ui config # ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta 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_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
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'}}] 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'}}]
NEXT_PUBLIC_GIT_TAG=v1.0.11
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND= NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=
NEXT_PUBLIC_FEATURED_NETWORKS= NEXT_PUBLIC_FEATURED_NETWORKS=
NEXT_PUBLIC_FOOTER_LINKS=
NEXT_PUBLIC_NETWORK_LOGO= NEXT_PUBLIC_NETWORK_LOGO=
NEXT_PUBLIC_NETWORK_LOGO_DARK= NEXT_PUBLIC_NETWORK_LOGO_DARK=
NEXT_PUBLIC_NETWORK_ICON= NEXT_PUBLIC_NETWORK_ICON=
......
...@@ -352,10 +352,6 @@ frontend: ...@@ -352,10 +352,6 @@ frontend:
environment: environment:
NEXT_PUBLIC_BLOCKSCOUT_VERSION: NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v5.1.5-beta _default: v5.1.5-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK:
_default: https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_APP_ENV: NEXT_PUBLIC_APP_ENV:
_default: stable _default: stable
NEXT_PUBLIC_APP_INSTANCE: NEXT_PUBLIC_APP_INSTANCE:
......
...@@ -275,10 +275,6 @@ frontend: ...@@ -275,10 +275,6 @@ frontend:
_default: / _default: /
NEXT_PUBLIC_BLOCKSCOUT_VERSION: NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v5.1.2-beta _default: v5.1.2-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK:
_default: https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_APP_ENV: NEXT_PUBLIC_APP_ENV:
_default: stable _default: stable
NEXT_PUBLIC_APP_INSTANCE: NEXT_PUBLIC_APP_INSTANCE:
......
...@@ -61,10 +61,6 @@ frontend: ...@@ -61,10 +61,6 @@ frontend:
environment: environment:
NEXT_PUBLIC_BLOCKSCOUT_VERSION: NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v5.1.0-beta _default: v5.1.0-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK:
_default: https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_APP_ENV: NEXT_PUBLIC_APP_ENV:
_default: preview _default: preview
NEXT_PUBLIC_APP_INSTANCE: NEXT_PUBLIC_APP_INSTANCE:
......
...@@ -57,18 +57,10 @@ frontend: ...@@ -57,18 +57,10 @@ frontend:
environment: environment:
NEXT_PUBLIC_BLOCKSCOUT_VERSION: NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v5.1.2-beta _default: v5.1.2-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK:
_default: https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_APP_ENV: NEXT_PUBLIC_APP_ENV:
_default: preview _default: preview
NEXT_PUBLIC_APP_INSTANCE: NEXT_PUBLIC_APP_INSTANCE:
_default: eth_goerli _default: eth_goerli
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK:
_default: https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK:
_default: https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_NETWORK_NAME: NEXT_PUBLIC_NETWORK_NAME:
_default: Göerli _default: Göerli
NEXT_PUBLIC_NETWORK_SHORT_NAME: NEXT_PUBLIC_NETWORK_SHORT_NAME:
......
...@@ -30,11 +30,8 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -30,11 +30,8 @@ The app instance could be customized by passing following variables to NodeJS en
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_FEATURED_NETWORKS | `string` | URL of configuration file (`.json` format only) which contains list of featured networks that will be shown in the network menu. See [below](#featured-network-configuration-properties) list of available properties for particular network | - | - | `https://example.com/featured_networks_config.json` | | NEXT_PUBLIC_FEATURED_NETWORKS | `string` | URL of configuration file (`.json` format only) which contains list of featured networks that will be shown in the network menu. See [below](#featured-network-configuration-properties) list of available properties for particular network | - | - | `https://example.com/featured_networks_config.json` |
| NEXT_PUBLIC_OTHER_LINKS | `Array<{url: string; text: string}>` | List of links for the "Other" navigation menu | - | - | `[{'url':'https://blockscout.com','text':'Blockscout'}]` | | NEXT_PUBLIC_OTHER_LINKS | `Array<{url: string; text: string}>` | List of links for the "Other" navigation menu | - | - | `[{'url':'https://blockscout.com','text':'Blockscout'}]` |
| NEXT_PUBLIC_FOOTER_LINKS | `string` | URL of configuration file (`.json` format only) which contains list of link groups to be displayed in the footer. See [below](#footer-links-configuration-properties) list of available properties for particular group | - | - | `https://example.com/footer_links_config.json` |
| NEXT_PUBLIC_BLOCKSCOUT_VERSION | `string` | Current running version of Blockscout (used to display link to release in the footer) | - | - | `v.5.1.0-beta` | NEXT_PUBLIC_BLOCKSCOUT_VERSION | `string` | Current running version of Blockscout (used to display link to release in the footer) | - | - | `v.5.1.0-beta`
| NEXT_PUBLIC_FOOTER_GITHUB_LINK | `string` | Link to Github in the footer | - | - | `https://github.com/blockscout/blockscout` |
| NEXT_PUBLIC_FOOTER_TWITTER_LINK | `string` | Link to Twitter in the footer | - | - | `https://www.twitter.com/blockscoutcom` |
| NEXT_PUBLIC_FOOTER_TELEGRAM_LINK | `string` | Link to Telegram in the footer | - | - | `https://t.me/poa_network` |
| NEXT_PUBLIC_FOOTER_STAKING_LINK | `string` | Link to staking dashboard in the footer | - | - | `https://duneanalytics.com/maxaleks/xdai-staking` |
| NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains list of apps that will be shown on the marketplace page. See [below](#marketplace-app-configuration-properties) list of available properties for an app | - | - | `https://example.com/marketplace_config.json` | | NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains list of apps that will be shown on the marketplace page. See [below](#marketplace-app-configuration-properties) list of available properties for an app | - | - | `https://example.com/marketplace_config.json` |
| NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | - | - | `https://airtable.com/shrqUAcjgGJ4jU88C` | | NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | - | - | `https://airtable.com/shrqUAcjgGJ4jU88C` |
| NEXT_PUBLIC_NETWORK_EXPLORERS | `Array<NetworkExplorer>` where `NetworkExplorer` can have following [properties](#network-explorer-configuration-properties) | Used to build up links to transactions, blocks, addresses in other chain explorers. | - | - | `[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/tx'}}]` | | NEXT_PUBLIC_NETWORK_EXPLORERS | `Array<NetworkExplorer>` where `NetworkExplorer` can have following [properties](#network-explorer-configuration-properties) | Used to build up links to transactions, blocks, addresses in other chain explorers. | - | - | `[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/tx'}}]` |
...@@ -108,6 +105,13 @@ For each application, you need to specify the `MarketplaceCategoryId` to which i ...@@ -108,6 +105,13 @@ For each application, you need to specify the `MarketplaceCategoryId` to which i
*Note* The url of an entity will be constructed as `<baseUrl><paths[<entity-type>]><entity-id>`, e.g `https://explorer.anyblock.tools/ethereum/poa/core/tx/<tx-id>` *Note* The url of an entity will be constructed as `<baseUrl><paths[<entity-type>]><entity-id>`, e.g `https://explorer.anyblock.tools/ethereum/poa/core/tx/<tx-id>`
## Footer links configuration properties
| Variable | Type| Description | Is required | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| title | `string` | Title of link group | yes | - | `Company` |
| links | `Array<{'text':string;'url':string;}>` | list of links | yes | - | `[{'text':'Homepage','url':'https://www.blockscout.com'}]` |
## App configuration ## App configuration
......
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" d="M13.76 7.444h2.88a5.76 5.76 0 0 1 0 11.52v2.52c-3.6-1.44-8.64-3.6-8.64-8.28a5.76 5.76 0 0 1 5.76-5.76Zm1.44 10.08h1.44a4.32 4.32 0 1 0 0-8.64h-2.88a4.32 4.32 0 0 0-4.32 4.32c0 2.6 1.772 4.296 5.76 6.106v-1.786Z"/>
<path fill="currentColor" d="M10.4 3.444H7.2a6.32 6.32 0 0 0-4.526 1.923A6.65 6.65 0 0 0 .8 10.008c0 1.741.674 3.41 1.874 4.642A6.32 6.32 0 0 0 7.2 16.572v2.872c4-1.64 9.6-4.102 9.6-9.436a6.65 6.65 0 0 0-1.875-4.641A6.32 6.32 0 0 0 10.4 3.444ZM8.8 14.931H7.2c-.63 0-1.255-.127-1.837-.374a4.793 4.793 0 0 1-1.557-1.068c-.446-.457-.8-1-1.04-1.597a5.033 5.033 0 0 1 0-3.768 4.933 4.933 0 0 1 1.04-1.597c.445-.457.975-.82 1.557-1.067A4.696 4.696 0 0 1 7.2 5.085h3.2a4.74 4.74 0 0 1 3.394 1.442 4.988 4.988 0 0 1 1.406 3.481c0 2.962-1.97 4.895-6.4 6.958v-2.035Z"/>
</svg>
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path d="M15 25c5.523 0 10-4.477 10-10S20.523 5 15 5 5 9.477 5 15s4.477 10 10 10Z" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linejoin="round"/>
<path d="M21.667 22.333a6.666 6.666 0 1 0-13.334 0" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linejoin="round"/>
<path d="M15 15.667a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linejoin="round"/>
</svg>
This diff is collapsed.
import React from 'react';
import appConfig from 'configs/app/config';
import isBrowser from 'lib/isBrowser';
const base = 'https://github.com/blockscout/blockscout/issues/new/';
const bodyTemplate = `*Describe your issue here.*
### Environment
* Backend Version/branch/commit: ${ appConfig.blockScoutVersion }
* Frontend Version+commit: ${ [ appConfig.frontendVersion, appConfig.frontendCommit ].filter(Boolean).join('+') }
* User Agent: __userAgent__
### Steps to reproduce
*Tell us how to reproduce this issue. ❤️ if you can push up a branch to your fork with a regression test we can run to reproduce locally.*
### Expected behaviour
*Tell us what should happen.*
### Actual behaviour
*Tell us what happens instead.*`;
const labels = 'new UI';
const title = `${ appConfig.network.name }: <Issue Title>`;
export default function useIssueUrl() {
const [ userAgent, setUserAgent ] = React.useState('');
const isInBrowser = isBrowser();
React.useEffect(() => {
if (isInBrowser) {
setUserAgent(window.navigator.userAgent);
}
}, [ isInBrowser ]);
const params = new URLSearchParams({ labels, title, body: bodyTemplate.replace('__userAgent__', userAgent) });
return base + '?' + params.toString();
}
...@@ -15,7 +15,6 @@ import globeIcon from 'icons/globe-b.svg'; ...@@ -15,7 +15,6 @@ import globeIcon from 'icons/globe-b.svg';
import graphQLIcon from 'icons/graphQL.svg'; import graphQLIcon from 'icons/graphQL.svg';
import outputRootsIcon from 'icons/output_roots.svg'; import outputRootsIcon from 'icons/output_roots.svg';
import privateTagIcon from 'icons/privattags.svg'; import privateTagIcon from 'icons/privattags.svg';
import profileIcon from 'icons/profile.svg';
import publicTagIcon from 'icons/publictags.svg'; import publicTagIcon from 'icons/publictags.svg';
import apiDocsIcon from 'icons/restAPI.svg'; import apiDocsIcon from 'icons/restAPI.svg';
import rpcIcon from 'icons/RPC.svg'; import rpcIcon from 'icons/RPC.svg';
...@@ -27,6 +26,7 @@ import txnBatchIcon from 'icons/txn_batches.svg'; ...@@ -27,6 +26,7 @@ import txnBatchIcon from 'icons/txn_batches.svg';
import verifiedIcon from 'icons/verified.svg'; import verifiedIcon from 'icons/verified.svg';
import watchlistIcon from 'icons/watchlist.svg'; import watchlistIcon from 'icons/watchlist.svg';
import { rightLineArrow } from 'lib/html-entities'; import { rightLineArrow } from 'lib/html-entities';
import UserAvatar from 'ui/shared/UserAvatar';
interface ReturnType { interface ReturnType {
mainNavItems: Array<NavItem | NavGroupItem>; mainNavItems: Array<NavItem | NavGroupItem>;
...@@ -213,7 +213,7 @@ export default function useNavItems(): ReturnType { ...@@ -213,7 +213,7 @@ export default function useNavItems(): ReturnType {
const profileItem = { const profileItem = {
text: 'My profile', text: 'My profile',
nextRoute: { pathname: '/auth/profile' as const }, nextRoute: { pathname: '/auth/profile' as const },
icon: profileIcon, iconComponent: UserAvatar,
isActive: pathname === '/auth/profile', isActive: pathname === '/auth/profile',
}; };
......
...@@ -5,13 +5,11 @@ import metamaskIcon from 'icons/wallets/metamask.svg'; ...@@ -5,13 +5,11 @@ import metamaskIcon from 'icons/wallets/metamask.svg';
export const WALLETS_INFO: Record<WalletType, WalletInfo> = { export const WALLETS_INFO: Record<WalletType, WalletInfo> = {
metamask: { metamask: {
add_token_text: 'Add token to MetaMask', name: 'MetaMask',
add_network_text: 'Add network to MetaMask',
icon: metamaskIcon, icon: metamaskIcon,
}, },
coinbase: { coinbase: {
add_token_text: 'Add token to Coinbase Wallet', name: 'Coinbase Wallet',
add_network_text: 'Add network to Coinbase Wallet',
icon: coinbaseIcon, icon: coinbaseIcon,
}, },
}; };
import type { CustomLinksGroup } from 'types/footerLinks';
export const FOOTER_LINKS: Array<CustomLinksGroup> = [
{
title: 'Company',
links: [
{
text: 'Advertise',
url: 'https://coinzilla.com/',
},
{
text: 'Staking',
url: '',
},
{
text: 'Contact us',
url: '',
},
{
text: 'Brand assets',
url: '',
},
{
text: 'Term of service',
url: '',
},
],
},
{
title: 'Community',
links: [
{
text: 'API docs',
url: '',
},
{
text: 'Knowledge base',
url: '',
},
{
text: 'Network status',
url: '',
},
{
text: 'Learn Alphabet',
url: '',
},
],
},
{
title: 'Product',
links: [
{
text: 'Stake Alphabet',
url: '',
},
{
text: 'Build token',
url: '',
},
{
text: 'Build DAPPS',
url: '',
},
{
text: 'NFT marketplace',
url: '',
},
{
text: 'Become validator',
url: '',
},
],
},
];
...@@ -18,7 +18,7 @@ const variantPrimary = defineStyle((props) => { ...@@ -18,7 +18,7 @@ const variantPrimary = defineStyle((props) => {
const variantSecondary = defineStyle((props) => { const variantSecondary = defineStyle((props) => {
return { return {
color: mode('gray.500', 'gray.500')(props), color: mode('gray.600', 'gray.500')(props),
_hover: { _hover: {
color: mode('gray.600', 'gray.400')(props), color: mode('gray.600', 'gray.400')(props),
}, },
......
import type { Route } from 'nextjs-routes'; import type { Route } from 'nextjs-routes';
import type React from 'react';
type NavIconOrComponent = {
icon?: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
} | {
iconComponent?: React.FC<{size?: number}>;
};
type NavItemCommon = { type NavItemCommon = {
text: string; text: string;
icon?: React.FunctionComponent<React.SVGAttributes<SVGElement>>; } & NavIconOrComponent;
}
export type NavItemInternal = NavItemCommon & { export type NavItemInternal = NavItemCommon & {
nextRoute: Route; nextRoute: Route;
......
...@@ -3,8 +3,7 @@ import type { providers } from 'ethers'; ...@@ -3,8 +3,7 @@ import type { providers } from 'ethers';
export type WalletType = 'metamask' | 'coinbase'; export type WalletType = 'metamask' | 'coinbase';
export interface WalletInfo { export interface WalletInfo {
add_token_text: string; name: string;
add_network_text: string;
icon: React.ElementType; icon: React.ElementType;
} }
......
type CustomLink = {
text: string;
url: string;
}
export type CustomLinksGroup = {
title: string;
links: Array<CustomLink>;
}
...@@ -135,7 +135,6 @@ const AddressPageContent = () => { ...@@ -135,7 +135,6 @@ const AddressPageContent = () => {
{ /* should stay before tabs to scroll up with pagination */ } { /* should stay before tabs to scroll up with pagination */ }
<Box ref={ tabsScrollRef }></Box> <Box ref={ tabsScrollRef }></Box>
{ addressQuery.isPlaceholderData ? <SkeletonTabs tabs={ tabs }/> : content } { addressQuery.isPlaceholderData ? <SkeletonTabs tabs={ tabs }/> : content }
{ !addressQuery.isPlaceholderData && !addressQuery.isError && <Box h={{ base: 0, lg: '40vh' }}/> }
</> </>
); );
}; };
......
...@@ -8,7 +8,6 @@ import Stats from 'ui/home/Stats'; ...@@ -8,7 +8,6 @@ import Stats from 'ui/home/Stats';
import Transactions from 'ui/home/Transactions'; import Transactions from 'ui/home/Transactions';
import AdBanner from 'ui/shared/ad/AdBanner'; import AdBanner from 'ui/shared/ad/AdBanner';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import ColorModeToggler from 'ui/snippets/header/ColorModeToggler';
import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
import SearchBar from 'ui/snippets/searchBar/SearchBar'; import SearchBar from 'ui/snippets/searchBar/SearchBar';
...@@ -33,15 +32,9 @@ const Home = () => { ...@@ -33,15 +32,9 @@ const Home = () => {
> >
Welcome to { appConfig.network.name } explorer Welcome to { appConfig.network.name } explorer
</Heading> </Heading>
<Flex <Box display={{ base: 'none', lg: 'block' }}>
alignItems="center"
display={{ base: 'none', lg: 'flex' }}
columnGap={ 12 }
pl={ 4 }
>
<ColorModeToggler trackBg="blackAlpha.900"/>
{ appConfig.isAccountSupported && <ProfileMenuDesktop/> } { appConfig.isAccountSupported && <ProfileMenuDesktop/> }
</Flex> </Box>
</Flex> </Flex>
<LightMode> <LightMode>
<SearchBar isHomepage/> <SearchBar isHomepage/>
......
...@@ -9,7 +9,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -9,7 +9,7 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import UserAvatar from 'ui/shared/UserAvatar'; import UserAvatar from 'ui/shared/UserAvatar';
const MyProfile = () => { const MyProfile = () => {
const { data, isLoading, isError, error, isFetched } = useFetchProfileInfo(); const { data, isLoading, isError, error } = useFetchProfileInfo();
useRedirectForInvalidAuthToken(); useRedirectForInvalidAuthToken();
const content = (() => { const content = (() => {
...@@ -26,7 +26,7 @@ const MyProfile = () => { ...@@ -26,7 +26,7 @@ const MyProfile = () => {
return ( return (
<VStack maxW="412px" mt={ 8 } gap={ 5 } alignItems="stretch"> <VStack maxW="412px" mt={ 8 } gap={ 5 } alignItems="stretch">
<UserAvatar size={ 64 } data={ data } isFetched={ isFetched }/> <UserAvatar size={ 64 }/>
<FormControl variant="floating" id="name" isRequired size="lg"> <FormControl variant="floating" id="name" isRequired size="lg">
<Input <Input
required required
......
...@@ -276,8 +276,6 @@ const TokenPageContent = () => { ...@@ -276,8 +276,6 @@ const TokenPageContent = () => {
stickyEnabled={ !isMobile } stickyEnabled={ !isMobile }
/> />
) } ) }
{ !tokenQuery.isLoading && !tokenQuery.isError && <Box h={{ base: 0, lg: '40vh' }}/> }
</> </>
); );
}; };
......
...@@ -183,8 +183,6 @@ const TokenInstanceContent = () => { ...@@ -183,8 +183,6 @@ const TokenInstanceContent = () => {
stickyEnabled={ !isMobile } stickyEnabled={ !isMobile }
/> />
) } ) }
{ !tokenInstanceQuery.isLoading && !tokenInstanceQuery.isError && <Box h={{ base: 0, lg: '40vh' }}/> }
</> </>
); );
}; };
......
import { Box, Icon, Tooltip, chakra } from '@chakra-ui/react'; import { Button, Icon, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
...@@ -59,11 +59,10 @@ const NetworkAddToWallet = ({ className }: Props) => { ...@@ -59,11 +59,10 @@ const NetworkAddToWallet = ({ className }: Props) => {
const defaultWallet = appConfig.web3.defaultWallet; const defaultWallet = appConfig.web3.defaultWallet;
return ( return (
<Tooltip label={ WALLETS_INFO[defaultWallet].add_network_text }> <Button variant="outline" size="sm" onClick={ handleClick } className={ className }>
<Box className={ className } display="inline-flex" cursor="pointer" onClick={ handleClick }> <Icon as={ WALLETS_INFO[defaultWallet].icon } boxSize={ 5 } mr={ 2 }/>
<Icon as={ WALLETS_INFO[defaultWallet].icon } boxSize={ 5 }/> Add { appConfig.network.name }
</Box> </Button>
</Tooltip>
); );
}; };
......
import { Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import getErrorCauseStatusCode from 'lib/errors/getErrorCauseStatusCode'; import getErrorCauseStatusCode from 'lib/errors/getErrorCauseStatusCode';
...@@ -12,6 +12,7 @@ import AppErrorInvalidTxHash from 'ui/shared/AppError/AppErrorInvalidTxHash'; ...@@ -12,6 +12,7 @@ import AppErrorInvalidTxHash from 'ui/shared/AppError/AppErrorInvalidTxHash';
import AppErrorUnverifiedEmail from 'ui/shared/AppError/AppErrorUnverifiedEmail'; import AppErrorUnverifiedEmail from 'ui/shared/AppError/AppErrorUnverifiedEmail';
import ErrorBoundary from 'ui/shared/ErrorBoundary'; import ErrorBoundary from 'ui/shared/ErrorBoundary';
import PageContent from 'ui/shared/Page/PageContent'; import PageContent from 'ui/shared/Page/PageContent';
import Footer from 'ui/snippets/footer/Footer';
import Header from 'ui/snippets/header/Header'; import Header from 'ui/snippets/header/Header';
import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop'; import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop';
...@@ -73,18 +74,21 @@ const Page = ({ ...@@ -73,18 +74,21 @@ const Page = ({
) : children; ) : children;
return ( return (
<Flex w="100%" minH="100vh" alignItems="stretch"> <Box minWidth={{ base: '100vw', lg: 'fit-content' }}>
<NavigationDesktop/> <Flex w="100%" minH="100vh" alignItems="stretch">
<Flex flexDir="column" flexGrow={ 1 } w={{ base: '100%', lg: 'auto' }}> <NavigationDesktop/>
{ renderHeader ? <Flex flexDir="column" flexGrow={ 1 } w={{ base: '100%', lg: 'auto' }}>
renderHeader() : { renderHeader ?
<Header isHomePage={ isHomePage }/> renderHeader() :
} <Header isHomePage={ isHomePage }/>
<ErrorBoundary renderErrorScreen={ renderErrorScreen }> }
{ renderedChildren } <ErrorBoundary renderErrorScreen={ renderErrorScreen }>
</ErrorBoundary> { renderedChildren }
</ErrorBoundary>
</Flex>
</Flex> </Flex>
</Flex> <Footer/>
</Box>
); );
}; };
......
...@@ -2,10 +2,9 @@ import { useColorModeValue, useToken, SkeletonCircle, Image, Box } from '@chakra ...@@ -2,10 +2,9 @@ import { useColorModeValue, useToken, SkeletonCircle, Image, Box } from '@chakra
import React from 'react'; import React from 'react';
import Identicon from 'react-identicons'; import Identicon from 'react-identicons';
import type { UserInfo } from 'types/api/account';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
const IdenticonComponent = typeof Identicon === 'object' && 'default' in Identicon ? Identicon.default : Identicon; const IdenticonComponent = typeof Identicon === 'object' && 'default' in Identicon ? Identicon.default : Identicon;
...@@ -34,14 +33,13 @@ const FallbackImage = ({ size, id }: { size: number; id: string }) => { ...@@ -34,14 +33,13 @@ const FallbackImage = ({ size, id }: { size: number; id: string }) => {
interface Props { interface Props {
size: number; size: number;
data?: UserInfo;
isFetched: boolean;
} }
const UserAvatar = ({ size, data, isFetched }: Props) => { const UserAvatar = ({ size }: Props) => {
const appProps = useAppContext(); const appProps = useAppContext();
const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, appProps.cookies)); const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, appProps.cookies));
const [ isImageLoadError, setImageLoadError ] = React.useState(false); const [ isImageLoadError, setImageLoadError ] = React.useState(false);
const { data, isFetched } = useFetchProfileInfo();
const sizeString = `${ size }px`; const sizeString = `${ size }px`;
......
...@@ -67,7 +67,7 @@ const AddressAddToWallet = ({ className, token, isLoading }: Props) => { ...@@ -67,7 +67,7 @@ const AddressAddToWallet = ({ className, token, isLoading }: Props) => {
const defaultWallet = appConfig.web3.defaultWallet; const defaultWallet = appConfig.web3.defaultWallet;
return ( return (
<Tooltip label={ WALLETS_INFO[defaultWallet].add_token_text }> <Tooltip label={ `Add token to ${ WALLETS_INFO[defaultWallet].name }` }>
<Box className={ className } display="inline-flex" cursor="pointer" onClick={ handleClick }> <Box className={ className } display="inline-flex" cursor="pointer" onClick={ handleClick }>
<Icon as={ WALLETS_INFO[defaultWallet].icon } boxSize={ 6 }/> <Icon as={ WALLETS_INFO[defaultWallet].icon } boxSize={ 6 }/>
</Box> </Box>
......
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { FOOTER_LINKS } from 'mocks/config/footerLinks';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import Footer from './Footer';
const FOOTER_LINKS_URL = 'https://localhost:3000/footer-links.json';
base.describe('with custom links, 4 cols', () => {
const test = base.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_FOOTER_LINKS', value: FOOTER_LINKS_URL },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
]) as any,
});
test('base view +@dark-mode +@mobile +@desktop-xl', async({ mount, page }) => {
await page.route(FOOTER_LINKS_URL, (route) => {
return route.fulfill({
body: JSON.stringify(FOOTER_LINKS),
});
});
await mount(
<TestApp>
<Footer/>
</TestApp>,
);
await expect(page).toHaveScreenshot();
});
});
base.describe('with custom links, 2 cols', () => {
const test = base.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_FOOTER_LINKS', value: FOOTER_LINKS_URL },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
]) as any,
});
test('base view +@dark-mode +@mobile', async({ mount, page }) => {
await page.route(FOOTER_LINKS_URL, (route) => {
return route.fulfill({
body: JSON.stringify([ FOOTER_LINKS[0] ]),
});
});
await mount(
<TestApp>
<Footer/>
</TestApp>,
);
await expect(page).toHaveScreenshot();
});
});
base.describe('without custom links', () => {
base('base view +@dark-mode +@mobile', async({ mount, page }) => {
await page.evaluate(() => {
window.ethereum = {
providers: [ { isMetaMask: true } ],
};
});
await mount(
<TestApp>
<Footer/>
</TestApp>,
);
await expect(page).toHaveScreenshot();
});
});
import { Box, Grid, Flex, Text, Link, VStack, Skeleton } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { CustomLinksGroup } from 'types/footerLinks';
import appConfig from 'configs/app/config';
import discussionsIcon from 'icons/discussions.svg';
import editIcon from 'icons/edit.svg';
import discordIcon from 'icons/social/discord.svg';
import gitIcon from 'icons/social/git.svg';
import twitterIcon from 'icons/social/tweet.svg';
import type { ResourceError } from 'lib/api/resources';
import useFetch from 'lib/hooks/useFetch';
import useIssueUrl from 'lib/hooks/useIssueUrl';
import NetworkAddToWallet from 'ui/shared/NetworkAddToWallet';
import ColorModeToggler from '../header/ColorModeToggler';
import FooterLinkItem from './FooterLinkItem';
const MAX_LINKS_COLUMNS = 3;
const API_VERSION_URL = `https://github.com/blockscout/blockscout/tree/${ appConfig.blockScoutVersion }`;
// const FRONT_VERSION_URL = `https://github.com/blockscout/frontend/tree/${ appConfig.frontendVersion }`;
const Footer = () => {
const issueUrl = useIssueUrl();
const BLOCSKOUT_LINKS = [
{
icon: editIcon,
iconSize: '16px',
text: 'Submit an issue',
url: issueUrl,
},
{
icon: gitIcon,
iconSize: '18px',
text: 'Contribute',
url: 'https://github.com/blockscout/blockscout',
},
{
icon: twitterIcon,
iconSize: '18px',
text: 'Twitter',
url: 'https://www.twitter.com/blockscoutcom',
},
{
icon: discordIcon,
iconSize: '18px',
text: 'Discord',
url: 'https://discord.gg/blockscout',
},
{
icon: discussionsIcon,
iconSize: '20px',
text: 'Discussions',
url: 'https://github.com/orgs/blockscout/discussions',
},
];
const fetch = useFetch();
const { isLoading, data: linksData } = useQuery<unknown, ResourceError<unknown>, Array<CustomLinksGroup>>(
[ 'footer-links' ],
async() => fetch(appConfig.footerLinks || ''),
{
enabled: Boolean(appConfig.footerLinks),
staleTime: Infinity,
});
return (
<Flex direction={{ base: 'column', lg: 'row' }} p={{ base: 4, lg: 9 }} borderTop="1px solid" borderColor="divider">
<Box flexGrow="1" mb={{ base: 8, lg: 0 }}>
<Flex>
<ColorModeToggler/>
<NetworkAddToWallet ml={ 8 }/>
</Flex>
<Box mt={{ base: 5, lg: '44px' }}>
<Link fontSize="xs" href="https://www.blockscout.com">blockscout.com</Link>
</Box>
<Text mt={ 3 } mr={{ base: 0, lg: '114px' }} maxW={{ base: 'unset', lg: '470px' }} fontSize="xs">
Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks.
</Text>
<VStack spacing={ 1 } mt={ 6 } alignItems="start">
{ appConfig.blockScoutVersion && (
<Text fontSize="xs">
Backend: <Link href={ API_VERSION_URL } target="_blank">{ appConfig.blockScoutVersion }</Link>
</Text>
) }
{ (appConfig.frontendVersion || appConfig.frontendCommit) && (
<Text fontSize="xs">
{ /* Frontend: <Link href={ FRONT_VERSION_URL } target="_blank">{ appConfig.frontendVersion }</Link> */ }
Frontend: { [ appConfig.frontendVersion, appConfig.frontendCommit ].filter(Boolean).join('+') }
</Text>
) }
</VStack>
</Box>
<Grid
gap={{ base: 6, lg: 12 }}
gridTemplateColumns={ appConfig.footerLinks ?
{ base: 'repeat(auto-fill, 160px)', lg: `repeat(${ (linksData?.length || MAX_LINKS_COLUMNS) + 1 }, 160px)` } :
'auto'
}
>
<Box minW="160px" w={ appConfig.footerLinks ? '160px' : '100%' }>
{ appConfig.footerLinks && <Text fontWeight={ 500 } mb={ 3 }>Blockscout</Text> }
<Grid
gap={ 1 }
gridTemplateColumns={ appConfig.footerLinks ? '160px' : { base: 'repeat(auto-fill, 160px)', lg: 'repeat(3, 160px)' } }
gridTemplateRows={{ base: 'auto', lg: appConfig.footerLinks ? 'auto' : 'repeat(2, auto)' }}
gridAutoFlow={{ base: 'row', lg: appConfig.footerLinks ? 'row' : 'column' }}
mt={{ base: 0, lg: appConfig.footerLinks ? 0 : '100px' }}
>
{ BLOCSKOUT_LINKS.map(link => <FooterLinkItem { ...link } key={ link.text }/>) }
</Grid>
</Box>
{ appConfig.footerLinks && isLoading && (
Array.from(Array(3)).map((i, index) => (
<Box minW="160px" key={ index }>
<Skeleton w="120px" h="20px" mb={ 6 }/>
<VStack spacing={ 5 } alignItems="start" mb={ 2 }>
{ Array.from(Array(5)).map((i, index) => <Skeleton w="160px" h="14px" key={ index }/>) }
</VStack>
</Box>
))
) }
{ appConfig.footerLinks && linksData && (
linksData.slice(0, MAX_LINKS_COLUMNS).map(linkGroup => (
<Box minW="160px" key={ linkGroup.title }>
<Text fontWeight={ 500 } mb={ 3 }>{ linkGroup.title }</Text>
<VStack spacing={ 1 } alignItems="start">
{ linkGroup.links.map(link => <FooterLinkItem { ...link } key={ link.text }/>) }
</VStack>
</Box>
))
) }
</Grid>
</Flex>
);
};
export default Footer;
import { Center, Icon, Link } from '@chakra-ui/react';
import React from 'react';
type Props = {
icon?: React.FC<React.SVGAttributes<SVGElement>>;
iconSize?: string;
text: string;
url: string;
}
const FooterLinkItem = ({ icon, iconSize, text, url }: Props) => {
return (
<Link href={ url } display="flex" alignItems="center" h="30px" variant="secondary" target="_blank" fontSize="xs">
{ icon && (
<Center minW={ 6 } mr="6px">
<Icon boxSize={ iconSize || 5 } as={ icon }/>
</Center>
) }
{ text }
</Link>
);
};
export default FooterLinkItem;
...@@ -110,7 +110,7 @@ test.describe('auth', () => { ...@@ -110,7 +110,7 @@ test.describe('auth', () => {
}, },
}); });
extendedTest.use({ viewport: { width: devices['iPhone 13 Pro'].viewport.width, height: 1000 } }); extendedTest.use({ viewport: { width: devices['iPhone 13 Pro'].viewport.width, height: 800 } });
extendedTest('base view', async({ mount, page }) => { extendedTest('base view', async({ mount, page }) => {
const component = await mount( const component = await mount(
......
...@@ -10,7 +10,6 @@ import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile'; ...@@ -10,7 +10,6 @@ import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile';
import SearchBar from 'ui/snippets/searchBar/SearchBar'; import SearchBar from 'ui/snippets/searchBar/SearchBar';
import Burger from './Burger'; import Burger from './Burger';
import ColorModeToggler from './ColorModeToggler';
type Props = { type Props = {
isHomePage?: boolean; isHomePage?: boolean;
...@@ -66,7 +65,6 @@ const Header = ({ isHomePage, renderSearchBar }: Props) => { ...@@ -66,7 +65,6 @@ const Header = ({ isHomePage, renderSearchBar }: Props) => {
<Box width="100%"> <Box width="100%">
{ searchBar } { searchBar }
</Box> </Box>
<ColorModeToggler/>
{ appConfig.isAccountSupported && <ProfileMenuDesktop/> } { appConfig.isAccountSupported && <ProfileMenuDesktop/> }
</HStack> </HStack>
) } ) }
......
import { Box, Text, Stack, Icon, Link, VStack } from '@chakra-ui/react';
import React from 'react';
import appConfig from 'configs/app/config';
import ghIcon from 'icons/social/git.svg';
import statsIcon from 'icons/social/stats.svg';
import tgIcon from 'icons/social/telega.svg';
import twIcon from 'icons/social/tweet.svg';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import NetworkAddToWallet from 'ui/shared/NetworkAddToWallet';
const SOCIAL_LINKS = [
{ link: appConfig.footerLinks.github, icon: ghIcon, label: 'Github link' },
{ link: appConfig.footerLinks.twitter, icon: twIcon, label: 'Twitter link' },
{ link: appConfig.footerLinks.telegram, icon: tgIcon, label: 'Telegram link' },
{ link: appConfig.footerLinks.staking, icon: statsIcon, label: 'Staking analytic link' },
].filter(({ link }) => link);
const VERSION_URL = `https://github.com/blockscout/blockscout/tree/${ appConfig.blockScoutVersion }`;
interface Props {
isCollapsed?: boolean;
hasAccount?: boolean;
}
const NavFooter = ({ isCollapsed, hasAccount }: Props) => {
const isExpanded = isCollapsed === false;
const marginTop = (() => {
if (!hasAccount) {
return 'auto';
}
return { base: 6, lg: 20 };
})();
return (
<VStack
as="footer"
borderTop="1px solid"
borderColor="divider"
width={{ base: '100%', lg: isExpanded ? '180px' : '20px', xl: isCollapsed ? '20px' : '180px' }}
marginTop={ marginTop }
alignItems="flex-start"
alignSelf="center"
color="gray.500"
fontSize="xs"
{ ...getDefaultTransitionProps({ transitionProperty: 'width' }) }
>
<Stack
direction={{ base: 'row', lg: isExpanded ? 'row' : 'column', xl: isCollapsed ? 'column' : 'row' }}
mt={{ base: 6, lg: 8 }}
_empty={{
display: 'none',
}}
>
<NetworkAddToWallet/>
{ SOCIAL_LINKS.map(sl => {
return (
<Link href={ sl.link } key={ sl.link } variant="secondary" w={ 5 } h={ 5 } aria-label={ sl.label } target="_blank">
<Icon as={ sl.icon } boxSize={ 5 }/>
</Link>
);
}) }
</Stack>
<Box display={{ base: 'block', lg: isExpanded ? 'block' : 'none', xl: isCollapsed ? 'none' : 'block' }}>
<Text variant="secondary" mt={ 8 }>
Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks.
</Text>
{ appConfig.blockScoutVersion &&
<Text variant="secondary" mt={ 8 }>Version: <Link href={ VERSION_URL } target="_blank">{ appConfig.blockScoutVersion }</Link></Text> }
</Box>
</VStack>
);
};
export default NavFooter;
import { Link, Icon, Text, HStack, Tooltip, Box, useBreakpointValue, chakra, shouldForwardProp } from '@chakra-ui/react'; import { Link, Text, HStack, Tooltip, Box, useBreakpointValue, chakra, shouldForwardProp } from '@chakra-ui/react';
import NextLink from 'next/link'; import NextLink from 'next/link';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
...@@ -8,6 +8,7 @@ import type { NavItem } from 'types/client/navigation-items'; ...@@ -8,6 +8,7 @@ import type { NavItem } from 'types/client/navigation-items';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { isInternalItem } from 'lib/hooks/useNavItems'; import { isInternalItem } from 'lib/hooks/useNavItems';
import NavLinkIcon from './NavLinkIcon';
import useColors from './useColors'; import useColors from './useColors';
import useNavLinkStyleProps from './useNavLinkStyleProps'; import useNavLinkStyleProps from './useNavLinkStyleProps';
...@@ -50,7 +51,7 @@ const NavLink = ({ item, isCollapsed, px, className }: Props) => { ...@@ -50,7 +51,7 @@ const NavLink = ({ item, isCollapsed, px, className }: Props) => {
color={ isInternalLink && item.isActive ? colors.text.active : colors.text.hover } color={ isInternalLink && item.isActive ? colors.text.active : colors.text.hover }
> >
<HStack spacing={ 3 } overflow="hidden"> <HStack spacing={ 3 } overflow="hidden">
{ item.icon && <Icon as={ item.icon } boxSize="30px"/> } <NavLinkIcon item={ item }/>
<Text { ...styleProps.textProps }> <Text { ...styleProps.textProps }>
{ item.text } { item.text }
</Text> </Text>
......
...@@ -17,16 +17,18 @@ import type { NavGroupItem } from 'types/client/navigation-items'; ...@@ -17,16 +17,18 @@ import type { NavGroupItem } from 'types/client/navigation-items';
import chevronIcon from 'icons/arrows/east-mini.svg'; import chevronIcon from 'icons/arrows/east-mini.svg';
import NavLink from './NavLink'; import NavLink from './NavLink';
import NavLinkIcon from './NavLinkIcon';
import useNavLinkStyleProps from './useNavLinkStyleProps'; import useNavLinkStyleProps from './useNavLinkStyleProps';
type Props = NavGroupItem & { type Props = {
item: NavGroupItem;
isCollapsed?: boolean; isCollapsed?: boolean;
} }
const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Props) => { const NavLinkGroupDesktop = ({ item, isCollapsed }: Props) => {
const isExpanded = isCollapsed === false; const isExpanded = isCollapsed === false;
const styleProps = useNavLinkStyleProps({ isCollapsed, isExpanded, isActive }); const styleProps = useNavLinkStyleProps({ isCollapsed, isExpanded, isActive: item.isActive });
return ( return (
<Box as="li" listStyleType="none" w="100%"> <Box as="li" listStyleType="none" w="100%">
...@@ -41,15 +43,15 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr ...@@ -41,15 +43,15 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr
w={{ lg: isExpanded ? '180px' : '60px', xl: isCollapsed ? '60px' : '180px' }} w={{ lg: isExpanded ? '180px' : '60px', xl: isCollapsed ? '60px' : '180px' }}
pl={{ lg: isExpanded ? 3 : '15px', xl: isCollapsed ? '15px' : 3 }} pl={{ lg: isExpanded ? 3 : '15px', xl: isCollapsed ? '15px' : 3 }}
pr={{ lg: isExpanded ? 0 : '15px', xl: isCollapsed ? '15px' : 0 }} pr={{ lg: isExpanded ? 0 : '15px', xl: isCollapsed ? '15px' : 0 }}
aria-label={ `${ text } link group` } aria-label={ `${ item.text } link group` }
position="relative" position="relative"
> >
<HStack spacing={ 3 } overflow="hidden"> <HStack spacing={ 3 } overflow="hidden">
<Icon as={ icon } boxSize="30px"/> <NavLinkIcon item={ item }/>
<Text <Text
{ ...styleProps.textProps } { ...styleProps.textProps }
> >
{ text } { item.text }
</Text> </Text>
<Icon <Icon
as={ chevronIcon } as={ chevronIcon }
...@@ -68,10 +70,10 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr ...@@ -68,10 +70,10 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr
<PopoverContent width="252px" top={{ lg: isExpanded ? '-16px' : 0, xl: isCollapsed ? 0 : '-16px' }}> <PopoverContent width="252px" top={{ lg: isExpanded ? '-16px' : 0, xl: isCollapsed ? 0 : '-16px' }}>
<PopoverBody p={ 4 }> <PopoverBody p={ 4 }>
<Text variant="secondary" fontSize="sm" mb={ 2 } display={{ lg: isExpanded ? 'none' : 'block', xl: isCollapsed ? 'block' : 'none' }}> <Text variant="secondary" fontSize="sm" mb={ 2 } display={{ lg: isExpanded ? 'none' : 'block', xl: isCollapsed ? 'block' : 'none' }}>
{ text } { item.text }
</Text> </Text>
<VStack spacing={ 1 } alignItems="start"> <VStack spacing={ 1 } alignItems="start">
{ subItems.map((item, index) => Array.isArray(item) ? ( { item.subItems.map((subItem, index) => Array.isArray(subItem) ? (
<Box <Box
key={ index } key={ index }
w="100%" w="100%"
...@@ -83,10 +85,10 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr ...@@ -83,10 +85,10 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr
borderColor: 'divider', borderColor: 'divider',
}} }}
> >
{ item.map(subItem => <NavLink key={ subItem.text } item={ subItem } isCollapsed={ false }/>) } { subItem.map(subSubItem => <NavLink key={ subSubItem.text } item={ subSubItem } isCollapsed={ false }/>) }
</Box> </Box>
) : ) :
<NavLink key={ item.text } item={ item } isCollapsed={ false }/>, <NavLink key={ item.text } item={ subItem } isCollapsed={ false }/>,
) } ) }
</VStack> </VStack>
</PopoverBody> </PopoverBody>
......
...@@ -11,15 +11,16 @@ import type { NavGroupItem } from 'types/client/navigation-items'; ...@@ -11,15 +11,16 @@ import type { NavGroupItem } from 'types/client/navigation-items';
import chevronIcon from 'icons/arrows/east-mini.svg'; import chevronIcon from 'icons/arrows/east-mini.svg';
import NavLinkIcon from './NavLinkIcon';
import useNavLinkStyleProps from './useNavLinkStyleProps'; import useNavLinkStyleProps from './useNavLinkStyleProps';
type Props = NavGroupItem & { type Props = {
isCollapsed?: boolean; item: NavGroupItem;
onClick: () => void; onClick: () => void;
} }
const NavLinkGroup = ({ text, icon, isActive, onClick }: Props) => { const NavLinkGroup = ({ item, onClick }: Props) => {
const styleProps = useNavLinkStyleProps({ isActive }); const styleProps = useNavLinkStyleProps({ isActive: item.isActive });
return ( return (
<Box as="li" listStyleType="none" w="100%" onClick={ onClick }> <Box as="li" listStyleType="none" w="100%" onClick={ onClick }>
...@@ -27,15 +28,15 @@ const NavLinkGroup = ({ text, icon, isActive, onClick }: Props) => { ...@@ -27,15 +28,15 @@ const NavLinkGroup = ({ text, icon, isActive, onClick }: Props) => {
{ ...styleProps.itemProps } { ...styleProps.itemProps }
w="100%" w="100%"
px={ 3 } px={ 3 }
aria-label={ `${ text } link group` } aria-label={ `${ item.text } link group` }
> >
<Flex justifyContent="space-between" width="100%" alignItems="center" pr={ 1 }> <Flex justifyContent="space-between" width="100%" alignItems="center" pr={ 1 }>
<HStack spacing={ 3 } overflow="hidden"> <HStack spacing={ 3 } overflow="hidden">
<Icon as={ icon } boxSize="30px"/> <NavLinkIcon item={ item }/>
<Text <Text
{ ...styleProps.textProps } { ...styleProps.textProps }
> >
{ text } { item.text }
</Text> </Text>
</HStack> </HStack>
<Icon as={ chevronIcon } transform="rotate(180deg)" boxSize={ 6 }/> <Icon as={ chevronIcon } transform="rotate(180deg)" boxSize={ 6 }/>
......
import { Icon } from '@chakra-ui/react';
import React from 'react';
import type { NavItem, NavGroupItem } from 'types/client/navigation-items';
const NavLinkIcon = ({ item }: { item: NavItem | NavGroupItem}) => {
if ('icon' in item) {
return <Icon as={ item.icon } boxSize="30px"/>;
}
if ('iconComponent' in item && item.iconComponent) {
const IconComponent = item.iconComponent;
return <IconComponent size={ 30 }/>;
}
return null;
};
export default NavLinkIcon;
...@@ -26,13 +26,7 @@ const test = base.extend({ ...@@ -26,13 +26,7 @@ const test = base.extend({
]) as any, ]) as any,
}); });
test('no auth +@desktop-xl +@dark-mode-xl', async({ page, mount }) => { test('no auth +@desktop-xl +@dark-mode-xl', async({ mount }) => {
await page.evaluate(() => {
window.ethereum = {
providers: [ { isMetaMask: true } ],
};
});
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<Flex w="100%" minH="100vh" alignItems="stretch"> <Flex w="100%" minH="100vh" alignItems="stretch">
......
...@@ -12,7 +12,6 @@ import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; ...@@ -12,7 +12,6 @@ import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import NetworkMenu from 'ui/snippets/networkMenu/NetworkMenu'; import NetworkMenu from 'ui/snippets/networkMenu/NetworkMenu';
import NavFooter from './NavFooter';
import NavLink from './NavLink'; import NavLink from './NavLink';
import NavLinkGroupDesktop from './NavLinkGroupDesktop'; import NavLinkGroupDesktop from './NavLinkGroupDesktop';
...@@ -83,7 +82,7 @@ const NavigationDesktop = () => { ...@@ -83,7 +82,7 @@ const NavigationDesktop = () => {
<VStack as="ul" spacing="1" alignItems="flex-start"> <VStack as="ul" spacing="1" alignItems="flex-start">
{ mainNavItems.map((item) => { { mainNavItems.map((item) => {
if (isGroupItem(item)) { if (isGroupItem(item)) {
return <NavLinkGroupDesktop key={ item.text } { ...item } isCollapsed={ isCollapsed }/>; return <NavLinkGroupDesktop key={ item.text } item={ item } isCollapsed={ isCollapsed }/>;
} else { } else {
return <NavLink key={ item.text } item={ item } isCollapsed={ isCollapsed }/>; return <NavLink key={ item.text } item={ item } isCollapsed={ isCollapsed }/>;
} }
...@@ -97,7 +96,6 @@ const NavigationDesktop = () => { ...@@ -97,7 +96,6 @@ const NavigationDesktop = () => {
</VStack> </VStack>
</Box> </Box>
) } ) }
<NavFooter isCollapsed={ isCollapsed } hasAccount={ hasAccount }/>
<Icon <Icon
as={ chevronIcon } as={ chevronIcon }
width={ 6 } width={ 6 }
......
...@@ -5,7 +5,6 @@ import React, { useCallback } from 'react'; ...@@ -5,7 +5,6 @@ import React, { useCallback } from 'react';
import chevronIcon from 'icons/arrows/east-mini.svg'; import chevronIcon from 'icons/arrows/east-mini.svg';
import useHasAccount from 'lib/hooks/useHasAccount'; import useHasAccount from 'lib/hooks/useHasAccount';
import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems'; import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems';
import NavFooter from 'ui/snippets/navigation/NavFooter';
import NavLink from 'ui/snippets/navigation/NavLink'; import NavLink from 'ui/snippets/navigation/NavLink';
import NavLinkGroupMobile from './NavLinkGroupMobile'; import NavLinkGroupMobile from './NavLinkGroupMobile';
...@@ -58,7 +57,7 @@ const NavigationMobile = () => { ...@@ -58,7 +57,7 @@ const NavigationMobile = () => {
> >
{ mainNavItems.map((item, index) => { { mainNavItems.map((item, index) => {
if (isGroupItem(item)) { if (isGroupItem(item)) {
return <NavLinkGroupMobile key={ item.text } { ...item } onClick={ onGroupItemOpen(index) }/>; return <NavLinkGroupMobile key={ item.text } item={ item } onClick={ onGroupItemOpen(index) }/>;
} else { } else {
return <NavLink key={ item.text } item={ item }/>; return <NavLink key={ item.text } item={ item }/>;
} }
...@@ -78,7 +77,6 @@ const NavigationMobile = () => { ...@@ -78,7 +77,6 @@ const NavigationMobile = () => {
</VStack> </VStack>
</Box> </Box>
) } ) }
<NavFooter hasAccount={ hasAccount }/>
</Box> </Box>
{ openedGroupIndex >= 0 && ( { openedGroupIndex >= 0 && (
<Box <Box
......
...@@ -53,6 +53,6 @@ test.describe('auth', () => { ...@@ -53,6 +53,6 @@ test.describe('auth', () => {
); );
await component.getByAltText(/Profile picture/i).click(); await component.getByAltText(/Profile picture/i).click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 550 } }); await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 600 } });
}); });
}); });
...@@ -7,7 +7,7 @@ import UserAvatar from 'ui/shared/UserAvatar'; ...@@ -7,7 +7,7 @@ import UserAvatar from 'ui/shared/UserAvatar';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent'; import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuDesktop = () => { const ProfileMenuDesktop = () => {
const { data, isFetched } = useFetchProfileInfo(); const { data } = useFetchProfileInfo();
const loginUrl = useLoginUrl(); const loginUrl = useLoginUrl();
return ( return (
...@@ -21,7 +21,7 @@ const ProfileMenuDesktop = () => { ...@@ -21,7 +21,7 @@ const ProfileMenuDesktop = () => {
as={ data ? undefined : 'a' } as={ data ? undefined : 'a' }
href={ data ? undefined : loginUrl } href={ data ? undefined : loginUrl }
> >
<UserAvatar size={ 50 } data={ data } isFetched={ isFetched }/> <UserAvatar size={ 50 }/>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
{ data && ( { data && (
......
...@@ -8,19 +8,26 @@ import buildApiUrl from 'playwright/utils/buildApiUrl'; ...@@ -8,19 +8,26 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import ProfileMenuMobile from './ProfileMenuMobile'; import ProfileMenuMobile from './ProfileMenuMobile';
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('no auth', async({ mount, page }) => { test('no auth', async({ mount, page }) => {
const hooksConfig = {
router: {
asPath: '/',
pathname: '/',
},
};
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<ProfileMenuMobile/> <ProfileMenuMobile/>
</TestApp>, </TestApp>,
{ hooksConfig },
); );
await component.locator('.identicon').click(); await component.locator('.identicon').click();
await expect(page).toHaveScreenshot(); expect(page.url()).toBe('http://localhost:3100/auth/auth0?path=%2F');
}); });
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test.describe('auth', () => { test.describe('auth', () => {
const extendedTest = test.extend({ const extendedTest = test.extend({
context: ({ context }, use) => { context: ({ context }, use) => {
...@@ -49,8 +56,5 @@ test.describe('auth', () => { ...@@ -49,8 +56,5 @@ test.describe('auth', () => {
await component.getByAltText(/Profile picture/i).click(); await component.getByAltText(/Profile picture/i).click();
await expect(page).toHaveScreenshot(); await expect(page).toHaveScreenshot();
await page.locator('div[aria-label="Toggle color mode"]').click();
await expect(page).toHaveScreenshot();
}); });
}); });
import { Flex, Box, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, Button } from '@chakra-ui/react'; import { Box, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, Button } 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 UserAvatar from 'ui/shared/UserAvatar'; import UserAvatar from 'ui/shared/UserAvatar';
import ColorModeToggler from 'ui/snippets/header/ColorModeToggler';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent'; import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuMobile = () => { const ProfileMenuMobile = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const { data, isFetched } = useFetchProfileInfo(); const { data } = useFetchProfileInfo();
const loginUrl = useLoginUrl(); const loginUrl = useLoginUrl();
return ( return (
<> <>
<Box padding={ 2 } onClick={ onOpen }> <Box padding={ 2 } onClick={ data ? onOpen : undefined }>
<UserAvatar size={ 24 } data={ data } isFetched={ isFetched }/> <Button
variant="unstyled"
height="auto"
as={ data ? undefined : 'a' }
href={ data ? undefined : loginUrl }
>
<UserAvatar size={ 24 }/>
</Button>
</Box> </Box>
<Drawer { data && (
isOpen={ isOpen } <Drawer
placement="right" isOpen={ isOpen }
onClose={ onClose } placement="right"
autoFocus={ false } onClose={ onClose }
> autoFocus={ false }
<DrawerOverlay/> >
<DrawerContent maxWidth="260px"> <DrawerOverlay/>
<DrawerBody p={ 6 }> <DrawerContent maxWidth="260px">
<Flex <DrawerBody p={ 6 }>
justifyContent="space-between" <ProfileMenuContent { ...data }/>
alignItems="center" </DrawerBody>
mb={ 6 } </DrawerContent>
> </Drawer>
<ColorModeToggler/> ) }
<Box onClick={ onClose }>
<UserAvatar size={ 24 } data={ data } isFetched={ isFetched }/>
</Box>
</Flex>
{ data ? <ProfileMenuContent { ...data }/> : (
<Button size="sm" width="full" variant="outline" as="a" href={ loginUrl }>Sign In</Button>
) }
</DrawerBody>
</DrawerContent>
</Drawer>
</> </>
); );
}; };
......
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