diff --git a/client/src/binding/DietaryRestriction.ts b/client/src/binding/DietaryRestriction.ts new file mode 100644 index 0000000000000000000000000000000000000000..8c48a7249ecb886717654fa194d30d87a61210a6 --- /dev/null +++ b/client/src/binding/DietaryRestriction.ts @@ -0,0 +1,8 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DietaryRestriction = + | "none" + | "vegetarian" + | "vegan" + | "halal" + | "other"; diff --git a/client/src/binding/ParticipantInfo.ts b/client/src/binding/ParticipantInfo.ts index 4d2a8110be81fc7c8585173953df878e22b72901..bac4e0fd16f26fc5d26a6a8e6558c232dc5ebabe 100644 --- a/client/src/binding/ParticipantInfo.ts +++ b/client/src/binding/ParticipantInfo.ts @@ -1,4 +1,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DietaryRestriction } from "./DietaryRestriction"; import type { TshirtSize } from "./TshirtSize"; export type ParticipantInfo = { @@ -6,11 +7,13 @@ export type ParticipantInfo = { allergies: string | null; pronouns: string | null; supper: string | null; - is_vegetarian: boolean | null; phone_number: string | null; tshirt_size: TshirtSize | null; comments: string | null; - emergency_contact: string | null; + dietary_restrictions: DietaryRestriction | null; + emergency_contact_name: string | null; + emergency_contact_phone: string | null; + emergency_contact_relationship: string | null; has_monthly_opus_card: boolean | null; reduced_mobility: string | null; study_proof: File; diff --git a/client/src/binding/University.ts b/client/src/binding/University.ts index f62004e94784db01e193ac3a53db8cab9d9d1619..8a2573e20ac92cfcd81bb228bbeae3ae2d1a5e6e 100644 --- a/client/src/binding/University.ts +++ b/client/src/binding/University.ts @@ -12,6 +12,6 @@ export type University = | "ets" | "polymtl" | "ulaval" - | "ulaval_agriculture" + | "drummond" | "uds" | "none"; diff --git a/client/src/components/ReturnDashboard.tsx b/client/src/components/ReturnDashboard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2250863446a0c261122020ec75fde77b514837a7 --- /dev/null +++ b/client/src/components/ReturnDashboard.tsx @@ -0,0 +1,16 @@ +import { CaretCircleLeft } from "phosphor-solid-js" +import PrefetchLink from "./PrefetchLink" +import { t } from "../stores/locale" + +export default function Goback() { + return ( + <PrefetchLink + to="/dashboard" + file="Dashboard" + class="absolute left-8 top-0 flex flex-row items-center gap-2" + > + <CaretCircleLeft size="2rem" /> + <span>{t("dashboard.goback")}</span> + </PrefetchLink> + ) +} diff --git a/client/src/components/forms-component/PasswordInput.tsx b/client/src/components/forms-component/PasswordInput.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9fa7ea5aadd0716b68e9680a0bae2ddd98db7ab4 --- /dev/null +++ b/client/src/components/forms-component/PasswordInput.tsx @@ -0,0 +1,82 @@ +import clsx from "clsx" +import { createMemo, createSignal, JSX, splitProps } from "solid-js" +import { InputError } from "./InputError" +import { InputLabel } from "./InputLabel" +import { Eye, EyeSlash } from "phosphor-solid-js" + +type PasswordInputProps = { + ref: (element: HTMLInputElement) => void + name: string + value: string | number | undefined + 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" +} + +/** + * Text input field that users can type into. Various decorations can be + * displayed in or around the field to communicate the entry requirements. + */ +export function PasswordInput(props: PasswordInputProps) { + // Split input element props + const [, inputProps] = splitProps(props, [ + "class", + "value", + "label", + "error", + "padding", + ]) + + // Create memoized value + const getValue = createMemo<string | number | undefined>( + (prevValue) => + props.value === undefined + ? "" + : !Number.isNaN(props.value) + ? props.value + : prevValue, + "", + ) + + const [visibility, setVisibility] = createSignal(false) + + return ( + <div class={clsx("w-full", props.class)}> + <InputLabel + name={props.name} + label={props.label} + required={props.required} + /> + <div class="relative"> + <input + {...inputProps} + class={clsx( + "h-12 w-full rounded-2xl border-2 bg-white px-5 outline-none placeholder:text-slate-500 dark:bg-gray-900 md:text-lg lg:px-6 lg:text-xl", + props.error + ? "border-red-600/50 dark:border-red-400/50" + : "border-slate-200 hover:border-slate-300 focus:border-sky-600/50 dark:border-slate-800 dark:hover:border-slate-700 dark:focus:border-sky-400/50", + )} + type={clsx(visibility() ? "text" : "password")} + placeholder="********" + id={props.name} + value={getValue()} + aria-invalid={!!props.error} + aria-errormessage={`${props.name}-error`} + /> + <button + type="button" + class="absolute right-5 top-1/2 -translate-y-1/2 transform" + onClick={() => setVisibility(!visibility())} + > + {visibility() ? <Eye /> : <EyeSlash />} + </button> + </div> + <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 77290903dd6dd523ac662563169cf4e24c74ee4d..fc77ff1a73608c7d622290cba1429324051471ed 100644 --- a/client/src/components/forms/AdditionnalInfoForm.tsx +++ b/client/src/components/forms/AdditionnalInfoForm.tsx @@ -6,12 +6,15 @@ import { ParticipantInfo } from "../../binding/ParticipantInfo" import { Select } from "../forms-component/Select" import { FileInput } from "../forms-component/FileInput" import { getParticipant, patchParticipantInfo } from "../../request/routes" -import { createEffect } from "solid-js" +import { createEffect, createSignal } from "solid-js" import { t } from "../../stores/locale" +import { SubmitError } from "../forms-component/SubmitError" +import { SubmitSuccess } from "../forms-component/SubmitSuccess" export function AdditionalInfoForm() { - const navigate = useNavigate() const [loginForm, { Form, Field }] = createForm<ParticipantInfo>() + const [error, setError] = createSignal<string | null>(null) + const [success, setSuccess] = createSignal<string | null>(null) const handleSubmit: SubmitHandler<ParticipantInfo> = async ( values, @@ -20,9 +23,9 @@ export function AdditionalInfoForm() { event.preventDefault() const response = await patchParticipantInfo(values) if (response && response.status == 201) { - navigate("/dashboard") + setSuccess(t("additionalInfo.success")) } else { - console.log(response) + setError(t("additionalInfo.error")) } } @@ -38,7 +41,7 @@ export function AdditionalInfoForm() { return ( <Form - class="flex w-1/2 flex-col space-y-6" + class="flex w-1/3 flex-col space-y-6" action="#" method="post" onSubmit={handleSubmit} @@ -82,13 +85,35 @@ export function AdditionalInfoForm() { )} </Field> - <Field name="is_vegetarian" type="boolean"> + <Field name="dietary_restrictions"> {(field, props) => ( - <Checkbox + <Select {...props} - checked={field.value || false} + value={field.value || "none"} error={field.error} - label={t("additionalInfo.vegetarianLabel")} + label={t("additionalInfo.dietaryRestrictions")} + options={[ + { + label: "Aucune", + value: "none", + }, + { + label: "Végan", + value: "vegan", + }, + { + label: "Végétarien", + value: "vegetarian", + }, + { + label: "Halal", + value: "halal", + }, + { + label: "Autre à indiquer en commentaire", + value: "other", + }, + ]} /> )} </Field> @@ -143,15 +168,45 @@ export function AdditionalInfoForm() { )} </Field> - <Field name="emergency_contact"> + <Field name="emergency_contact_name"> + {(field, props) => ( + <TextInput + {...props} + value={field.value || ""} + error={field.error} + label={t("additionalInfo.emergencyContactNameLabel")} + type="text" + placeholder={t("additionalInfo.emergencyContactName")} + /> + )} + </Field> + + <Field name="emergency_contact_phone"> + {(field, props) => ( + <TextInput + {...props} + value={field.value || ""} + error={field.error} + label={t("additionalInfo.emergencyContactPhoneLabel")} + type="text" + placeholder={t("additionalInfo.emergencyContactPhone")} + /> + )} + </Field> + + <Field name="emergency_contact_relationship"> {(field, props) => ( <TextInput {...props} value={field.value || ""} error={field.error} - label={t("additionalInfo.emergencyContactLabel")} + label={t( + "additionalInfo.emergencyContactRelationshipLabel", + )} type="text" - placeholder={t("additionalInfo.emergencyContact")} + placeholder={t( + "additionalInfo.emergencyContactRelationship", + )} /> )} </Field> @@ -235,6 +290,8 @@ export function AdditionalInfoForm() { > Mettre à jour les renseignements personnels </button> + <SubmitError error={error} /> + <SubmitSuccess success={success} /> </div> </Form> ) diff --git a/client/src/components/forms/LoginForm.tsx b/client/src/components/forms/LoginForm.tsx index 397c1b767eef343dffe69e19b6d9a68785cd08bd..6be67793a0aef07832b1cf70576aa6e948509bce 100644 --- a/client/src/components/forms/LoginForm.tsx +++ b/client/src/components/forms/LoginForm.tsx @@ -13,6 +13,7 @@ import { SubmitError } from "../forms-component/SubmitError" import { createSignal } from "solid-js" import { t } from "../../stores/locale" import PrefetchLink from "../PrefetchLink" +import { PasswordInput } from "../forms-component/PasswordInput" export default function LoginForm() { const [_loginForm, { Form, Field }] = createForm<AuthPayload>() @@ -65,13 +66,11 @@ export default function LoginForm() { ]} > {(field, props) => ( - <TextInput + <PasswordInput {...props} value={field.value} error={field.error} - type="password" label={t("loginPage.password")} - placeholder="********" required /> )} diff --git a/client/src/components/forms/NewPasswordForm.tsx b/client/src/components/forms/NewPasswordForm.tsx index a3849abe648a85b46ec068e0710b587875e7594f..7a8267ba125f50b3812830af523bd5361b003e53 100644 --- a/client/src/components/forms/NewPasswordForm.tsx +++ b/client/src/components/forms/NewPasswordForm.tsx @@ -1,33 +1,45 @@ -import { createForm } from "@modular-forms/solid" +import { createForm, minLength, required } from "@modular-forms/solid" import { ChangePasswordPayload } from "../../binding/ChangePasswordPayload" import { changePassword } from "../../request/routes" -import { TextInput } from "../forms-component/TextInput" import { SubmitError } from "../forms-component/SubmitError" import { createEffect, createSignal } from "solid-js" +import { t } from "../../stores/locale" +import { SubmitSuccess } from "../forms-component/SubmitSuccess" +import { PasswordInput } from "../forms-component/PasswordInput" export function NewPassword() { const [_form, { Form, Field }] = createForm<ChangePasswordPayload>() const [error, setError] = createSignal<string | null>(null) + const [success, setSuccess] = createSignal<string | null>(null) const onSubmit = async (data: ChangePasswordPayload) => { const response = await changePassword(data) if (response.error) { setError("Erreur lors du changement de mot de passe") + } else { + setSuccess("Votre mot de passe a été changé avec succès") } } createEffect(async () => {}) return ( <Form onSubmit={onSubmit} class="flex flex-col gap-8"> - <Field name="new_password"> + <Field + name="new_password" + validate={[ + required(t("loginPage.requiredPassword")), + minLength( + 8, + "You password must have 8 characters or more.", + ), + ]} + > {(field, props) => ( - <TextInput + <PasswordInput {...props} value={field.value} error={field.error} class="w-96" - type="password" label="Nouveau mot de passe" - placeholder="Nouveau mot de passe" required /> )} @@ -40,6 +52,7 @@ export function NewPassword() { Changer le mot de passe </button> <SubmitError error={error} /> + <SubmitSuccess success={success} /> </div> </Form> ) diff --git a/client/src/components/forms/ParticipantForm.tsx b/client/src/components/forms/ParticipantForm.tsx index 8de21106d4001d6c078e1232b0e9b38ec31a8e5c..0a2a8c1396a3a508822f253db5d68248e851ab58 100644 --- a/client/src/components/forms/ParticipantForm.tsx +++ b/client/src/components/forms/ParticipantForm.tsx @@ -1,6 +1,6 @@ import { PlusCircle, Trash } from "phosphor-solid-js" import { MinimalParticipant } from "../../binding/MinimalParticipant" -import { createResource, For } from "solid-js" +import { createResource, For, createSignal } from "solid-js" import { createForm } from "@modular-forms/solid" import { Select } from "../forms-component/Select" import { TextInput } from "../forms-component/TextInput" @@ -10,6 +10,7 @@ import { fetchParticipants, submitMinimalParticipant, } from "../../request/routes" +import { t } from "../../stores/locale" interface ParticipantRowProps { participant: ParticipantPreview @@ -18,39 +19,60 @@ interface ParticipantRowProps { function ParticipantRow(props: ParticipantRowProps) { const p = props.participant + const [isModalOpen, setIsModalOpen] = createSignal(false) + const deleteParticipantOnClick = async () => { await deleteParticipant(p) props.refetch() + setIsModalOpen(false) } - return ( - <tr class="border-b border-gray-200"> - <td class="p-2 text-center">{p.first_name}</td> - <td class="p-2 text-center">{p.last_name}</td> - <td class="p-2 text-center">{p.email}</td> - <td class="p-2 text-center">{p.competition}</td> - <td class="p-2 text-center">{p.role}</td> - {localStorage.getItem("role") === "organizer" && ( - <td class="p-2 text-center">{p.university}</td> - )} - <td class="flex flex-row gap-4 p-2 text-center"> - {/* <button - type="button" - class="rounded bg-blue-500 p-1 font-bold text-white hover:bg-blue-700" - > - <Info class="h-8 w-8"></Info> - </button> */} - {localStorage.getItem("role") !== "volunteer" && ( - <button - type="button" - class="rounded bg-red-500 p-1 font-bold text-white hover:bg-red-700" - onClick={deleteParticipantOnClick} - > - <Trash class="h-8 w-8"></Trash> - </button> + return ( + <> + <tr class="border-b border-gray-200"> + <td class="p-2 text-center">{p.first_name}</td> + <td class="p-2 text-center">{p.last_name}</td> + <td class="p-2 text-center">{p.email}</td> + <td class="p-2 text-center">{p.competition}</td> + <td class="p-2 text-center">{p.role}</td> + {localStorage.getItem("role") === "organizer" && ( + <td class="p-2 text-center">{p.university}</td> )} - </td> - </tr> + <td class="flex flex-row gap-4 p-2 text-center"> + {localStorage.getItem("role") !== "volunteer" && ( + <button + type="button" + class="rounded bg-red-500 p-1 font-bold text-white hover:bg-red-700" + onClick={() => setIsModalOpen(true)} + > + <Trash class="h-8 w-8"></Trash> + </button> + )} + </td> + </tr> + {isModalOpen() && ( + <div class="p-1/2 absolute left-0 right-0 top-0 z-10 ml-auto mr-auto w-1/2 rounded-lg bg-gray-50 p-4 shadow-2xl"> + <h2 class="text-2xl font-bold"> + {t("participantsList.confirmDeleteTitle")} + </h2> + <p>{t("participantsList.confirmDelete")}</p> + <div class="mt-4 flex justify-end gap-2"> + <button + class="rounded bg-gray-500 p-2 text-white hover:bg-gray-700" + onClick={() => setIsModalOpen(false)} + > + {t("participantsList.cancel")} + </button> + <button + class="rounded bg-red-500 p-2 text-white hover:bg-red-700" + onClick={deleteParticipantOnClick} + > + {t("participantsList.delete")} + </button> + </div> + </div> + )} + </> ) } @@ -107,6 +129,7 @@ export default function ParticipantForm() { console.log("refetching") refetch() } + return ( <Form onSubmit={onSubmit}> <table class="min-w-full border border-gray-300 bg-white"> @@ -155,7 +178,7 @@ export default function ParticipantForm() { {...props} value={field.value} error={field.error} - class="w-52" + class="w-48" type="text" placeholder="Prénom" required @@ -170,7 +193,7 @@ export default function ParticipantForm() { {...props} value={field.value} error={field.error} - class="w-52" + class="w-48" type="text" placeholder="Nom" required @@ -313,8 +336,8 @@ export default function ParticipantForm() { value: "ulaval", }, { - label: "ULaval Agroalimentaire", - value: "ulaval-agriculture", + label: "Drummondville", + value: "drummond", }, { label: "UDS", diff --git a/client/src/i18n/en.ts b/client/src/i18n/en.ts index 3d63291de8436137e4ede6f4079a5462e80ef542..d27ab515d2af998dbec492ddc280fff518a09713 100644 --- a/client/src/i18n/en.ts +++ b/client/src/i18n/en.ts @@ -108,18 +108,40 @@ const additionalInfo = { allergiesLabel: "Please specify any allergies that we should be aware of.", pronouns: "Pronouns", pronounsLabel: "Please specify your pronouns.", - vegetarianLabel: "Are you vegetarian?", + dietaryRestrictions: "Dietary restrictions", phoneNumber: "Phone number", phoneNumberLabel: "Please specify your phone number.", tshirtSize: "T-shirt size", - emergencyContact: "Emergency contact", - emergencyContactLabel: "Please specify your emergency contact.", - hasMonthlyOpusCard: "Do you have a monthly OPUS card?", + 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.", + hasMonthlyOpusCard: + "Do you have a monthly OPUS card for the Montréal region?", reducedMobilityLabel: "Any acommodation for reduced mobility?", reducedMobility: "What do you need?", - studyProofLabel: "Study proof", - photoLabel: "Photo", - cvLabel: "CV", + studyProofLabel: "Study proof with number of credits (pdf/jpg/jpeg/png)", + photoLabel: "Photo (png/jpg/jpeg)", + cvLabel: "CV (pdf/docx/odt)", + success: "Your information has been saved.", + error: "An error occured. Please try again.", +} + +const dashboard = { + dashboard: "Dashboard", + goback: "Go back", +} + +const participantsList = { + confirmDeleteTitle: "Delete confirmation", + confirmDelete: "Are you sure you want to delete this participant?", + delete: "Delete", + cancel: "Cancel", } export const dict = { @@ -133,6 +155,8 @@ export const dict = { documents: documents, login: "Login", loginPage: loginPage, + dashboard: dashboard, additionalInfo: additionalInfo, + participantsList: participantsList, madeBy: "Made by", } diff --git a/client/src/i18n/fr.ts b/client/src/i18n/fr.ts index 1195552625ac38c0e569a439ab33e2c06fa85df6..0d8d8af8f4414af4ffffb6a8cbe87ba510ffda5f 100644 --- a/client/src/i18n/fr.ts +++ b/client/src/i18n/fr.ts @@ -109,18 +109,40 @@ const additionalInfo = { "Veuillez spécifier toute allergie dont nous devrions être informés.", pronouns: "Pronoms", pronounsLabel: "Veuillez spécifier vos pronoms.", - vegetarianLabel: "Êtes-vous végétarien?", + dietaryRestrictions: "Restrictions alimentaires", phoneNumber: "Numéro de téléphone", phoneNumberLabel: "Veuillez spécifier votre numéro de téléphone.", tshirtSize: "Taille de T-shirt", - emergencyContact: "Contact d'urgence", - emergencyContactLabel: "Veuillez spécifier votre contact d'urgence.", - hasMonthlyOpusCard: "Avez-vous une carte OPUS mensuelle?", + emergencyContactName: "Nom complet du contact d'urgence", + emergencyContactNameLabel: "Veuillez spécifier votre contact d'urgence.", + emergencyContactPhone: "Numéro de téléphone d'urgence", + emergencyContactPhoneLabel: + "Veuillez spécifier le numéro de téléphone de votre contact d'urgence.", + emergencyContactRelationship: "Relation avec le contact d'urgence", + emergencyContactRelationshipLabel: + "Veuillez spécifier la relation avec votre contact d'urgence.", + hasMonthlyOpusCard: + "Avez-vous une passe OPUS mensuelle pour la région de Montréal?", reducedMobilityLabel: "Besoin d'aménagement pour mobilité réduite?", reducedMobility: "Qu'avez-vous besoin?", - studyProofLabel: "Preuve d'études", - photoLabel: "Photo", - cvLabel: "CV", + studyProofLabel: + "Preuve d'études avec nombre de crédits (pdf/jpg/jpeg/png)", + photoLabel: "Photo (png/jpg/jpeg)", + cvLabel: "CV (pdf/docx/odt)", + success: "Informations ajoutées avec succès", + error: "Erreur lors de l'ajout des informations", +} + +const dashboard = { + dashboard: "Tableau de bord", + goback: "Retour", +} + +const participantsList = { + confirmDelete: "Êtes-vous sûr de vouloir supprimer cette personne?", + delete: "Supprimer", + cancel: "Annuler", + confirmDeleteTitle: "Confirmer la suppression", } export const dict = { @@ -135,5 +157,7 @@ export const dict = { login: "Connexion", madeBy: "Créé par", loginPage: loginPage, + dashboard: dashboard, + participantsList: participantsList, additionalInfo: additionalInfo, } diff --git a/client/src/request/routes.ts b/client/src/request/routes.ts index 0f9976d5d5967606cbbd5b06e9b61de81df467c8..950d62f07883238c2134a91c9376357c02d0cd07 100644 --- a/client/src/request/routes.ts +++ b/client/src/request/routes.ts @@ -46,7 +46,7 @@ export async function changePassword(auth: ChangePasswordPayload) { if (!request) { return { error: "No token" } } - return await request.json() + return request } export async function testAuth() { diff --git a/client/src/routes/AdditionalForm.tsx b/client/src/routes/AdditionalForm.tsx index 378b7fb6cb6bede2f7855b2acf7f1157a998f48c..dabf77e7820c5bcb8b179c251c682c03869edb0a 100644 --- a/client/src/routes/AdditionalForm.tsx +++ b/client/src/routes/AdditionalForm.tsx @@ -1,6 +1,7 @@ import FixedImage from "../components/FixedImage" import { AdditionalInfoForm } from "../components/forms/AdditionnalInfoForm" import { ProtectedRoute } from "../components/ProtectedRoute" +import Goback from "../components/ReturnDashboard" export default function AdditionalForm() { return ( @@ -11,7 +12,8 @@ export default function AdditionalForm() { {"Tableau de bord"} </h1> </FixedImage> - <div class="-mt-32 flex flex-col items-center"> + <div class="relative -mt-32 flex w-full flex-col items-center"> + <Goback /> <AdditionalInfoForm /> </div> </div> diff --git a/client/src/routes/ChangePassword.tsx b/client/src/routes/ChangePassword.tsx index 1c341afdd7b42fc6ba3991cf86b20b774cd0814a..77246b66b0ee897e51d551e33ef2ae9561ccde62 100644 --- a/client/src/routes/ChangePassword.tsx +++ b/client/src/routes/ChangePassword.tsx @@ -3,6 +3,7 @@ import { createEffect } from "solid-js" import { testAuth } from "../request/routes" import FixedImage from "../components/FixedImage" import { NewPassword } from "../components/forms/NewPasswordForm" +import Goback from "../components/ReturnDashboard" export default function ChangePassword() { const params = useParams() @@ -26,7 +27,8 @@ export default function ChangePassword() { Changement de mot de passe </h1> </FixedImage> - <div class="-mt-32 flex h-full w-full flex-row items-center justify-center gap-4 p-4 font-futur text-xl font-bold"> + <div class="relative -mt-32 flex h-full w-full flex-row items-center justify-center gap-4 p-4 font-futur text-xl font-bold"> + <Goback /> <NewPassword /> </div> </div> diff --git a/client/src/routes/ForgottenPassword.tsx b/client/src/routes/ForgottenPassword.tsx index fcc7e4d7e6ac738894025ca64cedca9ca5261dea..ea4f8786ac11f05988b34780f4f0f0a38ace92b8 100644 --- a/client/src/routes/ForgottenPassword.tsx +++ b/client/src/routes/ForgottenPassword.tsx @@ -1,5 +1,6 @@ import FixedImage from "../components/FixedImage" import { EmailResetForm } from "../components/forms/EmailResetForm" +import ReturnDashboard from "../components/ReturnDashboard" import { t } from "../stores/locale" export default function ForgottenPassword() { @@ -10,7 +11,8 @@ export default function ForgottenPassword() { {t("loginPage.forgotPassword")} </h1> </FixedImage> - <div class="-mt-32 flex h-full w-full flex-row items-center justify-center gap-4 p-4 font-futur text-xl font-bold"> + <div class="relative -mt-32 flex h-full w-full flex-row items-center justify-center gap-4 p-4 font-futur text-xl font-bold"> + <ReturnDashboard /> <EmailResetForm /> </div> </div> diff --git a/client/src/routes/ListParticipant.tsx b/client/src/routes/ListParticipant.tsx index 963e97628200bce8508027d02c929f3d14fca99d..c727a12f8d7d90666f43b8199d80692c4fea7d60 100644 --- a/client/src/routes/ListParticipant.tsx +++ b/client/src/routes/ListParticipant.tsx @@ -1,6 +1,7 @@ import FixedImage from "../components/FixedImage" import ParticipantForm from "../components/forms/ParticipantForm" import { ProtectedRoute } from "../components/ProtectedRoute" +import Goback from "../components/ReturnDashboard" export default function ListParticipant() { return ( @@ -11,8 +12,9 @@ export default function ListParticipant() { Tableau de bord </h1> </FixedImage> - <div class="-mt-32 flex h-full w-full flex-row items-center justify-center gap-4 p-4 font-futur text-xl font-bold"> - <div class="overflow-x-auto"> + <div class="relative -mt-32 flex h-full w-full flex-row items-center justify-center gap-4 p-4 font-futur text-xl font-bold"> + <Goback /> + <div class="mt-16 overflow-x-auto"> <ParticipantForm /> </div> </div> diff --git a/server/.sqlx/query-11746d3bbe0047f2995a6366ba00cfd84d0dbc1c83bef77cfc83af19e69d5b94.json b/server/.sqlx/query-11746d3bbe0047f2995a6366ba00cfd84d0dbc1c83bef77cfc83af19e69d5b94.json index 2cd25e169e027ed22cf1ec5cfa5ac5786afccb0b..157064769822db99123886bf53e98c4f20886361 100644 --- a/server/.sqlx/query-11746d3bbe0047f2995a6366ba00cfd84d0dbc1c83bef77cfc83af19e69d5b94.json +++ b/server/.sqlx/query-11746d3bbe0047f2995a6366ba00cfd84d0dbc1c83bef77cfc83af19e69d5b94.json @@ -58,7 +58,7 @@ "ets", "polymtl", "ulaval", - "ulaval-agriculture", + "drummond", "uds", "none" ] diff --git a/server/.sqlx/query-668d7e49b4dae36348dbddca35bd8292cb59f2c0f8c20f4d0ac5faf058b183ac.json b/server/.sqlx/query-668d7e49b4dae36348dbddca35bd8292cb59f2c0f8c20f4d0ac5faf058b183ac.json index db0e348f87eeaa1e60014bcdaa9c2e1efcee4ccc..d5ef2275b7915582365a6b0bcb9211c398d6a1b8 100644 --- a/server/.sqlx/query-668d7e49b4dae36348dbddca35bd8292cb59f2c0f8c20f4d0ac5faf058b183ac.json +++ b/server/.sqlx/query-668d7e49b4dae36348dbddca35bd8292cb59f2c0f8c20f4d0ac5faf058b183ac.json @@ -22,7 +22,7 @@ "ets", "polymtl", "ulaval", - "ulaval-agriculture", + "drummond", "uds", "none" ] diff --git a/server/.sqlx/query-8e6f6c7229ba7d13f4690c70229bac29698bbccae3adfb0f2cdcdff1daa9fe15.json b/server/.sqlx/query-8e6f6c7229ba7d13f4690c70229bac29698bbccae3adfb0f2cdcdff1daa9fe15.json index 34f167eaf2146e1ca99811d668eac3bd1041698a..6dfe7a95851b675fb729a2dd9f3c3dd27817dae2 100644 --- a/server/.sqlx/query-8e6f6c7229ba7d13f4690c70229bac29698bbccae3adfb0f2cdcdff1daa9fe15.json +++ b/server/.sqlx/query-8e6f6c7229ba7d13f4690c70229bac29698bbccae3adfb0f2cdcdff1daa9fe15.json @@ -44,7 +44,7 @@ "ets", "polymtl", "ulaval", - "ulaval-agriculture", + "drummond", "uds", "none" ] diff --git a/server/.sqlx/query-a902b8930ff8374bde666f05bbab23a168364c617c4de636fe799cf00f8bc16b.json b/server/.sqlx/query-a902b8930ff8374bde666f05bbab23a168364c617c4de636fe799cf00f8bc16b.json index 5ef37548fba369ddac240234ce3b4e192708889c..a875b5a9575bd5181777f67984736ef91f80c53e 100644 --- a/server/.sqlx/query-a902b8930ff8374bde666f05bbab23a168364c617c4de636fe799cf00f8bc16b.json +++ b/server/.sqlx/query-a902b8930ff8374bde666f05bbab23a168364c617c4de636fe799cf00f8bc16b.json @@ -49,7 +49,7 @@ "ets", "polymtl", "ulaval", - "ulaval-agriculture", + "drummond", "uds", "none" ] diff --git a/server/.sqlx/query-b2cf8ace57c4a960f64b49060612f3c4918a7eb20626e5827dd2ebd0b5c65294.json b/server/.sqlx/query-b2cf8ace57c4a960f64b49060612f3c4918a7eb20626e5827dd2ebd0b5c65294.json deleted file mode 100644 index 746f3d3cd9ed62f5f2696605234d9c9d493fac42..0000000000000000000000000000000000000000 --- a/server/.sqlx/query-b2cf8ace57c4a960f64b49060612f3c4918a7eb20626e5827dd2ebd0b5c65294.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE participants SET (medical_conditions, allergies, is_vegetarian, pronouns, phone_number, tshirt_size, comments, emergency_contact, has_monthly_opus_card, reduced_mobility, study_proof, photo, cv)\n = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) WHERE id = $14", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Bool", - "Text", - "Text", - { - "Custom": { - "name": "tshirt_size", - "kind": { - "Enum": [ - "xs", - "s", - "m", - "l", - "xl", - "xxl" - ] - } - } - }, - "Text", - "Text", - "Bool", - "Text", - "Bytea", - "Bytea", - "Bytea", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "b2cf8ace57c4a960f64b49060612f3c4918a7eb20626e5827dd2ebd0b5c65294" -} diff --git a/server/.sqlx/query-b619a4bd63f93ae2a43618f72b91f5ef61bb19c225bfa5714d2d3d0a94547d0a.json b/server/.sqlx/query-b619a4bd63f93ae2a43618f72b91f5ef61bb19c225bfa5714d2d3d0a94547d0a.json new file mode 100644 index 0000000000000000000000000000000000000000..aa8f8773657b13368d8d58c5d02b411bf0e7cf0e --- /dev/null +++ b/server/.sqlx/query-b619a4bd63f93ae2a43618f72b91f5ef61bb19c225bfa5714d2d3d0a94547d0a.json @@ -0,0 +1,56 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE participants SET (medical_conditions, allergies, pronouns, phone_number, tshirt_size, comments, dietary_restrictions, emergency_contact_name, emergency_contact_phone, emergency_contact_relationship, has_monthly_opus_card, reduced_mobility, study_proof, photo, cv)\n = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE id = $16", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text", + { + "Custom": { + "name": "tshirt_size", + "kind": { + "Enum": [ + "xs", + "s", + "m", + "l", + "xl", + "xxl" + ] + } + } + }, + "Text", + { + "Custom": { + "name": "dietary_restriction", + "kind": { + "Enum": [ + "none", + "vegetarian", + "vegan", + "halal", + "other" + ] + } + } + }, + "Text", + "Text", + "Text", + "Bool", + "Text", + "Bytea", + "Bytea", + "Bytea", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "b619a4bd63f93ae2a43618f72b91f5ef61bb19c225bfa5714d2d3d0a94547d0a" +} diff --git a/server/migrations/20241028145047_change_field_participants_table.sql b/server/migrations/20241028145047_change_field_participants_table.sql new file mode 100644 index 0000000000000000000000000000000000000000..0f518ea32e3e80aa9004e207dfc6c6a60a51f60d --- /dev/null +++ b/server/migrations/20241028145047_change_field_participants_table.sql @@ -0,0 +1,19 @@ +-- Add migration script here +ALTER TYPE UNIVERSITY RENAME TO UNIVERSITY_OLD; + +CREATE TYPE UNIVERSITY AS ENUM ('uqac', 'uqar', 'uqat', 'uqo', 'uqtr', 'mcgill', 'mcgill_macdonald', 'concordia', 'ets', 'polymtl', 'ulaval', 'drummond', 'uds', 'none'); + +ALTER TABLE participants ALTER COLUMN university TYPE UNIVERSITY USING university::text::UNIVERSITY; + +DROP TYPE UNIVERSITY_OLD; + +CREATE TYPE DIETARY_RESTRICTION AS ENUM ('none', 'vegetarian', 'vegan', 'halal', 'other'); + +ALTER TABLE participants ADD COLUMN dietary_restrictions DIETARY_RESTRICTION; + +ALTER TABLE participants ALTER COLUMN is_vegetarian DROP NOT NULL; + +ALTER TABLE participants ADD COLUMN emergency_contact_name TEXT; +ALTER TABLE participants ADD COLUMN emergency_contact_phone TEXT; +ALTER TABLE participants ADD COLUMN emergency_contact_relationship TEXT; +ALTER TABLE participants DROP COLUMN emergency_contact; diff --git a/server/src/model/dietary_restriction.rs b/server/src/model/dietary_restriction.rs new file mode 100644 index 0000000000000000000000000000000000000000..3658b55e1f93891958e53b21e08af0813899a05a --- /dev/null +++ b/server/src/model/dietary_restriction.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, sqlx::Type, TS)] +#[serde(rename_all = "snake_case")] +#[sqlx(rename_all = "snake_case", type_name = "DIETARY_RESTRICTION")] +#[ts(export)] +pub enum DietaryRestriction { + None, + Vegetarian, + Vegan, + Halal, + Other, +} diff --git a/server/src/model/mod.rs b/server/src/model/mod.rs index babe45612533fbd96c9c0f86bbd7cec63502ae2e..df739ac14906399cdb78e29a67420d50bdb6d7e3 100644 --- a/server/src/model/mod.rs +++ b/server/src/model/mod.rs @@ -1,4 +1,5 @@ pub mod competition; +pub mod dietary_restriction; pub mod minimal_participant; pub mod participant_info; pub mod preview_participant; diff --git a/server/src/model/participant_info.rs b/server/src/model/participant_info.rs index 834ee49439d84d1680931ed901ad80b9df6b9dfb..4b55c9f1de01d28f8ab69a7a1f81ac8cfb84e23d 100644 --- a/server/src/model/participant_info.rs +++ b/server/src/model/participant_info.rs @@ -7,7 +7,9 @@ use uuid::Uuid; use crate::utility::{deserialize_base64, serialize_base64}; -use super::{tshirt_size::TshirtSize, university::University}; +use super::{ + dietary_restriction::DietaryRestriction, tshirt_size::TshirtSize, university::University, +}; #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, TS)] #[ts(export)] @@ -16,11 +18,13 @@ pub struct ParticipantInfo { pub allergies: Option<String>, pub pronouns: Option<String>, pub supper: Option<String>, - pub is_vegetarian: Option<bool>, pub phone_number: Option<String>, pub tshirt_size: Option<TshirtSize>, pub comments: Option<String>, - pub emergency_contact: Option<String>, + pub dietary_restrictions: Option<DietaryRestriction>, + pub emergency_contact_name: Option<String>, + pub emergency_contact_phone: Option<String>, + pub emergency_contact_relationship: Option<String>, pub has_monthly_opus_card: Option<bool>, pub reduced_mobility: Option<String>, #[serde( @@ -65,16 +69,18 @@ impl ParticipantInfo { pub async fn write_to_database(&self, id: Uuid, db: &PgPool) -> Result<(), sqlx::Error> { sqlx::query!( - r#"UPDATE participants SET (medical_conditions, allergies, is_vegetarian, pronouns, phone_number, tshirt_size, comments, emergency_contact, has_monthly_opus_card, reduced_mobility, study_proof, photo, cv) - = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) WHERE id = $14"#, + r#"UPDATE participants SET (medical_conditions, allergies, pronouns, phone_number, tshirt_size, comments, dietary_restrictions, emergency_contact_name, emergency_contact_phone, emergency_contact_relationship, has_monthly_opus_card, reduced_mobility, study_proof, photo, cv) + = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE id = $16"#, self.medical_conditions, self.allergies, - self.is_vegetarian, self.pronouns, self.phone_number, self.tshirt_size as Option<TshirtSize>, self.comments, - self.emergency_contact, + self.dietary_restrictions as Option<DietaryRestriction>, + self.emergency_contact_name, + self.emergency_contact_phone, + self.emergency_contact_relationship, self.has_monthly_opus_card, self.reduced_mobility, self.study_proof, diff --git a/server/src/model/university.rs b/server/src/model/university.rs index 882aaf173af8976210f2526b57bab09a92e69462..32aa81818c23cdb46d7f395ac75009cf1db8d843 100644 --- a/server/src/model/university.rs +++ b/server/src/model/university.rs @@ -17,7 +17,7 @@ pub enum University { Ets, Polymtl, Ulaval, - UlavalAgriculture, + Drummond, Uds, None, }