const FORM_DATA_KEY = "formData";
const REGISTRATIONS_KEY = "registrations";
/**
* Calculate a person's age in years.
*
* @param {Date} birthDate - The birth date of the person.
* @returns {Number} - The age in years of the person.
*/
function calculateAge(birthDate) {
if (!birthDate) {
throw new Error("missing param birthDate");
}
if (!(birthDate instanceof Date)) {
throw new Error("birthDate is not a Date");
}
if (isNaN(birthDate.getTime())) {
throw new Error("birthDate is not a valid Date");
}
let dateDiff = new Date(Date.now() - birthDate.getTime());
let age = Math.abs(dateDiff.getUTCFullYear() - 1970);
return age;
}
/**
* Validate the name string.
*
* @param {String} name - The name string to validate.
* @returns {String} - The validated name string.
*/
function validateName(name) {
if (!name) {
throw new Error("name is required");
}
if (typeof name !== 'string') {
throw new Error("name must be a string");
}
const trimmedName = name.trim();
if (!trimmedName.match(/^[\p{L}]+(?:[-' ][\p{L}]+)*$/u)) {
throw new Error("name must be a valid name");
}
return trimmedName;
}
/**
* Validate the prenom string.
*
* @param {String} prenom - The prenom string to validate.
* @returns {String} - The validated prenom string.
*/
function validatePrenom(prenom) {
if (!prenom) {
throw new Error("prenom is required");
}
if (typeof prenom !== 'string') {
throw new Error("prenom must be a string");
}
const trimmedPrenom = prenom.trim();
if (!trimmedPrenom.match(/^[\p{L}]+(?:[-' ][\p{L}]+)*$/u)) {
throw new Error("prenom must be a valid prenom");
}
return trimmedPrenom;
}
/**
* Validate the email string.
*
* @param {String} email - The email string to validate.
* @returns {String} - The validated email string.
*/
function validateEmail(email) {
if (!email) {
throw new Error("email is required");
}
if (typeof email !== 'string') {
throw new Error("email must be a string");
}
const trimmedEmail = email.trim();
if (!trimmedEmail.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
throw new Error("email must be a valid email address");
}
return trimmedEmail;
}
/**
* Validate the date of birth string.
*
* @param {String} dateOfBirth - The date of birth string to validate.
* @returns {String} - The validated date of birth string.
*/
function validateDateOfBirth(dateOfBirth) {
if (!dateOfBirth) {
throw new Error("date of birth is required");
}
if (typeof dateOfBirth !== 'string') {
throw new Error("date of birth must be a string");
}
const trimmedDateOfBirth = dateOfBirth.trim();
const parsedDate = parseDateOfBirth(trimmedDateOfBirth);
if (!parsedDate) {
throw new Error("date of birth must be a valid date");
}
if (!isAdult(parsedDate)) {
throw new Error("date of birth must be at least 18 years old");
}
return trimmedDateOfBirth;
}
/**
* Validate the ville string.
*
* @param {String} ville - The ville string to validate.
* @returns {String} - The validated ville string.
*/
function validateVille(ville) {
if (!ville) {
throw new Error("ville is required");
}
if (typeof ville !== 'string') {
throw new Error("ville must be a string");
}
const trimmedVille = ville.trim();
if (!trimmedVille.match(/^[\p{L}]+(?:[-' ][\p{L}]+)*$/u)) {
throw new Error("ville must be a valid ville");
}
return trimmedVille;
}
/**
* Validate the code postal string.
*
* @param {String} codePostal - The code postal string to validate.
* @returns {String} - The validated code postal string.
*/
function validateCodePostal(codePostal) {
if (!codePostal) {
throw new Error("code postal is required");
}
if (typeof codePostal !== 'string') {
throw new Error("code postal must be a string");
}
const trimmedCodePostal = codePostal.trim();
if (!isFrenchCodePostal(trimmedCodePostal)) {
throw new Error("code postal must be a valid code postal");
}
return trimmedCodePostal;
}
/**
* Validate the form data object.
*
* @param {FormData} formData - The form data object to validate.
* @returns {Object} - The validated form data object.
*/
function validateFormData(formData) {
if (!formData) {
throw new Error("form data is required");
}
if (!(formData instanceof FormData)) {
throw new Error("form data must be a FormData");
}
const data = Object.fromEntries(formData);
return {
nom: validateName(data.nom),
prenom: validatePrenom(data.prenom),
email: validateEmail(data.email),
dateOfBirth: validateDateOfBirth(data.dateOfBirth),
ville: validateVille(data.ville),
codePostal: validateCodePostal(data.codePostal)
};
}
/**
* Check if the date of birth string is an adult.
*
* @param {Date} dateOfBirth - The date of birth to check.
* @returns {Boolean} - True if the date of birth string is an adult, false otherwise.
*/
function isAdult(dateOfBirth) {
const age = calculateAge(dateOfBirth);
return age >= 18;
}
/**
* Parse date of birth from supported formats:
* YYYY-MM-DD, YYYY/MM/DD, DD/MM/YYYY, DD-MM-YYYY.
*
* @param {String} rawDateOfBirth - The date string to parse.
* @returns {Date|null} - Parsed date if valid, otherwise null.
*/
function parseDateOfBirth(rawDateOfBirth) {
const dateOfBirth = rawDateOfBirth.trim();
const yearFirstMatch = dateOfBirth.match(/^(\d{4})[-/](\d{2})[-/](\d{2})$/);
if (yearFirstMatch) {
return buildValidDate(
Number(yearFirstMatch[1]),
Number(yearFirstMatch[2]),
Number(yearFirstMatch[3])
);
}
const dayFirstMatch = dateOfBirth.match(/^(\d{2})[-/](\d{2})[-/](\d{4})$/);
if (dayFirstMatch) {
return buildValidDate(
Number(dayFirstMatch[3]),
Number(dayFirstMatch[2]),
Number(dayFirstMatch[1])
);
}
return null;
}
/**
* Build a date and ensure day/month/year are real.
*
* @param {Number} year - Year component.
* @param {Number} month - Month component (1-12).
* @param {Number} day - Day component (1-31).
* @returns {Date|null} - Date when valid, otherwise null.
*/
function buildValidDate(year, month, day) {
const date = new Date(year, month - 1, day);
if (
date.getFullYear() !== year ||
date.getMonth() !== month - 1 ||
date.getDate() !== day
) {
return null;
}
return date;
}
/**
* Check if the code postal is a valid French code postal.
*
* @param {String} codePostal - The code postal string to check.
* @returns {Boolean} - True if the code postal is a valid French code postal, false otherwise.
*/
function isFrenchCodePostal(codePostal) {
if (typeof codePostal !== 'string') {
return false;
}
const trimmedCodePostal = codePostal.trim();
// Metropolitan France (01-95), including Corsica postal codes (20xxx).
const metropolitanPattern = /^(?:0[1-9]|[1-8]\d|9[0-5])\d{3}$/;
// Overseas departments/collectivities (971-978, 984-989).
const overseasPattern = /^(?:97[1-8]|98[4-9])\d{2}$/;
return metropolitanPattern.test(trimmedCodePostal) || overseasPattern.test(trimmedCodePostal);
}
/**
* Save the form data object to localStorage.
*
* @param {Object} formData - The form data object to save.
*/
function saveFormData(formData) {
try {
localStorage.setItem(FORM_DATA_KEY, JSON.stringify(formData));
} catch (error) {
throw new Error("unable to save form data");
}
}
/**
* Get the form data object from localStorage.
*
* @returns {Object} - The form data object.
*/
function getFormData() {
const rawFormData = localStorage.getItem(FORM_DATA_KEY);
if (!rawFormData) {
return null;
}
try {
return JSON.parse(rawFormData);
} catch (error) {
throw new Error("unable to parse saved form data");
}
}
/**
* Clear the form data object from localStorage.
*/
function clearFormData() {
localStorage.removeItem(FORM_DATA_KEY);
}
/**
* Save registrations list to localStorage.
*
* @param {Object[]} registrations - Registrations list.
*/
function saveRegistrations(registrations) {
try {
localStorage.setItem(REGISTRATIONS_KEY, JSON.stringify(registrations));
} catch (error) {
throw new Error("unable to save registrations");
}
}
/**
* Get registrations list from localStorage.
*
* @returns {Object[]} - Registrations list.
*/
function getRegistrations() {
const rawRegistrations = localStorage.getItem(REGISTRATIONS_KEY);
if (!rawRegistrations) {
return [];
}
try {
const parsedRegistrations = JSON.parse(rawRegistrations);
if (!Array.isArray(parsedRegistrations)) {
throw new Error("invalid registrations format");
}
return parsedRegistrations;
} catch (error) {
throw new Error("unable to parse saved registrations");
}
}
/**
* Append a validated registration to localStorage.
*
* @param {Object} registration - Validated registration data.
* @returns {Object} - Saved registration data.
*/
function appendRegistration(registration) {
const registrations = getRegistrations();
const nextRegistrations = [...registrations, registration];
saveRegistrations(nextRegistrations);
return registration;
}
/**
* Handle the form submission event.
*
* @param {Event} e - The form submission event.
*/
function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const validatedData = validateFormData(formData);
appendRegistration(validatedData);
return validatedData;
}
export { calculateAge, validateName, validatePrenom, validateEmail, validateDateOfBirth, validateVille, validateCodePostal, validateFormData, isAdult, isFrenchCodePostal, saveFormData, getFormData, clearFormData, saveRegistrations, getRegistrations, appendRegistration, handleSubmit };