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 (
+
+
+
+