Commit d69b809d authored by tom goriunov's avatar tom goriunov Committed by GitHub

ENVs integrity check (#1039)

* group account envs in docs

* remove NEXT_PUBLIC_LOGOUT_RETURN_URL env

* simple ENVs checker

* add types for all envs

* group values in config

* global envs types

* text tweaks

* fixes

* [skip ci] fix docker build
parent b65d96a4
Dockerfile
.dockerignore
node_modules
/**/node_modules
node_modules_linux
npm-debug.log
README.md
.next
.git
*.tsbuildinfo
.eslintcache
/test-results/
/playwright-report/
\ No newline at end of file
......@@ -36,7 +36,6 @@ 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_SUBMIT_FORM=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM__
NEXT_PUBLIC_LOGOUT_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_URL__
NEXT_PUBLIC_LOGOUT_RETURN_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_RETURN_URL__
NEXT_PUBLIC_HOMEPAGE_CHARTS=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_CHARTS__
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR__
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND__
......
......@@ -289,7 +289,7 @@ module.exports = {
},
},
{
files: [ 'configs/**/*.js', 'configs/**/*.ts', '*.config.ts', 'playwright/**/*.ts' ],
files: [ 'configs/**/*.js', 'configs/**/*.ts', '*.config.ts', 'playwright/**/*.ts', 'deploy/tools/**/*.ts' ],
rules: {
// for configs allow to consume env variables from process.env directly
'no-restricted-properties': [ 0 ],
......
......@@ -3,6 +3,7 @@
# dependencies
/node_modules
/node_modules_linux
/**/node_modules
/.pnp
.pnp.js
......@@ -48,3 +49,8 @@ yarn-error.log*
/playwright/envs.js
**.dec**
# tools: envs-validator
/deploy/tools/envs-validator/index.js
/deploy/tools/envs-validator/envs.ts
/deploy/tools/envs-validator/schema.ts
\ No newline at end of file
# Install dependencies only when needed
# *****************************
# *** STAGE 1: Dependencies ***
# *****************************
FROM node:18-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
# Install dependencies for App
WORKDIR /app
COPY package.json yarn.lock ./
RUN apk add git
RUN yarn --frozen-lockfile
# Rebuild the source code only when needed
# Install dependencies for ENVs checker
WORKDIR /envs-validator
COPY ./deploy/tools/envs-validator/package.json ./deploy/tools/envs-validator/yarn.lock ./
RUN yarn --frozen-lockfile
# *****************************
# ****** STAGE 2: Build *******
# *****************************
FROM node:18-alpine AS builder
# Build app for production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
COPY .env.template .env.production
RUN rm -rf ./deploy/tools/envs-validator
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
ARG SENTRY_DSN
ARG NEXT_PUBLIC_SENTRY_DSN
ARG SENTRY_CSP_REPORT_URI
ARG SENTRY_AUTH_TOKEN
# Build ENVs checker
WORKDIR /envs-validator
COPY --from=deps /envs-validator/node_modules ./node_modules
COPY ./deploy/tools/envs-validator .
COPY ./types/envs.ts .
RUN yarn build
# *****************************
# ******* STAGE 3: Run ********
# *****************************
# Production image, copy all the files and run next
FROM node:18-alpine AS runner
WORKDIR /app
......@@ -50,6 +66,7 @@ RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /envs-validator/index.js ./envs-validator.js
# Copy scripts and ENV templates file
COPY ./deploy/scripts/entrypoint.sh .
......@@ -61,7 +78,6 @@ COPY .env.template .
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# Execute script for replace build ENV with run ones
RUN apk add --no-cache --upgrade bash
RUN ["chmod", "+x", "./entrypoint.sh"]
RUN ["chmod", "+x", "./replace_envs.sh"]
......
......@@ -8,7 +8,7 @@ import type { ChainIndicatorId } from 'ui/home/indicators/types';
import stripTrailingSlash from 'lib/stripTrailingSlash';
const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"');
const getEnvValue = <T extends string>(env: T | undefined): T | undefined => env?.replaceAll('\'', '"') as T;
const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
try {
return JSON.parse(env || 'null') as DataType | null;
......@@ -70,8 +70,9 @@ const logoutUrl = (() => {
try {
const envUrl = getEnvValue(process.env.NEXT_PUBLIC_LOGOUT_URL);
const auth0ClientId = getEnvValue(process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID);
const returnUrl = getEnvValue(process.env.NEXT_PUBLIC_LOGOUT_RETURN_URL);
if (!envUrl || !auth0ClientId || !returnUrl) {
const returnUrl = authUrl + '/auth/logout';
if (!envUrl || !auth0ClientId) {
throw Error();
}
......@@ -113,20 +114,30 @@ const config = Object.freeze({
rpcUrl: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_RPC_URL),
isTestnet: getEnvValue(process.env.NEXT_PUBLIC_IS_TESTNET) === 'true',
},
navigation: {
otherLinks: parseEnvJson<Array<NavItemExternal>>(getEnvValue(process.env.NEXT_PUBLIC_OTHER_LINKS)) || [],
featuredNetworks: getEnvValue(process.env.NEXT_PUBLIC_FEATURED_NETWORKS),
footerLinks: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_LINKS),
},
footer: {
links: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_LINKS),
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',
marketplaceConfigUrl: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_CONFIG_URL),
marketplaceSubmitForm: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM),
},
marketplace: {
configUrl: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_CONFIG_URL),
submitForm: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM),
},
account: {
isEnabled: getEnvValue(process.env.NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED) === 'true',
authUrl,
logoutUrl,
},
app: {
protocol: appSchema,
host: appHost,
port: appPort,
baseUrl,
authUrl,
logoutUrl,
},
ad: {
adBannerProvider: getAdBannerProvider(),
adTextProvider: getAdTextProvider(),
......
......@@ -27,10 +27,10 @@ NEXT_PUBLIC_NETWORK_CURRENCY_NAME=POA
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=POA
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0x029a799563238d0e75e20be2f4bda0ea68d00172
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
#NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://core.poa.network
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
#NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
......
#!/bin/bash
# Check run-time ENVs values integrity
node "$(dirname "$0")/envs-validator.js" "$input"
if [ $? != 0 ]; then
echo ENV integrity check failed. 1>&2 && exit 1
fi
# Execute script for replace build-time ENVs placeholders with their values at runtime
./replace_envs.sh
echo "starting Nextjs"
......
/* eslint-disable no-console */
import type { ZodError } from 'zod-validation-error';
import { fromZodError } from 'zod-validation-error';
import { nextPublicEnvsSchema } from './schema';
try {
const appEnvs = Object.entries(process.env)
.filter(([ key ]) => key.startsWith('NEXT_PUBLIC_'))
.reduce((result, [ key, value ]) => {
result[key] = value || '';
return result;
}, {} as Record<string, string>);
console.log(`⏳ Validating environment variables schema...`);
nextPublicEnvsSchema.parse(appEnvs);
console.log('👍 All good!\n');
} catch (error) {
const validationError = fromZodError(
error as ZodError,
{
prefix: '',
prefixSeparator: '\n ',
issueSeparator: ';\n ',
},
);
console.log(validationError);
console.log('🚨 ENV set is invalid\n');
process.exit(1);
}
{
"name": "envs-validator",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "yarn ts-to-zod ./envs.ts ./schema.ts && yarn webpack-cli -c ./webpack.config.js",
"validate": "node ./index.js",
"dev": "cp ../../../types/envs.ts ./ && yarn build && yarn dotenv -e ../../../configs/envs/.env.poa_core yarn validate"
},
"dependencies": {
"ts-loader": "^9.4.4",
"ts-to-zod": "^3.1.3",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"zod": "^3.21.4",
"zod-validation-error": "^1.3.1"
},
"devDependencies": {
"dotenv-cli": "^7.2.1"
}
}
{
"compilerOptions": {
"target": "es6",
"skipLibCheck": true,
"strict": true,
"esModuleInterop": true,
"module": "CommonJS",
"moduleResolution": "node",
"isolatedModules": true,
"incremental": true,
"baseUrl": "."
},
"include": ["./schema.ts"],
"exclude": ["node_modules"]
}
const path = require('path');
module.exports = {
mode: 'production',
entry: path.resolve(__dirname) + '/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
},
output: {
filename: 'index.js',
path: path.resolve(__dirname),
},
};
This diff is collapsed.
......@@ -328,8 +328,6 @@ frontend:
_default: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_LOGOUT_URL:
_default: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_LOGOUT_RETURN_URL:
_default: https://blockscout-optimism-goerli.k8s-dev.blockscout.com/auth/logout
NEXT_PUBLIC_STATS_API_HOST:
_default: https://stats-optimism-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_API_SPEC_URL:
......
......@@ -299,8 +299,6 @@ frontend:
_default: blockscout-main.k8s-dev.blockscout.com
NEXT_PUBLIC_LOGOUT_URL:
_default: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_LOGOUT_RETURN_URL:
_default: https://blockscout-main.k8s-dev.blockscout.com/auth/logout
NEXT_PUBLIC_NETWORK_RPC_URL:
_default: https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_HOMEPAGE_CHARTS:
......
......@@ -97,8 +97,6 @@ frontend:
_default: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_LOGOUT_URL:
_default: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_LOGOUT_RETURN_URL:
_default: https://blockscout-optimism-goerli.k8s-dev.blockscout.com/auth/logout
NEXT_PUBLIC_STATS_API_HOST:
_default: https://stats-optimism-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_API_SPEC_URL:
......
......@@ -95,8 +95,6 @@ frontend:
_default: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_LOGOUT_URL:
_default: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_LOGOUT_RETURN_URL:
_default: https://blockscout-main.k8s-dev.blockscout.com/auth/logout
NEXT_PUBLIC_HOMEPAGE_CHARTS:
_default: "['daily_txs','coin_price','market_cap']"
NEXT_PUBLIC_APP_HOST:
......
......@@ -21,7 +21,6 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_NETWORK_LOGO_DARK | `string` | Network logo for dark color mode; if not provided, **inverted** regular logo will be used instead | - | - | `https://placekitten.com/240/40` |
| NEXT_PUBLIC_NETWORK_ICON | `string` | Network icon; used as a replacement for regular network logo when nav bar is collapsed; if not provided, placeholder will be shown; *Note* the icon size should be at least 60px by 60px | - | - | `https://placekitten.com/60/60` |
| NEXT_PUBLIC_NETWORK_ICON_DARK | `string` | Network icon for dark color mode; if not provided, **inverted** regular icon will be used instead | - | - | `https://placekitten.com/60/60` |
| NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED | `boolean` | Set to true if network has account feature | - | `false` | `true` |
| NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` |
## UI configuration
......@@ -35,9 +34,6 @@ The app instance could be customized by passing following variables to NodeJS en
| 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_VERIFICATION_TYPE | `validation` or `mining` | Verification type in the network | - | `mining` | `validation` |
| NEXT_PUBLIC_AUTH_URL | `string` | Account auth base url; the login path (`/auth/auth0`) will be added to it at execution time. Required if account is supported for the app instance. | - | - | `https://blockscout.com` |
| NEXT_PUBLIC_LOGOUT_URL | `string` | Account logout url. Required if account is supported for the app instance. | - | - | `https://blockscoutcom.us.auth0.com/v2/logout` |
| NEXT_PUBLIC_LOGOUT_RETURN_URL | `string` | Account logout return url. Required if account is supported for the app instance. | - | - | `https://blockscout.com/poa/core/auth/logout` |
| NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'coin_price' \| 'market_cap'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` |
| NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR | `string` | Text color of the hero plate on the homepage (escape "#" symbol if you use HEX color codes) | `\#FFFFFF \| rgb(220, 254, 118)` | `\#DCFE76` |
| NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND | `string` | Background css value for hero plate on the homepage (escape "#" symbol if you use HEX color codes) | - | `radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)` | `radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%)` \| `no-repeat bottom 20% right 0px/100% url(https://placekitten/1400/200)` |
......@@ -106,13 +102,23 @@ 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>`
## Footer links configuration properties
### 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'}]` |
## Account configuration
In order to enable "My Account" feature you have to configure following set of variables. All variables are **required**.
| Variable | Type| Description | Is required | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED | `boolean` | Set to true if network has account feature | yes | `false` | `true` |
| NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` | Client id for [Auth0](https://auth0.com/) provider | yes | - | `<secret>` |
| NEXT_PUBLIC_AUTH_URL | `string` | Account auth base url; it is used for building login URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/auth0`) and logout return URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/logout`); if not provided the base app URL will be used instead | yes | - | `https://blockscout.com` |
| NEXT_PUBLIC_LOGOUT_URL | `string` | Account logout url. Required if account is supported for the app instance. | yes | - | `https://blockscoutcom.us.auth0.com/v2/logout` |
## App configuration
......@@ -129,13 +135,14 @@ For each application, you need to specify the `MarketplaceCategoryId` to which i
| Variable | Type| Description | Is required | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_API_PROTOCOL | `http \| https` | Main API protocol | - | `https` | `http` |
| NEXT_PUBLIC_API_HOST | `string` | Main API host | - | `blockscout.com` | `my-host.com` |
| NEXT_PUBLIC_API_HOST | `string` | Main API host | yes | - | `blockscout.com` |
| NEXT_PUBLIC_API_PORT | `number` | Port where API is running on the host | - | - | `3001` |
| NEXT_PUBLIC_API_BASE_PATH | `string` | Base path for Main API endpoint url | - | - | `/poa/core` |
| NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL | `ws \| wss` | Main API websocket protocol | - | `wss` | `ws` |
| NEXT_PUBLIC_STATS_API_HOST | `string` | Stats API endpoint url | - | - | `https://my-host.com` |
| NEXT_PUBLIC_VISUALIZE_API_HOST | `string` | Visualize API endpoint url | - | - | `https://my-host.com` |
| NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | - | - | `https://my-host.com` |
| NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | `string` | Admin Service API endpoint url | - | - | `https://my-host.com` |
| NEXT_PUBLIC_STATS_API_HOST | `string` | Stats API endpoint url | - | - | `https://stats.services.blockscout.com` |
| NEXT_PUBLIC_VISUALIZE_API_HOST | `string` | Visualize API endpoint url | - | - | `https://visualizer.services.blockscout.com` |
| NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | - | - | `https://contracts-info.services.blockscout.com` |
| NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | `string` | Admin Service API endpoint url | - | - | `https://admin-rs.services.blockscout.com` |
## External services configuration
......@@ -144,7 +151,6 @@ For each application, you need to specify the `MarketplaceCategoryId` to which i
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_SENTRY_DSN | `string` | Client key for your Sentry.io app | - | - | `<secret>` |
| SENTRY_CSP_REPORT_URI | `string` | URL for sending CSP-reports to your Sentry.io app | - | - | `<secret>` |
| NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` | Client id for [Auth0](https://auth0.com/) provider | - | - | `<secret>` |
| NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID | `string` | Project id for [WalletConnect](https://docs.walletconnect.com/2.0/web3modal/react/installation#obtain-project-id) integration | - | - | `<secret>` |
| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | Site key for [reCAPTCHA](https://developers.google.com/recaptcha) service | - | - | `<secret>` |
| NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID | `string` | Property ID for [Google Analytics](https://analytics.google.com/) service | - | - | `UA-XXXXXX-X` |
......
......@@ -19,4 +19,10 @@ declare global {
};
abkw: string;
}
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production';
}
}
}
......@@ -12,7 +12,7 @@ export default function buildUrl<R extends ResourceName>(
queryParams?: Record<string, string | Array<string> | number | null | undefined>,
): string {
const resource: ApiResource = RESOURCES[resourceName];
const baseUrl = isNeedProxy() ? appConfig.baseUrl : (resource.endpoint || appConfig.api.endpoint);
const baseUrl = isNeedProxy() ? appConfig.app.baseUrl : (resource.endpoint || appConfig.api.endpoint);
const basePath = resource.basePath !== undefined ? resource.basePath : appConfig.api.basePath;
const path = isNeedProxy() ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path;
const url = new URL(compile(path)(pathParams), baseUrl);
......
......@@ -5,5 +5,5 @@ import appConfig from 'configs/app/config';
// unsuccessfully tried different ways, even custom local dev domain
// so for local development we have to use next.js api as proxy server
export default function isNeedProxy() {
return appConfig.host === 'localhost' && appConfig.host !== appConfig.api.host;
return appConfig.app.host === 'localhost' && appConfig.app.host !== appConfig.api.host;
}
......@@ -5,8 +5,8 @@ import appConfig from 'configs/app/config';
import { KEY_WORDS } from '../utils';
const MAIN_DOMAINS = [
`*.${ appConfig.host }`,
appConfig.host,
`*.${ appConfig.app.host }`,
appConfig.app.host,
appConfig.visualizeApi.endpoint,
].filter(Boolean);
// eslint-disable-next-line no-restricted-properties
......
......@@ -5,7 +5,7 @@ import * as cookies from 'lib/cookies';
export default function useHasAccount() {
const appProps = useAppContext();
if (!appConfig.isAccountSupported) {
if (!appConfig.account.isEnabled) {
return false;
}
......
......@@ -24,7 +24,7 @@ export default function useIssueUrl(backendVersion: string | undefined) {
### Environment
* Backend Version/branch/commit: ${ backendVersion }
* Frontend Version+commit: ${ [ appConfig.frontendVersion, appConfig.frontendCommit ].filter(Boolean).join('+') }
* Frontend Version+commit: ${ [ appConfig.footer.frontendVersion, appConfig.footer.frontendCommit ].filter(Boolean).join('+') }
* User Agent: ${ userAgent }
### Steps to reproduce
......
......@@ -5,5 +5,5 @@ import appConfig from 'configs/app/config';
export default function useLoginUrl() {
const router = useRouter();
return appConfig.authUrl + route({ pathname: '/auth/auth0', query: { path: router.asPath } });
return appConfig.account.authUrl + route({ pathname: '/auth/auth0', query: { path: router.asPath } });
}
......@@ -43,7 +43,7 @@ export function isInternalItem(item: NavItem): item is NavItemInternal {
}
export default function useNavItems(): ReturnType {
const isMarketplaceAvailable = Boolean(appConfig.marketplaceConfigUrl && appConfig.network.rpcUrl);
const isMarketplaceAvailable = Boolean(appConfig.marketplace.configUrl && appConfig.network.rpcUrl);
const hasAPIDocs = appConfig.apiDoc.specUrl;
const router = useRouter();
......@@ -166,10 +166,10 @@ export default function useNavItems(): ReturnType {
isActive: apiNavItems.some(item => isInternalItem(item) && item.isActive),
subItems: apiNavItems,
},
appConfig.otherLinks.length > 0 ? {
appConfig.navigation.otherLinks.length > 0 ? {
text: 'Other',
icon: gearIcon,
subItems: appConfig.otherLinks,
subItems: appConfig.navigation.otherLinks,
} : null,
].filter(Boolean);
......
......@@ -3,7 +3,7 @@ import appConfig from 'configs/app/config';
import { getServerSideProps as base } from '../getServerSideProps';
export const getServerSideProps: typeof base = async(...args) => {
if (!appConfig.isAccountSupported) {
if (!appConfig.account.isEnabled) {
return {
notFound: true,
};
......@@ -12,7 +12,7 @@ export const getServerSideProps: typeof base = async(...args) => {
};
export const getServerSidePropsForVerifiedAddresses: typeof base = async(...args) => {
if (!appConfig.isAccountSupported || !appConfig.adminServiceApi.endpoint || !appConfig.contractInfoApi.endpoint) {
if (!appConfig.account.isEnabled || !appConfig.adminServiceApi.endpoint || !appConfig.contractInfoApi.endpoint) {
return {
notFound: true,
};
......
......@@ -8,7 +8,7 @@ import { DAY } from 'lib/consts';
import * as cookies from 'lib/cookies';
export function account(req: NextRequest) {
if (!appConfig.isAccountSupported) {
if (!appConfig.account.isEnabled) {
return;
}
......@@ -24,7 +24,7 @@ export function account(req: NextRequest) {
const isProfileRoute = req.nextUrl.pathname.includes('/auth/profile');
if ((isAccountRoute || isProfileRoute)) {
const authUrl = appConfig.authUrl + route({ pathname: '/auth/auth0', query: { path: req.nextUrl.pathname } });
const authUrl = appConfig.account.authUrl + route({ pathname: '/auth/auth0', query: { path: req.nextUrl.pathname } });
return NextResponse.redirect(authUrl);
}
}
......@@ -35,7 +35,7 @@ export function account(req: NextRequest) {
if (apiTokenCookie) {
// temporary solution
// TODO check app for integrity https://github.com/blockscout/frontend/issues/1028 and make typescript happy here
if (!appConfig.logoutUrl) {
if (!appConfig.account.logoutUrl) {
httpLogger.logger.error({
message: 'Logout URL is not configured',
});
......@@ -46,7 +46,7 @@ export function account(req: NextRequest) {
// logout URL is always external URL in auth0.com sub-domain
// at least we hope so
const res = NextResponse.redirect(appConfig.logoutUrl);
const res = NextResponse.redirect(appConfig.account.logoutUrl);
res.cookies.delete(cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED); // reset cookie to show email verification page again
return res;
......@@ -55,7 +55,7 @@ export function account(req: NextRequest) {
// if user hasn't seen email verification page, make redirect to it
if (!req.cookies.get(cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED)) {
if (!req.nextUrl.pathname.includes('/auth/unverified-email')) {
const url = appConfig.baseUrl + route({ pathname: '/auth/unverified-email' });
const url = appConfig.app.baseUrl + route({ pathname: '/auth/unverified-email' });
const res = NextResponse.redirect(url);
res.cookies.set({
name: cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED,
......
......@@ -47,11 +47,11 @@ class MyDocument extends Document {
// eslint-disable-next-line max-len
content="Blockscout is the #1 open-source blockchain explorer available today. 100+ chains and counting rely on Blockscout data availability, APIs, and ecosystem tools to support their networks."
/>
<meta property="og:image" content={ appConfig.baseUrl + '/static/og.png' }/>
<meta property="og:image" content={ appConfig.app.baseUrl + '/static/og.png' }/>
<meta property="og:site_name" content="Blockscout"/>
<meta property="og:type" content="website"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta property="twitter:image" content={ appConfig.baseUrl + '/static/og_twitter.png' }/>
<meta property="twitter:image" content={ appConfig.app.baseUrl + '/static/og_twitter.png' }/>
</Head>
<body>
<ColorModeScript initialColorMode={ theme.config.initialColorMode }/>
......
......@@ -17,5 +17,5 @@
"baseUrl": ".",
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "decs.d.ts", "global.d.ts"],
"exclude": ["node_modules", "node_modules_linux"],
"exclude": ["node_modules", "node_modules_linux", "./deploy/tools/envs-validator"],
}
export type NextPublicEnvs = {
// network config
NEXT_PUBLIC_NETWORK_NAME: string;
NEXT_PUBLIC_NETWORK_SHORT_NAME?: string;
NEXT_PUBLIC_NETWORK_ID: string;
NEXT_PUBLIC_NETWORK_RPC_URL?: string;
NEXT_PUBLIC_NETWORK_CURRENCY_NAME?: string;
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL?: string;
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS?: string;
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS?: string;
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME?: string;
NEXT_PUBLIC_NETWORK_LOGO?: string;
NEXT_PUBLIC_NETWORK_LOGO_DARK?: string;
NEXT_PUBLIC_NETWORK_ICON?: string;
NEXT_PUBLIC_NETWORK_ICON_DARK?: string;
NEXT_PUBLIC_NETWORK_EXPLORERS?: string;
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE?: 'validation' | 'mining';
NEXT_PUBLIC_IS_TESTNET?: 'true' | '';
NEXT_PUBLIC_HAS_BEACON_CHAIN?: 'true' | '';
// UI config
NEXT_PUBLIC_FEATURED_NETWORKS?: string;
NEXT_PUBLIC_OTHER_LINKS?: string;
NEXT_PUBLIC_FOOTER_LINKS?: string;
NEXT_PUBLIC_API_SPEC_URL?: string;
NEXT_PUBLIC_GRAPHIQL_TRANSACTION?: string;
NEXT_PUBLIC_WEB3_DEFAULT_WALLET?: 'metamask' | 'coinbase';
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET?: 'true' | 'false';
NEXT_PUBLIC_HIDE_INDEXING_ALERT?: 'true' | 'false';
// Homepage config
NEXT_PUBLIC_HOMEPAGE_CHARTS?: string;
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR?: string;
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND?: string;
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER?: 'true' | 'false';
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME?: 'true' | 'false';
// Ads config
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP?: string;
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE?: string;
NEXT_PUBLIC_AD_BANNER_PROVIDER?: 'slise' | 'adbutler' | 'coinzilla' | 'none';
NEXT_PUBLIC_AD_TEXT_PROVIDER?: 'coinzilla' | 'none';
// App config
NEXT_PUBLIC_APP_INSTANCE?: string;
NEXT_PUBLIC_APP_PROTOCOL?: 'http' | 'https';
NEXT_PUBLIC_APP_HOST: string;
NEXT_PUBLIC_APP_PORT?: string;
NEXT_PUBLIC_APP_ENV?: string;
// API config
NEXT_PUBLIC_API_PROTOCOL?: 'http' | 'https';
NEXT_PUBLIC_API_HOST: string;
NEXT_PUBLIC_API_PORT?: string;
NEXT_PUBLIC_API_BASE_PATH?: string;
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL?: 'ws' | 'wss';
NEXT_PUBLIC_STATS_API_HOST?: string;
NEXT_PUBLIC_VISUALIZE_API_HOST?: string;
NEXT_PUBLIC_CONTRACT_INFO_API_HOST?: string;
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST?: string;
// external services config
NEXT_PUBLIC_SENTRY_DSN?: string;
SENTRY_CSP_REPORT_URI?: string;
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID?: string;
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY?: string;
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID?: string;
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN?: string;
// utilities
NEXT_PUBLIC_GIT_TAG?: string;
NEXT_PUBLIC_GIT_COMMIT_SHA?: string;
}
& NextPublicEnvsAccount
& NextPublicEnvsMarketplace
& NextPublicEnvsRollup;
type NextPublicEnvsAccount =
{
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED?: undefined;
NEXT_PUBLIC_AUTH_URL?: undefined;
NEXT_PUBLIC_LOGOUT_URL?: undefined;
NEXT_PUBLIC_AUTH0_CLIENT_ID?: undefined;
} |
{
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED: 'true';
NEXT_PUBLIC_AUTH_URL: string;
NEXT_PUBLIC_LOGOUT_URL: string;
NEXT_PUBLIC_AUTH0_CLIENT_ID: string;
}
type NextPublicEnvsMarketplace =
{
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: string;
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: string;
} |
{
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL?: undefined;
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM?: undefined;
}
type NextPublicEnvsRollup =
{
NEXT_PUBLIC_IS_L2_NETWORK: 'true';
NEXT_PUBLIC_L1_BASE_URL: string;
NEXT_PUBLIC_L2_WITHDRAWAL_URL: string;
} |
{
NEXT_PUBLIC_IS_L2_NETWORK?: undefined;
NEXT_PUBLIC_L1_BASE_URL?: undefined;
NEXT_PUBLIC_L2_WITHDRAWAL_URL?: undefined;
}
......@@ -23,7 +23,7 @@ const ContractMethodsAccordionItem = <T extends SmartContractMethod>({ data, ind
return '';
}
return config.baseUrl + route({
return config.app.baseUrl + route({
pathname: '/address/[hash]',
query: {
hash: addressHash ?? '',
......
......@@ -50,7 +50,7 @@ const GraphQL = () => {
// or the older one subscriptions-transport-ws
// so we (isstuev & vbaranov) decided to configure playground without subscriptions
// in case of any complaint consider reconfigure the graphql ws server with absinthe_graphql_ws package
// subscriptionUrl: `wss://${appConfig.host}/socket/`,
// subscriptionUrl: `wss://${appConfig.app.host}/socket/`,
});
return (
......
......@@ -47,7 +47,7 @@ export default function useMarketplace() {
const apiFetch = useApiFetch();
const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<MarketplaceAppOverview>>(
[ 'marketplace-apps' ],
async() => apiFetch(appConfig.marketplaceConfigUrl || ''),
async() => apiFetch(appConfig.marketplace.configUrl || ''),
{
select: (data) => (data as Array<MarketplaceAppOverview>).sort((a, b) => a.title.localeCompare(b.title)),
placeholderData: Array(9).fill(MARKETPLACE_APP),
......
......@@ -33,7 +33,7 @@ const Home = () => {
Welcome to { appConfig.network.name } explorer
</Heading>
<Box display={{ base: 'none', lg: 'block' }}>
{ appConfig.isAccountSupported && <ProfileMenuDesktop/> }
{ appConfig.account.isEnabled && <ProfileMenuDesktop/> }
</Box>
</Flex>
<LightMode>
......
......@@ -21,7 +21,7 @@ const Login = () => {
React.useEffect(() => {
const token = cookies.get(cookies.NAMES.API_TOKEN);
setFormVisibility(Boolean(!token && appConfig.isAccountSupported));
setFormVisibility(Boolean(!token && appConfig.account.isEnabled));
}, []);
const checkSentry = React.useCallback(() => {
......
......@@ -74,7 +74,7 @@ const Marketplace = () => {
/>
) }
{ config.marketplaceSubmitForm && (
{ config.marketplace.submitForm && (
<Skeleton
isLoaded={ !isPlaceholderData }
marginTop={{ base: 8, sm: 16 }}
......@@ -84,7 +84,7 @@ const Marketplace = () => {
fontWeight="bold"
display="inline-flex"
alignItems="baseline"
href={ config.marketplaceSubmitForm }
href={ config.marketplace.submitForm }
isExternal
>
<Icon
......
......@@ -32,7 +32,7 @@ const MarketplaceApp = () => {
const { isLoading, isError, error, data } = useQuery<unknown, ResourceError<unknown>, MarketplaceAppOverview>(
[ 'marketplace-apps', id ],
async() => {
const result = await apiFetch<Array<MarketplaceAppOverview>, unknown>(appConfig.marketplaceConfigUrl || '');
const result = await apiFetch<Array<MarketplaceAppOverview>, unknown>(appConfig.marketplace.configUrl || '');
if (!Array.isArray(result)) {
throw result;
}
......@@ -57,9 +57,9 @@ const MarketplaceApp = () => {
if (data && !isFrameLoading) {
const message = {
blockscoutColorMode: colorMode,
blockscoutRootUrl: appConfig.baseUrl + route({ pathname: '/' }),
blockscoutAddressExplorerUrl: appConfig.baseUrl + route({ pathname: '/address/[hash]', query: { hash: '' } }),
blockscoutTransactionExplorerUrl: appConfig.baseUrl + route({ pathname: '/tx/[hash]', query: { hash: '' } }),
blockscoutRootUrl: appConfig.app.baseUrl + route({ pathname: '/' }),
blockscoutAddressExplorerUrl: appConfig.app.baseUrl + route({ pathname: '/address/[hash]', query: { hash: '' } }),
blockscoutTransactionExplorerUrl: appConfig.app.baseUrl + route({ pathname: '/tx/[hash]', query: { hash: '' } }),
blockscoutNetworkName: appConfig.network.name,
blockscoutNetworkId: Number(appConfig.network.id),
blockscoutNetworkCurrency: appConfig.network.currency,
......
......@@ -37,7 +37,7 @@ const AddressActions = ({ isLoading }: Props) => {
</MenuButton>
</Skeleton>
<MenuList minWidth="180px" zIndex="popover">
{ isTokenPage && appConfig.contractInfoApi.endpoint && appConfig.adminServiceApi.endpoint && appConfig.isAccountSupported &&
{ isTokenPage && appConfig.contractInfoApi.endpoint && appConfig.adminServiceApi.endpoint && appConfig.account.isEnabled &&
<TokenInfoMenuItem py={ 2 } px={ 4 } hash={ hash } onBeforeClick={ isAccountActionAllowed }/> }
<PrivateTagMenuItem py={ 2 } px={ 4 } hash={ hash } onBeforeClick={ isAccountActionAllowed }/>
<PublicTagMenuItem py={ 2 } px={ 4 } hash={ hash } onBeforeClick={ isAccountActionAllowed }/>
......
......@@ -36,11 +36,11 @@ const AddressHeadingInfo = ({ address, token, isLinkDisabled, isLoading }: Props
/>
<CopyToClipboard text={ address.hash } isLoading={ isLoading }/>
{ !isLoading && address.is_contract && token && <AddressAddToWallet ml={ 2 } token={ token }/> }
{ !isLoading && !address.is_contract && appConfig.isAccountSupported && (
{ !isLoading && !address.is_contract && appConfig.account.isEnabled && (
<AddressFavoriteButton hash={ address.hash } watchListId={ address.watchlist_address_id } ml={ 3 }/>
) }
<AddressQrCode hash={ address.hash } ml={ 2 } isLoading={ isLoading } flexShrink={ 0 }/>
{ appConfig.isAccountSupported && <AddressActionsMenu isLoading={ isLoading }/> }
{ appConfig.account.isEnabled && <AddressActionsMenu isLoading={ isLoading }/> }
</Flex>
);
};
......
......@@ -28,7 +28,7 @@ const NetworkAddToWallet = ({ className }: Props) => {
decimals: appConfig.network.currency.decimals,
},
rpcUrls: [ appConfig.network.rpcUrl ],
blockExplorerUrls: [ appConfig.baseUrl ],
blockExplorerUrls: [ appConfig.app.baseUrl ],
} ],
// in wagmi types for wallet_addEthereumChain method is not provided
// eslint-disable-next-line @typescript-eslint/no-explicit-any
......
......@@ -34,7 +34,7 @@ const getConfig = () => {
blockExplorers: {
'default': {
name: 'Blockscout',
url: appConfig.baseUrl,
url: appConfig.app.baseUrl,
},
},
};
......
......@@ -23,7 +23,7 @@ import getApiVersionUrl from './utils/getApiVersionUrl';
const MAX_LINKS_COLUMNS = 3;
// const FRONT_VERSION_URL = `https://github.com/blockscout/frontend/tree/${ appConfig.frontendVersion }`;
// const FRONT_VERSION_URL = `https://github.com/blockscout/frontend/tree/${ appConfig.footer.frontendVersion }`;
const Footer = () => {
......@@ -71,9 +71,9 @@ const Footer = () => {
const { isLoading, data: linksData } = useQuery<unknown, ResourceError<unknown>, Array<CustomLinksGroup>>(
[ 'footer-links' ],
async() => fetch(appConfig.footerLinks || ''),
async() => fetch(appConfig.footer.links || ''),
{
enabled: Boolean(appConfig.footerLinks),
enabled: Boolean(appConfig.footer.links),
staleTime: Infinity,
});
......@@ -97,34 +97,34 @@ const Footer = () => {
Backend: <Link href={ apiVersionUrl } target="_blank">{ backendVersionData?.backend_version }</Link>
</Text>
) }
{ (appConfig.frontendVersion || appConfig.frontendCommit) && (
{ (appConfig.footer.frontendVersion || appConfig.footer.frontendCommit) && (
<Text fontSize="xs">
{ /* Frontend: <Link href={ FRONT_VERSION_URL } target="_blank">{ appConfig.frontendVersion }</Link> */ }
Frontend: { [ appConfig.frontendVersion, appConfig.frontendCommit ].filter(Boolean).join('+') }
{ /* Frontend: <Link href={ FRONT_VERSION_URL } target="_blank">{ appConfig.footer.frontendVersion }</Link> */ }
Frontend: { [ appConfig.footer.frontendVersion, appConfig.footer.frontendCommit ].filter(Boolean).join('+') }
</Text>
) }
</VStack>
</Box>
<Grid
gap={{ base: 6, lg: 12 }}
gridTemplateColumns={ appConfig.footerLinks ?
gridTemplateColumns={ appConfig.footer.links ?
{ 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> }
<Box minW="160px" w={ appConfig.footer.links ? '160px' : '100%' }>
{ appConfig.footer.links && <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' }}
gridTemplateColumns={ appConfig.footer.links ? '160px' : { base: 'repeat(auto-fill, 160px)', lg: 'repeat(3, 160px)' } }
gridTemplateRows={{ base: 'auto', lg: appConfig.footer.links ? 'auto' : 'repeat(2, auto)' }}
gridAutoFlow={{ base: 'row', lg: appConfig.footer.links ? 'row' : 'column' }}
mt={{ base: 0, lg: appConfig.footer.links ? 0 : '100px' }}
>
{ BLOCKSCOUT_LINKS.map(link => <FooterLinkItem { ...link } key={ link.text }/>) }
</Grid>
</Box>
{ appConfig.footerLinks && isLoading && (
{ appConfig.footer.links && isLoading && (
Array.from(Array(3)).map((i, index) => (
<Box minW="160px" key={ index }>
<Skeleton w="120px" h="20px" mb={ 6 }/>
......@@ -134,7 +134,7 @@ const Footer = () => {
</Box>
))
) }
{ appConfig.footerLinks && linksData && (
{ appConfig.footer.links && linksData && (
linksData.slice(0, MAX_LINKS_COLUMNS).map(linkGroup => (
<Box minW="160px" key={ linkGroup.title }>
<Text fontWeight={ 500 } mb={ 3 }>{ linkGroup.title }</Text>
......
......@@ -47,7 +47,7 @@ const Burger = () => {
{ appConfig.network.isTestnet && <Icon as={ testnetIcon } h="14px" w="auto" color="red.400" alignSelf="flex-start"/> }
<Flex alignItems="center" justifyContent="space-between">
<NetworkLogo onClick={ handleNetworkLogoClick }/>
{ appConfig.featuredNetworks ? (
{ appConfig.navigation.featuredNetworks ? (
<NetworkMenuButton
isMobile
isActive={ networkMenu.isOpen }
......
......@@ -43,7 +43,7 @@ const Header = ({ isHomePage, renderSearchBar }: Props) => {
>
<Burger/>
<NetworkLogo/>
{ appConfig.isAccountSupported ? <ProfileMenuMobile/> : <Box boxSize={ 10 }/> }
{ appConfig.account.isEnabled ? <ProfileMenuMobile/> : <Box boxSize={ 10 }/> }
</Flex>
{ !isHomePage && searchBar }
</Box>
......@@ -65,7 +65,7 @@ const Header = ({ isHomePage, renderSearchBar }: Props) => {
<Box width="100%">
{ searchBar }
</Box>
{ appConfig.isAccountSupported && <ProfileMenuDesktop/> }
{ appConfig.account.isEnabled && <ProfileMenuDesktop/> }
</HStack>
) }
</Box>
......
......@@ -76,7 +76,7 @@ const NavigationDesktop = () => {
transitionTimingFunction="ease"
>
<NetworkLogo isCollapsed={ isCollapsed }/>
{ Boolean(appConfig.featuredNetworks) && <NetworkMenu isCollapsed={ isCollapsed }/> }
{ Boolean(appConfig.navigation.featuredNetworks) && <NetworkMenu isCollapsed={ isCollapsed }/> }
</Box>
<Box as="nav" mt={ 8 } w="100%">
<VStack as="ul" spacing="1" alignItems="flex-start">
......
......@@ -16,9 +16,9 @@ export default function useNetworkMenu() {
const apiFetch = useApiFetch();
const { isLoading, data } = useQuery<unknown, ResourceError<unknown>, Array<FeaturedNetwork>>(
[ 'featured-network' ],
async() => apiFetch(appConfig.featuredNetworks || ''),
async() => apiFetch(appConfig.navigation.featuredNetworks || ''),
{
enabled: Boolean(appConfig.featuredNetworks) && isOpen,
enabled: Boolean(appConfig.navigation.featuredNetworks) && isOpen,
staleTime: Infinity,
});
......
......@@ -46,7 +46,7 @@ const ProfileMenuContent = ({ data }: Props) => {
</VStack>
</Box>
<Box mt={ 2 } pt={ 3 } borderTopColor="divider" borderTopWidth="1px" { ...getDefaultTransitionProps() }>
<Button size="sm" width="full" variant="outline" as="a" href={ appConfig.logoutUrl }>Sign Out</Button>
<Button size="sm" width="full" variant="outline" as="a" href={ appConfig.account.logoutUrl }>Sign Out</Button>
</Box>
</Box>
);
......
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