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

Commit

Permalink
Bugfix/form validation (#170)
Browse files Browse the repository at this point in the history
* Add form validation to application form

* Add back essay questions to be validated

* unskip tests

* uncomment test in apply

* Fix ordering of components in AppForm

* Fix imports in text test files

* Convert rest of fields to use formik

* Remove react-hook-form
  • Loading branch information
ntsummers1 authored Mar 19, 2024
1 parent e522a25 commit ab23d8c
Show file tree
Hide file tree
Showing 17 changed files with 338 additions and 218 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
"@aws-sdk/lib-dynamodb": "^3.529.1",
"@trussworks/react-uswds": "7.0.0",
"@uswds/uswds": "^3.7.1",
"formik": "^2.4.5",
"mixpanel-browser": "^2.49.0",
"next": "14.1.3",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.51.0",
"sharp": "^0.33.2"
"sharp": "^0.33.2",
"yup": "^1.4.0"
},
"devDependencies": {
"@testing-library/cypress": "^10.0.1",
Expand Down
4 changes: 2 additions & 2 deletions src/app/[id]/apply/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApplicationForm } from "../../components/form/ApplicationForm";
import { AppForm } from "../../components/form/AppForm";

export default function ApplyPage({ params }: Props) {
return <ApplicationForm institutionId={params.id} />;
return <AppForm institutionId={params.id} />;
}

type Props = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import { useForm } from "react-hook-form";
import * as yup from "yup";
import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
// components
import {
Button,
Expand All @@ -10,32 +12,31 @@ import {
CardBody,
GridContainer,
} from "@trussworks/react-uswds";
import { TextField, TextArea, Spinner } from "@/src/app/components";
import NotFound from "@/src/app/not-found";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Spinner, TextArea, TextField, USWDSForm } from "../index";
// pages
import NotFound from "../../not-found";
// utils
import { getInstitutionApplication } from "@/src/app/utils/institutions";
import { saveApplication } from "@/src/app/utils/applications";
import { getInstitutionApplication } from "../../utils/institutions";
import { saveApplication } from "../../utils/applications";
// schemas
import { email, number, numberOptional, text } from "../../schemas/schemas";

export const ApplicationForm = ({ institutionId }: Props) => {
export const AppForm = ({ institutionId }: Props) => {
const router = useRouter();
const [application, setApplication] = useState<
Record<string, any> | undefined
>();
const [loading, setLoading] = useState(true);

const appq = application?.questions;

useEffect(() => {
getInstitutionApplication(Number(institutionId)).then((application) => {
setApplication(application);
setLoading(false);
});
}, [institutionId]);

const { handleSubmit, register } = useForm();

const appq = application?.questions;

const onSubmit = async (data: any) => {
const submission = {
questions: appq,
Expand All @@ -53,15 +54,45 @@ export const ApplicationForm = ({ institutionId }: Props) => {

//Handle SAT score questions
const hasSATQ: boolean = appq?.includes("What is your SAT score?");
//Handle EssayQuestions
const satSchema = hasSATQ ? number() : numberOptional();

const validationSchema = yup.object().shape({
"first-name": text(),
"last-name": text(),
email: email(),
phone: text(),
"math-score": satSchema,
"reading-score": satSchema,
"writing-score": satSchema,
"question-1": text(),
"question-2": text(),
"question-3": text(),
});
// Handle EssayQuestions
const essayQ1: string | undefined = appq?.[4];
const essayQ2: string | undefined = appq?.[5];
const essayQ3: string | undefined = appq?.[6];

const ApplicationView = !application ? (
<NotFound />
) : (
<div className="application">
<form onSubmit={handleSubmit(onSubmit)}>
<main className="application">
<USWDSForm
initialValues={{
"first-name": "",
"last-name": "",
email: "",
phone: "",
"math-score": "",
"reading-score": "",
"writing-score": "",
"question-1": "",
"question-2": "",
"question-3": "",
}}
validationSchema={validationSchema}
submit={onSubmit}
>
<div className="application_header">
<h1 className="application_header-title">
{application?.institutionName}
Expand All @@ -80,32 +111,24 @@ export const ApplicationForm = ({ institutionId }: Props) => {
</legend>
<div className="application_questions-grid">
<TextField
id={"given-name"}
label={"First name (required)"}
name={"first-name"}
required
registerField={register}
/>
<TextField
id={"family-name"}
label={"Last name (required)"}
name={"last-name"}
required
registerField={register}
/>
<TextField
id={"email-address"}
label={"Email (required)"}
name={"email"}
required
registerField={register}
/>
<TextField
id={"phone-number"}
label={"Phone number (required)"}
name={"phone"}
required
registerField={register}
/>
</div>
</fieldset>
Expand All @@ -120,27 +143,21 @@ export const ApplicationForm = ({ institutionId }: Props) => {
</legend>
<div className="application_questions-grid">
<TextField
id={"math-sat"}
label={"Math (required)"}
name={"math-score"}
required={hasSATQ}
registerField={register}
/>

<TextField
id={"crit-reading-sat"}
label={"Critical reading (required)"}
name={"reading-score"}
required={hasSATQ}
registerField={register}
/>

<TextField
id={"writing-sat"}
label={"Writing (required)"}
name={"writing-score"}
required={hasSATQ}
registerField={register}
/>
</div>
</fieldset>
Expand Down Expand Up @@ -172,7 +189,6 @@ export const ApplicationForm = ({ institutionId }: Props) => {
label={essayQ1}
name={"question-1"}
required={false}
registerField={register}
/>
</fieldset>
</CardBody>
Expand All @@ -190,7 +206,6 @@ export const ApplicationForm = ({ institutionId }: Props) => {
label={essayQ2}
name={"question-2"}
required={false}
registerField={register}
/>
</fieldset>
</CardBody>
Expand All @@ -209,7 +224,6 @@ export const ApplicationForm = ({ institutionId }: Props) => {
label={essayQ3}
name={"question-3"}
required={false}
registerField={register}
/>
</fieldset>
</CardBody>
Expand All @@ -224,8 +238,8 @@ export const ApplicationForm = ({ institutionId }: Props) => {
<Button type={"submit"}>Submit application</Button>
</ButtonGroup>
</div>
</form>
</div>
</USWDSForm>
</main>
);

return <main>{loading ? <Spinner /> : ApplicationView}</main>;
Expand Down
12 changes: 7 additions & 5 deletions src/app/components/form/CheckboxField.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { FieldValues, UseFormRegister } from "react-hook-form";
"use client";

import { Field } from "formik";
//types
import { CheckboxOptions } from "../../types";

export const CheckboxField = ({ name, registerField, options }: Props) => (
export const CheckboxField = ({ name, options }: Props) => (
<fieldset className="usa-fieldset">
<div>
{options.map((option: CheckboxOptions) => (
<div className="usa-checkbox" key={option.id}>
<input
<Field
className="usa-checkbox__input"
id={option.id}
type="checkbox"
value={option.id}
{...registerField(`${name}`)}
name={name}
/>
<label className="usa-checkbox__label" htmlFor={option.id}>
{option.label}
Expand All @@ -25,6 +28,5 @@ export const CheckboxField = ({ name, registerField, options }: Props) => (
type Props = {
id: string;
name: string;
registerField: UseFormRegister<FieldValues>;
options: CheckboxOptions[];
};
13 changes: 7 additions & 6 deletions src/app/components/form/DropdownField.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { FieldValues, UseFormRegister } from "react-hook-form";
"use client";

import { FieldHint, DropdownOptions } from "../../types";
import { Field } from "formik";

export const DropdownField = ({
id,
label,
required,
name,
registerField,
options,
hint,
}: Props) => {
Expand All @@ -21,19 +22,20 @@ export const DropdownField = ({
{hint.text}
</div>
)}
<select
<Field
className="usa-select"
id={id}
{...registerField(`${name}`)}
name={name}
aria-describedby={hint?.id}
as="select"
>
<option value={undefined}>- Select -</option>
{options.map((option: DropdownOptions) => (
<option key={option.id} value={option.id}>
{option.label}
</option>
))}
</select>
</Field>
</>
);
};
Expand All @@ -43,7 +45,6 @@ type Props = {
label: string;
required: boolean;
name: string;
registerField: UseFormRegister<FieldValues>;
options: DropdownOptions[];
hint?: FieldHint;
};
30 changes: 9 additions & 21 deletions src/app/components/form/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,21 @@
import { FieldHint } from "../../types";
import { FieldValues, UseFormRegister } from "react-hook-form";
"use client";

export const TextArea = ({
id,
label,
name,
required,
registerField,
hint,
}: Props) => {
import { ErrorMessage, Field } from "formik";

export const TextArea = ({ id, label, name, required }: Props) => {
const fieldLabel = required ? `${label} *` : `${label}`;
return (
<div>
<label className="usa-labe input_textarea-label" htmlFor={id}>
{fieldLabel}
</label>
{hint && (
<div className="usa-hint" id={hint.id}>
{hint.text}
</div>
)}
<textarea
id={id}
aria-describedby={hint?.id}
<Field
id={name}
name={name}
className="usa-textarea input_textarea-input"
{...registerField(`${name}`, { required: required })}
as="textarea"
/>
<ErrorMessage name={name} component="div" className="formError" />
</div>
);
};
Expand All @@ -35,6 +25,4 @@ type Props = {
label?: string;
name: string;
required: boolean;
registerField: UseFormRegister<FieldValues>;
hint?: FieldHint;
};
Loading

0 comments on commit ab23d8c

Please sign in to comment.