Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
Move newsletter pages to app router
Browse files Browse the repository at this point in the history
  • Loading branch information
acouch committed May 22, 2024
1 parent 1e261e3 commit b509ef8
Show file tree
Hide file tree
Showing 10 changed files with 435 additions and 439 deletions.
217 changes: 217 additions & 0 deletions frontend/src/app/[locale]/newsletter/NewsletterForm.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>) => {
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<HTMLFormElement>) => {
e.preventDefault();
setFormSubmitted(true);
submitForm().catch((err) => {
console.error("catch block", err);
});
};

return (
<form data-testid="sendy-form" onSubmit={handleSubmit} noValidate>
{sendyError ? (
<Alert
type="error"
heading={
sendyError === "Already subscribed."
? "You’re already signed up!"
: "An error occurred"
}
headingLevel="h3"
>
{t.rich(
sendyError === "Already subscribed."
? "errors.already_subscribed"
: "errors.sendy",
{
email: (chunks) => (
<a
href={`mailto:${email}`}
target="_blank"
rel="noopener noreferrer"
>
{chunks}
</a>
),
sendy_error: (chunks) => (
<a
href={`mailto:${sendyError}`}
target="_blank"
rel="noopener noreferrer"
>
{chunks}
</a>
),
email_address: (chunks) => (
<a
href={`mailto:${erroredEmail}`}
target="_blank"
rel="noopener noreferrer"
>
{chunks}
</a>
),
},
)}
</Alert>
) : (
<></>
)}
<FormGroup error={showError("name")}>
<Label htmlFor="name">
First Name{" "}
<span title="required" className="usa-hint usa-hint--required ">
(required)
</span>
</Label>
{showError("name") ? (
<ErrorMessage className="maxw-mobile-lg">
{validateField("name")}
</ErrorMessage>
) : (
<></>
)}
<TextInput
aria-required
type="text"
name="name"
id="name"
value={formData.name}
onChange={handleInput}
/>
</FormGroup>
<Label htmlFor="LastName" hint=" (optional)">
Last Name
</Label>
<TextInput
type="text"
name="LastName"
id="LastName"
value={formData.LastName}
onChange={handleInput}
/>
<FormGroup error={showError("email")}>
<Label htmlFor="email">
Email{" "}
<span title="required" className="usa-hint usa-hint--required ">
(required)
</span>
</Label>
{showError("email") ? (
<ErrorMessage className="maxw-mobile-lg">
{validateField("email")}
</ErrorMessage>
) : (
<></>
)}
<TextInput
aria-required
type="email"
name="email"
id="email"
value={formData.email}
onChange={handleInput}
/>
</FormGroup>
<div className="display-none">
<Label htmlFor="hp">HP</Label>
<TextInput
type="text"
name="hp"
id="hp"
value={formData.hp}
onChange={handleInput}
/>
</div>
<Button type="submit" name="submit" id="submit" className="margin-top-4">
Subscribe
</Button>
</form>
);
}
66 changes: 66 additions & 0 deletions frontend/src/app/[locale]/newsletter/confirmation/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<PageSEO title={t("page_title")} description={t("intro")} />
<BetaAlert />
<Breadcrumbs breadcrumbList={NEWSLETTER_CONFIRMATION_CRUMBS} />

<GridContainer className="padding-bottom-5 tablet:padding-top-0 desktop-lg:padding-top-0 border-bottom-2px border-base-lightest">
<h1 className="margin-0 tablet-lg:font-sans-xl desktop-lg:font-sans-2xl">
{t("title")}
</h1>
<p className="usa-intro font-sans-md tablet:font-sans-lg desktop-lg:font-sans-xl margin-bottom-0">
{t("intro")}
</p>
<Grid row gap className="flex-align-start">
<Grid tabletLg={{ col: 6 }}>
<p className="usa-intro">{t("paragraph_1")}</p>
</Grid>
<Grid tabletLg={{ col: 6 }}>
<h2 className="tablet-lg:font-sans-lg tablet-lg:margin-bottom-05">
{t("heading")}
</h2>
<p className="margin-top-0 font-sans-md line-height-sans-4 desktop-lg:line-height-sans-6">
{t.rich("paragraph_2", {
strong: (chunks) => <strong>{chunks}</strong>,
"process-link": (chunks) => (
<Link href="/process">{chunks}</Link>
),
"research-link": (chunks) => (
<Link href="/research">{chunks}</Link>
),
})}
</p>
</Grid>
</Grid>
</GridContainer>
<GridContainer className="padding-bottom-5 tablet:padding-top-3 desktop-lg:padding-top-3">
<p className="font-sans-3xs text-base-dark">{t("disclaimer")}</p>
</GridContainer>
</>
);
}
71 changes: 71 additions & 0 deletions frontend/src/app/[locale]/newsletter/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<PageSEO title={t("page_title")} description={t("intro")} />
<BetaAlert />
<Breadcrumbs breadcrumbList={NEWSLETTER_CRUMBS} />

<GridContainer className="padding-bottom-5 tablet:padding-top-0 desktop-lg:padding-top-0 border-bottom-2px border-base-lightest">
<h1 className="margin-0 tablet-lg:font-sans-xl desktop-lg:font-sans-2xl">
{t("title")}
</h1>
<p className="usa-intro font-sans-md tablet:font-sans-lg desktop-lg:font-sans-xl margin-bottom-0">
{t("intro")}
</p>
<Grid row gap className="flex-align-start">
<Grid tabletLg={{ col: 6 }}>
<p className="usa-intro">{t("paragraph_1")}</p>
{t.rich("list", {
ul: (chunks) => (
<ul className="usa-list margin-top-0 tablet-lg:margin-top-3 font-sans-md line-height-sans-4">
{chunks}
</ul>
),
li: (chunks) => <li>{chunks}</li>,
})}
</Grid>
<Grid tabletLg={{ col: 6 }}>
<NextIntlClientProvider
locale="en"
messages={pick(messages, "Newsletter")}
>
<NewsletterForm />
</NextIntlClientProvider>
</Grid>
</Grid>
</GridContainer>
<GridContainer className="padding-bottom-5 tablet:padding-top-3 desktop-lg:padding-top-3">
<p className="font-sans-3xs text-base-dark">{t("disclaimer")}</p>
</GridContainer>
</>
);
}
Loading

0 comments on commit b509ef8

Please sign in to comment.