diff --git a/src/app/contexts/layout.tsx b/src/app/contexts/layout.tsx
index 8d8a61f1b..ce5af9305 100644
--- a/src/app/contexts/layout.tsx
+++ b/src/app/contexts/layout.tsx
@@ -18,17 +18,19 @@ type LayoutParameters = {
const defaultLayoutParameters: LayoutParameters = {name: 'default'};
function useContextValue() {
- const [layoutParameters, setLayoutParameters] = React.useReducer(
- (state: LayoutParameters, newState: LayoutParameters) => {
- if (newState === undefined) {
- return defaultLayoutParameters;
+ const [layoutParameters, setLayoutParameters] = React.useState(defaultLayoutParameters);
+ const updateIfNotEqual = React.useCallback(
+ (newValue?: LayoutParameters) => {
+ if (newValue === undefined) {
+ setLayoutParameters(defaultLayoutParameters);
+ return;
}
- if (deepEqual(state, newState)) {
- return state;
+ if (deepEqual(layoutParameters, newValue)) {
+ return;
}
- return newState;
+ setLayoutParameters(newValue);
},
- defaultLayoutParameters
+ [layoutParameters]
);
const LoadableLayout = React.useMemo(
() =>
@@ -45,7 +47,7 @@ function useContextValue() {
[LoadableLayout, layoutParameters.data]
);
- return {Layout, setLayoutParameters, layoutParameters};
+ return {Layout, setLayoutParameters: updateIfNotEqual, layoutParameters};
}
const {useContext, ContextProvider} = buildContext({useContextValue});
diff --git a/src/app/pages/about/about.js b/src/app/pages/about/about.tsx
similarity index 57%
rename from src/app/pages/about/about.js
rename to src/app/pages/about/about.tsx
index ef5d13779..a70a54b07 100644
--- a/src/app/pages/about/about.js
+++ b/src/app/pages/about/about.tsx
@@ -7,9 +7,22 @@ import './about.scss';
const slug = 'pages/about';
-function translateCard(c) {
- const imgEntry = c.find((v) => v.type === 'image');
- const textEntry = c.find((v) => v.type === 'paragraph');
+type ImageCard = {
+ type: 'image';
+ value: {
+ image: string;
+ link: string;
+ };
+};
+type TextCard = {
+ type: 'paragraph';
+ value: string;
+};
+type RawCard = [ImageCard, TextCard];
+
+function translateCard(c: RawCard) {
+ const imgEntry = c.find((v) => v.type === 'image') as ImageCard;
+ const textEntry = c.find((v) => v.type === 'paragraph') as TextCard;
return {
image: imgEntry.value.image,
@@ -18,29 +31,61 @@ function translateCard(c) {
};
}
-function Card({link, image, text}) {
+function Card({
+ link,
+ image,
+ text
+}: {
+ link: string;
+ image: string;
+ text: string;
+}) {
const optimizedImage = useOptimizedImage(image);
return (
-
- {text}
-
+ {text}
);
}
-function About({data: {
- whoHeading, whoParagraph, whoImageUrl,
- whatHeading, whatParagraph, whatCards,
- whereHeading, whereParagraph, whereMapUrl: map, whereMapAlt
-}}) {
+type AboutData = {
+ whoHeading: string;
+ whoParagraph: string;
+ whoImageUrl: string;
+ whatHeading: string;
+ whatParagraph: string;
+ whatCards: RawCard[];
+ whereHeading: string;
+ whereParagraph: string;
+ whereMapUrl: string;
+ whereMapAlt: string;
+};
+
+function About({
+ data: {
+ whoHeading,
+ whoParagraph,
+ whoImageUrl,
+ whatHeading,
+ whatParagraph,
+ whatCards,
+ whereHeading,
+ whereParagraph,
+ whereMapUrl: map,
+ whereMapAlt
+ }
+}: {
+ data: AboutData;
+}) {
const cards = React.useMemo(
- () => (whatCards || []).map(translateCard),
+ () => whatCards.map(translateCard),
[whatCards]
);
- const mapAlt = whereMapAlt || 'animated map suggesting where our books are being adopted';
+ const mapAlt =
+ whereMapAlt ||
+ 'animated map suggesting where our books are being adopted';
const maxDim = window.innerWidth < 1920 ? 1015 : null;
const optimizedWhoImage = useOptimizedImage(whoImageUrl, maxDim);
@@ -63,13 +108,14 @@ function About({data: {
- {cards.map(
- ({link, image, text}) =>
-
- )}
+ {cards.map(({link, image, text}) => (
+
+ ))}
@@ -92,7 +138,5 @@ function About({data: {
}
export default function AboutLoader() {
- return (
-
- );
+ return ;
}
diff --git a/src/app/pages/adopters/adopters.js b/src/app/pages/adopters/adopters.js
deleted file mode 100644
index 5ad1ab557..000000000
--- a/src/app/pages/adopters/adopters.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import usePageData from '~/helpers/use-page-data';
-import './adopters.scss';
-
-export default function Adopters() {
- const pageData = usePageData('adopters');
-
- if (!pageData) {
- return null;
- }
- if (pageData.error) {
- return (
-
-
Data missing in the CMS
-
- );
- }
-
- return (
-
- Complete list of institutions that have adopted OpenStax
-
- {
- pageData.map((adopter) =>
- - {adopter.name}
- )
- }
-
-
- );
-}
diff --git a/src/app/pages/adopters/adopters.tsx b/src/app/pages/adopters/adopters.tsx
new file mode 100644
index 000000000..f86ecf134
--- /dev/null
+++ b/src/app/pages/adopters/adopters.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import LoaderPage from '~/components/jsx-helpers/loader-page';
+import './adopters.scss';
+
+const slug = 'adopters';
+
+type Data = {
+ name: string;
+ description: string;
+ website: string;
+};
+
+function Adopters({data}: {data: Data[]}) {
+ return (
+
+ Complete list of institutions that have adopted OpenStax
+
+ {data.map((adopter) => (
+ -
+ {adopter.name}
+
+ ))}
+
+
+ );
+}
+
+export default function AdoptersLoader() {
+ return ;
+}
diff --git a/test/helpers/mock-lazyload.tsx b/test/helpers/mock-lazyload.tsx
new file mode 100644
index 000000000..8f843e3c9
--- /dev/null
+++ b/test/helpers/mock-lazyload.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+
+jest.mock('react-lazyload', () => ({
+ __esModule: true,
+ default: ({children}: React.PropsWithChildren