diff --git a/configs/app/apis.ts b/configs/app/apis.ts index faba471128..60144e5a81 100644 --- a/configs/app/apis.ts +++ b/configs/app/apis.ts @@ -143,6 +143,17 @@ const tacApi = (() => { }); })(); +const userOpsApi = (() => { + const apiHost = getEnvValue('NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST'); + if (!apiHost) { + return; + } + + return Object.freeze({ + endpoint: apiHost, + }); +})(); + const visualizeApi = (() => { const apiHost = getEnvValue('NEXT_PUBLIC_VISUALIZE_API_HOST'); if (!apiHost) { @@ -169,6 +180,7 @@ const apis: Apis = Object.freeze({ rewards: rewardsApi, stats: statsApi, tac: tacApi, + userOps: userOpsApi, visualize: visualizeApi, }); diff --git a/configs/app/features/apiDocs.ts b/configs/app/features/apiDocs.ts new file mode 100644 index 0000000000..0f59de9f52 --- /dev/null +++ b/configs/app/features/apiDocs.ts @@ -0,0 +1,40 @@ +import type { Feature } from './types'; +import type { ApiDocsTabId } from 'types/views/apiDocs'; +import { API_DOCS_TABS } from 'types/views/apiDocs'; + +import { getEnvValue, parseEnvJson } from '../utils'; + +const graphqlDefaultTxnHash = getEnvValue('NEXT_PUBLIC_GRAPHIQL_TRANSACTION'); + +const tabs = (() => { + const value = (parseEnvJson>(getEnvValue('NEXT_PUBLIC_API_DOCS_TABS')) || API_DOCS_TABS) + .filter((tab) => API_DOCS_TABS.includes(tab)) + .filter((tab) => !graphqlDefaultTxnHash && tab === 'graphql_api' ? false : true); + + return value.length > 0 ? value : undefined; +})(); + +const title = 'API documentation'; + +const config: Feature<{ + tabs: Array; + coreApiSwaggerUrl: string; + graphqlDefaultTxnHash?: string; +}> = (() => { + if (tabs) { + return Object.freeze({ + title, + isEnabled: true, + tabs, + coreApiSwaggerUrl: getEnvValue('NEXT_PUBLIC_API_SPEC_URL') || `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml`, + graphqlDefaultTxnHash, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/graphqlApiDocs.ts b/configs/app/features/graphqlApiDocs.ts deleted file mode 100644 index d26c3bfde3..0000000000 --- a/configs/app/features/graphqlApiDocs.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Feature } from './types'; - -import { getEnvValue } from '../utils'; - -const defaultTxHash = getEnvValue('NEXT_PUBLIC_GRAPHIQL_TRANSACTION'); - -const title = 'GraphQL API documentation'; - -const config: Feature<{ defaultTxHash: string | undefined }> = (() => { - - if (defaultTxHash === 'none') { - return Object.freeze({ - title, - isEnabled: false, - }); - } - - return Object.freeze({ - title, - isEnabled: true, - defaultTxHash, - }); -})(); - -export default config; diff --git a/configs/app/features/index.ts b/configs/app/features/index.ts index 2d5899877a..9f3d936821 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -5,6 +5,7 @@ export { default as addressMetadata } from './addressMetadata'; export { default as address3rdPartyWidgets } from './address3rdPartyWidgets'; export { default as adsBanner } from './adsBanner'; export { default as adsText } from './adsText'; +export { default as apiDocs } from './apiDocs'; export { default as beaconChain } from './beaconChain'; export { default as bridgedTokens } from './bridgedTokens'; export { default as blockchainInteraction } from './blockchainInteraction'; @@ -19,7 +20,6 @@ export { default as faultProofSystem } from './faultProofSystem'; export { default as gasTracker } from './gasTracker'; export { default as getGasButton } from './getGasButton'; export { default as googleAnalytics } from './googleAnalytics'; -export { default as graphqlApiDocs } from './graphqlApiDocs'; export { default as growthBook } from './growthBook'; export { default as marketplace } from './marketplace'; export { default as metasuites } from './metasuites'; @@ -30,7 +30,6 @@ export { default as nameService } from './nameService'; export { default as opSuperchain } from './opSuperchain'; export { default as pools } from './pools'; export { default as publicTagsSubmission } from './publicTagsSubmission'; -export { default as restApiDocs } from './restApiDocs'; export { default as rewards } from './rewards'; export { default as rollbar } from './rollbar'; export { default as rollup } from './rollup'; diff --git a/configs/app/features/restApiDocs.ts b/configs/app/features/restApiDocs.ts deleted file mode 100644 index ae25f05c0a..0000000000 --- a/configs/app/features/restApiDocs.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Feature } from './types'; - -import { getEnvValue } from '../utils'; - -const DEFAULT_URL = `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml`; -const envValue = getEnvValue('NEXT_PUBLIC_API_SPEC_URL'); - -const title = 'REST API documentation'; - -const config: Feature<{ specUrl: string }> = (() => { - if (envValue === 'none') { - return Object.freeze({ - title, - isEnabled: false, - }); - } - - return Object.freeze({ - title, - isEnabled: true, - specUrl: envValue || DEFAULT_URL, - }); -})(); - -export default config; diff --git a/configs/app/ui.ts b/configs/app/ui.ts index ba0d3ad362..e27a92d518 100644 --- a/configs/app/ui.ts +++ b/configs/app/ui.ts @@ -1,5 +1,5 @@ import type { ContractCodeIde } from 'types/client/contract'; -import { NAVIGATION_LINK_IDS, type NavItemExternal, type NavigationLinkId, type NavigationLayout } from 'types/client/navigation'; +import { type NavItemExternal, type NavigationLayout } from 'types/client/navigation'; import { HOME_STATS_WIDGET_IDS, type ChainIndicatorId, type HeroBannerConfig, type HomeStatsWidgetId } from 'types/homepage'; import type { NetworkExplorer } from 'types/networks'; import type { ColorThemeId } from 'types/settings'; @@ -11,21 +11,6 @@ import * as features from './features'; 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; -})(); - const homePageStats: Array = (() => { const parsedValue = parseEnvJson>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_STATS')); @@ -43,7 +28,7 @@ const homePageStats: Array = (() => { })(); const highlightedRoutes = (() => { - const parsedValue = parseEnvJson>(getEnvValue('NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES')); + const parsedValue = parseEnvJson>(getEnvValue('NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES')); return Array.isArray(parsedValue) ? parsedValue : []; })(); @@ -62,7 +47,6 @@ const UI = Object.freeze({ 'default': getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON'), dark: getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON_DARK'), }, - hiddenLinks, highlightedRoutes, otherLinks: parseEnvJson>(getEnvValue('NEXT_PUBLIC_OTHER_LINKS')) || [], featuredNetworks: getExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS'), diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 7053d72c90..683e5296d6 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -9,6 +9,8 @@ NEXT_PUBLIC_APP_PORT=3000 NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws +NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST=https://user-ops-indexer-base-mainnet.k8s-prod-2.blockscout.com + # Instance ENVs NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ diff --git a/configs/envs/.env.pw b/configs/envs/.env.pw index 65457a3352..5b996815f3 100644 --- a/configs/envs/.env.pw +++ b/configs/envs/.env.pw @@ -54,6 +54,7 @@ NEXT_PUBLIC_METADATA_SERVICE_API_HOST=http://localhost:3007 NEXT_PUBLIC_NAME_SERVICE_API_HOST=http://localhost:3008 NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=http://localhost:3009 NEXT_PUBLIC_TAC_OPERATION_LIFECYCLE_API_HOST=http://localhost:3100 +NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST=http://localhost:3110 NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32'] diff --git a/configs/envs/.env.rari_testnet b/configs/envs/.env.rari_testnet index 8d64dd5e52..cb360d121e 100644 --- a/configs/envs/.env.rari_testnet +++ b/configs/envs/.env.rari_testnet @@ -22,7 +22,6 @@ NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=radial-gradient(farthest-corner at 0% 0%, rgba(183, 148, 244, 0.80) 0%, rgba(0, 163, 196, 0.80) 100%) NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255,255,255) NEXT_PUBLIC_IS_TESTNET=true -NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS=[] NEXT_PUBLIC_NAVIGATION_LAYOUT=vertical NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 59b77212fa..d1005eca1f 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -20,8 +20,9 @@ import { GAS_UNITS } from '../../../types/client/gasTracker'; import type { GasUnit } from '../../../types/client/gasTracker'; import type { MarketplaceAppOverview, MarketplaceAppSecurityReportRaw, MarketplaceAppSecurityReport } from '../../../types/client/marketplace'; import type { MultichainProviderConfig } from '../../../types/client/multichainProviderConfig'; -import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation'; -import type { NavItemExternal, NavigationLinkId, NavigationLayout } from '../../../types/client/navigation'; +import type { ApiDocsTabId } from '../../../types/views/apiDocs'; +import { API_DOCS_TABS } from '../../../types/views/apiDocs'; +import type { NavItemExternal, NavigationLayout } from '../../../types/client/navigation'; import { ROLLUP_TYPES } from '../../../types/client/rollup'; import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token'; import { PROVIDERS as TX_INTERPRETATION_PROVIDERS } from '../../../types/client/txInterpretation'; @@ -450,6 +451,35 @@ const celoSchema = yup NEXT_PUBLIC_CELO_ENABLED: yup.boolean(), }); +const apiDocsScheme = yup + .object() + .shape({ + NEXT_PUBLIC_API_DOCS_TABS: yup.array() + .transform(replaceQuotes) + .json() + .of(yup.string().oneOf(API_DOCS_TABS)), + NEXT_PUBLIC_API_SPEC_URL: yup + .string() + .test(urlTest), + NEXT_PUBLIC_GRAPHIQL_TRANSACTION: yup + .string() + .matches(regexp.HEX_REGEXP), + }); + +const userOpsSchema = yup + .object() + .shape({ + NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(), + NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST: yup + .string() + .test(urlTest) + .when('NEXT_PUBLIC_HAS_USER_OPS', { + is: (value: boolean) => value, + then: (schema) => schema, + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST can only be used if NEXT_PUBLIC_HAS_USER_OPS is set to \'true\''), + }), + }); + const adButlerConfigSchema = yup .object() .transform(replaceQuotes) @@ -870,11 +900,6 @@ 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_NAVIGATION_HIGHLIGHTED_ROUTES: yup .array() .transform(replaceQuotes) @@ -988,14 +1013,6 @@ const schema = yup NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED: yup.boolean(), // 5. Features configuration - NEXT_PUBLIC_API_SPEC_URL: yup - .mixed() - .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_API_SPEC_URL, it should be either URL-string or "none" string literal', (data) => { - const isNoneSchema = yup.string().oneOf([ 'none' ]); - const isUrlStringSchema = yup.string().test(urlTest); - - return isNoneSchema.isValidSync(data) || isUrlStringSchema.isValidSync(data); - }), NEXT_PUBLIC_STATS_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_STATS_API_BASE_PATH: yup.string(), NEXT_PUBLIC_VISUALIZE_API_HOST: yup.string().test(urlTest), @@ -1003,14 +1020,6 @@ const schema = yup NEXT_PUBLIC_CONTRACT_INFO_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_NAME_SERVICE_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: yup.string().test(urlTest), - NEXT_PUBLIC_GRAPHIQL_TRANSACTION: yup - .mixed() - .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_GRAPHIQL_TRANSACTION, it should be either Hex-string or "none" string literal', (data) => { - const isNoneSchema = yup.string().oneOf([ 'none' ]); - const isHashStringSchema = yup.string().matches(regexp.HEX_REGEXP); - - return isNoneSchema.isValidSync(data) || isHashStringSchema.isValidSync(data); - }), NEXT_PUBLIC_WEB3_WALLETS: yup .mixed() .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_WEB3_WALLETS, it should be either array or "none" string literal', (data) => { @@ -1033,7 +1042,6 @@ const schema = yup NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED: yup.boolean(), NEXT_PUBLIC_SAFE_TX_SERVICE_URL: yup.string().test(urlTest), NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(), - NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(), NEXT_PUBLIC_METASUITES_ENABLED: yup.boolean(), NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG: yup .array() @@ -1156,8 +1164,10 @@ const schema = yup .concat(beaconChainSchema) .concat(bridgedTokensSchema) .concat(sentrySchema) + .concat(apiDocsScheme) .concat(tacSchema) .concat(address3rdPartyWidgetsConfigSchema) - .concat(addressMetadataSchema); + .concat(addressMetadataSchema) + .concat(userOpsSchema); export default schema; diff --git a/deploy/tools/envs-validator/test/.env.alt b/deploy/tools/envs-validator/test/.env.alt index 7141adb60b..be2db5a7bd 100644 --- a/deploy/tools/envs-validator/test/.env.alt +++ b/deploy/tools/envs-validator/test/.env.alt @@ -1,5 +1,4 @@ -NEXT_PUBLIC_GRAPHIQL_TRANSACTION=none -NEXT_PUBLIC_API_SPEC_URL=none +NEXT_PUBLIC_API_DOCS_TABS=[] NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none NEXT_PUBLIC_HOMEPAGE_STATS=[] NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32'] @@ -8,4 +7,6 @@ NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated NEXT_PUBLIC_NETWORK_RPC_URL=['https://example.com','https://example2.com'] NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://example.com -NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED=false \ No newline at end of file +NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED=false +NEXT_PUBLIC_HAS_USER_OPS=true +NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST=https://example.com \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/.env.base b/deploy/tools/envs-validator/test/.env.base index 40f748bcb4..04db4d54b2 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -46,7 +46,6 @@ NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE='Hello' NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://example.com NEXT_PUBLIC_METASUITES_ENABLED=true -NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS=['eth_rpc_api','rpc_api'] NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH diff --git a/docs/DEPRECATED_ENVS.md b/docs/DEPRECATED_ENVS.md index 54b0c4ce72..1e97e271e3 100644 --- a/docs/DEPRECATED_ENVS.md +++ b/docs/DEPRECATED_ENVS.md @@ -13,3 +13,4 @@ | NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | v1.24.0 | v1.31.0 | Replaced by NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | | NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` | v1.0.x+ | v1.35.0 | Replaced by NEXT_PUBLIC_HOMEPAGE_STATS | | NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK | `number` | Indicates the block number when the Celo-type chain transitioned to L2. This is used to display links to the Epoch block page from a regular block page. | - | - | `26369280` | v1.37.0+ | v2.2.0 | Removed; configuration done on the API side | +| 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']` | v1.16.0+ | v2.3.0 | Use NEXT_PUBLIC_API_DOCS_TABS instead to hide tabs on the API docs page. | diff --git a/docs/ENVS.md b/docs/ENVS.md index c2c2012a58..872667cf07 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -47,7 +47,7 @@ All json-like values should be single-quoted. If it contains a hash (`#`) or a d - [Mixpanel analytics](#mixpanel-analytics) - [GrowthBook feature flagging and A/B testing](#growthbook-feature-flagging-and-ab-testing) - [GraphQL API documentation](#graphql-api-documentation) - - [REST API documentation](#rest-api-documentation) + - [API documentation](#api-documentation) - [Marketplace](#marketplace) - [Solidity to UML diagrams](#solidity-to-uml-diagrams) - [Blockchain statistics](#blockchain-statistics) @@ -163,7 +163,6 @@ _Note_ Here, all values are arrays of up to two strings. The first string repres | 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` | v1.0.x+ | | NEXT_PUBLIC_FEATURED_NETWORKS | `string` | URL of configuration file (`.json` format only) or file content string representation. It 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` \| `[{'title':'Astar(EVM)','url':'https://astar.blockscout.com/','group':'Mainnets','icon':'https://example.com/astar.svg'}]` | v1.0.x+ | | NEXT_PUBLIC_OTHER_LINKS | `Array<{url: string; text: string}>` | List of links for the "Other" navigation menu | - | - | `[{'url':'https://blockscout.com','text':'Blockscout'}]` | v1.0.x+ | -| 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']` | v1.16.0+ | | NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES | `Array` | List of menu item routes that should have a lightning label | - | - | `['/accounts']` | v1.31.0+ | | NEXT_PUBLIC_NAVIGATION_LAYOUT | `vertical \| horizontal` | Navigation menu layout type | - | `vertical` | `horizontal` | v1.32.0+ | @@ -460,6 +459,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi | Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | --- | --- | --- | --- | --- | --- | --- | | NEXT_PUBLIC_HAS_USER_OPS | `boolean` | Set to true to show user operations related data and pages | - | - | `true` | v1.23.0+ | +| NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST | `boolean` | The user operations indexer API host; pass to show API documentation for the service | - | - | `true` | v2.3.0+ |   @@ -527,23 +527,13 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi   -### GraphQL API documentation - -This feature is **always enabled**, but you can disable it by passing `none` value to `NEXT_PUBLIC_GRAPHIQL_TRANSACTION` variable. - -| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | -| --- | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_GRAPHIQL_TRANSACTION | `string` | Txn hash for default query at GraphQl playground page. Pass `none` to disable the feature. | - | - | `0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62` | v1.0.x+ | - -  - -### REST API documentation - -This feature is **always enabled**, but you can disable it by passing `none` value to `NEXT_PUBLIC_API_SPEC_URL` variable. +### API documentation | Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | --- | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_API_SPEC_URL | `string` | Spec to be displayed on `/api-docs` page. Pass `none` to disable the feature. | - | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` | v1.0.x+ | +| NEXT_PUBLIC_API_DOCS_TABS | `Array` | Controls which tabs appear on the API documentation page. Possible values for `TabId` are `rest_api`, `eth_rpc_api`, `rpc_api`, and `graphql_api`. **Note** that this variable has a default value, so the feature is enabled by default. Pass an empty array to disable it. | - | `['rest_api','eth_rpc_api','rpc_api','graphql_api']` | `[]` | v2.3.x+ | +| NEXT_PUBLIC_API_SPEC_URL | `string` | Spec of Blockscout core API to be displayed on the page. | - | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` | v1.0.x+ | +| NEXT_PUBLIC_GRAPHIQL_TRANSACTION | `string` | Txn hash for default query at GraphQl API. | - | - | `0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62` | v1.0.x+ |   diff --git a/icons/graphQL.svg b/icons/graphQL.svg deleted file mode 100644 index 9332276ac2..0000000000 --- a/icons/graphQL.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lib/api/resources.ts b/lib/api/resources.ts index 9c70ad9797..e8071de1a7 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -22,6 +22,7 @@ import type { TacOperationLifecycleApiResourceName, TacOperationLifecycleApiResourcePayload, } from './services/tac-operation-lifecycle'; +import { USER_OPS_API_RESOURCES } from './services/userOps'; import type { IsPaginated } from './services/utils'; import { VISUALIZE_API_RESOURCES } from './services/visualize'; import type { VisualizeApiResourceName, VisualizeApiResourcePayload } from './services/visualize'; @@ -36,6 +37,7 @@ export const RESOURCES = { rewards: REWARDS_API_RESOURCES, stats: STATS_API_RESOURCES, tac: TAC_OPERATION_LIFECYCLE_API_RESOURCES, + userOps: USER_OPS_API_RESOURCES, visualize: VISUALIZE_API_RESOURCES, } satisfies Record>; diff --git a/lib/api/services/userOps.ts b/lib/api/services/userOps.ts new file mode 100644 index 0000000000..6e10b1aafd --- /dev/null +++ b/lib/api/services/userOps.ts @@ -0,0 +1,8 @@ +import type { ApiResource } from '../types'; + +export const USER_OPS_API_RESOURCES = { +} satisfies Record; + +export type UserOpsApiResourceName = `userOps:${ keyof typeof USER_OPS_API_RESOURCES }`; + +export type UserOpsApiResourcePayload = never; diff --git a/lib/api/types.ts b/lib/api/types.ts index 1933fe8c85..dcee835b85 100644 --- a/lib/api/types.ts +++ b/lib/api/types.ts @@ -1,4 +1,4 @@ -export type ApiName = 'general' | 'admin' | 'bens' | 'contractInfo' | 'metadata' | 'multichain' | 'rewards' | 'stats' | 'tac' | 'visualize'; +export type ApiName = 'general' | 'admin' | 'bens' | 'contractInfo' | 'metadata' | 'multichain' | 'rewards' | 'stats' | 'tac' | 'userOps' | 'visualize'; export interface ApiResource { path: string; diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index b98c784ca0..092866f818 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -237,30 +237,12 @@ export default function useNavItems(): ReturnType { }, ].filter(Boolean); - const apiNavItems: Array = [ - config.features.restApiDocs.isEnabled ? { - text: 'REST API', - nextRoute: { pathname: '/api-docs' as const }, - icon: 'restAPI', - isActive: pathname === '/api-docs', - } : null, - config.features.graphqlApiDocs.isEnabled ? { - text: 'GraphQL', - nextRoute: { pathname: '/graphiql' as const }, - icon: 'graphQL', - isActive: pathname === '/graphiql', - } : null, - !config.UI.navigation.hiddenLinks?.rpc_api && { - text: 'RPC API', - icon: 'RPC', - url: 'https://docs.blockscout.com/for-users/api/rpc-endpoints', - }, - !config.UI.navigation.hiddenLinks?.eth_rpc_api && { - text: 'Eth RPC API', - icon: 'RPC', - url: ' https://docs.blockscout.com/for-users/api/eth-rpc', - }, - ].filter(Boolean); + const apiNavItem: NavItem | null = config.features.apiDocs.isEnabled ? { + text: 'API', + nextRoute: { pathname: '/api-docs' as const }, + icon: 'restAPI', + isActive: pathname.startsWith('/api-docs'), + } : null; const otherNavItems: Array | Array> = [ { @@ -311,12 +293,7 @@ export default function useNavItems(): ReturnType { icon: 'stats', isActive: pathname.startsWith('/stats'), } : null, - apiNavItems.length > 0 && { - text: 'API', - icon: 'restAPI', - isActive: apiNavItems.some(item => isInternalItem(item) && item.isActive), - subItems: apiNavItems, - }, + apiNavItem, { text: 'Other', icon: 'gear', diff --git a/lib/metadata/getCanonicalUrl.ts b/lib/metadata/getCanonicalUrl.ts index 2a868419a8..9fcaef9c69 100644 --- a/lib/metadata/getCanonicalUrl.ts +++ b/lib/metadata/getCanonicalUrl.ts @@ -12,7 +12,6 @@ const CANONICAL_ROUTES: Array = [ '/tokens', '/stats', '/api-docs', - '/graphiql', '/gas-tracker', '/apps', ]; diff --git a/lib/metadata/getPageOgType.ts b/lib/metadata/getPageOgType.ts index c8b7a0c116..6273f7a497 100644 --- a/lib/metadata/getPageOgType.ts +++ b/lib/metadata/getPageOgType.ts @@ -26,7 +26,6 @@ const OG_TYPE_DICT: Record = { '/stats': 'Root page', '/stats/[id]': 'Regular page', '/api-docs': 'Regular page', - '/graphiql': 'Regular page', '/search-results': 'Regular page', '/auth/profile': 'Root page', '/account/merits': 'Regular page', diff --git a/lib/metadata/templates/description.ts b/lib/metadata/templates/description.ts index efac0a116d..b2f8538ad9 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -29,7 +29,6 @@ const TEMPLATE_MAP: Record = { '/stats': DEFAULT_TEMPLATE, '/stats/[id]': DEFAULT_TEMPLATE, '/api-docs': DEFAULT_TEMPLATE, - '/graphiql': DEFAULT_TEMPLATE, '/search-results': DEFAULT_TEMPLATE, '/auth/profile': DEFAULT_TEMPLATE, '/account/merits': DEFAULT_TEMPLATE, diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index 255384619f..30487ce539 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -26,7 +26,6 @@ const TEMPLATE_MAP: Record = { '/stats': '%network_name% stats - %network_name% network insights', '/stats/[id]': '%network_name% stats - %id% chart', '/api-docs': '%network_name% API docs - %network_name% developer tools', - '/graphiql': 'GraphQL for %network_name% - %network_name% data query', '/search-results': '%network_name% search result for %q%', '/auth/profile': '%network_name% - my profile', '/account/merits': '%network_name% - Merits', diff --git a/lib/mixpanel/getPageType.ts b/lib/mixpanel/getPageType.ts index 49818832e3..3a7312e768 100644 --- a/lib/mixpanel/getPageType.ts +++ b/lib/mixpanel/getPageType.ts @@ -24,7 +24,6 @@ export const PAGE_TYPE_DICT: Record = { '/stats': 'Stats', '/stats/[id]': 'Stats chart', '/api-docs': 'REST API', - '/graphiql': 'GraphQL', '/search-results': 'Search results', '/auth/profile': 'Profile', '/account/merits': 'Merits', diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index 977a5a8e66..f663c3528a 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -193,17 +193,7 @@ Promise>> => { }; export const apiDocs: GetServerSideProps = async(context) => { - if (!config.features.restApiDocs.isEnabled) { - return { - notFound: true, - }; - } - - return base(context); -}; - -export const graphIQl: GetServerSideProps = async(context) => { - if (!config.features.graphqlApiDocs.isEnabled) { + if (!config.features.apiDocs.isEnabled) { return { notFound: true, }; diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index 7f6754ba52..36a777555e 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -50,7 +50,6 @@ declare module "nextjs-routes" { | DynamicRoute<"/epochs/[number]", { "number": string }> | StaticRoute<"/epochs"> | StaticRoute<"/gas-tracker"> - | StaticRoute<"/graphiql"> | StaticRoute<"/"> | StaticRoute<"/internal-txs"> | StaticRoute<"/interop-messages"> diff --git a/nextjs/redirects.js b/nextjs/redirects.js index 733f28e100..5fd13b4af1 100644 --- a/nextjs/redirects.js +++ b/nextjs/redirects.js @@ -342,10 +342,19 @@ const ETHERSCAN_URLS = [ }, ]; +const DEPRECATED_ROUTES = [ + { + source: '/graphiql', + destination: '/api-docs?tab=graphql_api', + permanent: false, + }, +]; + async function redirects() { return [ ...OLD_UI_URLS.map((item) => ({ ...item, permanent: false })), ...ETHERSCAN_URLS.map((item) => ({ ...item, permanent: true })), + ...DEPRECATED_ROUTES, ]; } diff --git a/pages/api-docs.tsx b/pages/api-docs.tsx index 64148d64e2..9a71d65d5f 100644 --- a/pages/api-docs.tsx +++ b/pages/api-docs.tsx @@ -1,19 +1,15 @@ import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; import React from 'react'; import PageNextJs from 'nextjs/PageNextJs'; -import config from 'configs/app'; -import SwaggerUI from 'ui/apiDocs/SwaggerUI'; -import PageTitle from 'ui/shared/Page/PageTitle'; +const ApiDocs = dynamic(() => import('ui/pages/ApiDocs'), { ssr: false }); const Page: NextPage = () => { return ( - - + ); }; diff --git a/pages/graphiql.tsx b/pages/graphiql.tsx deleted file mode 100644 index 8bcff9cf93..0000000000 --- a/pages/graphiql.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { NextPage } from 'next'; -import dynamic from 'next/dynamic'; -import React from 'react'; - -import PageNextJs from 'nextjs/PageNextJs'; - -import config from 'configs/app'; -import ContentLoader from 'ui/shared/ContentLoader'; -import PageTitle from 'ui/shared/Page/PageTitle'; - -const GraphQL = dynamic(() => import('ui/graphQL/GraphQL'), { - loading: () => , - ssr: false, -}); - -const Page: NextPage = () => { - - return ( - - - - - ); -}; - -export default Page; - -export { graphIQl as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/playwright/fixtures/mockEnvs.ts b/playwright/fixtures/mockEnvs.ts index 6b3020c4e4..9e16a0c48a 100644 --- a/playwright/fixtures/mockEnvs.ts +++ b/playwright/fixtures/mockEnvs.ts @@ -53,6 +53,7 @@ export const ENVS_MAP: Record> = { ], userOps: [ [ 'NEXT_PUBLIC_HAS_USER_OPS', 'true' ], + [ 'NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST', 'http://localhost:3110' ], ], hasContractAuditReports: [ [ 'NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS', 'true' ], diff --git a/public/icons/name.d.ts b/public/icons/name.d.ts index 461b06acd9..da0700a74e 100644 --- a/public/icons/name.d.ts +++ b/public/icons/name.d.ts @@ -80,7 +80,6 @@ | "gear" | "globe-b" | "globe" - | "graphQL" | "heart_filled" | "heart_outline" | "hourglass_slim" diff --git a/toolkit/chakra/accordion.tsx b/toolkit/chakra/accordion.tsx index 333aa6ee66..2922d941b1 100644 --- a/toolkit/chakra/accordion.tsx +++ b/toolkit/chakra/accordion.tsx @@ -1,5 +1,6 @@ import { Accordion, Icon } from '@chakra-ui/react'; import * as React from 'react'; +import { scroller } from 'react-scroll'; import IndicatorIcon from 'icons/arrows/east-mini.svg'; @@ -89,3 +90,37 @@ export const AccordionRoot = (props: Accordion.RootProps) => { }; export const AccordionItem = Accordion.Item; + +export function useAccordion(items: Array<{ id: string }>) { + const [ value, setValue ] = React.useState>([]); + + const onValueChange = React.useCallback(({ value }: { value: Array }) => { + setValue(value); + }, []); + + const scrollToItemFromUrl = React.useCallback(() => { + const hash = window.location.hash.replace('#', ''); + + if (!hash) { + return; + } + + const itemToScroll = items.find((item) => item.id === hash); + if (itemToScroll) { + scroller.scrollTo(itemToScroll.id, { + duration: 500, + smooth: true, + offset: -100, + }); + setValue([ itemToScroll.id ]); + } + }, [ items ]); + + return React.useMemo(() => { + return { + value, + onValueChange, + scrollToItemFromUrl, + }; + }, [ value, onValueChange, scrollToItemFromUrl ]); +} diff --git a/types/client/navigation.ts b/types/client/navigation.ts index 5085615267..a503da8661 100644 --- a/types/client/navigation.ts +++ b/types/client/navigation.ts @@ -31,9 +31,4 @@ export type NavGroupItem = NavItemCommon & { subItems: Array | Array>; }; -import type { ArrayElement } from '../utils'; - -export const NAVIGATION_LINK_IDS = [ 'rpc_api', 'eth_rpc_api' ] as const; -export type NavigationLinkId = ArrayElement; - export type NavigationLayout = 'vertical' | 'horizontal'; diff --git a/types/views/apiDocs.ts b/types/views/apiDocs.ts new file mode 100644 index 0000000000..e5f4cd2bb9 --- /dev/null +++ b/types/views/apiDocs.ts @@ -0,0 +1,8 @@ +export const API_DOCS_TABS = [ + 'rest_api', + 'eth_rpc_api', + 'rpc_api', + 'graphql_api', +] as const; + +export type ApiDocsTabId = typeof API_DOCS_TABS[ number ]; diff --git a/ui/apiDocs/EthRpcApi.tsx b/ui/apiDocs/EthRpcApi.tsx new file mode 100644 index 0000000000..199f5687e1 --- /dev/null +++ b/ui/apiDocs/EthRpcApi.tsx @@ -0,0 +1,19 @@ +import { Box, Text } from '@chakra-ui/react'; +import React from 'react'; + +import { Link } from 'toolkit/chakra/link'; + +const EthRpcApi = () => { + return ( + + + In addition to the custom RPC endpoints documented here, + the Blockscout ETH RPC API supports 3 methods in the exact format specified for Ethereum nodes, + ee the Ethereum JSON-RPC Specification for more details. + + View examples + + ); +}; + +export default React.memo(EthRpcApi); diff --git a/ui/graphQL/GraphQL.tsx b/ui/apiDocs/GraphQL.tsx similarity index 92% rename from ui/graphQL/GraphQL.tsx rename to ui/apiDocs/GraphQL.tsx index 1a04d094ce..2cb7080ebc 100644 --- a/ui/graphQL/GraphQL.tsx +++ b/ui/apiDocs/GraphQL.tsx @@ -9,7 +9,7 @@ import 'graphiql/graphiql.css'; import { useColorMode } from 'toolkit/chakra/color-mode'; import { isBrowser } from 'toolkit/utils/isBrowser'; -const feature = config.features.graphqlApiDocs; +const feature = config.features.apiDocs; const graphQLStyle = { '.graphiql-container': { @@ -35,13 +35,13 @@ const GraphQL = () => { } }, [ colorMode, graphqlTheme ]); - if (!feature.isEnabled) { + if (!feature.isEnabled || !feature.graphqlDefaultTxnHash) { return null; } const initialQuery = `{ transaction( - hash: "${ feature.defaultTxHash }" + hash: "${ feature.graphqlDefaultTxnHash }" ) { hash blockNumber diff --git a/ui/apiDocs/RestApi.tsx b/ui/apiDocs/RestApi.tsx new file mode 100644 index 0000000000..4ed6099389 --- /dev/null +++ b/ui/apiDocs/RestApi.tsx @@ -0,0 +1,52 @@ +import React from 'react'; + +import { route } from 'nextjs-routes'; + +import config from 'configs/app'; +import { AccordionItem, AccordionItemContent, AccordionItemTrigger, AccordionRoot, useAccordion } from 'toolkit/chakra/accordion'; +import CopyToClipboard from 'ui/shared/CopyToClipboard'; + +import SwaggerUI from './SwaggerUI'; +import { REST_API_SECTIONS } from './utils'; + +const RestApi = () => { + const { value, onValueChange, scrollToItemFromUrl } = useAccordion(REST_API_SECTIONS); + + React.useEffect(() => { + scrollToItemFromUrl(); + // runs only on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ ]); + + if (REST_API_SECTIONS.length === 0) { + return null; + } + + if (REST_API_SECTIONS.length === 1) { + return ; + } + + return ( + + { REST_API_SECTIONS.map((section, index) => ( + + + + { section.title } + + + + + + )) } + + ); +}; + +export default React.memo(RestApi); diff --git a/ui/apiDocs/RpcApi.tsx b/ui/apiDocs/RpcApi.tsx new file mode 100644 index 0000000000..f681693693 --- /dev/null +++ b/ui/apiDocs/RpcApi.tsx @@ -0,0 +1,18 @@ +import { Box, Text } from '@chakra-ui/react'; +import React from 'react'; + +import { Link } from 'toolkit/chakra/link'; + +const RpcApi = () => { + return ( + + + This API is provided for developers transitioning applications from Etherscan to BlockScout and applications requiring general API and data support. + It supports GET and POST requests. + + View modules + + ); +}; + +export default React.memo(RpcApi); diff --git a/ui/apiDocs/SwaggerUI.tsx b/ui/apiDocs/SwaggerUI.tsx index c71e26f677..0ad0dd9b66 100644 --- a/ui/apiDocs/SwaggerUI.tsx +++ b/ui/apiDocs/SwaggerUI.tsx @@ -9,15 +9,12 @@ import { Box, useToken } from '@chakra-ui/react'; import dynamic from 'next/dynamic'; import React from 'react'; -import config from 'configs/app'; +import type { SwaggerRequest } from './types'; + import ContentLoader from 'ui/shared/ContentLoader'; import 'swagger-ui-react/swagger-ui.css'; -const feature = config.features.restApiDocs; - -const DEFAULT_SERVER = 'blockscout.com/poa/core'; - const NeverShowInfoPlugin = () => { return { components: { @@ -28,7 +25,12 @@ const NeverShowInfoPlugin = () => { }; }; -const SwaggerUI = () => { +interface Props { + url: string; + requestInterceptor?: (request: SwaggerRequest) => SwaggerRequest; +} + +const SwaggerUI = ({ url, requestInterceptor }: Props) => { const mainColor = { _light: 'blackAlpha.800', _dark: 'whiteAlpha.800' }; const borderColor = useToken('colors', 'border.divider'); const mainBgColor = { _light: 'blackAlpha.100', _dark: 'whiteAlpha.200' }; @@ -111,32 +113,12 @@ const SwaggerUI = () => { }, }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const reqInterceptor = React.useCallback((req: any) => { - if (!req.loadSpec) { - const newUrl = new URL(req.url.replace(DEFAULT_SERVER, config.apis.general.host)); - - newUrl.protocol = config.apis.general.protocol + ':'; - - if (config.apis.general.port) { - newUrl.port = config.apis.general.port; - } - - req.url = newUrl.toString(); - } - return req; - }, []); - - if (!feature.isEnabled) { - return null; - } - return ( ); diff --git a/ui/apiDocs/types.ts b/ui/apiDocs/types.ts new file mode 100644 index 0000000000..a9d1c4ac0d --- /dev/null +++ b/ui/apiDocs/types.ts @@ -0,0 +1,4 @@ +export interface SwaggerRequest { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [k: string]: any; +} diff --git a/ui/apiDocs/utils.ts b/ui/apiDocs/utils.ts new file mode 100644 index 0000000000..55eb9a4564 --- /dev/null +++ b/ui/apiDocs/utils.ts @@ -0,0 +1,77 @@ +import type { SwaggerRequest } from './types'; + +import config from 'configs/app'; +import type { ApiPropsBase } from 'configs/app/apis'; + +const feature = config.features.apiDocs; + +const microserviceRequestInterceptorFactory = (api: ApiPropsBase) => (req: SwaggerRequest) => { + try { + const url = new URL(req.url); + if (api?.basePath && !url.pathname.includes(api.basePath)) { + url.pathname = (api?.basePath ?? '') + url.pathname; + } + req.url = url.toString(); + } catch (error) {} + return req; +}; + +const getMicroserviceSwaggerUrl = (api: ApiPropsBase) => `${ api.endpoint }${ api.basePath ?? '' }/api/v1/docs/swagger.yaml`; + +export const REST_API_SECTIONS = [ + feature.isEnabled && { + id: 'blockscout-core-api', + title: 'Blockscout core API', + swagger: { + url: feature.coreApiSwaggerUrl, + requestInterceptor: (req: SwaggerRequest) => { + const DEFAULT_SERVER = 'blockscout.com/poa/core'; + + if (!req.loadSpec) { + const newUrl = new URL(req.url.replace(DEFAULT_SERVER, config.apis.general.host)); + + newUrl.protocol = config.apis.general.protocol + ':'; + + if (config.apis.general.port) { + newUrl.port = config.apis.general.port; + } + + req.url = newUrl.toString(); + } + return req; + }, + }, + }, + config.apis.stats && { + id: 'stats-api', + title: 'Stats API', + swagger: { + url: getMicroserviceSwaggerUrl(config.apis.stats), + requestInterceptor: microserviceRequestInterceptorFactory(config.apis.stats), + }, + }, + config.apis.bens && { + id: 'bens-api', + title: 'Name service API', + swagger: { + url: getMicroserviceSwaggerUrl(config.apis.bens), + requestInterceptor: microserviceRequestInterceptorFactory(config.apis.bens), + }, + }, + config.apis.userOps && { + id: 'user-ops-api', + title: 'User ops indexer API', + swagger: { + url: getMicroserviceSwaggerUrl(config.apis.userOps), + requestInterceptor: microserviceRequestInterceptorFactory(config.apis.userOps), + }, + }, + config.apis.tac && { + id: 'tac-api', + title: 'TAC operation lifecycle API', + swagger: { + url: getMicroserviceSwaggerUrl(config.apis.tac), + requestInterceptor: microserviceRequestInterceptorFactory(config.apis.tac), + }, + }, +].filter(Boolean); diff --git a/ui/pages/ApiDocs.tsx b/ui/pages/ApiDocs.tsx new file mode 100644 index 0000000000..560061847e --- /dev/null +++ b/ui/pages/ApiDocs.tsx @@ -0,0 +1,36 @@ +import { Text } from '@chakra-ui/react'; +import React from 'react'; + +import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; + +import config from 'configs/app'; +import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; +import EthRpcApi from 'ui/apiDocs/EthRpcApi'; +import GraphQL from 'ui/apiDocs/GraphQL'; +import RestApi from 'ui/apiDocs/RestApi'; +import RpcApi from 'ui/apiDocs/RpcApi'; +import { REST_API_SECTIONS } from 'ui/apiDocs/utils'; +import PageTitle from 'ui/shared/Page/PageTitle'; + +const feature = config.features.apiDocs; + +const ApiDocs = () => { + + const tabs: Array = [ + { id: 'rest_api', title: 'REST API', component: , count: REST_API_SECTIONS.length }, + { id: 'eth_rpc_api', title: 'ETH RPC API', component: }, + { id: 'rpc_api', title: 'RPC API endpoints', component: }, + { id: 'graphql_api', title: 'GraphQL API', component: }, + ].filter(({ id }) => feature.isEnabled && feature.tabs.includes(id)); + + return ( + <> + + { tabs.length > 0 ? : No API documentation available } + + ); +}; + +export default React.memo(ApiDocs); diff --git a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_auth-base-view-1.png b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_auth-base-view-1.png index d219471294..0ea33f7d37 100644 Binary files a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_auth-base-view-1.png and b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_auth-base-view-1.png differ diff --git a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_base-view-1.png b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_base-view-1.png index de14b9cc03..bc224a108d 100644 Binary files a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_base-view-1.png and b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_base-view-1.png differ diff --git a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_dark-mode-base-view-1.png b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_dark-mode-base-view-1.png index 38ff1ba6ae..4617d709fc 100644 Binary files a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_dark-mode-base-view-1.png and b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_dark-mode-base-view-1.png differ diff --git a/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_base-view-dark-mode-1.png index c653cafb92..7ad979194e 100644 Binary files a/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ diff --git a/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_default_base-view-dark-mode-1.png b/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_default_base-view-dark-mode-1.png index ca907c4dc9..d92953f384 100644 Binary files a/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_default_base-view-dark-mode-1.png and b/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_default_base-view-dark-mode-1.png differ diff --git a/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_default_with-groped-items-1.png b/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_default_with-groped-items-1.png index 1f3e8f42c9..6572facdb9 100644 Binary files a/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_default_with-groped-items-1.png and b/ui/snippets/navigation/horizontal/__screenshots__/NavigationDesktop.pw.tsx_default_with-groped-items-1.png differ diff --git a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-xl-screen-dark-mode-1.png b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-xl-screen-dark-mode-1.png index 863ea1e615..ebe1faa63e 100644 Binary files a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-xl-screen-dark-mode-1.png b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-xl-screen-dark-mode-1.png index 863ea1e615..ebe1faa63e 100644 Binary files a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_with-highlighted-routes-xl-screen-dark-mode-1.png b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_with-highlighted-routes-xl-screen-dark-mode-1.png index 4159c4ad89..ee41a1d5ad 100644 Binary files a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_with-highlighted-routes-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_with-highlighted-routes-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_hover-xl-screen-dark-mode-1.png b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_hover-xl-screen-dark-mode-1.png index 128dd7665b..c88c0267f4 100644 Binary files a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_hover-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_hover-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-xl-screen-dark-mode-1.png b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-xl-screen-dark-mode-1.png index 128dd7665b..c88c0267f4 100644 Binary files a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_with-highlighted-routes-xl-screen-dark-mode-1.png b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_with-highlighted-routes-xl-screen-dark-mode-1.png index 2562936ce4..688899d64e 100644 Binary files a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_with-highlighted-routes-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_with-highlighted-routes-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-xl-screen-base-view-1.png b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-xl-screen-base-view-1.png index b62c6657c6..6c6d651df3 100644 Binary files a/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-xl-screen-base-view-1.png and b/ui/snippets/navigation/vertical/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-xl-screen-base-view-1.png differ