diff --git a/client/src/components/forms-component/YesNo.tsx b/client/src/components/forms-component/YesNo.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b8d8b77c518bb8cb3dc3e356e4faac024627fbc4 --- /dev/null +++ b/client/src/components/forms-component/YesNo.tsx @@ -0,0 +1,80 @@ +import { JSX, splitProps } from "solid-js" +import { InputError } from "./InputError" +import clsx from "clsx" +import { t } from "../../stores/locale" + +type YesNoProps = { + ref: (element: HTMLInputElement) => void + name: string + value?: "yes" | "no" + onInput: JSX.EventHandler<HTMLInputElement, InputEvent> + onChange: JSX.EventHandler<HTMLInputElement, Event> + onBlur: JSX.EventHandler<HTMLInputElement, FocusEvent> + required?: boolean + class?: string + label: string + error?: string + padding?: "none" +} + +/** + * Radio button group that provides Yes/No options. The label above the + * radio buttons describes what the user is choosing between. + */ +export function YesNo(props: YesNoProps) { + const [, inputProps] = splitProps(props, [ + "class", + "value", + "label", + "error", + "padding", + ]) + + return ( + <div class={clsx(props.class)}> + <fieldset> + <legend class="mb-2 font-medium md:text-lg lg:text-xl"> + {props.label} + {props.required && ( + <span class="ml-1 text-red-600 dark:text-red-400"> + * + </span> + )} + </legend> + <div class="flex space-x-6"> + <label class="flex select-none items-center space-x-2"> + <input + {...inputProps} + class="h-4 w-4 cursor-pointer lg:h-5 lg:w-5" + type="radio" + id={`${props.name}-yes`} + value="yes" + checked={props.value === "yes"} + aria-invalid={!!props.error} + aria-errormessage={`${props.name}-error`} + /> + <span class="font-medium md:text-lg lg:text-xl"> + {t("additionalInfo.yes")} + </span> + </label> + <label class="flex select-none items-center space-x-2"> + <input + {...inputProps} + class="h-4 w-4 cursor-pointer lg:h-5 lg:w-5" + type="radio" + id={`${props.name}-no`} + value="no" + checked={props.value === "no"} + aria-invalid={!!props.error} + aria-errormessage={`${props.name}-error`} + /> + <span class="font-medium md:text-lg lg:text-xl"> + {t("additionalInfo.no")} + </span> + </label> + </div> + </fieldset> + <InputError name={props.name} error={props.error} /> + </div> + ) +} diff --git a/client/src/components/forms/AdditionnalInfoForm.tsx b/client/src/components/forms/AdditionnalInfoForm.tsx index afa7c4b29ea8a936818bf35c21530735c8ae6b73..4bdf1000d1db9acbc7c8821642e0c79ed2ab4ee9 100644 --- a/client/src/components/forms/AdditionnalInfoForm.tsx +++ b/client/src/components/forms/AdditionnalInfoForm.tsx @@ -1,4 +1,9 @@ -import { createForm, setValues, SubmitHandler } from "@modular-forms/solid" +import { + createForm, + required, + setValues, + SubmitHandler, +} from "@modular-forms/solid" import { TextInput } from "../forms-component/TextInput" import { Checkbox } from "../forms-component/Checkbox" import { ParticipantInfo } from "../../binding/ParticipantInfo" @@ -9,6 +14,8 @@ import { createEffect, createSignal } from "solid-js" import { t } from "../../stores/locale" import { SubmitError } from "../forms-component/SubmitError" import { SubmitSuccess } from "../forms-component/SubmitSuccess" +import { YesNo } from "../forms-component/YesNo" +import { InputLabel } from "../forms-component/InputLabel" export function AdditionalInfoForm() { const [loginForm, { Form, Field }] = createForm<ParticipantInfo>() @@ -45,7 +52,10 @@ export function AdditionalInfoForm() { method="post" onSubmit={handleSubmit} > - <Field name="pronouns"> + <Field + name="pronouns" + validate={[required(t("additionalInfo.required"))]} + > {(field, props) => ( <TextInput {...props} @@ -54,6 +64,7 @@ export function AdditionalInfoForm() { label={t("additionalInfo.pronounsLabel")} type="text" placeholder={t("additionalInfo.pronouns")} + required /> )} </Field> @@ -71,7 +82,10 @@ export function AdditionalInfoForm() { )} </Field> - <Field name="dietary_restrictions"> + <Field + name="dietary_restrictions" + validate={[required(t("additionalInfo.required"))]} + > {(field, props) => ( <Select {...props} @@ -100,6 +114,7 @@ export function AdditionalInfoForm() { value: "other", }, ]} + required /> )} </Field> @@ -129,45 +144,69 @@ export function AdditionalInfoForm() { )} </Field> - <Field name="emergency_contact_name"> - {(field, props) => ( - <TextInput - {...props} - value={field.value || ""} - error={field.error} - label={t("additionalInfo.emergencyContactNameLabel")} - type="text" - /> - )} - </Field> + <h1 class="mb-2 mt-6 text-xl"> + {t("additionalInfo.emergencyContact")} + </h1> + <div class="pl-10"> + <Field + name="emergency_contact_name" + validate={[required(t("additionalInfo.required"))]} + > + {(field, props) => ( + <TextInput + {...props} + value={field.value || ""} + error={field.error} + label={t( + "additionalInfo.emergencyContactNameLabel", + )} + type="text" + required + /> + )} + </Field> - <Field name="emergency_contact_phone"> - {(field, props) => ( - <TextInput - {...props} - value={field.value || ""} - error={field.error} - label={t("additionalInfo.emergencyContactPhoneLabel")} - type="text" - /> - )} - </Field> + <Field + name="emergency_contact_phone" + validate={[required(t("additionalInfo.required"))]} + > + {(field, props) => ( + <TextInput + {...props} + value={field.value || ""} + error={field.error} + label={t( + "additionalInfo.emergencyContactPhoneLabel", + )} + type="text" + required + /> + )} + </Field> - <Field name="emergency_contact_relationship"> - {(field, props) => ( - <TextInput - {...props} - value={field.value || ""} - error={field.error} - label={t( - "additionalInfo.emergencyContactRelationshipLabel", - )} - type="text" - /> - )} - </Field> + <Field + name="emergency_contact_relationship" + validate={[required(t("additionalInfo.required"))]} + > + {(field, props) => ( + <TextInput + {...props} + value={field.value || ""} + error={field.error} + label={t( + "additionalInfo.emergencyContactRelationshipLabel", + )} + type="text" + required + /> + )} + </Field> + </div> - <Field name="phone_number"> + <Field + name="phone_number" + validate={[required(t("additionalInfo.required"))]} + > {(field, props) => ( <TextInput {...props} @@ -176,11 +215,15 @@ export function AdditionalInfoForm() { label={t("additionalInfo.phoneNumberLabel")} type="text" placeholder={t("additionalInfo.phoneNumber")} + required /> )} </Field> - <Field name="tshirt_size"> + <Field + name="tshirt_size" + validate={[required(t("additionalInfo.required"))]} + > {(field, props) => ( <Select {...props} @@ -213,33 +256,49 @@ export function AdditionalInfoForm() { value: "xxl", }, ]} + required /> )} </Field> - <Field name="has_monthly_opus_card" type="boolean"> + <Field + name="has_monthly_opus_card" + type="boolean" + validate={[required(t("additionalInfo.required"))]} + > {(field, props) => ( - <Checkbox + <YesNo {...props} + value={"no"} error={field.error} label={t("additionalInfo.hasMonthlyOpusCard")} + required /> )} </Field> - <Field name="study_proof" type="File"> + <Field + name="study_proof" + type="File" + validate={[required(t("additionalInfo.required"))]} + > {(field, props) => ( <FileInput {...props} value={field.value} error={field.error} label={t("additionalInfo.studyProofLabel")} - accept="image/*,.pdf,.doc,.docx,.odt,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" + accept="image/*,.pdf" + required /> )} </Field> - <Field name="photo" type="File"> + <Field + name="photo" + type="File" + validate={[required(t("additionalInfo.required"))]} + > {(field, props) => ( <FileInput {...props} @@ -247,36 +306,46 @@ export function AdditionalInfoForm() { error={field.error} label={t("additionalInfo.photoLabel")} accept="image/*" + required /> )} </Field> - <Field name="cv" type="File"> + <Field + name="cv" + type="File" + validate={[required(t("additionalInfo.required"))]} + > {(field, props) => ( <FileInput {...props} value={field.value} error={field.error} label={t("additionalInfo.cvLabel")} - accept=".pdf,.doc,.docx,.odt,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" + accept=".pdf" + required /> )} </Field> - <Field name="comments"> + <Field + name="comments" + validate={[required(t("additionalInfo.required"))]} + > {(field, props) => ( <TextInput {...props} value={field.value || ""} error={field.error} label="Commentaires" + class="w-full" type="text" placeholder="Commentaires" /> )} </Field> - <div class="flex justify-center"> + <div class="flex flex-col justify-center"> <button type="submit" class="flex justify-center rounded-md bg-light-highlight px-5 py-4 text-xl font-semibold leading-6 text-white shadow-sm hover:bg-light-highlight focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-light-highlight" diff --git a/client/src/i18n/en.ts b/client/src/i18n/en.ts index 62d226cb7dcddfffc9ebd3cce83b5f1bf8126d62..fe18526b0360f0c2d2da2649525fa1b9abf6b1e4 100644 --- a/client/src/i18n/en.ts +++ b/client/src/i18n/en.ts @@ -105,32 +105,31 @@ const additionalInfo = { medicalConditionsLabel: "Please specify any medical conditions that we should be aware of.", allergies: "Allergies", - allergiesLabel: "Please specify any allergies that we should be aware of.", + allergiesLabel: "Do you have any allergies? If so, please specify.", pronouns: "Pronouns", - pronounsLabel: "Please specify your pronouns.", + pronounsLabel: "What are your pronouns?", dietaryRestrictions: "Dietary restrictions", phoneNumber: "Phone number", - phoneNumberLabel: "Please specify your phone number.", + phoneNumberLabel: "Your phone number.", tshirtSize: "T-shirt size", - emergencyContactName: "Emergency contact full name", - emergencyContactNameLabel: - "Please specify the name of your emergency contact.", - emergencyContactPhone: "Emergency contact phone number", - emergencyContactPhoneLabel: - "Please specify the phone number of your emergency contact.", - emergencyContactRelationship: "Emergency contact relationship", - emergencyContactRelationshipLabel: - "Please specify the relationship of your emergency contact.", + emergencyContact: + "Please provide the information of your emergency contact.", + emergencyContactNameLabel: "Emergency contact full name", + emergencyContactPhoneLabel: "Emergency contact phone number", + emergencyContactRelationshipLabel: "Emergency contact relationship", hasMonthlyOpusCard: - "Do you have a monthly OPUS card for the Montréal region?", - reducedMobilityLabel: "Any acommodation for reduced mobility?", - reducedMobility: "What do you need?", + "Do you have a monthly OPUS card for the Montréal region at the moment of the CQI?", + reducedMobilityLabel: + "Do you need reduced mobility assistance? If so, please specify below.", studyProofLabel: "Study proof with number of credits (pdf/jpg/jpeg/png)", - photoLabel: "Photo (png/jpg/jpeg)", - cvLabel: "CV (pdf/docx/odt)", + photoLabel: "Photo 512x512 (png)", + cvLabel: "CV (pdf)", success: "Your information has been saved.", error: "An error occured. Please try again.", file: "Click or drag and drop a file", + yes: "Yes", + no: "No", + required: "This field is required", } const dashboard = { diff --git a/client/src/i18n/fr.ts b/client/src/i18n/fr.ts index 5cc07a47349f8980b3f455b6aa7215210f89e5dd..a79b95967e85d076bd19fbdeee0f3f3b2afbc8f4 100644 --- a/client/src/i18n/fr.ts +++ b/client/src/i18n/fr.ts @@ -103,7 +103,7 @@ const loginPage = { const additionalInfo = { medicalConditions: "Conditions médicales", medicalConditionsLabel: - "Veuillez spécifier toute condition médicale dont nous devrions être informés.", + "Veuillez spécifier toutes conditions médicales dont nous devrions être informés.", allergies: "Allergies", allergiesLabel: "Avez-vous des allergies/intolérances? Si oui, lesquelles?", pronouns: "Pronoms", @@ -112,20 +112,25 @@ const additionalInfo = { phoneNumber: "Numéro de téléphone", phoneNumberLabel: "Votre téléphone", tshirtSize: "Taille de T-shirt", + emergencyContact: + "Veuillez fournir les informations de votre contact d'urgence", emergencyContactNameLabel: "Nom complet du contact d'urgence", emergencyContactPhoneLabel: "Numéro de téléphone du contact d'urgence", emergencyContactRelationshipLabel: "Relation avec le contact d'urgence", hasMonthlyOpusCard: "Avez-vous, au moment de la CQI, une passe OPUS mensuelle pour la région de Montréal?", reducedMobilityLabel: - "Avez-vous besoin d'aménagement pour mobilité réduite? Si oui, veuillez spécifier.", + "Avez-vous besoin d'aménagement pour mobilité réduite? Si oui, veuillez spécifier ci-dessous.", studyProofLabel: "Preuve d'études avec nombre de crédits (pdf/jpg/jpeg/png)", - photoLabel: "Photo 128x128: (png)", + photoLabel: "Photo 512x512: (png)", cvLabel: "CV (pdf)", success: "Informations ajoutées avec succès", error: "Erreur lors de l'ajout des informations", file: "Cliquer ou glissez-déposez un fichier", + yes: "Oui", + no: "Non", + required: "Cette information est requise", } const dashboard = {