diff --git a/configs/app/api.ts b/configs/app/api.ts index 1f834eee17..0afe9a8aec 100644 --- a/configs/app/api.ts +++ b/configs/app/api.ts @@ -22,6 +22,8 @@ const socketEndpoint = [ const api = Object.freeze({ host: apiHost, + protocol: apiSchema, + port: apiPort, endpoint: apiEndpoint, socket: socketEndpoint, basePath: stripTrailingSlash(getEnvValue('NEXT_PUBLIC_API_BASE_PATH') || ''), diff --git a/configs/app/ui.ts b/configs/app/ui.ts index c9e13934d2..ecb35ca1ec 100644 --- a/configs/app/ui.ts +++ b/configs/app/ui.ts @@ -1,10 +1,25 @@ -import type { NavItemExternal } from 'types/client/navigation-items'; +import { NAVIGATION_LINK_IDS, type NavItemExternal, type NavigationLinkId } from 'types/client/navigation-items'; import type { ChainIndicatorId } from 'types/homepage'; import type { NetworkExplorer } from 'types/networks'; import * as views from './ui/views'; import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from './utils'; +const hiddenLinks = (() => { + const parsedValue = parseEnvJson>(getEnvValue('NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS')) || []; + + if (!Array.isArray(parsedValue)) { + return undefined; + } + + const result = NAVIGATION_LINK_IDS.reduce((result, item) => { + result[item] = parsedValue.includes(item); + return result; + }, {} as Record); + + return result; +})(); + // eslint-disable-next-line max-len const HOMEPAGE_PLATE_BACKGROUND_DEFAULT = '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)'; @@ -18,6 +33,7 @@ const UI = Object.freeze({ 'default': getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON'), dark: getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON_DARK'), }, + hiddenLinks, otherLinks: parseEnvJson>(getEnvValue('NEXT_PUBLIC_OTHER_LINKS')) || [], featuredNetworks: getExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS'), }, diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 8ce8d5eef6..cbda80fada 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -12,7 +12,8 @@ import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS } from '../../../types/client/adProviders'; import type { AdTextProviders, AdBannerProviders } from '../../../types/client/adProviders'; import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; -import type { NavItemExternal } from '../../../types/client/navigation-items'; +import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items'; +import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items'; import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token'; import type { WalletType } from '../../../types/client/wallets'; import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; @@ -354,6 +355,11 @@ const schema = yup .transform(replaceQuotes) .json() .of(navItemExternalSchema), + NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS: yup + .array() + .transform(replaceQuotes) + .json() + .of(yup.string().oneOf(NAVIGATION_LINK_IDS)), NEXT_PUBLIC_NETWORK_LOGO: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_LOGO_DARK: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_ICON: yup.string().test(urlTest), diff --git a/docs/ENVS.md b/docs/ENVS.md index df1ae1f7a5..a3a39f37f7 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -110,6 +110,7 @@ The app instance could be customized by passing following variables to NodeJS en | 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_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_NAVIGATION_HIDDEN_LINKS | `Array` | List of external links hidden in the navigation. Supported ids are `eth_rpc_api`, `rpc_api` | - | - | `['eth_rpc_api']` | #### Featured network configuration properties diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index 411087f5b1..46bcd5bca2 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -120,12 +120,12 @@ export default function useNavItems(): ReturnType { icon: graphQLIcon, isActive: pathname === '/graphiql', } : null, - { + !config.UI.sidebar.hiddenLinks?.rpc_api && { text: 'RPC API', icon: rpcIcon, url: 'https://docs.blockscout.com/for-users/api/rpc-endpoints', }, - { + !config.UI.sidebar.hiddenLinks?.eth_rpc_api && { text: 'Eth RPC API', icon: rpcIcon, url: ' https://docs.blockscout.com/for-users/api/eth-rpc', @@ -157,7 +157,7 @@ export default function useNavItems(): ReturnType { icon: statsIcon, isActive: pathname === '/stats', } : null, - { + apiNavItems.length > 0 && { text: 'API', icon: apiDocsIcon, isActive: apiNavItems.some(item => isInternalItem(item) && item.isActive), diff --git a/package.json b/package.json index f4340c2b5f..874cf7876c 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "lint:eslint": "eslint . --ext .js,.jsx,.ts,.tsx", "lint:eslint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", "lint:tsc": "tsc -p ./tsconfig.json", + "lint:envs-validator:dev": "cd ./deploy/tools/envs-validator && ./dev.sh", "prepare": "husky install", "format-svg": "svgo -r ./icons", "test:pw": "./tools/scripts/pw.sh", diff --git a/types/client/navigation-items.ts b/types/client/navigation-items.ts index 2d10cdee81..6393a1e957 100644 --- a/types/client/navigation-items.ts +++ b/types/client/navigation-items.ts @@ -28,3 +28,8 @@ export type NavGroupItem = NavItemCommon & { isActive?: boolean; subItems: Array | Array>; } + +import type { ArrayElement } from '../utils'; + +export const NAVIGATION_LINK_IDS = [ 'rpc_api', 'eth_rpc_api' ] as const; +export type NavigationLinkId = ArrayElement; diff --git a/ui/apiDocs/SwaggerUI.tsx b/ui/apiDocs/SwaggerUI.tsx index 6d1df168a8..13d645cda4 100644 --- a/ui/apiDocs/SwaggerUI.tsx +++ b/ui/apiDocs/SwaggerUI.tsx @@ -4,7 +4,8 @@ const SwaggerUIReact = dynamic(() => import('swagger-ui-react'), { ssr: false, }); -import { Box, useColorModeValue } from '@chakra-ui/react'; +import type { SystemStyleObject } from '@chakra-ui/react'; +import { Box, useColorModeValue, useToken } from '@chakra-ui/react'; import dynamic from 'next/dynamic'; import React from 'react'; @@ -28,12 +29,16 @@ const NeverShowInfoPlugin = () => { }; const SwaggerUI = () => { - const swaggerStyle = { - '.scheme-container, .opblock-tag': { + const mainColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800'); + const borderColor = useToken('colors', 'divider'); + const mainBgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200'); + + const swaggerStyle: SystemStyleObject = { + '.swagger-ui .scheme-container, .opblock-tag': { display: 'none', }, '.swagger-ui': { - color: useColorModeValue('blackAlpha.800', 'whiteAlpha.800'), + color: mainColor, }, '.swagger-ui .opblock-summary-control:focus': { outline: 'none', @@ -54,15 +59,70 @@ const SwaggerUI = () => { '.swagger-ui .wrapper': { padding: 0, }, + '.swagger-ui .prop-type': { + color: useColorModeValue('blue.600', 'blue.400'), + }, + '.swagger-ui .btn.try-out__btn': { + borderColor: useToken('colors', 'link'), + color: useToken('colors', 'link'), + borderRadius: 'sm', + }, + '.swagger-ui .btn.try-out__btn:hover': { + boxShadow: 'none', + borderColor: useToken('colors', 'link_hovered'), + color: useToken('colors', 'link_hovered'), + }, + '.swagger-ui .btn.try-out__btn.cancel': { + borderColor: useToken('colors', 'error'), + color: useToken('colors', 'error'), + }, + '.swagger-ui .btn.try-out__btn.cancel:hover': { + borderColor: useColorModeValue('red.600', 'red.500'), + color: useColorModeValue('red.500', 'red.400'), + }, + + // MODELS + '.swagger-ui section.models': { + borderColor: borderColor, + }, + '.swagger-ui section.models h4': { + color: mainColor, + }, + '.swagger-ui section.models .model-container': { + bgColor: mainBgColor, + }, + '.swagger-ui .model-title': { + wordBreak: 'break-all', + color: mainColor, + }, + '.swagger-ui .model': { + color: mainColor, + }, + '.swagger-ui .model-box-control:focus': { + outline: 'none', + }, + '.swagger-ui .model-toggle': { + bgColor: useColorModeValue('transparent', 'whiteAlpha.700'), + borderRadius: 'sm', + }, + '.swagger-ui .model .property.primitive': { + color: useToken('colors', 'text_secondary'), + wordBreak: 'break-all', + }, }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const reqInterceptor = React.useCallback((req: any) => { if (!req.loadSpec) { - req.url = req.url.replace(DEFAULT_SERVER, config.api.host); - const url = new URL(req.url); - url.protocol = 'https:'; - req.url = url.toString(); + const newUrl = new URL(req.url.replace(DEFAULT_SERVER, config.api.host)); + + newUrl.protocol = config.api.protocol + ':'; + + if (config.api.port) { + newUrl.port = config.api.port; + } + + req.url = newUrl.toString(); } return req; }, []);