From d24e85f4d9a9be80f65ec96ed2926c50e1aac55b Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 16:42:07 -0400 Subject: [PATCH 01/26] Move process page to app --- .../src/app/[locale]/process/ProcessIntro.tsx | 51 ++++++ .../app/[locale]/process/ProcessInvolved.tsx | 67 ++++++++ .../[locale]/process}/ProcessMilestones.tsx | 147 ++++++++---------- frontend/src/app/[locale]/process/page.tsx | 38 +++++ .../[locale]/research/ResearchArchetypes.tsx | 135 ++++++++++++++++ .../app/[locale]/research/ResearchImpact.tsx | 80 ++++++++++ .../app/[locale]/research/ResearchIntro.tsx | 25 +++ .../[locale]/research/ResearchMethodology.tsx | 58 +++++++ .../app/[locale]/research/ResearchThemes.tsx | 51 ++++++ frontend/src/app/[locale]/research/page.tsx | 41 +++++ .../content/ProcessAndResearchContent.tsx | 19 +-- frontend/src/pages/content/ProcessIntro.tsx | 58 ------- .../src/pages/content/ProcessInvolved.tsx | 70 --------- frontend/src/pages/process.tsx | 45 ------ frontend/stories/pages/process.stories.tsx | 2 +- .../ProcessAndResearchContent.test.tsx | 4 +- .../tests/components/ProcessIntro.test.tsx | 4 +- .../tests/components/ProcessInvolved.test.tsx | 4 +- .../components/ProcessMilestones.test.tsx | 4 +- frontend/tests/pages/process.test.tsx | 5 +- 20 files changed, 630 insertions(+), 278 deletions(-) create mode 100644 frontend/src/app/[locale]/process/ProcessIntro.tsx create mode 100644 frontend/src/app/[locale]/process/ProcessInvolved.tsx rename frontend/src/{pages/content => app/[locale]/process}/ProcessMilestones.tsx (50%) create mode 100644 frontend/src/app/[locale]/process/page.tsx create mode 100644 frontend/src/app/[locale]/research/ResearchArchetypes.tsx create mode 100644 frontend/src/app/[locale]/research/ResearchImpact.tsx create mode 100644 frontend/src/app/[locale]/research/ResearchIntro.tsx create mode 100644 frontend/src/app/[locale]/research/ResearchMethodology.tsx create mode 100644 frontend/src/app/[locale]/research/ResearchThemes.tsx create mode 100644 frontend/src/app/[locale]/research/page.tsx rename frontend/src/{pages => components}/content/ProcessAndResearchContent.tsx (77%) delete mode 100644 frontend/src/pages/content/ProcessIntro.tsx delete mode 100644 frontend/src/pages/content/ProcessInvolved.tsx delete mode 100644 frontend/src/pages/process.tsx diff --git a/frontend/src/app/[locale]/process/ProcessIntro.tsx b/frontend/src/app/[locale]/process/ProcessIntro.tsx new file mode 100644 index 000000000..f3b481318 --- /dev/null +++ b/frontend/src/app/[locale]/process/ProcessIntro.tsx @@ -0,0 +1,51 @@ +import { Grid } from "@trussworks/react-uswds"; +import { useTranslations, useMessages } from "next-intl"; +import ContentLayout from "src/components/ContentLayout"; + +const ProcessIntro = () => { + const t = useTranslations("Process"); + + const messages = useMessages() as unknown as IntlMessages; + const keys = Object.keys(messages.Process.intro.boxes); + + return ( + + + +

+ {t("intro.content")} +

+
+
+ + + {keys.map((key) => { + const title = t(`intro.boxes.${key}.title`); + const content = t.rich(`intro.boxes.${key}.content`, { + italics: (chunks) => {chunks}, + }); + return ( + +
+

{title}

+

+ {content} +

+
+
+ ); + })} +
+
+ ); +}; + +export default ProcessIntro; diff --git a/frontend/src/app/[locale]/process/ProcessInvolved.tsx b/frontend/src/app/[locale]/process/ProcessInvolved.tsx new file mode 100644 index 000000000..81294b3f8 --- /dev/null +++ b/frontend/src/app/[locale]/process/ProcessInvolved.tsx @@ -0,0 +1,67 @@ +import { ExternalRoutes } from "src/constants/routes"; + +import { useTranslations } from "next-intl"; +import { Grid } from "@trussworks/react-uswds"; + +import ContentLayout from "src/components/ContentLayout"; + +const ProcessInvolved = () => { + const t = useTranslations("Process"); + + const email = ExternalRoutes.EMAIL_SIMPLERGRANTSGOV; + const para1 = t.rich("involved.paragraph_1", { + email: (chunks) => ( + + {chunks} + + ), + strong: (chunks) => {chunks} , + }); + const para2 = t.rich("involved.paragraph_2", { + github: (chunks) => ( + + {chunks} + + ), + wiki: (chunks) => ( + + {chunks} + + ), + strong: (chunks) => {chunks} , + }); + return ( + + + +

+ {t("involved.title_1")} +

+

+ {para1} +

+
+ +

+ {t("involved.title_2")} +

+

+ {para2} +

+
+
+
+ ); +}; + +export default ProcessInvolved; diff --git a/frontend/src/pages/content/ProcessMilestones.tsx b/frontend/src/app/[locale]/process/ProcessMilestones.tsx similarity index 50% rename from frontend/src/pages/content/ProcessMilestones.tsx rename to frontend/src/app/[locale]/process/ProcessMilestones.tsx index d99c82e17..f275dbd09 100644 --- a/frontend/src/pages/content/ProcessMilestones.tsx +++ b/frontend/src/app/[locale]/process/ProcessMilestones.tsx @@ -1,38 +1,35 @@ import { ExternalRoutes } from "src/constants/routes"; +import React from "react"; -import { Trans, useTranslation } from "next-i18next"; +import { useTranslations, useMessages } from "next-intl"; import Link from "next/link"; import { Button, Grid, - Icon, IconList, IconListContent, IconListIcon, IconListItem, IconListTitle, } from "@trussworks/react-uswds"; +import { USWDSIcon } from "src/components/USWDSIcon"; import ContentLayout from "src/components/ContentLayout"; -type Boxes = { - title: string; - content: string; -}; - const ProcessMilestones = () => { - const { t } = useTranslation("common", { keyPrefix: "Process" }); + const t = useTranslations("Process"); - const iconList: Boxes[] = t("milestones.icon_list", { returnObjects: true }); + const messages = useMessages() as unknown as IntlMessages; + const keys = Object.keys(messages.Process.milestones.icon_list); const getIcon = (iconIndex: number) => { switch (iconIndex) { case 0: - return ; + return ; case 1: - return ; + return ; case 2: - return ; + return ; default: return <>; } @@ -47,50 +44,55 @@ const ProcessMilestones = () => { bottomBorder="dark" gridGap={6} > - {!Array.isArray(iconList) - ? "" - : iconList.map((box, index) => { - return ( - - - - {getIcon(index)} - - - {box.title} - -

- ), - chevron: ( - - ), - }} + {keys.map((key, index) => { + const title = t(`milestones.icon_list.${key}.title`); + const content = t.rich(`milestones.icon_list.${key}.content`, { + p: (chunks) => ( +

+ {chunks} +

+ ), + italics: (chunks) => {chunks}, + }); + + return ( + + + + {getIcon(index)} + + + {title} + +
+ {content} +
+ { + // Don't show the chevron in the last row item. + index < keys.length - 1 ? ( + -
-
-
-
- ); - })} + ) : ( + "" + ) + } +
+
+
+
+ ); + })} {t("milestones.roadmap_1")} - {t("milestones.title_1")} @@ -120,10 +122,9 @@ const ProcessMilestones = () => { @@ -134,10 +135,9 @@ const ProcessMilestones = () => { <> {t("milestones.roadmap_2")} - {t("milestones.title_2")} @@ -156,37 +156,14 @@ const ProcessMilestones = () => {

{t("milestones.sub_title_3")}

-

- - ), - }} - /> -

-

- , - }} - /> -

+

+

diff --git a/frontend/src/app/[locale]/process/page.tsx b/frontend/src/app/[locale]/process/page.tsx new file mode 100644 index 000000000..788627afa --- /dev/null +++ b/frontend/src/app/[locale]/process/page.tsx @@ -0,0 +1,38 @@ +import { PROCESS_CRUMBS } from "src/constants/breadcrumbs"; + +import BetaAlert from "src/components/BetaAlert"; + +import Breadcrumbs from "src/components/Breadcrumbs"; +import PageSEO from "src/components/PageSEO"; +import { Metadata } from "next"; +import ProcessIntro from "src/app/[locale]/process/ProcessIntro"; +import ProcessInvolved from "src/app/[locale]/process/ProcessInvolved"; +import ProcessMilestones from "src/app/[locale]/process/ProcessMilestones"; +import { useTranslations } from "next-intl"; +import { getTranslations } from "next-intl/server"; + +export async function generateMetadata() { + const t = await getTranslations({ locale: "en" }); + const meta: Metadata = { + title: t("Process.page_title"), + description: t("Process.meta_description"), + }; + return meta; +} + +export default function Process() { + const t = useTranslations("Process"); + + return ( + <> + + + + +
+ +
+ + + ); +} diff --git a/frontend/src/app/[locale]/research/ResearchArchetypes.tsx b/frontend/src/app/[locale]/research/ResearchArchetypes.tsx new file mode 100644 index 000000000..adf597d80 --- /dev/null +++ b/frontend/src/app/[locale]/research/ResearchArchetypes.tsx @@ -0,0 +1,135 @@ +import Image from "next/image"; +import { Grid } from "@trussworks/react-uswds"; +import { useTranslations } from "next-intl"; +import ContentLayout from "src/components/ContentLayout"; +import embarrassed from "public/img/noun-embarrassed.svg"; +import goal from "public/img/noun-goal.svg"; +import hiring from "public/img/noun-hiring.svg"; +import leadership from "public/img/noun-leadership.svg"; + +const ResearchArchetypes = () => { + const t = useTranslations("Research"); + + return ( + + +

{t("archetypes.paragraph_1")}

+
+ + + embarrased + + +

+ {t("archetypes.novice.title")} +

+

+ {t("archetypes.novice.paragraph_1")} +

+

+ {t("archetypes.novice.paragraph_2")} +

+
+
+ + + leadership + + +

+ {t("archetypes.collaborator.title")} +

+

+ {t("archetypes.collaborator.paragraph_1")} +

+

+ {t("archetypes.collaborator.paragraph_2")} +

+
+
+ + + goal + + +

+ {t("archetypes.maestro.title")} +

+

+ {t("archetypes.maestro.paragraph_1")} +

+

+ {t("archetypes.maestro.paragraph_2")} +

+
+
+ + + hiring + + +

+ {t("archetypes.supervisor.title")} +

+

+ {t("archetypes.supervisor.paragraph_1")} +

+

+ {t("archetypes.supervisor.paragraph_2")} +

+
+
+
+ ); +}; + +export default ResearchArchetypes; diff --git a/frontend/src/app/[locale]/research/ResearchImpact.tsx b/frontend/src/app/[locale]/research/ResearchImpact.tsx new file mode 100644 index 000000000..14873332f --- /dev/null +++ b/frontend/src/app/[locale]/research/ResearchImpact.tsx @@ -0,0 +1,80 @@ +import { ExternalRoutes } from "src/constants/routes"; +import { useMessages, useTranslations } from "next-intl"; + +import { Grid } from "@trussworks/react-uswds"; + +import ContentLayout from "src/components/ContentLayout"; +import { USWDSIcon } from "src/components/USWDSIcon"; + +const ResearchImpact = () => { + const t = useTranslations("Research"); + + const email = ExternalRoutes.EMAIL_SIMPLERGRANTSGOV; + + const messages = useMessages() as unknown as IntlMessages; + const keys = Object.keys(messages.Research.impact.boxes); + + return ( + + +

{t("impact.paragraph_1")}

+

+ {t("impact.paragraph_2")} +

+
+ + {keys.map((key) => { + const title = t(`impact.boxes.${key}.title`); + const content = t(`impact.boxes.${key}.content`); + return ( + +
+

{title}

+

+ {content} +

+
+
+ ); + })} +
+ +

+ {t("impact.title_2")} +

+

+ {t.rich("impact.paragraph_3", { + email: (chunks) => ( + + {chunks} + + ), + strong: (chunks) => {chunks}, + newsletter: (chunks) => {chunks}, + arrowUpRightFromSquare: () => ( + + ), + })} +

+
+
+ ); +}; + +export default ResearchImpact; diff --git a/frontend/src/app/[locale]/research/ResearchIntro.tsx b/frontend/src/app/[locale]/research/ResearchIntro.tsx new file mode 100644 index 000000000..3f5fd3a14 --- /dev/null +++ b/frontend/src/app/[locale]/research/ResearchIntro.tsx @@ -0,0 +1,25 @@ +import { useTranslations } from "next-intl"; +import { Grid } from "@trussworks/react-uswds"; + +import ContentLayout from "src/components/ContentLayout"; + +const ResearchIntro = () => { + const t = useTranslations("Research"); + + return ( + + +

+ {t("intro.content")} +

+
+
+ ); +}; + +export default ResearchIntro; diff --git a/frontend/src/app/[locale]/research/ResearchMethodology.tsx b/frontend/src/app/[locale]/research/ResearchMethodology.tsx new file mode 100644 index 000000000..41c696c80 --- /dev/null +++ b/frontend/src/app/[locale]/research/ResearchMethodology.tsx @@ -0,0 +1,58 @@ +import { useTranslations } from "next-intl"; +import Link from "next/link"; +import { Button, Grid } from "@trussworks/react-uswds"; +import { USWDSIcon } from "src/components/USWDSIcon"; + +import ContentLayout from "src/components/ContentLayout"; + +const ResearchMethodology = () => { + const t = useTranslations("Research"); + + return ( + + +
+ {t.rich("methodology.paragraph_1", { + p: (chunks) => ( +

+ {chunks} +

+ ), + })} +
+
+ +

+ {t("methodology.title_2")} +

+ {t.rich("methodology.paragraph_2", { + ul: (chunks) => ( +
    + {chunks} +
+ ), + li: (chunks) =>
  • {chunks}
  • , + })} +

    + {t("methodology.title_3")} +

    + + + +
    +
    + ); +}; + +export default ResearchMethodology; diff --git a/frontend/src/app/[locale]/research/ResearchThemes.tsx b/frontend/src/app/[locale]/research/ResearchThemes.tsx new file mode 100644 index 000000000..ceaaf5a08 --- /dev/null +++ b/frontend/src/app/[locale]/research/ResearchThemes.tsx @@ -0,0 +1,51 @@ +import { useTranslations } from "next-intl"; +import { Grid } from "@trussworks/react-uswds"; + +import ContentLayout from "src/components/ContentLayout"; + +const ResearchThemes = () => { + const t = useTranslations("Research"); + + return ( + + +

    {t("themes.paragraph_1")}

    +
    + + +

    {t("themes.title_2")}

    +

    + {t("themes.paragraph_2")} +

    +
    + +

    {t("themes.title_3")}

    +

    + {t("themes.paragraph_3")} +

    +
    +
    + + +

    {t("themes.title_4")}

    +

    + {t("themes.paragraph_4")} +

    +
    + +

    {t("themes.title_5")}

    +

    + {t("themes.paragraph_5")} +

    +
    +
    +
    + ); +}; + +export default ResearchThemes; diff --git a/frontend/src/app/[locale]/research/page.tsx b/frontend/src/app/[locale]/research/page.tsx new file mode 100644 index 000000000..b6fc95076 --- /dev/null +++ b/frontend/src/app/[locale]/research/page.tsx @@ -0,0 +1,41 @@ +import { RESEARCH_CRUMBS } from "src/constants/breadcrumbs"; +import ResearchIntro from "src/app/[locale]/research/ResearchIntro"; + +import Breadcrumbs from "src/components/Breadcrumbs"; +import PageSEO from "src/components/PageSEO"; +import BetaAlert from "src/components/BetaAlert"; +import ResearchArchetypes from "src/app/[locale]/research/ResearchArchetypes"; +import ResearchImpact from "src/app/[locale]/research/ResearchImpact"; +import ResearchMethodology from "src/app/[locale]/research/ResearchMethodology"; +import ResearchThemes from "src/app/[locale]/research/ResearchThemes"; +import { Metadata } from "next"; +import { useTranslations } from "next-intl"; +import { getTranslations } from "next-intl/server"; + +export async function generateMetadata() { + const t = await getTranslations({ locale: "en" }); + const meta: Metadata = { + title: t("Research.page_title"), + description: t("Research.meta_description"), + }; + return meta; +} + +export default function Research() { + const t = useTranslations("Research"); + + return ( + <> + + + + + +
    + + +
    + + + ); +} diff --git a/frontend/src/pages/content/ProcessAndResearchContent.tsx b/frontend/src/components/content/ProcessAndResearchContent.tsx similarity index 77% rename from frontend/src/pages/content/ProcessAndResearchContent.tsx rename to frontend/src/components/content/ProcessAndResearchContent.tsx index 0706f32c3..e07bab385 100644 --- a/frontend/src/pages/content/ProcessAndResearchContent.tsx +++ b/frontend/src/components/content/ProcessAndResearchContent.tsx @@ -1,11 +1,12 @@ -import { useTranslation } from "next-i18next"; +import { useTranslations } from "next-intl"; import Link from "next/link"; -import { Button, Grid, Icon } from "@trussworks/react-uswds"; +import { Button, Grid } from "@trussworks/react-uswds"; +import { USWDSIcon } from "src/components/USWDSIcon"; import ContentLayout from "src/components/ContentLayout"; const ProcessAndResearchContent = () => { - const { t } = useTranslation("common", { keyPrefix: "Index" }); + const t = useTranslations("Index"); return ( { {t("process_and_research.cta_1")} - @@ -44,9 +45,9 @@ const ProcessAndResearchContent = () => { {t("process_and_research.cta_2")} - diff --git a/frontend/src/pages/content/ProcessIntro.tsx b/frontend/src/pages/content/ProcessIntro.tsx deleted file mode 100644 index af14025e8..000000000 --- a/frontend/src/pages/content/ProcessIntro.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Trans, useTranslation } from "next-i18next"; -import { Grid } from "@trussworks/react-uswds"; - -import ContentLayout from "src/components/ContentLayout"; - -type IntroBoxes = { - title: string; - content: string; -}; - -const ProcessIntro = () => { - const { t } = useTranslation("common", { keyPrefix: "Process" }); - - const boxes: IntroBoxes[] = t("intro.boxes", { returnObjects: true }); - - return ( - - - -

    - {t("intro.content")} -

    -
    -
    - - - {!Array.isArray(boxes) - ? "" - : boxes.map((box) => { - return ( - -
    -

    {box.title}

    -

    - }} - /> -

    -
    -
    - ); - })} -
    -
    - ); -}; - -export default ProcessIntro; diff --git a/frontend/src/pages/content/ProcessInvolved.tsx b/frontend/src/pages/content/ProcessInvolved.tsx deleted file mode 100644 index 662a4e3b2..000000000 --- a/frontend/src/pages/content/ProcessInvolved.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { ExternalRoutes } from "src/constants/routes"; - -import { Trans, useTranslation } from "next-i18next"; -import { Grid } from "@trussworks/react-uswds"; - -import ContentLayout from "src/components/ContentLayout"; - -const ProcessInvolved = () => { - const { t } = useTranslation("common", { keyPrefix: "Process" }); - - const email = ExternalRoutes.EMAIL_SIMPLERGRANTSGOV; - - return ( - - - -

    - {t("involved.title_1")} -

    -

    - - ), - }} - /> -

    -
    - -

    - {t("involved.title_2")} -

    -

    - - ), - wiki: ( - - ), - }} - /> -

    -
    -
    -
    - ); -}; - -export default ProcessInvolved; diff --git a/frontend/src/pages/process.tsx b/frontend/src/pages/process.tsx deleted file mode 100644 index 6ebe98a6e..000000000 --- a/frontend/src/pages/process.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { GetStaticProps, NextPage } from "next"; -import { PROCESS_CRUMBS } from "src/constants/breadcrumbs"; - -import { useTranslation } from "next-i18next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; - -import Breadcrumbs from "src/components/Breadcrumbs"; -import PageSEO from "src/components/PageSEO"; -import BetaAlert from "../components/BetaAlert"; -import ProcessContent from "./content/ProcessIntro"; -import ProcessInvolved from "./content/ProcessInvolved"; -import ProcessMilestones from "./content/ProcessMilestones"; - -const Process: NextPage = () => { - const { t } = useTranslation("common"); - // TODO: Remove during move to app router and next-intl upgrade - const beta_strings = { - alert_title: t("Beta_alert.alert_title"), - alert: t("Beta_alert.alert"), - }; - - return ( - <> - - - - -
    - -
    - - - ); -}; - -// Change this to GetServerSideProps if you're using server-side rendering -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const translations = await serverSideTranslations(locale ?? "en"); - return { props: { ...translations } }; -}; - -export default Process; diff --git a/frontend/stories/pages/process.stories.tsx b/frontend/stories/pages/process.stories.tsx index 114658618..f3776d62e 100644 --- a/frontend/stories/pages/process.stories.tsx +++ b/frontend/stories/pages/process.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import Process from "src/pages/process"; +import Process from "src/app/[locale]/process/page"; const meta: Meta = { title: "Pages/Process", diff --git a/frontend/tests/components/ProcessAndResearchContent.test.tsx b/frontend/tests/components/ProcessAndResearchContent.test.tsx index 3c51296b6..12b1293f6 100644 --- a/frontend/tests/components/ProcessAndResearchContent.test.tsx +++ b/frontend/tests/components/ProcessAndResearchContent.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import ProcessAndResearchContent from "src/pages/content/ProcessAndResearchContent"; +import { render, screen } from "tests/react-utils"; +import ProcessAndResearchContent from "src/components/content/ProcessAndResearchContent"; describe("Process Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/components/ProcessIntro.test.tsx b/frontend/tests/components/ProcessIntro.test.tsx index 9febcf13d..d2853aac9 100644 --- a/frontend/tests/components/ProcessIntro.test.tsx +++ b/frontend/tests/components/ProcessIntro.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import ProcessIntro from "src/pages/content/ProcessIntro"; +import { render, screen } from "tests/react-utils"; +import ProcessIntro from "src/app/[locale]/process/ProcessIntro"; describe("Process Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/components/ProcessInvolved.test.tsx b/frontend/tests/components/ProcessInvolved.test.tsx index 9ecd537db..14e6ca9c1 100644 --- a/frontend/tests/components/ProcessInvolved.test.tsx +++ b/frontend/tests/components/ProcessInvolved.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import ProcessInvolved from "src/pages/content/ProcessInvolved"; +import { render, screen } from "tests/react-utils"; +import ProcessInvolved from "src/app/[locale]/process/ProcessInvolved"; describe("Process Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/components/ProcessMilestones.test.tsx b/frontend/tests/components/ProcessMilestones.test.tsx index 9fc4155f4..2b4c2214c 100644 --- a/frontend/tests/components/ProcessMilestones.test.tsx +++ b/frontend/tests/components/ProcessMilestones.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import ProcessMilestones from "src/pages/content/ProcessMilestones"; +import { render, screen } from "tests/react-utils"; +import ProcessMilestones from "src/app/[locale]/process/ProcessMilestones"; describe("Process Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/pages/process.test.tsx b/frontend/tests/pages/process.test.tsx index 50070a6dd..bd9d43929 100644 --- a/frontend/tests/pages/process.test.tsx +++ b/frontend/tests/pages/process.test.tsx @@ -1,6 +1,7 @@ -import { render, screen, waitFor } from "@testing-library/react"; +import { render, screen, waitFor } from "tests/react-utils"; + import { axe } from "jest-axe"; -import Process from "src/pages/process"; +import Process from "src/app/[locale]/process/page"; describe("Process", () => { it("renders alert with grants.gov link", () => { From ee9984c8a47dfb3b7ca78720fd4d067bf2174e50 Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 16:48:45 -0400 Subject: [PATCH 02/26] Move research page to app --- .../src/pages/content/ResearchArchetypes.tsx | 136 ------------------ frontend/src/pages/content/ResearchImpact.tsx | 83 ----------- frontend/src/pages/content/ResearchIntro.tsx | 25 ---- .../src/pages/content/ResearchMethodology.tsx | 62 -------- frontend/src/pages/content/ResearchThemes.tsx | 51 ------- frontend/src/pages/research.tsx | 49 ------- .../components/ReaserchImpact.stories.tsx | 2 +- .../components/ReaserchIntro.stories.tsx | 2 +- .../components/ReaserchThemes.stories.tsx | 2 +- .../components/ResearchArchetypes.stories.tsx | 2 +- .../ResearchMethodology.stories.tsx | 2 +- frontend/stories/pages/research.stories.tsx | 2 +- .../components/ResearchArchetypes.test.tsx | 4 +- .../tests/components/ResearchImpact.test.tsx | 4 +- .../tests/components/ResearchIntro.test.tsx | 4 +- .../components/ResearchMethodology.test.tsx | 4 +- .../tests/components/ResearchThemes.test.tsx | 4 +- frontend/tests/pages/research.test.tsx | 5 +- 18 files changed, 19 insertions(+), 424 deletions(-) delete mode 100644 frontend/src/pages/content/ResearchArchetypes.tsx delete mode 100644 frontend/src/pages/content/ResearchImpact.tsx delete mode 100644 frontend/src/pages/content/ResearchIntro.tsx delete mode 100644 frontend/src/pages/content/ResearchMethodology.tsx delete mode 100644 frontend/src/pages/content/ResearchThemes.tsx delete mode 100644 frontend/src/pages/research.tsx diff --git a/frontend/src/pages/content/ResearchArchetypes.tsx b/frontend/src/pages/content/ResearchArchetypes.tsx deleted file mode 100644 index 80eb6931b..000000000 --- a/frontend/src/pages/content/ResearchArchetypes.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { useTranslation } from "next-i18next"; -import Image from "next/image"; -import { Grid } from "@trussworks/react-uswds"; - -import ContentLayout from "src/components/ContentLayout"; -import embarrassed from "../../../public/img/noun-embarrassed.svg"; -import goal from "../../../public/img/noun-goal.svg"; -import hiring from "../../../public/img/noun-hiring.svg"; -import leadership from "../../../public/img/noun-leadership.svg"; - -const ResearchArchetypes = () => { - const { t } = useTranslation("common", { keyPrefix: "Research" }); - - return ( - - -

    {t("archetypes.paragraph_1")}

    -
    - - - embarrased - - -

    - {t("archetypes.novice.title")} -

    -

    - {t("archetypes.novice.paragraph_1")} -

    -

    - {t("archetypes.novice.paragraph_2")} -

    -
    -
    - - - leadership - - -

    - {t("archetypes.collaborator.title")} -

    -

    - {t("archetypes.collaborator.paragraph_1")} -

    -

    - {t("archetypes.collaborator.paragraph_2")} -

    -
    -
    - - - goal - - -

    - {t("archetypes.maestro.title")} -

    -

    - {t("archetypes.maestro.paragraph_1")} -

    -

    - {t("archetypes.maestro.paragraph_2")} -

    -
    -
    - - - hiring - - -

    - {t("archetypes.supervisor.title")} -

    -

    - {t("archetypes.supervisor.paragraph_1")} -

    -

    - {t("archetypes.supervisor.paragraph_2")} -

    -
    -
    -
    - ); -}; - -export default ResearchArchetypes; diff --git a/frontend/src/pages/content/ResearchImpact.tsx b/frontend/src/pages/content/ResearchImpact.tsx deleted file mode 100644 index f7278de0a..000000000 --- a/frontend/src/pages/content/ResearchImpact.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { ExternalRoutes } from "src/constants/routes"; - -import { Trans, useTranslation } from "next-i18next"; -import Link from "next/link"; -import { Grid, Icon } from "@trussworks/react-uswds"; - -import ContentLayout from "src/components/ContentLayout"; - -type ImpactBoxes = { - title: string; - content: string; -}; - -const ResearchImpact = () => { - const { t } = useTranslation("common", { - keyPrefix: "Research", - }); - const email = ExternalRoutes.EMAIL_SIMPLERGRANTSGOV; - - const boxes: ImpactBoxes[] = t("impact.boxes", { returnObjects: true }); - - return ( - - -

    {t("impact.paragraph_1")}

    -

    - {t("impact.paragraph_2")} -

    -
    - - {!Array.isArray(boxes) - ? "" - : boxes.map((box) => { - return ( - -
    -

    {box.title}

    -

    - {box.content} -

    -
    -
    - ); - })} -
    - -

    - {t("impact.title_2")} -

    -

    - - ), - newsletter: , - arrowUpRightFromSquare: ( - - ), - }} - /> -

    -
    -
    - ); -}; - -export default ResearchImpact; diff --git a/frontend/src/pages/content/ResearchIntro.tsx b/frontend/src/pages/content/ResearchIntro.tsx deleted file mode 100644 index e98168a91..000000000 --- a/frontend/src/pages/content/ResearchIntro.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useTranslation } from "next-i18next"; -import { Grid } from "@trussworks/react-uswds"; - -import ContentLayout from "src/components/ContentLayout"; - -const ResearchIntro = () => { - const { t } = useTranslation("common", { keyPrefix: "Research" }); - - return ( - - -

    - {t("intro.content")} -

    -
    -
    - ); -}; - -export default ResearchIntro; diff --git a/frontend/src/pages/content/ResearchMethodology.tsx b/frontend/src/pages/content/ResearchMethodology.tsx deleted file mode 100644 index 86d904932..000000000 --- a/frontend/src/pages/content/ResearchMethodology.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Trans, useTranslation } from "next-i18next"; -import Link from "next/link"; -import { Button, Grid, Icon } from "@trussworks/react-uswds"; - -import ContentLayout from "src/components/ContentLayout"; - -const ResearchMethodology = () => { - const { t } = useTranslation("common", { keyPrefix: "Research" }); - - return ( - - -
    - - ), - }} - /> -
    -
    - -

    - {t("methodology.title_2")} -

    - - ), - li:
  • , - }} - /> -

    - {t("methodology.title_3")} -

    - - - - - - ); -}; - -export default ResearchMethodology; diff --git a/frontend/src/pages/content/ResearchThemes.tsx b/frontend/src/pages/content/ResearchThemes.tsx deleted file mode 100644 index be8a95103..000000000 --- a/frontend/src/pages/content/ResearchThemes.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useTranslation } from "next-i18next"; -import { Grid } from "@trussworks/react-uswds"; - -import ContentLayout from "src/components/ContentLayout"; - -const ResearchThemes = () => { - const { t } = useTranslation("common", { keyPrefix: "Research" }); - - return ( - - -

    {t("themes.paragraph_1")}

    -
    - - -

    {t("themes.title_2")}

    -

    - {t("themes.paragraph_2")} -

    -
    - -

    {t("themes.title_3")}

    -

    - {t("themes.paragraph_3")} -

    -
    -
    - - -

    {t("themes.title_4")}

    -

    - {t("themes.paragraph_4")} -

    -
    - -

    {t("themes.title_5")}

    -

    - {t("themes.paragraph_5")} -

    -
    -
    -
    - ); -}; - -export default ResearchThemes; diff --git a/frontend/src/pages/research.tsx b/frontend/src/pages/research.tsx deleted file mode 100644 index 01d7c1de5..000000000 --- a/frontend/src/pages/research.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { GetStaticProps, NextPage } from "next"; -import { RESEARCH_CRUMBS } from "src/constants/breadcrumbs"; -import ResearchIntro from "src/pages/content/ResearchIntro"; - -import { useTranslation } from "next-i18next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; - -import Breadcrumbs from "src/components/Breadcrumbs"; -import PageSEO from "src/components/PageSEO"; -import BetaAlert from "../components/BetaAlert"; -import ResearchArchetypes from "./content/ResearchArchetypes"; -import ResearchImpact from "./content/ResearchImpact"; -import ResearchMethodology from "./content/ResearchMethodology"; -import ResearchThemes from "./content/ResearchThemes"; - -const Research: NextPage = () => { - const { t } = useTranslation("common"); - // TODO: Remove during move to app router and next-intl upgrade - const beta_strings = { - alert_title: t("Beta_alert.alert_title"), - alert: t("Beta_alert.alert"), - }; - - return ( - <> - - - - - -
    - - -
    - - - ); -}; - -// Change this to GetServerSideProps if you're using server-side rendering -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const translations = await serverSideTranslations(locale ?? "en"); - return { props: { ...translations } }; -}; - -export default Research; diff --git a/frontend/stories/components/ReaserchImpact.stories.tsx b/frontend/stories/components/ReaserchImpact.stories.tsx index 4d9fb9e4e..5ec6e523a 100644 --- a/frontend/stories/components/ReaserchImpact.stories.tsx +++ b/frontend/stories/components/ReaserchImpact.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import ResearchImpact from "src/pages/content/ResearchImpact"; +import ResearchImpact from "src/app/[locale]/research/ResearchImpact"; const meta: Meta = { title: "Components/Content/Research Impact Content", diff --git a/frontend/stories/components/ReaserchIntro.stories.tsx b/frontend/stories/components/ReaserchIntro.stories.tsx index 1feaa669c..4510f6708 100644 --- a/frontend/stories/components/ReaserchIntro.stories.tsx +++ b/frontend/stories/components/ReaserchIntro.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import ResearchIntro from "src/pages/content/ResearchIntro"; +import ResearchIntro from "src/app/[locale]/research/ResearchIntro"; const meta: Meta = { title: "Components/Content/Research Intro Content", diff --git a/frontend/stories/components/ReaserchThemes.stories.tsx b/frontend/stories/components/ReaserchThemes.stories.tsx index e6da8670d..1eb108e4a 100644 --- a/frontend/stories/components/ReaserchThemes.stories.tsx +++ b/frontend/stories/components/ReaserchThemes.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import ResearchThemes from "src/pages/content/ResearchThemes"; +import ResearchThemes from "src/app/[locale]/research/ResearchThemes"; const meta: Meta = { title: "Components/Content/Research Themes Content", diff --git a/frontend/stories/components/ResearchArchetypes.stories.tsx b/frontend/stories/components/ResearchArchetypes.stories.tsx index 8786dc61d..834ad6183 100644 --- a/frontend/stories/components/ResearchArchetypes.stories.tsx +++ b/frontend/stories/components/ResearchArchetypes.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import ResearchArchetypes from "src/pages/content/ResearchArchetypes"; +import ResearchArchetypes from "src/app/[locale]/research/ResearchArchetypes"; const meta: Meta = { title: "Components/Content/Research Archetypes Content", diff --git a/frontend/stories/components/ResearchMethodology.stories.tsx b/frontend/stories/components/ResearchMethodology.stories.tsx index 47f065d41..0a54bcdee 100644 --- a/frontend/stories/components/ResearchMethodology.stories.tsx +++ b/frontend/stories/components/ResearchMethodology.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import ResearchMethodology from "src/pages/content/ResearchMethodology"; +import ResearchMethodology from "src/app/[locale]/research/ResearchMethodology"; const meta: Meta = { title: "Components/Content/Research Methodology Content", diff --git a/frontend/stories/pages/research.stories.tsx b/frontend/stories/pages/research.stories.tsx index 0d511e6cb..00801f8d5 100644 --- a/frontend/stories/pages/research.stories.tsx +++ b/frontend/stories/pages/research.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import Research from "src/pages/research"; +import Research from "src/app/[locale]/research/page"; const meta: Meta = { title: "Pages/Research", diff --git a/frontend/tests/components/ResearchArchetypes.test.tsx b/frontend/tests/components/ResearchArchetypes.test.tsx index 037fe8bbd..cfcb4bd7b 100644 --- a/frontend/tests/components/ResearchArchetypes.test.tsx +++ b/frontend/tests/components/ResearchArchetypes.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import ResearchArchetypes from "src/pages/content/ResearchArchetypes"; +import { render, screen } from "tests/react-utils"; +import ResearchArchetypes from "src/app/[locale]/research/ResearchArchetypes"; describe("Research Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/components/ResearchImpact.test.tsx b/frontend/tests/components/ResearchImpact.test.tsx index 5ca497b15..ec88d2664 100644 --- a/frontend/tests/components/ResearchImpact.test.tsx +++ b/frontend/tests/components/ResearchImpact.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import ResearchImpact from "src/pages/content/ResearchImpact"; +import { render, screen } from "tests/react-utils"; +import ResearchImpact from "src/app/[locale]/research/ResearchImpact"; describe("Research Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/components/ResearchIntro.test.tsx b/frontend/tests/components/ResearchIntro.test.tsx index 68c471022..f7d1f0f69 100644 --- a/frontend/tests/components/ResearchIntro.test.tsx +++ b/frontend/tests/components/ResearchIntro.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import ResearchIntro from "src/pages/content/ResearchIntro"; +import { render, screen } from "tests/react-utils"; +import ResearchIntro from "src/app/[locale]/research/ResearchIntro"; describe("Research Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/components/ResearchMethodology.test.tsx b/frontend/tests/components/ResearchMethodology.test.tsx index 087873455..388376570 100644 --- a/frontend/tests/components/ResearchMethodology.test.tsx +++ b/frontend/tests/components/ResearchMethodology.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import ResearchMethodology from "src/pages/content/ResearchMethodology"; +import { render, screen } from "tests/react-utils"; +import ResearchMethodology from "src/app/[locale]/research/ResearchMethodology"; describe("Research Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/components/ResearchThemes.test.tsx b/frontend/tests/components/ResearchThemes.test.tsx index dbd44bc94..6d7037dd2 100644 --- a/frontend/tests/components/ResearchThemes.test.tsx +++ b/frontend/tests/components/ResearchThemes.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import ResearchThemes from "src/pages/content/ResearchThemes"; +import { render, screen } from "tests/react-utils"; +import ResearchThemes from "src/app/[locale]/research/ResearchThemes"; describe("Research Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/pages/research.test.tsx b/frontend/tests/pages/research.test.tsx index 3b3035689..c4b55fed6 100644 --- a/frontend/tests/pages/research.test.tsx +++ b/frontend/tests/pages/research.test.tsx @@ -1,6 +1,7 @@ -import { render, screen, waitFor } from "@testing-library/react"; +import { render, screen, waitFor } from "tests/react-utils"; + import { axe } from "jest-axe"; -import Research from "src/pages/research"; +import Research from "src/app/[locale]/research/page"; describe("Research", () => { it("renders alert with grants.gov link", () => { From 49c4c6c8c3c1010cdda19d4115dd3919515437b5 Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 16:50:23 -0400 Subject: [PATCH 03/26] Move health page to app --- frontend/src/app/[locale]/health/page.tsx | 3 +++ frontend/src/pages/health.tsx | 15 --------------- 2 files changed, 3 insertions(+), 15 deletions(-) create mode 100644 frontend/src/app/[locale]/health/page.tsx delete mode 100644 frontend/src/pages/health.tsx diff --git a/frontend/src/app/[locale]/health/page.tsx b/frontend/src/app/[locale]/health/page.tsx new file mode 100644 index 000000000..1c4e40ea2 --- /dev/null +++ b/frontend/src/app/[locale]/health/page.tsx @@ -0,0 +1,3 @@ +export default function Health() { + return <>healthy; +} diff --git a/frontend/src/pages/health.tsx b/frontend/src/pages/health.tsx deleted file mode 100644 index efc1978b1..000000000 --- a/frontend/src/pages/health.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type { GetStaticProps, NextPage } from "next"; - -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import React from "react"; - -const Health: NextPage = () => { - return <>healthy; -}; - -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const translations = await serverSideTranslations(locale ?? "en"); - return { props: { ...translations } }; -}; - -export default Health; From 93a665fe093fecee51e6ea210711acf963bcc487 Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 16:52:11 -0400 Subject: [PATCH 04/26] Move feature flag page to app --- .../dev/feature-flags/FeatureFlagsTable.tsx | 61 +++++++++++++ .../app/[locale]/dev/feature-flags/page.tsx | 30 +++++++ frontend/src/pages/dev/feature-flags.tsx | 87 ------------------- .../tests/pages/dev/feature-flags.test.tsx | 12 +-- 4 files changed, 93 insertions(+), 97 deletions(-) create mode 100644 frontend/src/app/[locale]/dev/feature-flags/FeatureFlagsTable.tsx create mode 100644 frontend/src/app/[locale]/dev/feature-flags/page.tsx delete mode 100644 frontend/src/pages/dev/feature-flags.tsx diff --git a/frontend/src/app/[locale]/dev/feature-flags/FeatureFlagsTable.tsx b/frontend/src/app/[locale]/dev/feature-flags/FeatureFlagsTable.tsx new file mode 100644 index 000000000..604e58015 --- /dev/null +++ b/frontend/src/app/[locale]/dev/feature-flags/FeatureFlagsTable.tsx @@ -0,0 +1,61 @@ +"use client"; +import { useFeatureFlags } from "src/hooks/useFeatureFlags"; + +import React from "react"; +import { Button, Table } from "@trussworks/react-uswds"; + +/** + * View for managing feature flags + */ +export default function FeatureFlagsTable() { + const { featureFlagsManager, mounted, setFeatureFlag } = useFeatureFlags(); + + if (!mounted) { + return null; + } + + return ( + + + + + + + + + + {Object.entries(featureFlagsManager.featureFlags).map( + ([featureName, enabled]) => ( + + + + + + ), + )} + +
    StatusFeature FlagActions
    + {enabled ? "Enabled" : "Disabled"} + {featureName} + + +
    + ); +} diff --git a/frontend/src/app/[locale]/dev/feature-flags/page.tsx b/frontend/src/app/[locale]/dev/feature-flags/page.tsx new file mode 100644 index 000000000..8a25a8328 --- /dev/null +++ b/frontend/src/app/[locale]/dev/feature-flags/page.tsx @@ -0,0 +1,30 @@ +import { Metadata } from "next"; + +import Head from "next/head"; +import React from "react"; +import FeatureFlagsTable from "./FeatureFlagsTable"; + +export function generateMetadata() { + const meta: Metadata = { + title: "Feature flag manager", + }; + + return meta; +} + +/** + * View for managing feature flags + */ +export default function FeatureFlags() { + return ( + <> + + Manage Feature Flags + +
    +

    Manage Feature Flags

    + +
    + + ); +} diff --git a/frontend/src/pages/dev/feature-flags.tsx b/frontend/src/pages/dev/feature-flags.tsx deleted file mode 100644 index a24af1eb5..000000000 --- a/frontend/src/pages/dev/feature-flags.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { NextPage } from "next"; -import { useFeatureFlags } from "src/hooks/useFeatureFlags"; - -import Head from "next/head"; -import React from "react"; -import { Button, Table } from "@trussworks/react-uswds"; - -/** - * Disable this page in production - */ -export function getStaticProps() { - if (process.env.NEXT_PUBLIC_ENVIRONMENT === "prod") { - return { - notFound: true, - }; - } - - return { - props: {}, - }; -} - -/** - * View for managing feature flags - */ -const FeatureFlags: NextPage = () => { - const { featureFlagsManager, mounted, setFeatureFlag } = useFeatureFlags(); - - if (!mounted) { - return null; - } - - return ( - <> - - Manage Feature Flags - -
    -

    Manage Feature Flags

    - - - - - - - - - - {Object.entries(featureFlagsManager.featureFlags).map( - ([featureName, enabled]) => ( - - - - - - ), - )} - -
    StatusFeature FlagActions
    - {enabled ? "Enabled" : "Disabled"} - {featureName} - - -
    -
    - - ); -}; - -export default FeatureFlags; diff --git a/frontend/tests/pages/dev/feature-flags.test.tsx b/frontend/tests/pages/dev/feature-flags.test.tsx index 541aa9e62..9b6a2774a 100644 --- a/frontend/tests/pages/dev/feature-flags.test.tsx +++ b/frontend/tests/pages/dev/feature-flags.test.tsx @@ -3,9 +3,9 @@ */ import { fireEvent, render, screen } from "@testing-library/react"; -import FeatureFlags, { getStaticProps } from "src/pages/dev/feature-flags"; +import FeatureFlags from "src/app/[locale]/dev/feature-flags/page"; -import { mockDefaultFeatureFlags } from "../../utils/FeatureFlagTestUtils"; +import { mockDefaultFeatureFlags } from "tests/utils/FeatureFlagTestUtils"; describe("Feature flags page", () => { const MOCK_DEFAULT_FEATURE_FLAGS = { @@ -43,12 +43,4 @@ describe("Feature flags page", () => { expect(statusElement).toHaveTextContent("Enabled"); }); }); - - it("is disabled in production", () => { - expect(getStaticProps().notFound).toBeUndefined(); - const oldEnv = { ...process.env }; - process.env.NEXT_PUBLIC_ENVIRONMENT = "prod"; - expect(getStaticProps().notFound).toBe(true); - process.env = oldEnv; // Restore old environment - }); }); From 1e8a63e8cfb2c4f5d2490ed7e033a76fe516bc75 Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 16:56:25 -0400 Subject: [PATCH 05/26] Move search page to app router --- .../app/{ => [locale]}/search/SearchForm.tsx | 22 +++++++++---------- .../src/app/{ => [locale]}/search/actions.ts | 6 ++--- .../src/app/{ => [locale]}/search/error.tsx | 2 +- .../src/app/{ => [locale]}/search/loading.tsx | 2 +- .../src/app/{ => [locale]}/search/page.tsx | 15 +++++++------ .../components/search/SearchResultsList.tsx | 2 +- frontend/src/hooks/useSearchFormState.ts | 2 +- frontend/stories/pages/search.stories.tsx | 2 +- 8 files changed, 27 insertions(+), 26 deletions(-) rename frontend/src/app/{ => [locale]}/search/SearchForm.tsx (80%) rename frontend/src/app/{ => [locale]}/search/actions.ts (68%) rename frontend/src/app/{ => [locale]}/search/error.tsx (98%) rename frontend/src/app/{ => [locale]}/search/loading.tsx (88%) rename frontend/src/app/{ => [locale]}/search/page.tsx (71%) diff --git a/frontend/src/app/search/SearchForm.tsx b/frontend/src/app/[locale]/search/SearchForm.tsx similarity index 80% rename from frontend/src/app/search/SearchForm.tsx rename to frontend/src/app/[locale]/search/SearchForm.tsx index 047cfd71c..1f5d60341 100644 --- a/frontend/src/app/search/SearchForm.tsx +++ b/frontend/src/app/[locale]/search/SearchForm.tsx @@ -2,20 +2,20 @@ import SearchPagination, { PaginationPosition, -} from "../../components/search/SearchPagination"; +} from "../../../components/search/SearchPagination"; import { AgencyNamyLookup } from "src/utils/search/generateAgencyNameLookup"; -import { QueryParamData } from "../../services/search/searchfetcher/SearchFetcher"; -import { SearchAPIResponse } from "../../types/search/searchResponseTypes"; -import SearchBar from "../../components/search/SearchBar"; +import { QueryParamData } from "../../../services/search/searchfetcher/SearchFetcher"; +import { SearchAPIResponse } from "../../../types/search/searchResponseTypes"; +import SearchBar from "../../../components/search/SearchBar"; import SearchFilterAgency from "src/components/search/SearchFilterAgency"; -import SearchFilterCategory from "../../components/search/SearchFilterCategory"; -import SearchFilterEligibility from "../../components/search/SearchFilterEligibility"; -import SearchFilterFundingInstrument from "../../components/search/SearchFilterFundingInstrument"; -import SearchOpportunityStatus from "../../components/search/SearchOpportunityStatus"; -import SearchResultsHeader from "../../components/search/SearchResultsHeader"; -import SearchResultsList from "../../components/search/SearchResultsList"; -import { useSearchFormState } from "../../hooks/useSearchFormState"; +import SearchFilterCategory from "../../../components/search/SearchFilterCategory"; +import SearchFilterEligibility from "../../../components/search/SearchFilterEligibility"; +import SearchFilterFundingInstrument from "../../../components/search/SearchFilterFundingInstrument"; +import SearchOpportunityStatus from "../../../components/search/SearchOpportunityStatus"; +import SearchResultsHeader from "../../../components/search/SearchResultsHeader"; +import SearchResultsList from "../../../components/search/SearchResultsList"; +import { useSearchFormState } from "../../../hooks/useSearchFormState"; interface SearchFormProps { initialSearchResults: SearchAPIResponse; diff --git a/frontend/src/app/search/actions.ts b/frontend/src/app/[locale]/search/actions.ts similarity index 68% rename from frontend/src/app/search/actions.ts rename to frontend/src/app/[locale]/search/actions.ts index cfe9b5076..d8a56149b 100644 --- a/frontend/src/app/search/actions.ts +++ b/frontend/src/app/[locale]/search/actions.ts @@ -1,9 +1,9 @@ // All exports in this file are server actions "use server"; -import { FormDataService } from "../../services/search/FormDataService"; -import { SearchAPIResponse } from "../../types/search/searchResponseTypes"; -import { getSearchFetcher } from "../../services/search/searchfetcher/SearchFetcherUtil"; +import { FormDataService } from "../../../services/search/FormDataService"; +import { SearchAPIResponse } from "../../../types/search/searchResponseTypes"; +import { getSearchFetcher } from "../../../services/search/searchfetcher/SearchFetcherUtil"; // Gets MockSearchFetcher or APISearchFetcher based on environment variable const searchFetcher = getSearchFetcher(); diff --git a/frontend/src/app/search/error.tsx b/frontend/src/app/[locale]/search/error.tsx similarity index 98% rename from frontend/src/app/search/error.tsx rename to frontend/src/app/[locale]/search/error.tsx index 3716f9ceb..ddfc71891 100644 --- a/frontend/src/app/search/error.tsx +++ b/frontend/src/app/[locale]/search/error.tsx @@ -8,7 +8,7 @@ import { import PageSEO from "src/components/PageSEO"; import { QueryParamData } from "src/services/search/searchfetcher/SearchFetcher"; import SearchCallToAction from "src/components/search/SearchCallToAction"; -import { SearchForm } from "src/app/search/SearchForm"; +import { SearchForm } from "src/app/[locale]/search/SearchForm"; import { useEffect } from "react"; interface ErrorProps { diff --git a/frontend/src/app/search/loading.tsx b/frontend/src/app/[locale]/search/loading.tsx similarity index 88% rename from frontend/src/app/search/loading.tsx rename to frontend/src/app/[locale]/search/loading.tsx index e9e7487c6..8b4feb238 100644 --- a/frontend/src/app/search/loading.tsx +++ b/frontend/src/app/[locale]/search/loading.tsx @@ -1,5 +1,5 @@ import React from "react"; -import Spinner from "../../components/Spinner"; +import Spinner from "../../../components/Spinner"; export default function Loading() { // TODO (Issue #1937): Use translation utility for strings in this file diff --git a/frontend/src/app/search/page.tsx b/frontend/src/app/[locale]/search/page.tsx similarity index 71% rename from frontend/src/app/search/page.tsx rename to frontend/src/app/[locale]/search/page.tsx index 319d16961..36cb75ef4 100644 --- a/frontend/src/app/search/page.tsx +++ b/frontend/src/app/[locale]/search/page.tsx @@ -1,18 +1,18 @@ import { ServerSideRouteParams, ServerSideSearchParams, -} from "../../types/searchRequestURLTypes"; +} from "../../../types/searchRequestURLTypes"; -import BetaAlert from "../../components/AppBetaAlert"; +import BetaAlert from "src/components/BetaAlert"; import { Metadata } from "next"; import React from "react"; -import SearchCallToAction from "../../components/search/SearchCallToAction"; +import SearchCallToAction from "../../../components/search/SearchCallToAction"; import { SearchForm } from "./SearchForm"; -import { convertSearchParamsToProperTypes } from "../../utils/search/convertSearchParamsToProperTypes"; +import { convertSearchParamsToProperTypes } from "../../../utils/search/convertSearchParamsToProperTypes"; import { generateAgencyNameLookup } from "src/utils/search/generateAgencyNameLookup"; -import { getSearchFetcher } from "../../services/search/searchfetcher/SearchFetcherUtil"; +import { getSearchFetcher } from "../../../services/search/searchfetcher/SearchFetcherUtil"; import { getTranslations } from "next-intl/server"; -import withFeatureFlag from "../../hoc/search/withFeatureFlag"; +import withFeatureFlag from "../../../hoc/search/withFeatureFlag"; const searchFetcher = getSearchFetcher(); @@ -25,10 +25,11 @@ export async function generateMetadata() { const t = await getTranslations({ locale: "en" }); const meta: Metadata = { title: t("Search.title"), + description: t("Index.meta_description"), }; - return meta; } + async function Search({ searchParams }: ServerPageProps) { const convertedSearchParams = convertSearchParamsToProperTypes(searchParams); const initialSearchResults = await searchFetcher.fetchOpportunities( diff --git a/frontend/src/components/search/SearchResultsList.tsx b/frontend/src/components/search/SearchResultsList.tsx index eeddc716b..bca660db1 100644 --- a/frontend/src/components/search/SearchResultsList.tsx +++ b/frontend/src/components/search/SearchResultsList.tsx @@ -1,7 +1,7 @@ "use client"; import { AgencyNamyLookup } from "src/utils/search/generateAgencyNameLookup"; -import Loading from "../../app/search/loading"; +import Loading from "../../app/[locale]/search/loading"; import SearchErrorAlert from "src/components/search/error/SearchErrorAlert"; import { SearchResponseData } from "../../types/search/searchResponseTypes"; import SearchResultsListItem from "./SearchResultsListItem"; diff --git a/frontend/src/hooks/useSearchFormState.ts b/frontend/src/hooks/useSearchFormState.ts index f3a53bef8..0526effd9 100644 --- a/frontend/src/hooks/useSearchFormState.ts +++ b/frontend/src/hooks/useSearchFormState.ts @@ -4,7 +4,7 @@ import { useRef, useState } from "react"; import { QueryParamData } from "../services/search/searchfetcher/SearchFetcher"; import { SearchAPIResponse } from "../types/search/searchResponseTypes"; -import { updateResults } from "../app/search/actions"; +import { updateResults } from "../app/[locale]/search/actions"; import { useFormState } from "react-dom"; import { useSearchParamUpdater } from "./useSearchParamUpdater"; diff --git a/frontend/stories/pages/search.stories.tsx b/frontend/stories/pages/search.stories.tsx index e278ce84e..533c6f483 100644 --- a/frontend/stories/pages/search.stories.tsx +++ b/frontend/stories/pages/search.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import Search from "../../src/app/search/page"; +import Search from "../../src/app/[locale]/search/page"; const meta: Meta = { title: "Pages/Search", From 495f55e8c40e1a554764271c98d1f346e2b28daa Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 16:57:14 -0400 Subject: [PATCH 06/26] Move newsletter pages to app router --- .../[locale]/newsletter/NewsletterForm.tsx | 217 ++++++++++++++ .../[locale]/newsletter/confirmation/page.tsx | 66 +++++ frontend/src/app/[locale]/newsletter/page.tsx | 71 +++++ .../[locale]/newsletter/unsubscribe/page.tsx | 69 +++++ .../src/pages/newsletter/confirmation.tsx | 71 ----- frontend/src/pages/newsletter/index.tsx | 276 ------------------ frontend/src/pages/newsletter/unsubscribe.tsx | 78 ----- .../pages/newsletter/confirmation.test.tsx | 4 +- .../tests/pages/newsletter/index.test.tsx | 18 +- .../pages/newsletter/unsubscribe.test.tsx | 4 +- 10 files changed, 435 insertions(+), 439 deletions(-) create mode 100644 frontend/src/app/[locale]/newsletter/NewsletterForm.tsx create mode 100644 frontend/src/app/[locale]/newsletter/confirmation/page.tsx create mode 100644 frontend/src/app/[locale]/newsletter/page.tsx create mode 100644 frontend/src/app/[locale]/newsletter/unsubscribe/page.tsx delete mode 100644 frontend/src/pages/newsletter/confirmation.tsx delete mode 100644 frontend/src/pages/newsletter/index.tsx delete mode 100644 frontend/src/pages/newsletter/unsubscribe.tsx diff --git a/frontend/src/app/[locale]/newsletter/NewsletterForm.tsx b/frontend/src/app/[locale]/newsletter/NewsletterForm.tsx new file mode 100644 index 000000000..9495a2674 --- /dev/null +++ b/frontend/src/app/[locale]/newsletter/NewsletterForm.tsx @@ -0,0 +1,217 @@ +"use client"; +import { NEWSLETTER_CONFIRMATION } from "src/constants/breadcrumbs"; +import { ExternalRoutes } from "src/constants/routes"; + +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { + Alert, + Button, + ErrorMessage, + FormGroup, + Label, + TextInput, +} from "@trussworks/react-uswds"; + +import { Data } from "src/pages/api/subscribe"; +import { useTranslations } from "next-intl"; + +export default function NewsletterForm() { + const t = useTranslations("Newsletter"); + + const router = useRouter(); + const email = ExternalRoutes.EMAIL_SIMPLERGRANTSGOV; + + const [formSubmitted, setFormSubmitted] = useState(false); + + const [formData, setFormData] = useState({ + name: "", + LastName: "", + email: "", + hp: "", + }); + + const [sendyError, setSendyError] = useState(""); + const [erroredEmail, setErroredEmail] = useState(""); + + const validateField = (fieldName: string) => { + // returns the string "valid" or the i18n key for the error message + const emailRegex = + /^(\D)+(\w)*((\.(\w)+)?)+@(\D)+(\w)*((\.(\D)+(\w)*)+)?(\.)[a-z]{2,}$/g; + if (fieldName === "name" && formData.name === "") + return t("errors.missing_name"); + if (fieldName === "email" && formData.email === "") + return t("errors.missing_email"); + if (fieldName === "email" && !emailRegex.test(formData.email)) + return t("errors.invalid_email"); + return "valid"; + }; + + const showError = (fieldName: string): boolean => + formSubmitted && validateField(fieldName) !== "valid"; + + const handleInput = (e: React.ChangeEvent) => { + const fieldName = e.target.name; + const fieldValue = e.target.value; + + setFormData((prevState) => ({ + ...prevState, + [fieldName]: fieldValue, + })); + }; + + const submitForm = async () => { + const formURL = "api/subscribe"; + if (validateField("email") !== "valid" || validateField("name") !== "valid") + return; + + const res = await fetch(formURL, { + method: "POST", + body: JSON.stringify(formData), + headers: { + Accept: "application/json", + }, + }); + + if (res.ok) { + const { message } = (await res.json()) as Data; + router.push(`${NEWSLETTER_CONFIRMATION.path}?sendy=${message as string}`); + return setSendyError(""); + } else { + const { error } = (await res.json()) as Data; + console.error("client error", error); + setErroredEmail(formData.email); + return setSendyError(error || ""); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setFormSubmitted(true); + submitForm().catch((err) => { + console.error("catch block", err); + }); + }; + + return ( +
    + {sendyError ? ( + + {t.rich( + sendyError === "Already subscribed." + ? "errors.already_subscribed" + : "errors.sendy", + { + email: (chunks) => ( + + {chunks} + + ), + sendy_error: (chunks) => ( + + {chunks} + + ), + email_address: (chunks) => ( + + {chunks} + + ), + }, + )} + + ) : ( + <> + )} + + + {showError("name") ? ( + + {validateField("name")} + + ) : ( + <> + )} + + + + + + + {showError("email") ? ( + + {validateField("email")} + + ) : ( + <> + )} + + +
    + + +
    + + + ); +} diff --git a/frontend/src/app/[locale]/newsletter/confirmation/page.tsx b/frontend/src/app/[locale]/newsletter/confirmation/page.tsx new file mode 100644 index 000000000..e621b610a --- /dev/null +++ b/frontend/src/app/[locale]/newsletter/confirmation/page.tsx @@ -0,0 +1,66 @@ +import { NEWSLETTER_CONFIRMATION_CRUMBS } from "src/constants/breadcrumbs"; + +import Link from "next/link"; +import { Grid, GridContainer } from "@trussworks/react-uswds"; + +import Breadcrumbs from "src/components/Breadcrumbs"; +import PageSEO from "src/components/PageSEO"; +import BetaAlert from "src/components/BetaAlert"; +import { useTranslations } from "next-intl"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; + +export async function generateMetadata() { + const t = await getTranslations({ locale: "en" }); + const meta: Metadata = { + title: t("Newsletter.page_title"), + description: t("Index.meta_description"), + }; + + return meta; +} + +export default function NewsletterConfirmation() { + const t = useTranslations("Newsletter_confirmation"); + + return ( + <> + + + + + +

    + {t("title")} +

    +

    + {t("intro")} +

    + + +

    {t("paragraph_1")}

    +
    + +

    + {t("heading")} +

    +

    + {t.rich("paragraph_2", { + strong: (chunks) => {chunks}, + "process-link": (chunks) => ( + {chunks} + ), + "research-link": (chunks) => ( + {chunks} + ), + })} +

    +
    +
    +
    + +

    {t("disclaimer")}

    +
    + + ); +} diff --git a/frontend/src/app/[locale]/newsletter/page.tsx b/frontend/src/app/[locale]/newsletter/page.tsx new file mode 100644 index 000000000..080aaed5a --- /dev/null +++ b/frontend/src/app/[locale]/newsletter/page.tsx @@ -0,0 +1,71 @@ +import { NEWSLETTER_CRUMBS } from "src/constants/breadcrumbs"; + +import { Grid, GridContainer } from "@trussworks/react-uswds"; +import pick from "lodash/pick"; +import Breadcrumbs from "src/components/Breadcrumbs"; +import PageSEO from "src/components/PageSEO"; +import BetaAlert from "src/components/BetaAlert"; +import NewsletterForm from "src/app/[locale]/newsletter/NewsletterForm"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; +import { + useTranslations, + useMessages, + NextIntlClientProvider, +} from "next-intl"; + +export async function generateMetadata() { + const t = await getTranslations({ locale: "en" }); + const meta: Metadata = { + title: t("Newsletter.page_title"), + description: t("Index.meta_description"), + }; + + return meta; +} + +export default function Newsletter() { + const t = useTranslations("Newsletter"); + const messages = useMessages(); + + return ( + <> + + + + + +

    + {t("title")} +

    +

    + {t("intro")} +

    + + +

    {t("paragraph_1")}

    + {t.rich("list", { + ul: (chunks) => ( +
      + {chunks} +
    + ), + li: (chunks) =>
  • {chunks}
  • , + })} + + + + + + + + + +

    {t("disclaimer")}

    +
    + + ); +} diff --git a/frontend/src/app/[locale]/newsletter/unsubscribe/page.tsx b/frontend/src/app/[locale]/newsletter/unsubscribe/page.tsx new file mode 100644 index 000000000..9a095211a --- /dev/null +++ b/frontend/src/app/[locale]/newsletter/unsubscribe/page.tsx @@ -0,0 +1,69 @@ +import { NEWSLETTER_UNSUBSCRIBE_CRUMBS } from "src/constants/breadcrumbs"; + +import Link from "next/link"; +import { Grid, GridContainer } from "@trussworks/react-uswds"; + +import Breadcrumbs from "src/components/Breadcrumbs"; +import PageSEO from "src/components/PageSEO"; +import BetaAlert from "src/components/BetaAlert"; +import { useTranslations } from "next-intl"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; + +export async function generateMetadata() { + const t = await getTranslations({ locale: "en" }); + const meta: Metadata = { + title: t("Newsletter.page_title"), + description: t("Index.meta_description"), + }; + + return meta; +} + +export default function NewsletterUnsubscribe() { + const t = useTranslations("Newsletter_unsubscribe"); + + return ( + <> + + + + + +

    + {t("title")} +

    +

    + {t("intro")} +

    + + +

    {t("paragraph_1")}

    + + {t("button_resub")} + +
    + +

    + {t("heading")} +

    +

    + {t.rich("paragraph_2", { + strong: (chunks) => {chunks}, + "process-link": (chunks) => ( + {chunks} + ), + "research-link": (chunks) => ( + {chunks} + ), + })} +

    +
    +
    +
    + +

    {t("disclaimer")}

    +
    + + ); +} diff --git a/frontend/src/pages/newsletter/confirmation.tsx b/frontend/src/pages/newsletter/confirmation.tsx deleted file mode 100644 index 3a9abc063..000000000 --- a/frontend/src/pages/newsletter/confirmation.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import type { GetStaticProps, NextPage } from "next"; -import { NEWSLETTER_CONFIRMATION_CRUMBS } from "src/constants/breadcrumbs"; - -import { Trans, useTranslation } from "next-i18next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import Link from "next/link"; -import { Grid, GridContainer } from "@trussworks/react-uswds"; - -import Breadcrumbs from "src/components/Breadcrumbs"; -import PageSEO from "src/components/PageSEO"; -import BetaAlert from "../../components/BetaAlert"; - -const NewsletterConfirmation: NextPage = () => { - const { t } = useTranslation("common"); - - // TODO: Remove during move to app router and next-intl upgrade - const beta_strings = { - alert_title: t("Beta_alert.alert_title"), - alert: t("Beta_alert.alert"), - }; - - return ( - <> - - - - - -

    - {t("Newsletter_confirmation.title")} -

    -

    - {t("Newsletter_confirmation.intro")} -

    - - -

    {t("paragraph_1")}

    -
    - -

    - {t("Newsletter_confirmation.heading")} -

    -

    - , - "research-link": , - }} - /> -

    -
    -
    -
    - -

    - {t("Newsletter.disclaimer")} -

    -
    - - ); -}; - -// Change this to GetServerSideProps if you're using server-side rendering -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const translations = await serverSideTranslations(locale ?? "en"); - return { props: { ...translations } }; -}; - -export default NewsletterConfirmation; diff --git a/frontend/src/pages/newsletter/index.tsx b/frontend/src/pages/newsletter/index.tsx deleted file mode 100644 index c49e03f8c..000000000 --- a/frontend/src/pages/newsletter/index.tsx +++ /dev/null @@ -1,276 +0,0 @@ -import type { GetStaticProps, NextPage } from "next"; -import { - NEWSLETTER_CONFIRMATION, - NEWSLETTER_CRUMBS, -} from "src/constants/breadcrumbs"; -import { ExternalRoutes } from "src/constants/routes"; - -import { Trans, useTranslation } from "next-i18next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { - Alert, - Button, - ErrorMessage, - FormGroup, - Grid, - GridContainer, - Label, - TextInput, -} from "@trussworks/react-uswds"; - -import Breadcrumbs from "src/components/Breadcrumbs"; -import PageSEO from "src/components/PageSEO"; -import BetaAlert from "../../components/BetaAlert"; -import { Data } from "../api/subscribe"; - -const Newsletter: NextPage = () => { - const { t } = useTranslation("common"); - // TODO: Remove during move to app router and next-intl upgrade - const beta_strings = { - alert_title: t("Beta_alert.alert_title"), - alert: t("Beta_alert.alert"), - }; - const router = useRouter(); - const email = ExternalRoutes.EMAIL_SIMPLERGRANTSGOV; - - const [formSubmitted, setFormSubmitted] = useState(false); - - const [formData, setFormData] = useState({ - name: "", - LastName: "", - email: "", - hp: "", - }); - - const [sendyError, setSendyError] = useState(""); - const [erroredEmail, setErroredEmail] = useState(""); - - const validateField = (fieldName: string) => { - // returns the string "valid" or the i18n key for the error message - const emailRegex = - /^(\D)+(\w)*((\.(\w)+)?)+@(\D)+(\w)*((\.(\D)+(\w)*)+)?(\.)[a-z]{2,}$/g; - if (fieldName === "name" && formData.name === "") - return "Newsletter.errors.missing_name"; - if (fieldName === "email" && formData.email === "") - return "Newsletter.errors.missing_email"; - if (fieldName === "email" && !emailRegex.test(formData.email)) - return "Newsletter.errors.invalid_email"; - return "valid"; - }; - - const showError = (fieldName: string): boolean => - formSubmitted && validateField(fieldName) !== "valid"; - - const handleInput = (e: React.ChangeEvent) => { - const fieldName = e.target.name; - const fieldValue = e.target.value; - - setFormData((prevState) => ({ - ...prevState, - [fieldName]: fieldValue, - })); - }; - - const submitForm = async () => { - const formURL = "api/subscribe"; - if (validateField("email") !== "valid" || validateField("name") !== "valid") - return; - - const res = await fetch(formURL, { - method: "POST", - body: JSON.stringify(formData), - headers: { - Accept: "application/json", - }, - }); - - if (res.ok) { - const { message } = (await res.json()) as Data; - await router.push({ - pathname: NEWSLETTER_CONFIRMATION.path, - query: { sendy: message }, - }); - return setSendyError(""); - } else { - const { error }: Data = (await res.json()) as Data; - console.error("client error", error); - setErroredEmail(formData.email); - return setSendyError(error || ""); - } - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - setFormSubmitted(true); - submitForm().catch((err) => { - console.error("catch block", err); - }); - }; - - return ( - <> - - - - - -

    - {t("Newsletter.title")} -

    -

    - {t("Newsletter.intro")} -

    - - -

    {t("Newsletter.paragraph_1")}

    - - ), - li:
  • , - }} - /> - - -
    - {sendyError ? ( - - - ), - }} - /> - - ) : ( - <> - )} - - - {showError("name") ? ( - - {t(validateField("name"))} - - ) : ( - <> - )} - - - - - - - {showError("email") ? ( - - {t(validateField("email"))} - - ) : ( - <> - )} - - -
    - - -
    - - -
    - - - -

    {t("disclaimer")}

    -
    - - ); -}; - -// Change this to GetServerSideProps if you're using server-side rendering -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const translations = await serverSideTranslations(locale ?? "en"); - return { props: { ...translations } }; -}; - -export default Newsletter; diff --git a/frontend/src/pages/newsletter/unsubscribe.tsx b/frontend/src/pages/newsletter/unsubscribe.tsx deleted file mode 100644 index 8c25eca76..000000000 --- a/frontend/src/pages/newsletter/unsubscribe.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import type { GetStaticProps, NextPage } from "next"; -import { NEWSLETTER_UNSUBSCRIBE_CRUMBS } from "src/constants/breadcrumbs"; - -import { Trans, useTranslation } from "next-i18next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import Link from "next/link"; -import { Grid, GridContainer } from "@trussworks/react-uswds"; - -import Breadcrumbs from "src/components/Breadcrumbs"; -import PageSEO from "src/components/PageSEO"; -import BetaAlert from "../../components/BetaAlert"; - -const NewsletterUnsubscribe: NextPage = () => { - const { t } = useTranslation("common"); - // TODO: Remove during move to app router and next-intl upgrade - const beta_strings = { - alert_title: t("Beta_alert.alert_title"), - alert: t("Beta_alert.alert"), - }; - - return ( - <> - - - - - -

    - {t("Newsletter_unsubscribe.title")} -

    -

    - {t("Newsletter_unsubscribe.intro")} -

    - - -

    - {t("Newsletter_unsubscribe.paragraph_1")} -

    - - {t("Newsletter_unsubscribe.button_resub")} - -
    - -

    - {t("Newsletter_unsubscribe.heading")} -

    -

    - , - "research-link": , - }} - /> -

    -
    -
    -
    - -

    - {t("Newsletter_unsubscribe.disclaimer")} -

    -
    - - ); -}; - -// Change this to GetServerSideProps if you're using server-side rendering -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const translations = await serverSideTranslations(locale ?? "en"); - return { props: { ...translations } }; -}; - -export default NewsletterUnsubscribe; diff --git a/frontend/tests/pages/newsletter/confirmation.test.tsx b/frontend/tests/pages/newsletter/confirmation.test.tsx index 16c9fc1d0..7324e9c7c 100644 --- a/frontend/tests/pages/newsletter/confirmation.test.tsx +++ b/frontend/tests/pages/newsletter/confirmation.test.tsx @@ -1,6 +1,6 @@ -import { render, waitFor } from "@testing-library/react"; +import { render, waitFor } from "tests/react-utils"; import { axe } from "jest-axe"; -import NewsletterConfirmation from "src/pages/newsletter/confirmation"; +import NewsletterConfirmation from "src/app/[locale]/newsletter/confirmation/page"; describe("Newsletter", () => { it("passes accessibility scan", async () => { diff --git a/frontend/tests/pages/newsletter/index.test.tsx b/frontend/tests/pages/newsletter/index.test.tsx index 3df1b12ab..9f430ef2b 100644 --- a/frontend/tests/pages/newsletter/index.test.tsx +++ b/frontend/tests/pages/newsletter/index.test.tsx @@ -1,13 +1,12 @@ -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "tests/react-utils"; + import userEvent from "@testing-library/user-event"; import { axe } from "jest-axe"; -import Newsletter from "src/pages/newsletter"; +import Newsletter from "src/app/[locale]/newsletter/page"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; -jest.mock("next/router", () => ({ - useRouter: jest.fn(), -})); +jest.mock("next/navigation"); describe("Newsletter", () => { it("renders signup form with a submit button", () => { @@ -44,10 +43,9 @@ describe("Newsletter", () => { // Wait for the form submission await waitFor(() => { - expect(mockRouter.push).toHaveBeenCalledWith({ - pathname: "/newsletter/confirmation/", - query: { sendy: "Success" }, - }); + expect(mockRouter.push).toHaveBeenCalledWith( + "/newsletter/confirmation/?sendy=Success", + ); }); }); diff --git a/frontend/tests/pages/newsletter/unsubscribe.test.tsx b/frontend/tests/pages/newsletter/unsubscribe.test.tsx index d9b0c06ac..b544f941f 100644 --- a/frontend/tests/pages/newsletter/unsubscribe.test.tsx +++ b/frontend/tests/pages/newsletter/unsubscribe.test.tsx @@ -1,6 +1,6 @@ -import { render, waitFor } from "@testing-library/react"; +import { render, waitFor } from "tests/react-utils"; import { axe } from "jest-axe"; -import NewsletterUnsubscribe from "src/pages/newsletter/unsubscribe"; +import NewsletterUnsubscribe from "src/app/[locale]/newsletter/unsubscribe/page"; describe("Newsletter", () => { it("passes accessibility scan", async () => { From 0d35fb2f58bccaf33a9671d415ab9d1bc91486fd Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 17:16:05 -0400 Subject: [PATCH 07/26] Move home page to app router --- frontend/src/app/[locale]/page.tsx | 31 ++++++++ .../content/IndexGoalContent.tsx | 15 ++-- frontend/src/pages/content/FundingContent.tsx | 70 ------------------- frontend/src/pages/index.tsx | 40 ----------- .../components/FundingContent.stories.tsx | 2 +- .../components/GoalContent.stories.tsx | 2 +- .../components/ProcessContent.stories.tsx | 2 +- frontend/stories/pages/Index.stories.tsx | 2 +- .../tests/components/FundingContent.test.tsx | 4 +- .../tests/components/GoalContent.test.tsx | 4 +- frontend/tests/pages/index.test.tsx | 5 +- 11 files changed, 50 insertions(+), 127 deletions(-) create mode 100644 frontend/src/app/[locale]/page.tsx rename frontend/src/{pages => components}/content/IndexGoalContent.tsx (80%) delete mode 100644 frontend/src/pages/content/FundingContent.tsx delete mode 100644 frontend/src/pages/index.tsx diff --git a/frontend/src/app/[locale]/page.tsx b/frontend/src/app/[locale]/page.tsx new file mode 100644 index 000000000..795c8d84d --- /dev/null +++ b/frontend/src/app/[locale]/page.tsx @@ -0,0 +1,31 @@ +import BetaAlert from "src/components/BetaAlert"; +import PageSEO from "src/components/PageSEO"; +import Hero from "src/components/Hero"; +import IndexGoalContent from "src/components/content/IndexGoalContent"; +import ProcessAndResearchContent from "src/components/content/ProcessAndResearchContent"; +import { Metadata } from "next"; +import { useTranslations } from "next-intl"; +import { getTranslations } from "next-intl/server"; + +export async function generateMetadata() { + const t = await getTranslations({ locale: "en" }); + const meta: Metadata = { + title: t("Index.page_title"), + description: t("Index.meta_description"), + }; + return meta; +} + +export default function Home() { + const t = useTranslations("Index"); + + return ( + <> + + + + + + + ); +} diff --git a/frontend/src/pages/content/IndexGoalContent.tsx b/frontend/src/components/content/IndexGoalContent.tsx similarity index 80% rename from frontend/src/pages/content/IndexGoalContent.tsx rename to frontend/src/components/content/IndexGoalContent.tsx index 83d3c4d69..a1e064e6a 100644 --- a/frontend/src/pages/content/IndexGoalContent.tsx +++ b/frontend/src/components/content/IndexGoalContent.tsx @@ -1,11 +1,12 @@ -import { useTranslation } from "next-i18next"; -import Link from "next/link"; -import { Button, Grid, Icon } from "@trussworks/react-uswds"; +import { useTranslations } from "next-intl"; +import Link from "next/link"; +import { Button, Grid } from "@trussworks/react-uswds"; +import { USWDSIcon } from "../USWDSIcon"; import ContentLayout from "src/components/ContentLayout"; const IndexGoalContent = () => { - const { t } = useTranslation("common", { keyPrefix: "Index" }); + const t = useTranslations("Index"); return ( { diff --git a/frontend/src/pages/content/FundingContent.tsx b/frontend/src/pages/content/FundingContent.tsx deleted file mode 100644 index 60f0900e4..000000000 --- a/frontend/src/pages/content/FundingContent.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { nofoPdfs } from "src/constants/nofoPdfs"; - -import { useTranslation } from "next-i18next"; -import { Grid, GridContainer } from "@trussworks/react-uswds"; - -import NofoImageLink from "../../components/NofoImageLink"; - -const FundingContent = () => { - const { t } = useTranslation("common", { keyPrefix: "Index" }); - - return ( -
    - -

    - {t("fo_title")} -

    - - -

    - {t("fo_paragraph_1")} -

    -
    - -

    - {t("fo_paragraph_2")} -

    -
    -
    - -

    - {t("fo_title_2")} -

    - -

    {t("fo_paragraph_3")}

    - - - {nofoPdfs.map((pdf) => ( - - ))} - - - - -

    - {t("fo_title_3")} -

    -

    {t("fo_paragraph_4")}

    -
    - -

    - {t("fo_title_4")} -

    -

    - {t("fo_paragraph_5")} -

    -
    -
    -
    -
    - ); -}; - -export default FundingContent; diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx deleted file mode 100644 index a77846037..000000000 --- a/frontend/src/pages/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { GetStaticProps, NextPage } from "next"; - -import { useTranslation } from "next-i18next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; - -import BetaAlert from "../components/BetaAlert"; -import PageSEO from "src/components/PageSEO"; -import Hero from "../components/Hero"; -import IndexGoalContent from "./content/IndexGoalContent"; -import ProcessAndResearchContent from "./content/ProcessAndResearchContent"; - -const Home: NextPage = () => { - const { t } = useTranslation("common"); - // TODO: Remove during move to app router and next-intl upgrade - const beta_strings = { - alert_title: t("Beta_alert.alert_title"), - alert: t("Beta_alert.alert"), - }; - - return ( - <> - - - - - - - ); -}; - -// Change this to GetServerSideProps if you're using server-side rendering -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const translations = await serverSideTranslations(locale ?? "en"); - return { props: { ...translations } }; -}; - -export default Home; diff --git a/frontend/stories/components/FundingContent.stories.tsx b/frontend/stories/components/FundingContent.stories.tsx index 95465c0c1..7b00f3ad8 100644 --- a/frontend/stories/components/FundingContent.stories.tsx +++ b/frontend/stories/components/FundingContent.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import FundingContent from "src/pages/content/FundingContent"; +import FundingContent from "src/components/content/FundingContent"; const meta: Meta = { title: "Components/Content/Funding Content", diff --git a/frontend/stories/components/GoalContent.stories.tsx b/frontend/stories/components/GoalContent.stories.tsx index 8d581a32b..0606184c5 100644 --- a/frontend/stories/components/GoalContent.stories.tsx +++ b/frontend/stories/components/GoalContent.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import GoalContent from "src/pages/content/IndexGoalContent"; +import GoalContent from "src/components/content/IndexGoalContent"; const meta: Meta = { title: "Components/Content/Goal Content", diff --git a/frontend/stories/components/ProcessContent.stories.tsx b/frontend/stories/components/ProcessContent.stories.tsx index 736eea64b..4557b76c5 100644 --- a/frontend/stories/components/ProcessContent.stories.tsx +++ b/frontend/stories/components/ProcessContent.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import ProcessContent from "src/pages/content/ProcessIntro"; +import ProcessContent from "src/app/[locale]/process/ProcessIntro"; const meta: Meta = { title: "Components/Content/Process Content", diff --git a/frontend/stories/pages/Index.stories.tsx b/frontend/stories/pages/Index.stories.tsx index 669c8741e..4afb9c965 100644 --- a/frontend/stories/pages/Index.stories.tsx +++ b/frontend/stories/pages/Index.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import Index from "src/pages/index"; +import Index from "src/app/[locale]/page"; const meta: Meta = { title: "Pages/Home", diff --git a/frontend/tests/components/FundingContent.test.tsx b/frontend/tests/components/FundingContent.test.tsx index 2f52a21d2..79c6db0c0 100644 --- a/frontend/tests/components/FundingContent.test.tsx +++ b/frontend/tests/components/FundingContent.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import FundingContent from "src/pages/content/FundingContent"; +import { render, screen } from "tests/react-utils"; +import FundingContent from "src/components/content/FundingContent"; describe("Funding Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/components/GoalContent.test.tsx b/frontend/tests/components/GoalContent.test.tsx index 96127f5f6..20852f2fe 100644 --- a/frontend/tests/components/GoalContent.test.tsx +++ b/frontend/tests/components/GoalContent.test.tsx @@ -1,5 +1,5 @@ -import { render, screen } from "@testing-library/react"; -import GoalContent from "src/pages/content/IndexGoalContent"; +import { render, screen } from "tests/react-utils"; +import GoalContent from "src/components/content/IndexGoalContent"; describe("Goal Content", () => { it("Renders without errors", () => { diff --git a/frontend/tests/pages/index.test.tsx b/frontend/tests/pages/index.test.tsx index 67e1280f6..6924cbbd0 100644 --- a/frontend/tests/pages/index.test.tsx +++ b/frontend/tests/pages/index.test.tsx @@ -1,6 +1,7 @@ -import { render, screen, waitFor } from "@testing-library/react"; +import { waitFor, screen, render } from "tests/react-utils"; + import { axe } from "jest-axe"; -import Index from "src/pages/index"; +import Index from "src/app/[locale]/page"; describe("Index", () => { it("renders alert with grants.gov link", () => { From 3f35ec648745f23d65712e05ce8c165dc9727541 Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 17:16:23 -0400 Subject: [PATCH 08/26] Delete hello api --- frontend/src/pages/api/hello.ts | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 frontend/src/pages/api/hello.ts diff --git a/frontend/src/pages/api/hello.ts b/frontend/src/pages/api/hello.ts deleted file mode 100644 index ea77e8f35..000000000 --- a/frontend/src/pages/api/hello.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from "next"; - -type Data = { - name: string; -}; - -export default function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - res.status(200).json({ name: "John Doe" }); -} From 50ae02da72838ad02c20cbcb3eedc7814a1da77f Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 17:17:37 -0400 Subject: [PATCH 09/26] Move home page to app router --- .../src/components/content/FundingContent.tsx | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 frontend/src/components/content/FundingContent.tsx diff --git a/frontend/src/components/content/FundingContent.tsx b/frontend/src/components/content/FundingContent.tsx new file mode 100644 index 000000000..b72dc07be --- /dev/null +++ b/frontend/src/components/content/FundingContent.tsx @@ -0,0 +1,70 @@ +import { nofoPdfs } from "src/constants/nofoPdfs"; + +import { useTranslations } from "next-intl"; +import { Grid, GridContainer } from "@trussworks/react-uswds"; + +import NofoImageLink from "../../components/NofoImageLink"; + +const FundingContent = () => { + const t = useTranslations("Index"); + + return ( +
    + +

    + {t("fo_title")} +

    + + +

    + {t("fo_paragraph_1")} +

    +
    + +

    + {t("fo_paragraph_2")} +

    +
    +
    + +

    + {t("fo_title_2")} +

    + +

    {t("fo_paragraph_3")}

    + + + {nofoPdfs.map((pdf) => ( + + ))} + + + + +

    + {t("fo_title_3")} +

    +

    {t("fo_paragraph_4")}

    +
    + +

    + {t("fo_title_4")} +

    +

    + {t("fo_paragraph_5")} +

    +
    +
    +
    +
    + ); +}; + +export default FundingContent; From 2b85d7f9049687f825e718262e3665b2bca109e4 Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 17:18:35 -0400 Subject: [PATCH 10/26] Add USWDS icon component --- frontend/public/img/uswds-sprite.svg | 1 + frontend/src/components/USWDSIcon.tsx | 23 ++++++++++++++++++++ frontend/tests/components/USWDSIcon.test.tsx | 12 ++++++++++ 3 files changed, 36 insertions(+) create mode 100644 frontend/public/img/uswds-sprite.svg create mode 100644 frontend/src/components/USWDSIcon.tsx create mode 100644 frontend/tests/components/USWDSIcon.test.tsx diff --git a/frontend/public/img/uswds-sprite.svg b/frontend/public/img/uswds-sprite.svg new file mode 100644 index 000000000..8ae1eca92 --- /dev/null +++ b/frontend/public/img/uswds-sprite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/USWDSIcon.tsx b/frontend/src/components/USWDSIcon.tsx new file mode 100644 index 000000000..7414d8978 --- /dev/null +++ b/frontend/src/components/USWDSIcon.tsx @@ -0,0 +1,23 @@ +import SpriteSVG from "public/img/uswds-sprite.svg"; + +interface IconProps { + name: string; + className: string; + height?: string; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +const sprite_uri = SpriteSVG.src as string; + +export function USWDSIcon(props: IconProps) { + return ( + + ); +} diff --git a/frontend/tests/components/USWDSIcon.test.tsx b/frontend/tests/components/USWDSIcon.test.tsx new file mode 100644 index 000000000..dfb010693 --- /dev/null +++ b/frontend/tests/components/USWDSIcon.test.tsx @@ -0,0 +1,12 @@ +import { render, screen } from "tests/react-utils"; + +import { USWDSIcon } from "src/components/USWDSIcon"; + +describe("USWDSIcon", () => { + it("Renders without errors", () => { + render(); + const icon = screen.getByRole("img", { hidden: true }); + expect(icon).toBeInTheDocument(); + expect(icon).toHaveClass("usa-icon"); + }); +}); From 963341d72d5b0a5ff91c98b45592f56b48d1ff8d Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 17:17:57 -0400 Subject: [PATCH 11/26] Move 404 page to app router --- frontend/src/app/not-found.tsx | 15 ++++++++-- frontend/src/pages/404.tsx | 39 -------------------------- frontend/stories/pages/404.stories.tsx | 2 +- frontend/tests/e2e/404.spec.ts | 20 +++++++++++++ frontend/tests/pages/404.test.tsx | 27 ------------------ 5 files changed, 34 insertions(+), 69 deletions(-) delete mode 100644 frontend/src/pages/404.tsx create mode 100644 frontend/tests/e2e/404.spec.ts delete mode 100644 frontend/tests/pages/404.test.tsx diff --git a/frontend/src/app/not-found.tsx b/frontend/src/app/not-found.tsx index 6615f073a..08416f07f 100644 --- a/frontend/src/app/not-found.tsx +++ b/frontend/src/app/not-found.tsx @@ -1,12 +1,23 @@ -import BetaAlert from "src/components/AppBetaAlert"; +import BetaAlert from "src/components/BetaAlert"; import { GridContainer } from "@trussworks/react-uswds"; import Link from "next/link"; import { useTranslations } from "next-intl"; -import { unstable_setRequestLocale } from "next-intl/server"; +import { getTranslations, unstable_setRequestLocale } from "next-intl/server"; +import { Metadata } from "next"; + +export async function generateMetadata() { + const t = await getTranslations({ locale: "en" }); + const meta: Metadata = { + title: t("ErrorPages.page_not_found.title"), + description: t("Index.meta_description"), + }; + return meta; +} export default function NotFound() { unstable_setRequestLocale("en"); const t = useTranslations("ErrorPages.page_not_found"); + return ( <> diff --git a/frontend/src/pages/404.tsx b/frontend/src/pages/404.tsx deleted file mode 100644 index 3543f0e3d..000000000 --- a/frontend/src/pages/404.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { GetStaticProps, NextPage } from "next"; - -import { useTranslation } from "next-i18next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import Link from "next/link"; -import { GridContainer } from "@trussworks/react-uswds"; - -import BetaAlert from "../components/BetaAlert"; - -const PageNotFound: NextPage = () => { - const { t } = useTranslation("common"); - // TODO: Remove during move to app router and next-intl upgrade - const beta_strings = { - alert_title: t("Beta_alert.alert_title"), - alert: t("Beta_alert.alert"), - }; - return ( - <> - - -

    {t("page_not_found.title")}

    -

    - {t("ErrorPages.page_not_found.message_content_1")} -

    - - {t("ErrorPages.page_not_found.visit_homepage_button")} - -
    - - ); -}; - -// Change this to GetServerSideProps if you're using server-side rendering -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const translations = await serverSideTranslations(locale ?? "en"); - return { props: { ...translations } }; -}; - -export default PageNotFound; diff --git a/frontend/stories/pages/404.stories.tsx b/frontend/stories/pages/404.stories.tsx index b3f1fc877..ee03b30f5 100644 --- a/frontend/stories/pages/404.stories.tsx +++ b/frontend/stories/pages/404.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import PageNotFound from "src/pages/404"; +import PageNotFound from "src/app/not-found"; const meta: Meta = { title: "Pages/404", diff --git a/frontend/tests/e2e/404.spec.ts b/frontend/tests/e2e/404.spec.ts new file mode 100644 index 000000000..5335c4b8e --- /dev/null +++ b/frontend/tests/e2e/404.spec.ts @@ -0,0 +1,20 @@ +/* eslint-disable testing-library/prefer-screen-queries */ +import { test, expect } from "@playwright/test"; + +test.beforeEach(async ({ page }) => { + await page.goto("/imnothere"); +}); + +test.afterEach(async ({ context }) => { + await context.close(); +}); + +test("has title", async ({ page }) => { + await expect(page).toHaveTitle("Oops! Page Not Found"); +}); + +test("can view the home button", async ({ page }) => { + await expect(page.getByRole("link", { name: "Return Home" })).toHaveText( + "Return Home", + ); +}); diff --git a/frontend/tests/pages/404.test.tsx b/frontend/tests/pages/404.test.tsx deleted file mode 100644 index c1f99cf22..000000000 --- a/frontend/tests/pages/404.test.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { render, screen, waitFor } from "@testing-library/react"; -import { axe } from "jest-axe"; -import PageNotFound from "src/pages/404"; - -describe("PageNotFound", () => { - it("renders alert with grants.gov link", () => { - render(); - - const alert = screen.queryByTestId("alert"); - - expect(alert).toBeInTheDocument(); - }); - - it("links back to the home page", () => { - render(); - const link = screen.getByRole("link", { name: /Return Home/i }); - - expect(link).toBeInTheDocument(); - }); - - it("passes accessibility scan", async () => { - const { container } = render(); - const results = await waitFor(() => axe(container)); - - expect(results).toHaveNoViolations(); - }); -}); From 416fc94e3d1005a1b1266353e4ee0e6ead467650 Mon Sep 17 00:00:00 2001 From: Aaron Couch Date: Wed, 22 May 2024 17:20:01 -0400 Subject: [PATCH 12/26] Move layout and update for app router --- frontend/src/components/AppLayout.tsx | 59 --------------------- frontend/src/components/Layout.tsx | 64 +++++++---------------- frontend/tests/components/Layout.test.tsx | 10 ++-- 3 files changed, 24 insertions(+), 109 deletions(-) delete mode 100644 frontend/src/components/AppLayout.tsx diff --git a/frontend/src/components/AppLayout.tsx b/frontend/src/components/AppLayout.tsx deleted file mode 100644 index a539f90d5..000000000 --- a/frontend/src/components/AppLayout.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import Footer from "./Footer"; -import GrantsIdentifier from "./GrantsIdentifier"; -import Header from "./Header"; -import { useTranslations } from "next-intl"; - -type Props = { - children: React.ReactNode; - locale: string; -}; - -export default function Layout({ children, locale }: Props) { - const t = useTranslations(); - - const header_strings = { - title: t("Header.title"), - nav_menu_toggle: t("Header.nav_menu_toggle"), - nav_link_home: t("Header.nav_link_home"), - nav_link_search: "Search", - nav_link_process: t("Header.nav_link_process"), - nav_link_research: t("Header.nav_link_research"), - nav_link_newsletter: t("Header.nav_link_newsletter"), - }; - const footer_strings = { - agency_name: t("Footer.agency_name"), - agency_contact_center: t("Footer.agency_contact_center"), - telephone: t("Footer.telephone"), - return_to_top: t("Footer.return_to_top"), - link_twitter: t("Footer.link_twitter"), - link_youtube: t("Footer.link_youtube"), - link_blog: t("Footer.link_blog"), - link_newsletter: t("Footer.link_newsletter"), - link_rss: t("Footer.link_rss"), - link_github: t("Footer.link_github"), - logo_alt: t("Footer.logo_alt"), - }; - - const identifier_strings = { - link_about: t("Identifier.link_about"), - link_accessibility: t("Identifier.link_accessibility"), - link_foia: t("Identifier.link_foia"), - link_fear: t("Identifier.link_fear"), - link_ig: t("Identifier.link_ig"), - link_performance: t("Identifier.link_performance"), - link_privacy: t("Identifier.link_privacy"), - logo_alt: t("Identifier.logo_alt"), - }; - return ( - // Stick the footer to the bottom of the page -
    - - {t("Layout.skip_to_main")} - -
    -
    {children}
    -
    - -
    - ); -} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 7acb26f04..9cce89c04 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,50 +1,21 @@ import Footer from "./Footer"; import GrantsIdentifier from "./GrantsIdentifier"; import Header from "./Header"; -import { useTranslation } from "next-i18next"; +import { + useTranslations, + useMessages, + NextIntlClientProvider, +} from "next-intl"; +import pick from "lodash/pick"; type Props = { children: React.ReactNode; + locale: string; }; -const Layout = ({ children }: Props) => { - const { t } = useTranslation("common"); - - // TODO: Remove during move to app router and next-intl upgrade - const header_strings = { - title: t("Header.title"), - nav_menu_toggle: t("Header.nav_menu_toggle"), - nav_link_home: t("Header.nav_link_home"), - nav_link_search: t("Search"), - nav_link_process: t("Header.nav_link_process"), - nav_link_research: t("Header.nav_link_research"), - nav_link_newsletter: t("Header.nav_link_newsletter"), - }; - - const footer_strings = { - agency_name: t("Footer.agency_name"), - agency_contact_center: t("Footer.agency_contact_center"), - telephone: t("Footer.telephone"), - return_to_top: t("Footer.return_to_top"), - link_twitter: t("Footer.link_twitter"), - link_youtube: t("Footer.link_youtube"), - link_blog: t("Footer.link_blog"), - link_newsletter: t("Footer.link_newsletter"), - link_rss: t("Footer.link_rss"), - link_github: t("Footer.link_github"), - logo_alt: t("Footer.logo_alt"), - }; - - const identifier_strings = { - link_about: t("Identifier.link_about"), - link_accessibility: t("Identifier.link_accessibility"), - link_foia: t("Identifier.link_foia"), - link_fear: t("Identifier.link_fear"), - link_ig: t("Identifier.link_ig"), - link_performance: t("Identifier.link_performance"), - link_privacy: t("Identifier.link_privacy"), - logo_alt: t("Identifier.logo_alt"), - }; +export default function Layout({ children, locale }: Props) { + const t = useTranslations(); + const messages = useMessages(); return ( // Stick the footer to the bottom of the page @@ -52,12 +23,15 @@ const Layout = ({ children }: Props) => { {t("Layout.skip_to_main")} -
    + +
    +
    {children}
    -