Skip to content

Commit

Permalink
Add input validation & error messages for editing VSR
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminJohnson2204 committed Apr 7, 2024
1 parent ede5260 commit 0e8bb2f
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 152 deletions.
231 changes: 126 additions & 105 deletions frontend/src/app/vsr/page.tsx

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions frontend/src/components/VSRForm/VSRFormTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Common types for the VSR form (create & edit) input fields.
*/

import { FurnitureInput } from "@/api/VSRs";

export interface IVSRFormInput {
name: string;
maritalStatus: string;
gender: string;
spouseName: string;
age: number;
ethnicity: string[];
other_ethnicity: string;
employment_status: string;
income_level: string;
size_of_home: string;
num_boys: number;
num_girls: number;
agesOfBoys: number[];
agesOfGirls: number[];

streetAddress: string;
city: string;
state: string;
zipCode: number;
phoneNumber: string;
email: string;
branch: string[];
conflicts: string[];
other_conflicts: string;
dischargeStatus: string;
serviceConnected: boolean;
lastRank: string;
militaryID: number;
petCompanion: boolean;
hearFrom: string;
other_hearFrom: string;

selectedFurnitureItems: Record<string, FurnitureInput>;
additionalItems: string;
}

export interface ICreateVSRFormInput extends IVSRFormInput {
confirmEmail: string;
}

export type IEditVSRFormInput = IVSRFormInput;
75 changes: 75 additions & 0 deletions frontend/src/components/VSRForm/VSRFormValidators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import emailValidator from "email-validator";
import { RegisterOptions } from "react-hook-form";
import { IVSRFormInput } from "@/components/VSRForm/VSRFormTypes";

/**
* Defines common validators for the VSR form inputs.
*/
export const vsrInputFieldValidators: Partial<
Record<keyof IVSRFormInput, RegisterOptions<IVSRFormInput, keyof IVSRFormInput>>
> = {
name: { required: "Name is required" },
gender: { required: "Gender is required" },
age: {
required: "Age is required",
pattern: {
// Only allow up to 2 digits
value: /^[0-9]+$/,
message: "This field must be a positive number",
},
},
maritalStatus: { required: "Marital status is required" },
spouseName: {
required: "Spouse's Name is required",
},
ethnicity: { required: "Ethnicity is required" },
employment_status: { required: "Employment status is required" },
income_level: { required: "Income level is required" },
size_of_home: { required: "Size of home is required" },
streetAddress: { required: "Street address is required" },
city: { required: "City is required" },
state: { required: "State is required" },
zipCode: {
required: "Zip Code is required",
pattern: {
// Must be 5 digits
value: /^\d{5}$/,
message: "This field must be a 5 digit number",
},
},
phoneNumber: {
required: "Phone Number is required",
pattern: {
value: /^\d{10}$/,
message: "This field must be a 10 digit number",
},
},
email: {
required: "Email Address is required",
validate: {
validate: (emailAddress) =>
emailValidator.validate(emailAddress as string) ||
"This field must be a valid email address",
},
},
branch: { required: "Military Branch is required" },
conflicts: { required: "Military Conflicts is required" },
dischargeStatus: { required: "Discharge status is required" },
serviceConnected: {
validate: (value) =>
[true, false].includes(value as boolean) || "Service connected is required",
},
lastRank: { required: "Last rank is required" },
militaryID: {
required: "Last rank is required",
pattern: {
value: /^\d{4}$/,
message: "This field must be a 4 digit number",
},
},
petCompanion: {
validate: (value) =>
[true, false].includes(value as boolean) || "Companionship animal is required",
},
hearFrom: { required: "Referral source is required" },
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Controller, UseFormReturn } from "react-hook-form";
import { FieldDetail } from "@/components/VSRIndividual/FieldDetails/FieldDetail";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import BinaryChoice from "@/components/shared/input/BinaryChoice";
import { vsrInputFieldValidators } from "@/components/VSRForm/VSRFormValidators";

interface BinaryChoiceInputDetailProps {
title: string;
name: keyof IFormInput;
formProps: UseFormReturn<IFormInput>;
name: keyof IEditVSRFormInput;
formProps: UseFormReturn<IEditVSRFormInput>;
}

/**
Expand All @@ -23,6 +24,7 @@ export const BinaryChoiceInputDetail = ({
<Controller
name={name}
control={formProps.control}
rules={vsrInputFieldValidators[name]}
render={({ field }) => (
<BinaryChoice
label=""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Controller, UseFormReturn } from "react-hook-form";
import { FieldDetail } from "@/components/VSRIndividual/FieldDetails/FieldDetail";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import MultipleChoice from "@/components/shared/input/MultipleChoice";
import { vsrInputFieldValidators } from "@/components/VSRForm/VSRFormValidators";

interface MultipleChoiceInputDetailProps {
title: string;
name: keyof IFormInput;
name: keyof IEditVSRFormInput;
options: string[];
allowMultiple: boolean;
formProps: UseFormReturn<IFormInput>;
formProps: UseFormReturn<IEditVSRFormInput>;
}

/**
Expand All @@ -27,6 +28,7 @@ export const MultipleChoiceInputDetail = ({
<Controller
name={name}
control={formProps.control}
rules={vsrInputFieldValidators[name]}
render={({ field }) => (
<MultipleChoice
label=""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Controller, UseFormReturn } from "react-hook-form";
import { FieldDetail } from "@/components/VSRIndividual/FieldDetails/FieldDetail";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import MultipleChoice from "@/components/shared/input/MultipleChoice";
import TextField from "@/components/shared/input/TextField";
import { vsrInputFieldValidators } from "@/components/VSRForm/VSRFormValidators";

interface MultipleChoiceWithOtherInputDetailProps {
title: string;
name: keyof IFormInput;
otherName: keyof IFormInput;
name: keyof IEditVSRFormInput;
otherName: keyof IEditVSRFormInput;
options: string[];
allowMultiple: boolean;
formProps: UseFormReturn<IFormInput>;
formProps: UseFormReturn<IEditVSRFormInput>;
}

/**
Expand All @@ -32,6 +33,7 @@ export const MultipleChoiceWithOtherInputDetail = ({
<Controller
name={name}
control={formProps.control}
rules={vsrInputFieldValidators[name]}
render={({ field }) => (
<MultipleChoice
label=""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Controller, UseFormReturn } from "react-hook-form";
import { FieldDetail } from "@/components/VSRIndividual/FieldDetails/FieldDetail";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import Dropdown from "@/components/shared/input/Dropdown";
import { vsrInputFieldValidators } from "@/components/VSRForm/VSRFormValidators";

interface SelectInputDetailProps {
title: string;
name: keyof IFormInput;
name: keyof IEditVSRFormInput;
options: string[];
placeholder?: string;
formProps: UseFormReturn<IFormInput>;
formProps: UseFormReturn<IEditVSRFormInput>;
}

/**
Expand All @@ -27,6 +28,7 @@ export const SelectInputDetail = ({
<Controller
name={name}
control={formProps.control}
rules={vsrInputFieldValidators[name]}
render={({ field }) => (
<Dropdown
label=""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Controller, UseFormReturn } from "react-hook-form";
import { FieldDetail } from "@/components/VSRIndividual/FieldDetails/FieldDetail";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import TextField from "@/components/shared/input/TextField";
import { vsrInputFieldValidators } from "@/components/VSRForm/VSRFormValidators";

interface TextInputDetailProps {
title: string;
name: keyof IFormInput;
name: keyof IEditVSRFormInput;
placeholder?: string;
formProps: UseFormReturn<IFormInput>;
formProps: UseFormReturn<IEditVSRFormInput>;
type?: string;
}

Expand All @@ -30,8 +31,8 @@ export const TextInputDetail = ({
<TextField
label=""
variant="outlined"
name={name}
placeholder={placeholder}
{...formProps.register(name, vsrInputFieldValidators[name])}
value={field.value}
onChange={field.onChange}
required={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { type VSR } from "@/api/VSRs";
import { VSRIndividualAccordion } from "@/components/VSRIndividual/VSRIndividualAccordion";
import { BinaryChoiceInputDetail } from "@/components/VSRIndividual/FieldDetails/BinaryChoiceInputDetail";
import { UseFormReturn } from "react-hook-form";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import { MultipleChoiceWithOtherInputDetail } from "@/components/VSRIndividual/FieldDetails/MultipleChoiceWithOtherInputDetail";
import { hearFromOptions } from "@/constants/fieldOptions";

export interface AdditionalInfoProps {
vsr: VSR;
isEditing: boolean;
formProps: UseFormReturn<IFormInput>;
formProps: UseFormReturn<IEditVSRFormInput>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { type VSR } from "@/api/VSRs";
import { VSRIndividualAccordion } from "@/components/VSRIndividual/VSRIndividualAccordion";
import { TextInputDetail } from "@/components/VSRIndividual/FieldDetails/TextInputDetail";
import { UseFormReturn } from "react-hook-form";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import { SelectInputDetail } from "@/components/VSRIndividual/FieldDetails/SelectInputDetail";
import { genderOptions, stateOptions } from "@/constants/fieldOptions";

export interface ContactInfoProps {
vsr: VSR;
isEditing: boolean;
formProps: UseFormReturn<IFormInput>;
formProps: UseFormReturn<IEditVSRFormInput>;
}
/**
* The "Contact Information" section of the VSR individual page.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SingleDetail, ListDetail } from "@/components/VSRIndividual";
import { type VSR } from "@/api/VSRs";
import { VSRIndividualAccordion } from "@/components/VSRIndividual/VSRIndividualAccordion";
import { UseFormReturn } from "react-hook-form";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import { branchOptions, conflictsOptions, dischargeStatusOptions } from "@/constants/fieldOptions";
import { MultipleChoiceInputDetail } from "@/components/VSRIndividual/FieldDetails/MultipleChoiceInputDetail";
import { MultipleChoiceWithOtherInputDetail } from "@/components/VSRIndividual/FieldDetails/MultipleChoiceWithOtherInputDetail";
Expand All @@ -14,7 +14,7 @@ import { TextInputDetail } from "@/components/VSRIndividual/FieldDetails/TextInp
export interface MilitaryBackgroundProps {
vsr: VSR;
isEditing: boolean;
formProps: UseFormReturn<IFormInput>;
formProps: UseFormReturn<IEditVSRFormInput>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SingleDetail, ListDetail } from "@/components/VSRIndividual";
import { type VSR } from "@/api/VSRs";
import { VSRIndividualAccordion } from "@/components/VSRIndividual/VSRIndividualAccordion";
import { UseFormReturn } from "react-hook-form";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import { TextInputDetail } from "@/components/VSRIndividual/FieldDetails/TextInputDetail";
import { SelectInputDetail } from "@/components/VSRIndividual/FieldDetails/SelectInputDetail";
import {
Expand All @@ -22,7 +22,7 @@ import { ChildrenInput } from "@/components/shared/input/ChildrenInput";
export interface PersonalInformationProps {
vsr: VSR;
isEditing: boolean;
formProps: UseFormReturn<IFormInput>;
formProps: UseFormReturn<IEditVSRFormInput>;
}

/**
Expand Down Expand Up @@ -130,7 +130,7 @@ export const PersonalInformation = ({ vsr, isEditing, formProps }: PersonalInfor
<ListDetail title="Marital Status" values={[vsr.maritalStatus]} />
)}
</div>
{vsr.maritalStatus === "Married" ? (
{formProps.watch().maritalStatus === "Married" ? (
<div className={styles.row}>
{isEditing ? (
<TextInputDetail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { VSRIndividualAccordion } from "@/components/VSRIndividual/VSRIndividual
import { FurnitureItem } from "@/api/FurnitureItems";
import { useEffect, useMemo } from "react";
import { Controller, UseFormReturn } from "react-hook-form";
import { IFormInput } from "@/app/vsr/page";
import { IEditVSRFormInput } from "@/components/VSRForm/VSRFormTypes";
import { TextInputDetail } from "@/components/VSRIndividual/FieldDetails/TextInputDetail";
import { FurnitureItemSelection } from "@/components/VSRForm/FurnitureItemSelection";
import { FieldDetail } from "@/components/VSRIndividual/FieldDetails/FieldDetail";
Expand All @@ -14,7 +14,7 @@ export interface RequestedFurnishingsProps {
vsr: VSR;
furnitureItems: FurnitureItem[];
isEditing: boolean;
formProps: UseFormReturn<IFormInput>;
formProps: UseFormReturn<IEditVSRFormInput>;
}

/**
Expand Down
Loading

0 comments on commit 0e8bb2f

Please sign in to comment.