Source

pages/Home/index.jsx

import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useForm } from "../../hooks/useForm";
import { useDispatch } from "react-redux";

import { addEmployeeAction } from "../../store/employee";

import { states, departments } from "../../utils/statesAndDepartments";

import Input from "../../components/Input";
import Modal from "../../components/Modal";
import Dropdown from "../../components/Dropdown";
import DatePicker from "../../components/DatePicker";

import "./styles.scss";

/**
 * Render Home Page
 *
 * @category Pages
 * @component
 * @returns { React.Component } A React component
 */
function Home() {
	const navigate = useNavigate();
	const dispatch = useDispatch();

	const [showConfirmationModal, setShowConfirmationModal] = useState(false);
	// Regex to validate inputs
	const nameRegex = "^[a-zA-ZàáâäãåąčćęèéêëėįìíîïłńòóôöõøùúûüųūÿýżźñçčšžÀÁÂÄÃÅĄĆČĖĘÈÉÊËÌÍÎÏĮŁŃÒÓÔÖÕØÙÚÛÜŲŪŸÝŻŹÑßÇŒÆČŠŽ∂ð ,.'-]{1,128}$";
	const dateRegex = "^(0[1-9]|1[0-2])/(0[1-9]|[1-2][0-9]|3[0-1])/(19|20)\\d{2}$";
	const dateRegexOnChange = "^[\\d\\/]{0,10}$";
	const addressRegex = "^[a-zA-Z0-9àáâäãåąčćęèéêëėįìíîïłńòóôöõøùúûüųūÿýżźñçčšžÀÁÂÄÃÅĄĆČĖĘÈÉÊËÌÍÎÏĮŁŃÒÓÔÖÕØÙÚÛÜŲŪŸÝŻŹÑßÇŒÆČŠŽ∂ð ,.'-]{1,128}$";
	const zipRegex = "^\\d{4}$|^\\d{5}$";
	const zipRegexOnChange = "^\\d{0,5}$";

	const dateOfBirthYearsBack = 80;
	const dateOfBirthYearsForward = 0;
	const startDateYearsBack = 50;
	const startDateYearsForward = 1;

	useEffect(() => {
		document.title = "Create Employee - HRnet";
	}, []);

	const {
		handleSubmit, // handles form submission
		handleChange, // handles input changes
		handleValidation, // handles input validation
		data, // access to the form data
		errors, // includes the errors to show
	} = useForm({
		validations: {
			firstName: {
				required: {
					value: true,
					message: "First Name is required",
				},
				pattern: {
					value: nameRegex,
					message: "First Name must contain only letters, spaces and some special characters",
				},
			},
			lastName: {
				required: {
					value: true,
					message: "Last Name is required",
				},
				pattern: {
					value: nameRegex,
					message: "Last Name must contain only letters, spaces and some special characters",
				},
			},
			dateOfBirth: {
				required: {
					value: true,
					message: "Date of Birth is required",
				},
				pattern: {
					value: dateRegex,
					message: "Date of Birth isn't in the format MM/DD/YYYY",
				},
			},
			startDate: {
				required: {
					value: true,
					message: "Start Date is required",
				},
				pattern: {
					value: dateRegex,
					message: "Start Date isn't in the format MM/DD/YYYY",
				},
			},
			street: {
				required: {
					value: true,
					message: "Street is required",
				},
				pattern: {
					value: addressRegex,
					message: "Street must contain only letters, numbers, spaces and some special characters",
				},
			},
			city: {
				required: {
					value: true,
					message: "City is required",
				},
				pattern: {
					value: addressRegex,
					message: "City must contain only letters, numbers, spaces and some special characters",
				},
			},
			state: {
				required: {
					value: true,
					message: "State is required",
				},
				custom: {
					isValid: (value) => states.some((state) => state.value === value),
					message: "Your selection is not valid",
				},
			},
			zip: {
				required: {
					value: true,
					message: "Zip is required",
				},
				pattern: {
					value: zipRegex,
					message: "Zip must be 4 or 5 digits",
				},
			},
			department: {
				required: {
					value: true,
					message: "Department is required",
				},
				custom: {
					isValid: (value) => departments.includes(value),
					message: "Your selection is not valid",
				},
			},
		},
		// Creating an object with the data from the form and then dispatching an action to add the employee to the store.
		onSubmit: () => {
			const employee = {
				firstName: sanitizerOnSubmit(data.firstName),
				lastName: sanitizerOnSubmit(data.lastName),
				dateOfBirth: sanitizerOnSubmit(data.dateOfBirth),
				startDate: sanitizerOnSubmit(data.startDate),
				department: sanitizerOnSubmit(data.department),
				street: sanitizerOnSubmit(data.street),
				city: sanitizerOnSubmit(data.city),
				state: sanitizerOnSubmit(data.state),
				zipCode: sanitizerOnSubmit(data.zip),
			};
			dispatch(addEmployeeAction(employee));
			setShowConfirmationModal(true);
		},
		initialValues: {
			firstName: "",
			lastName: "",
			dateOfBirth: "",
			startDate: "",
			department: departments[0],
			street: "",
			city: "",
			state: states[0].value,
			zip: "",
		},
	});

	/**
	 * When the form is submitted, trim the value.
	 * @returns The value of the input field with the whitespace trimmed.
	 */
	const sanitizerOnSubmit = (value) => {
		return value.trim();
	};
	/**
	 * It takes an event/value and a regex as parameters
	 * @returns The value with the first letter capitalized and with all characters that don't match the regex removed
	 */
	const sanitizerOnChange = (event, regex) => {
		const value = event.target ? event.target.value : event;
		let valueWithFirstLetterUpperCase = value.charAt(0).toUpperCase() + value.slice(1);

		// check if first letter is a letter or a number, if not, remove it
		let regexFirstLetter = new RegExp("^[a-zA-Z0-9àáâäãåąčćęèéêëėįìíîïłńòóôöõøùúûüųūÿýżźñçčšžÀÁÂÄÃÅĄĆČĖĘÈÉÊËÌÍÎÏĮŁŃÒÓÔÖÕØÙÚÛÜŲŪŸÝŻŹÑßÇŒÆČŠŽ∂ð]");
		if (!regexFirstLetter.test(valueWithFirstLetterUpperCase.charAt(0))) {
			valueWithFirstLetterUpperCase = valueWithFirstLetterUpperCase.slice(0, -1);
		}

		// remove last character if not in the regex
		if (!RegExp(regex).test(valueWithFirstLetterUpperCase)) {
			valueWithFirstLetterUpperCase = valueWithFirstLetterUpperCase.slice(0, -1);
		}

		return valueWithFirstLetterUpperCase;
	};

	return (
		<main>
			<div className="container home-page">
				<div className="title">
					<h1>HRnet</h1>
				</div>
				<button className="button" onClick={() => navigate("/employee-list")} aria-label="Navigate to employee list page">
					Employee List
				</button>
				<h2>Create Employee</h2>

				<form
					id="create-employee"
					onSubmit={handleSubmit}
					onKeyPress={(event) => {
						if (event.key === "Enter") {
							event.preventDefault();
						}
					}}
				>
					<Input
						id="firstName"
						label="First Name"
						value={data.firstName}
						onChange={handleChange("firstName", (event) => sanitizerOnChange(event, nameRegex))}
						maxLength={128}
						onBlur={handleValidation}
						error={errors.firstName || ""}
						requiredFeedbackEnabled={true}
					/>

					<Input
						id="lastName"
						label="Last Name"
						value={data.lastName}
						onChange={handleChange("lastName", (event) => sanitizerOnChange(event, nameRegex))}
						maxLength={128}
						onBlur={handleValidation}
						error={errors.lastName || ""}
						requiredFeedbackEnabled={true}
					/>

					<DatePicker
						id="dateOfBirth"
						label="Date of Birth"
						value={data.dateOfBirth}
						onChange={handleChange("dateOfBirth", (event) => sanitizerOnChange(event, dateRegexOnChange))}
						onBlurFunction={handleValidation}
						maxLength={10}
						error={errors.dateOfBirth || ""}
						yearsBackNumber={dateOfBirthYearsBack}
						yearsForwardNumber={dateOfBirthYearsForward}
						requiredFeedbackEnabled={true}
						invalidClassName={data.dateOfBirth ? "" : "invalid"}
						errorClassName={data.dateOfBirth ? "error small" : "error"}
					/>

					<DatePicker
						id="startDate"
						label="Start Date"
						value={data.startDate}
						onChange={handleChange("startDate", (event) => sanitizerOnChange(event, dateRegexOnChange))}
						onBlurFunction={handleValidation}
						maxLength={10}
						error={errors.startDate || ""}
						yearsBackNumber={startDateYearsBack}
						yearsForwardNumber={startDateYearsForward}
						requiredFeedbackEnabled={true}
						invalidClassName={data.startDate ? "" : "invalid"}
						errorClassName={data.startDate ? "error small" : "error"}
					/>

					<fieldset className="address">
						<legend>Address</legend>

						<Input
							id="street"
							label="Street"
							value={data.street}
							onChange={handleChange("street", (event) => sanitizerOnChange(event, addressRegex))}
							maxLength={128}
							onBlur={handleValidation}
							error={errors.street || ""}
							requiredFeedbackEnabled={true}
						/>

						<Input
							id="city"
							label="City"
							value={data.city}
							onChange={handleChange("city", (event) => sanitizerOnChange(event, addressRegex))}
							maxLength={128}
							onBlur={handleValidation}
							error={errors.city || ""}
							requiredFeedbackEnabled={true}
						/>
						<div className="form-group">
							<Dropdown id="state" label="State" value={data.state} options={states} onChange={handleChange("state")} listLabel="Chose your state" showListLabel={true} requiredFeedbackEnabled={true} />
						</div>
						<Input
							id="zip"
							label="Zip Code"
							value={data.zip}
							onChange={handleChange("zip", (event) => sanitizerOnChange(event, zipRegexOnChange))}
							maxLength={5}
							onBlur={handleValidation}
							error={errors.zip || ""}
							requiredFeedbackEnabled={true}
						/>
					</fieldset>
					<div className="form-group">
						<Dropdown
							id="department"
							label="Department"
							value={data.department}
							options={departments}
							onChange={handleChange("department")}
							error={errors.department || ""}
							listLabel="Chose your department"
							showListLabel={true}
							requiredFeedbackEnabled={true}
						/>
					</div>
					<button
						id="form-submit-button"
						type="submit"
						className="button horizontal-center"
						aria-label="Create a new employee by submitting the form"
						onKeyDown={(event) => {
							if (event.key === "Enter" || event.key === " ") {
								handleSubmit();
							}
						}}
					>
						Create
					</button>
				</form>
				<Modal id="confirmation" modalContent="Employee Created!" isOpenStateInParent={showConfirmationModal} onClose={() => setShowConfirmationModal(false)} />
			</div>
		</main>
	);
}

export default Home;