diff --git a/package-lock.json b/package-lock.json index 8f908b5c..940d923a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,7 @@ "qrcode": "^1.5.4", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-helmet": "^6.1.0", + "react-helmet-async": "^2.0.5", "react-hook-form": "^7.52.1", "react-i18next": "^15.1.1", "react-oidc-context": "^3.1.1", @@ -10706,6 +10706,15 @@ "node": ">= 0.10" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -15936,19 +15945,18 @@ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", "license": "MIT" }, - "node_modules/react-helmet": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", - "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", - "license": "MIT", + "node_modules/react-helmet-async": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-2.0.5.tgz", + "integrity": "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==", + "license": "Apache-2.0", "dependencies": { - "object-assign": "^4.1.1", - "prop-types": "^15.7.2", - "react-fast-compare": "^3.1.1", - "react-side-effect": "^2.1.0" + "invariant": "^2.2.4", + "react-fast-compare": "^3.2.2", + "shallowequal": "^1.1.0" }, "peerDependencies": { - "react": ">=16.3.0" + "react": "^16.6.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/react-hook-form": { @@ -16040,15 +16048,6 @@ "react-dom": ">=16.8" } }, - "node_modules/react-side-effect": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", - "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", - "license": "MIT", - "peerDependencies": { - "react": "^16.3.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -16881,6 +16880,12 @@ "node": ">=8" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index a0a93a26..fa5e2e5e 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "qrcode": "^1.5.4", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-helmet": "^6.1.0", + "react-helmet-async": "^2.0.5", "react-hook-form": "^7.52.1", "react-i18next": "^15.1.1", "react-oidc-context": "^3.1.1", diff --git a/src/common/hooks/useIntersectionObserver.js b/src/common/hooks/useIntersectionObserver.js index 12e99b50..2c7e9db3 100644 --- a/src/common/hooks/useIntersectionObserver.js +++ b/src/common/hooks/useIntersectionObserver.js @@ -9,7 +9,7 @@ export default function useIntersectionObserver({ enabled = true, }) { useEffect(() => { - const el = target && target?.current + const el = target?.current if (!enabled || !el) { return } @@ -17,14 +17,17 @@ export default function useIntersectionObserver({ (entries) => entries.forEach((entry) => entry.isIntersecting && onIntersect()), { - root: root && root.current, + root: root?.current, rootMargin, threshold, }, ) observer.observe(el) + return () => { - observer.unobserve(el) + if (el) { + observer.unobserve(el) + } } - }, [target.current, enabled]) + }, [target, enabled, root, rootMargin, threshold, onIntersect]) } diff --git a/src/common/hooks/useSearchDomain.js b/src/common/hooks/useSearchDomain.js index ece366f2..71af51df 100644 --- a/src/common/hooks/useSearchDomain.js +++ b/src/common/hooks/useSearchDomain.js @@ -9,10 +9,7 @@ import { DOMAIN_TRANSLATION, } from 'common/constants' import useSearchParamsState from 'common/hooks/useSearchParamsState' -import { - makeTitleCase, - getPresentationPropertiesForType, -} from 'common/utils/stringHelpers' +import { getPresentationPropertiesForType } from 'common/utils/stringHelpers' function useSearchDomain({ searchType }) { const [searchDomainInUrl, setSearchDomainInUrl] = useSearchParamsState({ @@ -34,7 +31,7 @@ function useSearchDomain({ searchType }) { const searchDomainOptions = { [DOMAIN_BOTH]: 'All', [DOMAIN_TRANSLATION]: 'Translation', - [DOMAIN_LANGUAGE]: makeTitleCase(labels.plural), + [DOMAIN_LANGUAGE]: labels.titlecase, } const handleSearchDomainNavigation = (value) => { diff --git a/src/common/hooks/useSearchType.js b/src/common/hooks/useSearchType.js index 40a2eaad..578b0b65 100644 --- a/src/common/hooks/useSearchType.js +++ b/src/common/hooks/useSearchType.js @@ -32,7 +32,7 @@ function useSearchType({ initialSearchType = TYPE_ENTRY }) { const getSearchTypeLabel = ({ searchType, plural = false }) => { const labels = getPresentationPropertiesForType(searchType) - if (plural) return labels.plural + if (plural) return labels.lowercase return labels.singular } diff --git a/src/common/utils/stringHelpers.js b/src/common/utils/stringHelpers.js index 2e79f23d..aef3ad56 100644 --- a/src/common/utils/stringHelpers.js +++ b/src/common/utils/stringHelpers.js @@ -136,81 +136,90 @@ export const getPresentationPropertiesForType = (type) => { switch (type) { case TYPE_WORD: return { + lowercase: 'words', uppercase: 'WORDS', + titlecase: 'Words', singular: 'word', - plural: 'words', slug: 'words', color: 'word-color-500', textColor: 'word-color-700', } case TYPE_PHRASE: return { + lowercase: 'phrases', uppercase: 'PHRASES', + titlecase: 'Phrases', singular: 'phrase', - plural: 'phrases', slug: 'phrases', color: 'phrase-color-600', textColor: 'phrase-color-800', } case TYPE_SONG: return { + lowercase: 'songs', uppercase: 'SONGS', + titlecase: 'Songs', singular: 'song', - plural: 'songs', slug: 'songs', color: 'song-color-800', textColor: 'song-color-900', } case TYPE_STORY: return { + lowercase: 'stories', uppercase: 'STORIES', + titlecase: 'Stories', singular: 'story', - plural: 'stories', slug: 'stories', color: 'story-color-700', textColor: 'story-color-900', } case TYPE_DICTIONARY: return { + lowercase: 'words and phrases', uppercase: 'DICTIONARY', + titlecase: 'Dictionary', singular: 'word / phrase', - plural: 'words and phrases', slug: 'dictionary', color: 'word-color-500', textColor: 'word-color-700', } case TYPE_MEDIA: return { + lowercase: 'media', uppercase: 'MEDIA', + titlecase: 'Media', singular: 'media', - plural: 'media', slug: 'search', color: 'charcoal-500', textColor: 'charcoal-900', } case TYPE_AUDIO: return { + lowercase: 'audio', uppercase: 'AUDIO', + titlecase: 'Audio', singular: 'audio', - plural: 'audio', slug: 'audio', color: 'charcoal-500', textColor: 'charcoal-900', } case TYPE_IMAGE: return { - uppercase: 'IMAGE', + lowercase: 'images', + uppercase: 'IMAGES', + titlecase: 'Images', singular: 'image', - plural: 'images', slug: 'image', color: 'charcoal-500', textColor: 'charcoal-900', } case TYPE_VIDEO: return { - uppercase: 'VIDEO', + lowercase: 'videos', + uppercase: 'VIDEOS', + titlecase: 'Videos', singular: 'video', - plural: 'videos', slug: 'video', color: 'charcoal-500', textColor: 'charcoal-900', @@ -218,9 +227,10 @@ export const getPresentationPropertiesForType = (type) => { case TYPE_ENTRY: default: return { - uppercase: 'DICTIONARY', + lowercase: 'language entries', + uppercase: 'LANGUAGE ENTRIES', + titlecase: 'Language Entries', singular: 'language entry', - plural: 'language entries', slug: 'search', color: 'charcoal-500', textColor: 'charcoal-900', diff --git a/src/components/AboutFV/AboutFV.js b/src/components/About/About.js similarity index 97% rename from src/components/AboutFV/AboutFV.js rename to src/components/About/About.js index ff9523a9..0606200a 100644 --- a/src/components/AboutFV/AboutFV.js +++ b/src/components/About/About.js @@ -1,7 +1,10 @@ import React from 'react' + +// FPCC import SectionTitle from 'components/SectionTitle' +import DocHead from 'components/DocHead' -function AboutFV() { +function About() { const headerStyle = 'text-xl font-bold mb-1 mt-4' const paraStyle = 'mb-2' return ( @@ -9,6 +12,7 @@ function AboutFV() { className="pt-2 md:pt-4 lg:pt-8 bg-white" data-testid="AboutFirstVoices" > +
@@ -96,4 +100,4 @@ function AboutFV() { ) } -export default AboutFV +export default About diff --git a/src/components/About/index.js b/src/components/About/index.js new file mode 100644 index 00000000..e3cda7d5 --- /dev/null +++ b/src/components/About/index.js @@ -0,0 +1,3 @@ +import About from 'components/About/About' + +export default About diff --git a/src/components/AboutFV/index.js b/src/components/AboutFV/index.js deleted file mode 100644 index 27923eb4..00000000 --- a/src/components/AboutFV/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import AboutFV from 'components/AboutFV/AboutFV' - -export default AboutFV diff --git a/src/components/Alphabet/AlphabetPresentation.js b/src/components/Alphabet/AlphabetPresentation.js index 77ba7a87..38f5f4b8 100644 --- a/src/components/Alphabet/AlphabetPresentation.js +++ b/src/components/Alphabet/AlphabetPresentation.js @@ -5,6 +5,7 @@ import { Link } from 'react-router-dom' // FPCC import AlphabetPresentationSelected from 'components/Alphabet/AlphabetPresentationSelected' import SectionTitle from 'components/SectionTitle' +import SiteDocHead from 'components/SiteDocHead' function AlphabetPresentation({ characters, @@ -20,6 +21,7 @@ function AlphabetPresentation({ className="pt-2 md:pt-4 lg:pt-8 bg-white" data-testid="AlphabetPresentation" > +
{links && ( diff --git a/src/components/App/AppContainer.js b/src/components/App/AppContainer.js index e74f5cf0..bc1c2564 100644 --- a/src/components/App/AppContainer.js +++ b/src/components/App/AppContainer.js @@ -1,19 +1,20 @@ import React from 'react' import * as Sentry from '@sentry/react' import { Routes, Route, Navigate } from 'react-router-dom' -import { Helmet } from 'react-helmet' +import { HelmetProvider } from 'react-helmet-async' // FPCC -import AboutFV from 'components/AboutFV' import AppData from 'components/App/AppData' +import AppWrapper from 'components/App/AppWrapper' import { AudiobarProvider } from 'context/AudiobarContext' import { NotificationProvider } from 'context/NotificationContext' -import AppWrapper from 'components/App/AppWrapper' +import About from 'components/About' +import MobileApps from 'components/MobileApps' import ConditionsOfUse from 'components/ConditionsOfUse' import Disclaimer from 'components/Disclaimer' +import DocHead from 'components/DocHead' import ErrorHandler from 'components/ErrorHandler' -import FVApps from 'components/FVApps' import Keyboards from 'components/Keyboards' import LandingPage from 'components/LandingPage' import Languages from 'components/Languages' @@ -29,132 +30,128 @@ function AppContainer() { return ( - - FirstVoices - - }> - - - - - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - {/* Redirect legacy URLs */} - } - /> - } - /> - } - /> - } - /> - } - /> - } /> - } /> - {/* End of legacy URLs */} + + + + + + + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + {/* Redirect legacy URLs */} + } + /> + } + /> + } + /> + } + /> + } + /> + } /> + } /> + {/* End of legacy URLs */} - } /> - - - + } /> + + + + ) diff --git a/src/components/ByAlphabet/ByAlphabetContainer.js b/src/components/ByAlphabet/ByAlphabetContainer.js index e114b570..21e53806 100644 --- a/src/components/ByAlphabet/ByAlphabetContainer.js +++ b/src/components/ByAlphabet/ByAlphabetContainer.js @@ -5,6 +5,7 @@ import PropTypes from 'prop-types' import ByAlphabetPresentation from 'components/ByAlphabet/ByAlphabetPresentation' import ByAlphabetData from 'components/ByAlphabet/ByAlphabetData' import Loading from 'components/Loading' +import SiteDocHead from 'components/SiteDocHead' function ByAlphabetContainer({ kids = null }) { const { @@ -26,6 +27,10 @@ function ByAlphabetContainer({ kids = null }) { } = ByAlphabetData({ kids }) return ( + + + +
diff --git a/src/components/Dashboard/DashboardContainer.js b/src/components/Dashboard/DashboardContainer.js index d978804f..142668b7 100644 --- a/src/components/Dashboard/DashboardContainer.js +++ b/src/components/Dashboard/DashboardContainer.js @@ -13,11 +13,13 @@ import DashboardMedia from 'components/DashboardMedia' import DashboardReports from 'components/DashboardReports' import Loading from 'components/Loading' import { ASSISTANT } from 'common/constants/roles' +import SiteDocHead from 'components/SiteDocHead' function DashboardContainer() { const { currentUser, site, homeTiles, isLoading, logout } = DashboardData() return ( + + +
+