diff --git a/configs/app/ui.ts b/configs/app/ui.ts index 4e72fa16f2..1cab72dd9a 100644 --- a/configs/app/ui.ts +++ b/configs/app/ui.ts @@ -39,6 +39,9 @@ const UI = Object.freeze({ indexingAlert: { isHidden: getEnvValue(process.env.NEXT_PUBLIC_HIDE_INDEXING_ALERT), }, + maintenanceAlert: { + message: getEnvValue(process.env.NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE), + }, explorers: { items: parseEnvJson>(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_EXPLORERS)) || [], }, diff --git a/configs/envs/.env.pw b/configs/envs/.env.pw index 2f7eedc067..ffa7c9b667 100644 --- a/configs/envs/.env.pw +++ b/configs/envs/.env.pw @@ -43,6 +43,7 @@ NEXT_PUBLIC_FOOTER_LINKS= NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS= ## misc NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] +NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE= # app features NEXT_PUBLIC_APP_ENV=testing diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 2ebb0ba851..0a7ee5b10f 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -304,6 +304,7 @@ const schema = yup .json() .of(networkExplorerSchema), NEXT_PUBLIC_HIDE_INDEXING_ALERT: yup.boolean(), + NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(), // 5. Features configuration NEXT_PUBLIC_API_SPEC_URL: yup.string().url(), diff --git a/docs/ENVS.md b/docs/ENVS.md index 8bc91c16d3..c18a5dac9c 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -190,6 +190,7 @@ Settings for meta tags and OG tags | --- | --- | --- | --- | --- | --- | | NEXT_PUBLIC_NETWORK_EXPLORERS | `Array` 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_HIDE_INDEXING_ALERT | `boolean` | Set to `true` to hide indexing alert, if the chain indexing isn't completed | - | `false` | `true` | +| NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE | `string` | Used for displaying custom announcements or alerts in the header of the site. Could be a regular string or a HTML code. | - | - | `Hello world! 🤪` | #### Network explorer configuration properties diff --git a/ui/pages/SearchResults.tsx b/ui/pages/SearchResults.tsx index 75113aab1a..5d859f3252 100644 --- a/ui/pages/SearchResults.tsx +++ b/ui/pages/SearchResults.tsx @@ -3,7 +3,6 @@ import { useRouter } from 'next/router'; import type { FormEvent } from 'react'; import React from 'react'; -import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks'; import useMarketplaceApps from 'ui/marketplace/useMarketplaceApps'; import SearchResultListItem from 'ui/searchResults/SearchResultListItem'; import SearchResultsInput from 'ui/searchResults/SearchResultsInput'; @@ -17,6 +16,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/pagination/Pagination'; import Thead from 'ui/shared/TheadSticky'; import Header from 'ui/snippets/header/Header'; +import HeaderAlert from 'ui/snippets/header/HeaderAlert'; import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery'; const SearchResultsPageContent = () => { @@ -181,7 +181,7 @@ const SearchResultsPageContent = () => { return ( <> - +
diff --git a/ui/shared/layout/Layout.pw.tsx b/ui/shared/layout/Layout.pw.tsx index 1139453db7..3295dccd1c 100644 --- a/ui/shared/layout/Layout.pw.tsx +++ b/ui/shared/layout/Layout.pw.tsx @@ -1,6 +1,7 @@ -import { test, expect } from '@playwright/experimental-ct-react'; +import { test as base, expect } from '@playwright/experimental-ct-react'; import React from 'react'; +import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; import TestApp from 'playwright/TestApp'; import buildApiUrl from 'playwright/utils/buildApiUrl'; @@ -8,6 +9,16 @@ import Layout from './Layout'; const API_URL = buildApiUrl('homepage_indexing_status'); +const test = base.extend({ + context: contextWithEnvs([ + { + name: 'NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE', + value: 'We are currently lacking pictures of ducks. Please send us one.', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ]) as any, +}); + test('base view +@mobile', async({ mount, page }) => { await page.route(API_URL, (route) => route.fulfill({ status: 200, diff --git a/ui/shared/layout/Layout.tsx b/ui/shared/layout/Layout.tsx index 388b352c9c..eebd8b1182 100644 --- a/ui/shared/layout/Layout.tsx +++ b/ui/shared/layout/Layout.tsx @@ -2,9 +2,9 @@ import React from 'react'; import type { Props } from './types'; -import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; import Header from 'ui/snippets/header/Header'; +import HeaderAlert from 'ui/snippets/header/HeaderAlert'; import * as Layout from './components'; @@ -14,7 +14,7 @@ const LayoutDefault = ({ children }: Props) => { - +
diff --git a/ui/shared/layout/LayoutError.tsx b/ui/shared/layout/LayoutError.tsx index 9ba7655888..f088aba48f 100644 --- a/ui/shared/layout/LayoutError.tsx +++ b/ui/shared/layout/LayoutError.tsx @@ -2,9 +2,9 @@ import React from 'react'; import type { Props } from './types'; -import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; import Header from 'ui/snippets/header/Header'; +import HeaderAlert from 'ui/snippets/header/HeaderAlert'; import * as Layout from './components'; @@ -14,7 +14,7 @@ const LayoutError = ({ children }: Props) => { - +
diff --git a/ui/shared/layout/LayoutHome.tsx b/ui/shared/layout/LayoutHome.tsx index a601d20237..90a71ae8e1 100644 --- a/ui/shared/layout/LayoutHome.tsx +++ b/ui/shared/layout/LayoutHome.tsx @@ -2,9 +2,9 @@ import React from 'react'; import type { Props } from './types'; -import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; import Header from 'ui/snippets/header/Header'; +import HeaderAlert from 'ui/snippets/header/HeaderAlert'; import * as Layout from './components'; @@ -16,7 +16,7 @@ const LayoutHome = ({ children }: Props) => { - +
{ children } diff --git a/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png index d530bdecaf..d21fc8712c 100644 Binary files a/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/shared/layout/__screenshots__/Layout.pw.tsx_mobile_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/Layout.pw.tsx_mobile_base-view-mobile-1.png index 5bb3594334..7c7fe2cd8b 100644 Binary files a/ui/shared/layout/__screenshots__/Layout.pw.tsx_mobile_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/Layout.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/snippets/footer/Footer.tsx b/ui/snippets/footer/Footer.tsx index 41348421f1..880950491f 100644 --- a/ui/snippets/footer/Footer.tsx +++ b/ui/snippets/footer/Footer.tsx @@ -15,11 +15,11 @@ import type { ResourceError } from 'lib/api/resources'; import useApiQuery from 'lib/api/useApiQuery'; import useFetch from 'lib/hooks/useFetch'; import useIssueUrl from 'lib/hooks/useIssueUrl'; -import IndexingAlertIntTxs from 'ui/home/IndexingAlertIntTxs'; import NetworkAddToWallet from 'ui/shared/NetworkAddToWallet'; import ColorModeToggler from '../header/ColorModeToggler'; import FooterLinkItem from './FooterLinkItem'; +import IntTxsIndexingStatus from './IntTxsIndexingStatus'; import getApiVersionUrl from './utils/getApiVersionUrl'; const MAX_LINKS_COLUMNS = 3; @@ -109,7 +109,7 @@ const Footer = () => { - { !config.UI.indexingAlert.isHidden && } + { !config.UI.indexingAlert.isHidden && } diff --git a/ui/home/IndexingAlertIntTxs.tsx b/ui/snippets/footer/IntTxsIndexingStatus.tsx similarity index 97% rename from ui/home/IndexingAlertIntTxs.tsx rename to ui/snippets/footer/IntTxsIndexingStatus.tsx index 3e2b8273b3..3f0f64fa98 100644 --- a/ui/home/IndexingAlertIntTxs.tsx +++ b/ui/snippets/footer/IntTxsIndexingStatus.tsx @@ -11,7 +11,7 @@ import { apos, nbsp, ndash } from 'lib/html-entities'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; -const IndexingAlertIntTxs = () => { +const IntTxsIndexingStatus = () => { const { data, isError, isLoading } = useApiQuery('homepage_indexing_status'); @@ -98,4 +98,4 @@ const IndexingAlertIntTxs = () => { ); }; -export default IndexingAlertIntTxs; +export default IntTxsIndexingStatus; diff --git a/ui/snippets/header/HeaderAlert.tsx b/ui/snippets/header/HeaderAlert.tsx new file mode 100644 index 0000000000..fe861d57d6 --- /dev/null +++ b/ui/snippets/header/HeaderAlert.tsx @@ -0,0 +1,16 @@ +import { Flex } from '@chakra-ui/react'; +import React from 'react'; + +import IndexingBlocksAlert from './alerts/IndexingBlocksAlert'; +import MaintenanceAlert from './alerts/MaintenanceAlert'; + +const HeaderAlert = () => { + return ( + + + + + ); +}; + +export default React.memo(HeaderAlert); diff --git a/ui/home/IndexingAlertBlocks.tsx b/ui/snippets/header/alerts/IndexingBlocksAlert.tsx similarity index 84% rename from ui/home/IndexingAlertBlocks.tsx rename to ui/snippets/header/alerts/IndexingBlocksAlert.tsx index a8a8dbefcf..1a593e584b 100644 --- a/ui/home/IndexingAlertBlocks.tsx +++ b/ui/snippets/header/alerts/IndexingBlocksAlert.tsx @@ -1,4 +1,4 @@ -import { Alert, AlertIcon, AlertTitle, chakra, Skeleton } from '@chakra-ui/react'; +import { Alert, AlertIcon, AlertTitle, Skeleton } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import React from 'react'; @@ -8,14 +8,11 @@ import type { IndexingStatus } from 'types/api/indexingStatus'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import { useAppContext } from 'lib/contexts/app'; import * as cookies from 'lib/cookies'; -import useIsMobile from 'lib/hooks/useIsMobile'; import { nbsp, ndash } from 'lib/html-entities'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; -const IndexingAlertBlocks = ({ className }: { className?: string }) => { - const isMobile = useIsMobile(); - +const IndexingBlocksAlert = () => { const appProps = useAppContext(); const cookiesString = appProps.cookies; const [ hasAlertCookie ] = React.useState(cookies.get(cookies.NAMES.INDEXING_ALERT, cookiesString) === 'true'); @@ -57,7 +54,7 @@ const IndexingAlertBlocks = ({ className }: { className?: string }) => { } if (isLoading) { - return hasAlertCookie ? : null; + return hasAlertCookie ? : null; } if (data.finished_indexing_blocks !== false) { @@ -65,8 +62,8 @@ const IndexingAlertBlocks = ({ className }: { className?: string }) => { } return ( - - { !isMobile && } + + { `${ data.indexed_blocks_ratio && `${ Math.floor(Number(data.indexed_blocks_ratio) * 100) }% Blocks Indexed${ nbsp }${ ndash } ` } We're indexing this chain right now. Some of the counts may be inaccurate.` } @@ -75,4 +72,4 @@ const IndexingAlertBlocks = ({ className }: { className?: string }) => { ); }; -export default chakra(IndexingAlertBlocks); +export default React.memo(IndexingBlocksAlert); diff --git a/ui/snippets/header/alerts/MaintenanceAlert.tsx b/ui/snippets/header/alerts/MaintenanceAlert.tsx new file mode 100644 index 0000000000..baeef3ff80 --- /dev/null +++ b/ui/snippets/header/alerts/MaintenanceAlert.tsx @@ -0,0 +1,30 @@ +import { Alert, AlertIcon, AlertTitle } from '@chakra-ui/react'; +import React from 'react'; + +import config from 'configs/app'; + +const MaintenanceAlert = () => { + if (!config.UI.maintenanceAlert.message) { + return null; + } + + return ( + + + + + + ); +}; + +export default MaintenanceAlert;