diff --git a/configs/app/features/index.ts b/configs/app/features/index.ts index 042b11ad97..e2aadfda04 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -11,6 +11,7 @@ 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'; export { default as mixpanel } from './mixpanel'; export { default as nameService } from './nameService'; export { default as restApiDocs } from './restApiDocs'; diff --git a/configs/app/features/metasuites.ts b/configs/app/features/metasuites.ts new file mode 100644 index 0000000000..333e7d5a8a --- /dev/null +++ b/configs/app/features/metasuites.ts @@ -0,0 +1,21 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const title = 'MetaSuites extension'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (getEnvValue('NEXT_PUBLIC_METASUITES_ENABLED') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index f91179664b..5e4f7712e4 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -503,6 +503,7 @@ const schema = yup 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_SWAP_BUTTON_URL: yup.string(), NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE: yup.string().oneOf(VALIDATORS_CHAIN_TYPE), NEXT_PUBLIC_GAS_TRACKER_ENABLED: yup.boolean(), diff --git a/deploy/tools/envs-validator/test/.env.base b/deploy/tools/envs-validator/test/.env.base index 8b0a19ec8f..6c6f417c96 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -24,6 +24,7 @@ NEXT_PUBLIC_GAS_TRACKER_ENABLED=true NEXT_PUBLIC_GAS_TRACKER_UNITS=['gwei'] NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE='Hello' +NEXT_PUBLIC_METASUITES_ENABLED=true NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH diff --git a/deploy/values/review-l2/values.yaml.gotmpl b/deploy/values/review-l2/values.yaml.gotmpl index 4aa3eb1b53..eea25de5e8 100644 --- a/deploy/values/review-l2/values.yaml.gotmpl +++ b/deploy/values/review-l2/values.yaml.gotmpl @@ -30,10 +30,12 @@ frontend: kubernetes.io/ingress.class: internal-and-public nginx.ingress.kubernetes.io/proxy-body-size: 500m nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" + nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + nginx.ingress.kubernetes.io/proxy-buffers-number: "8" cert-manager.io/cluster-issuer: "zerossl-prod" hostname: review-l2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}.k8s-dev.blockscout.com diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index dc8459129e..1c8de7b2f8 100644 --- a/deploy/values/review/values.yaml.gotmpl +++ b/deploy/values/review/values.yaml.gotmpl @@ -30,10 +30,12 @@ frontend: kubernetes.io/ingress.class: internal-and-public nginx.ingress.kubernetes.io/proxy-body-size: 500m nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" + nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + nginx.ingress.kubernetes.io/proxy-buffers-number: "8" cert-manager.io/cluster-issuer: "zerossl-prod" hostname: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}.k8s-dev.blockscout.com @@ -89,4 +91,3 @@ frontend: FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN - diff --git a/docs/ENVS.md b/docs/ENVS.md index 2d0052e2f7..4fd1121fd6 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -52,6 +52,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [Bridged tokens](ENVS.md#bridged-tokens) - [Safe{Core} address tags](ENVS.md#safecore-address-tags) - [SUAVE chain](ENVS.md#suave-chain) + - [MetaSuites extension](ENVS.md#metasuites-extension) - [Sentry error monitoring](ENVS.md#sentry-error-monitoring) - [OpenTelemetry](ENVS.md#opentelemetry) - [Swap button](ENVS.md#swap-button) @@ -595,6 +596,16 @@ For blockchains that implement SUAVE architecture additional fields will be show   +### MetaSuites extension + +Enables [MetaSuites browser extension](https://github.com/blocksecteam/metasuites) to integrate with the app views. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_METASUITES_ENABLED | `boolean` | Set to true to enable integration | Required | - | `true` | + +  + ### Validators list The feature enables the Validators page which provides detailed information about the validators of the PoS chains. diff --git a/lib/hooks/useNotifyOnNavigation.tsx b/lib/hooks/useNotifyOnNavigation.tsx new file mode 100644 index 0000000000..a3b7a7c92f --- /dev/null +++ b/lib/hooks/useNotifyOnNavigation.tsx @@ -0,0 +1,24 @@ +import { usePathname } from 'next/navigation'; +import { useRouter } from 'next/router'; +import React from 'react'; + +import config from 'configs/app'; +import getQueryParamString from 'lib/router/getQueryParamString'; + +export default function useNotifyOnNavigation() { + const router = useRouter(); + const pathname = usePathname(); + const tab = getQueryParamString(router.query.tab); + + React.useEffect(() => { + if (config.features.metasuites.isEnabled) { + window.postMessage({ source: 'APP_ROUTER', type: 'PATHNAME_CHANGED' }, window.location.origin); + } + }, [ pathname ]); + + React.useEffect(() => { + if (config.features.metasuites.isEnabled) { + window.postMessage({ source: 'APP_ROUTER', type: 'TAB_CHANGED' }, window.location.origin); + } + }, [ tab ]); +} diff --git a/pages/_app.tsx b/pages/_app.tsx index 5086499177..15fc5edd91 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -15,6 +15,7 @@ import { ChakraProvider } from 'lib/contexts/chakra'; import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; import { growthBook } from 'lib/growthbook/init'; import useLoadFeatures from 'lib/growthbook/useLoadFeatures'; +import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation'; import { SocketProvider } from 'lib/socket/context'; import theme from 'theme'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; @@ -44,6 +45,7 @@ const ERROR_SCREEN_STYLES: ChakraProps = { function MyApp({ Component, pageProps }: AppPropsWithLayout) { useLoadFeatures(); + useNotifyOnNavigation(); const queryClient = useQueryClientConfig(); diff --git a/ui/pages/Address.tsx b/ui/pages/Address.tsx index 92b79618eb..96a8a581b3 100644 --- a/ui/pages/Address.tsx +++ b/ui/pages/Address.tsx @@ -236,6 +236,7 @@ const AddressPageContent = () => { secondRow={ titleSecondRow } isLoading={ isLoading } /> + { config.features.metasuites.isEnabled && } { /* should stay before tabs to scroll up with pagination */ } diff --git a/ui/shared/DetailsInfoItemDivider.tsx b/ui/shared/DetailsInfoItemDivider.tsx index 22c5b456e0..227837fa74 100644 --- a/ui/shared/DetailsInfoItemDivider.tsx +++ b/ui/shared/DetailsInfoItemDivider.tsx @@ -1,9 +1,16 @@ -import { GridItem } from '@chakra-ui/react'; +import { GridItem, chakra } from '@chakra-ui/react'; import React from 'react'; -const DetailsInfoItemDivider = () => { +interface Props { + className?: string; + id?: string; +} + +const DetailsInfoItemDivider = ({ className, id }: Props) => { return ( { ); }; -export default DetailsInfoItemDivider; +export default chakra(DetailsInfoItemDivider); diff --git a/ui/shared/EntityTags.tsx b/ui/shared/EntityTags.tsx index 7e0a62d94c..40245f7984 100644 --- a/ui/shared/EntityTags.tsx +++ b/ui/shared/EntityTags.tsx @@ -1,9 +1,10 @@ import type { ThemingProps } from '@chakra-ui/react'; -import { Flex, chakra, useDisclosure, Popover, PopoverTrigger, PopoverContent, PopoverBody } from '@chakra-ui/react'; +import { Flex, chakra, useDisclosure, Popover, PopoverTrigger, PopoverContent, PopoverBody, Box } from '@chakra-ui/react'; import React from 'react'; import type { UserTags } from 'types/api/addressParams'; +import config from 'configs/app'; import useIsMobile from 'lib/hooks/useIsMobile'; import Tag from 'ui/shared/chakra/Tag'; @@ -36,8 +37,12 @@ const EntityTags = ({ className, data, tagsBefore = [], tagsAfter = [], isLoadin ] .filter(Boolean); + const metaSuitesPlaceholder = config.features.metasuites.isEnabled ? + : + null; + if (tags.length === 0 && !contentAfter) { - return null; + return metaSuitesPlaceholder; } const content = (() => { @@ -60,6 +65,7 @@ const EntityTags = ({ className, data, tagsBefore = [], tagsAfter = [], isLoadin )) } + { metaSuitesPlaceholder } +{ tags.length - 1 } @@ -88,18 +94,23 @@ const EntityTags = ({ className, data, tagsBefore = [], tagsAfter = [], isLoadin ); } - return tags.map((tag) => ( - - { tag.display_name } - - )); + return ( + <> + { tags.map((tag) => ( + + { tag.display_name } + + )) } + { metaSuitesPlaceholder } + + ); })(); return ( diff --git a/ui/shared/TextSeparator.tsx b/ui/shared/TextSeparator.tsx index bc386c0ddb..ed929fafdb 100644 --- a/ui/shared/TextSeparator.tsx +++ b/ui/shared/TextSeparator.tsx @@ -2,8 +2,8 @@ import { chakra } from '@chakra-ui/react'; import type { StyleProps } from '@chakra-ui/styled-system'; import React from 'react'; -const TextSeparator = (props: StyleProps) => { - return |; +const TextSeparator = ({ id, ...props }: StyleProps & { id?: string }) => { + return |; }; export default React.memo(TextSeparator); diff --git a/ui/tx/details/TxInfo.tsx b/ui/tx/details/TxInfo.tsx index d7bf0f5688..204d1aa24e 100644 --- a/ui/tx/details/TxInfo.tsx +++ b/ui/tx/details/TxInfo.tsx @@ -109,6 +109,15 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { return ( + + { config.features.metasuites.isEnabled && ( + <> + + + + + ) } + { socketStatus && ( @@ -125,6 +134,13 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { + + { config.features.metasuites.isEnabled && ( + <> + + + + ) }