UNPKG

@newrelic/gatsby-theme-newrelic

Version:

[![Community Project header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Community_Project.png)](https://opensource.newrelic.com/oss-category/#community-project)

270 lines (250 loc) 7.43 kB
import React, { useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { css } from '@emotion/react'; import { navigate } from 'gatsby-link'; import { graphql, useStaticQuery } from 'gatsby'; import GlobalHeader from '../components/GlobalHeader'; import GlobalFooter from '../components/GlobalFooter'; import Link from '../components/Link'; import SearchInput from '../components/SearchInput'; import Tag from '../components/Tag'; import CreateIssueButton from '../components/CreateIssueButton'; import Button from '../components/Button'; import getLocale from '../../gatsby/utils/getLocale'; import useThemeTranslation from '../hooks/useThemeTranslation'; import Trans from '../components/Trans'; import { addPageAction } from '../utils/nrBrowserAgent.js'; import { suggest } from '../utils/searchGPT'; const NotFoundPage = ({ location, pageContext: { themeOptions } }) => { const { site: { siteMetadata: { siteUrl }, }, } = useStaticQuery(graphql` query { site { siteMetadata { siteUrl } } } `); const { t: translate } = useThemeTranslation(); const [searchTerm, setSearchTerm] = useState(null); const [searchResult, setSearchResult] = useState(null); const pageLocale = getLocale({ location }, themeOptions); const hostname = new URL(siteUrl).hostname; const nrSubDomain = /.*\.newrelic\.com/.test(hostname) ? hostname.split('.')[0] : null; const searchTermFilter = useCallback( (term) => { const termsToIgnore = ['docs', pageLocale]; return term && !termsToIgnore.includes(term); }, [pageLocale] ); const getSearchResults = useCallback(async () => { if (searchTerm === null) { return; } try { // suggest() is the lexical typeahead endpoint: not rate limited and // lightweight, which suits 404 path-based suggestions. const { results } = await suggest({ searchTerm, limit: 5 }); const trimmedResults = results.map((r) => ({ url: r.url, title: r.title, sourceLabel: r.sourceLabel, })); setSearchResult(trimmedResults); } catch { setSearchResult([]); } }, [searchTerm]); const displaySearchResults = () => { if (!searchResult || searchResult.length === 0) { return null; } return ( <div id="search-results"> {translate('404.searchResultMessage')} <ul css={css` list-style-type: none; padding: 0; line-height: 1.75rem; margin: 1rem 0 0; `} > {searchResult.map((result, index) => { return ( <li key={`result-${index}`}> <div> <Link to={result.url} css={css` text-decoration: none; `} instrumentation={{ position: index, component: '404SuggestedLink', href: result.url, searchTerm, searchResult: true, }} displayExternalIcon > {result.title} </Link> </div> <Tag css={css` font-size: 0.625rem; margin-right: 0.5em; `} uppercase > {result.sourceLabel} </Tag> </li> ); })} </ul> </div> ); }; useEffect(() => { setSearchTerm( location.pathname.split('/').filter(searchTermFilter).join(' ') ); }, [searchTermFilter, location.pathname]); useEffect(() => { getSearchResults(); }, [getSearchResults, searchTerm]); useEffect(() => { if (searchResult) { addPageAction({ eventName: 'error404', category: 'ErrorPage', path: location.pathname, resultCount: searchResult.length, searchTerm, }); } }, [location.pathname, searchResult, searchTerm]); return ( <> <GlobalHeader /> <div css={css` display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-rows: auto; grid-template-areas: '. . . .' '. content content .' 'footer footer footer footer'; min-height: calc(100vh - var(--global-header-height)); max-height: calc(100vh - var(--global-header-height)); `} > <div css={css` display: flex; grid-area: content; justify-self: start; `} > <div css={css` font-size: 0.85rem; padding: 8rem 0; `} > <h1 css={css` font-weight: normal; line-height: 1; `} > {translate('404.headingText')} </h1> <div id="search-section" css={css` margin-top: 2rem; margin-bottom: 2rem; `} > {translate('404.searchInputLabel')} <SearchInput placeholder={searchTerm} onFocus={() => navigate( `search-results/?query=${searchTerm.replaceAll( ' ', '+' )}&page=1` ) } css={css` min-width: 200px; max-width: 2000px; margin-top: 0.5rem; margin-bottom: 0.5rem; `} /> </div> {displaySearchResults()} <div css={css` > * { margin: 3em 0; } `} > {/* <p> {translate('404.docsHomeMessage')}{' '} <Link to="/">{{nrSubDomain}} home</Link>. </p> */} <Trans i18nKey="404.docsHomeMessage" parent="p"> Go back to <Link to="/">{{ nrSubDomain }} home</Link>. </Trans> <p> {translate('404.fileIssueMessage')}{' '} <CreateIssueButton pageTitle="404" variant={Button.VARIANT.OUTLINE} size={Button.SIZE.SMALL} labels={['bug', '404']} instrumentation={{ component: '404Page' }} css={css` margin-left: 1em; `} /> </p> </div> </div> </div> <GlobalFooter css={css` grid-area: footer; align-self: end; `} /> </div> </> ); }; NotFoundPage.propTypes = { location: PropTypes.shape({ href: PropTypes.string.isRequired, origin: PropTypes.string.isRequired, pathname: PropTypes.string.isRequired, }).isRequired, pageContext: PropTypes.shape({ themeOptions: PropTypes.object.isRequired, }).isRequired, }; export default NotFoundPage;