diff --git a/app/controllers/api/children_controller.rb b/app/controllers/api/children_controller.rb index 7c5a1003a..f923f2bb3 100644 --- a/app/controllers/api/children_controller.rb +++ b/app/controllers/api/children_controller.rb @@ -2,7 +2,7 @@ # API Controller for resources of type Child # Children are used to provide a way to manage multiple users in the family account -class API::ChildrenController < API::ApiController +class API::ChildrenController < API::APIController before_action :authenticate_user! before_action :set_child, only: %i[show update destroy] diff --git a/app/frontend/src/javascript/components/family-account/child-form.tsx b/app/frontend/src/javascript/components/family-account/child-form.tsx index fee281548..2a70e4b9b 100644 --- a/app/frontend/src/javascript/components/family-account/child-form.tsx +++ b/app/frontend/src/javascript/components/family-account/child-form.tsx @@ -1,22 +1,25 @@ import React from 'react'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import moment from 'moment'; import { Child } from '../../models/child'; import { TDateISODate } from '../../typings/date-iso'; import { FormInput } from '../form/form-input'; +import { FabButton } from '../base/fab-button'; interface ChildFormProps { child: Child; onChange: (field: string, value: string | TDateISODate) => void; + onSubmit: (data: Child) => void; } /** * A form for creating or editing a child. */ -export const ChildForm: React.FC = ({ child, onChange }) => { +export const ChildForm: React.FC = ({ child, onChange, onSubmit }) => { const { t } = useTranslation('public'); - const { register, formState } = useForm({ + const { register, formState, handleSubmit } = useForm({ defaultValues: child }); @@ -32,7 +35,7 @@ export const ChildForm: React.FC = ({ child, onChange }) => {
{t('app.public.child_form.child_form_info')}
-
+ = ({ child, onChange }) => { label={t('app.public.child_form.last_name')} onChange={handleChange} /> + moment(value).isBefore(moment().subtract(18, 'year')) }} + formState={formState} + label={t('app.public.child_form.birthday')} + type="date" + max={moment().subtract(18, 'year').format('YYYY-MM-DD')} + onChange={handleChange} + /> + + + +
+ + {t('app.public.child_form.save')} + +
); diff --git a/app/frontend/src/javascript/components/family-account/child-item.tsx b/app/frontend/src/javascript/components/family-account/child-item.tsx index 747d8212c..44ca25aea 100644 --- a/app/frontend/src/javascript/components/family-account/child-item.tsx +++ b/app/frontend/src/javascript/components/family-account/child-item.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Child } from '../../models/child'; import { useTranslation } from 'react-i18next'; import { FabButton } from '../base/fab-button'; +import FormatLib from '../../lib/format'; interface ChildItemProps { child: Child; @@ -27,7 +28,7 @@ export const ChildItem: React.FC = ({ child, onEdit, onDelete })
{t('app.public.child_item.birthday')}
-
{child.birthday}
+
{FormatLib.date(child.birthday)}
} onClick={() => onEdit(child)} className="edit-button" /> diff --git a/app/frontend/src/javascript/components/family-account/child-modal.tsx b/app/frontend/src/javascript/components/family-account/child-modal.tsx index 03f1486c4..baa9af408 100644 --- a/app/frontend/src/javascript/components/family-account/child-modal.tsx +++ b/app/frontend/src/javascript/components/family-account/child-modal.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FabModal, ModalSize } from '../base/fab-modal'; import { Child } from '../../models/child'; @@ -11,15 +11,20 @@ interface ChildModalProps { child?: Child; isOpen: boolean; toggleModal: () => void; + onSuccess: (child: Child) => void; + onError: (error: string) => void; } /** * A modal for creating or editing a child. */ -export const ChildModal: React.FC = ({ child, isOpen, toggleModal }) => { +export const ChildModal: React.FC = ({ child, isOpen, toggleModal, onSuccess, onError }) => { const { t } = useTranslation('public'); const [data, setData] = useState(child); - console.log(child, data); + + useEffect(() => { + setData(child); + }, [child]); /** * Save the child to the API @@ -32,18 +37,12 @@ export const ChildModal: React.FC = ({ child, isOpen, toggleMod await ChildAPI.create(data); } toggleModal(); + onSuccess(data); } catch (error) { - console.error(error); + onError(error); } }; - /** - * Check if the form is valid to save the child - */ - const isPreventedSaveChild = (): boolean => { - return !data?.first_name || !data?.last_name; - }; - /** * Handle the change of a child form field */ @@ -60,10 +59,9 @@ export const ChildModal: React.FC = ({ child, isOpen, toggleMod isOpen={isOpen} toggleModal={toggleModal} closeButton={true} - confirmButton={t('app.public.child_modal.save')} - onConfirm={handleSaveChild} - preventConfirm={isPreventedSaveChild()}> - + confirmButton={false} + onConfirm={handleSaveChild} > + ); }; diff --git a/app/frontend/src/javascript/components/family-account/children-list.tsx b/app/frontend/src/javascript/components/family-account/children-list.tsx index c737e2d98..287f69802 100644 --- a/app/frontend/src/javascript/components/family-account/children-list.tsx +++ b/app/frontend/src/javascript/components/family-account/children-list.tsx @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react'; import { react2angular } from 'react2angular'; import { Child } from '../../models/child'; -// import { ChildListItem } from './child-list-item'; import ChildAPI from '../../api/child'; import { User } from '../../models/user'; import { useTranslation } from 'react-i18next'; @@ -15,12 +14,14 @@ declare const Application: IApplication; interface ChildrenListProps { currentUser: User; + onSuccess: (error: string) => void; + onError: (error: string) => void; } /** * A list of children belonging to the current user. */ -export const ChildrenList: React.FC = ({ currentUser }) => { +export const ChildrenList: React.FC = ({ currentUser, onError }) => { const { t } = useTranslation('public'); const [children, setChildren] = useState>([]); @@ -52,10 +53,17 @@ export const ChildrenList: React.FC = ({ currentUser }) => { */ const deleteChild = (child: Child) => { ChildAPI.destroy(child.id).then(() => { - setChildren(children.filter(c => c.id !== child.id)); + ChildAPI.index({ user_id: currentUser.id }).then(setChildren); }); }; + /** + * Handle save child success from the API + */ + const handleSaveChildSuccess = () => { + ChildAPI.index({ user_id: currentUser.id }).then(setChildren); + }; + return (
@@ -70,7 +78,7 @@ export const ChildrenList: React.FC = ({ currentUser }) => { ))}
- setIsOpenChildModal(false)} /> + setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} /> ); }; @@ -83,4 +91,4 @@ const ChildrenListWrapper: React.FC = (props) => { ); }; -Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['currentUser'])); +Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['currentUser', 'onSuccess', 'onError'])); diff --git a/app/frontend/src/javascript/components/form/form-input.tsx b/app/frontend/src/javascript/components/form/form-input.tsx index d4a4331e9..b19d42ccc 100644 --- a/app/frontend/src/javascript/components/form/form-input.tsx +++ b/app/frontend/src/javascript/components/form/form-input.tsx @@ -22,13 +22,14 @@ type FormInputProps = FormComponent & Ab onChange?: (event: React.ChangeEvent) => void, nullable?: boolean, ariaLabel?: string, - maxLength?: number + maxLength?: number, + max?: number | string, } /** * This component is a template for an input component to use within React Hook Form */ -export const FormInput = ({ id, register, label, tooltip, defaultValue, icon, className, rules, disabled, type, addOn, addOnAction, addOnClassName, addOnAriaLabel, placeholder, error, warning, formState, step, onChange, debounce, accept, nullable = false, ariaLabel, maxLength }: FormInputProps) => { +export const FormInput = ({ id, register, label, tooltip, defaultValue, icon, className, rules, disabled, type, addOn, addOnAction, addOnClassName, addOnAriaLabel, placeholder, error, warning, formState, step, onChange, debounce, accept, nullable = false, ariaLabel, maxLength, max }: FormInputProps) => { const [characterCount, setCharacterCount] = useState(0); /** @@ -100,7 +101,8 @@ export const FormInput = ({ id, re disabled={typeof disabled === 'function' ? disabled(id) : disabled} placeholder={placeholder} accept={accept} - maxLength={maxLength} /> + maxLength={maxLength} + max={max} /> {(type === 'file' && placeholder) && {placeholder}} {maxLength && {characterCount} / {maxLength}} {addOn && addOnAction && } diff --git a/app/models/child.rb b/app/models/child.rb index 49ebdaf9e..93332bde8 100644 --- a/app/models/child.rb +++ b/app/models/child.rb @@ -6,9 +6,11 @@ class Child < ApplicationRecord validates :first_name, presence: true validates :last_name, presence: true + validates :email, presence: true, format: { with: Devise.email_regexp } validate :validate_age + # birthday should less than 18 years ago def validate_age - errors.add(:birthday, 'You should be over 18 years old.') if birthday.blank? && birthday < 18.years.ago + errors.add(:birthday, I18n.t('.errors.messages.birthday_less_than_18_years_ago')) if birthday.blank? || birthday > 18.years.ago end end diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index 4428d8470..12e8c9785 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -478,6 +478,22 @@ en: start_typing: "Start typing..." children_list: heading: "My children" + add_child: "Add a child" + child_modal: + edit_child: "Edit child" + new_child: "New child" + child_form: + child_form_info: "Note that you can only add your children under 18 years old. Supporting documents are requested by your administrator, they will be useful to validate your child's account and authorize the reservation of events." + first_name: "First name" + last_name: "Last name" + birthday: "Birthday" + email: "Email" + phone: "Phone" + save: "Save" + child_item: + first_name: "First name of the child" + last_name: "Last name of the child" + birthday: "Birthday" tour: conclusion: title: "Thank you for your attention" diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index aa33053e7..3ef09b458 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -482,11 +482,14 @@ fr: child_modal: edit_child: "Modifier un enfant" new_child: "Ajouter un enfant" - save: "Enregistrer" child_form: child_form_info: "Notez que vous ne pouvez ajouter que vos enfants de moins de 18 ans. Des pièces justificatives sont demandés par votre administrateur, elles lui seront utiles pour valider le compte de votre enfant et ainsi autoriser la réservation d'événements." first_name: "Prénom" last_name: "Nom" + birthday: "Date de naissance" + email: "Courriel" + phone: "Téléphone" + save: "Enregistrer" child_item: first_name: "Prénom de l'enfant" last_name: "Nom de l'enfant" diff --git a/config/locales/en.yml b/config/locales/en.yml index ef51539d0..f3b148b89 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -49,6 +49,7 @@ en: gateway_amount_too_large: "Payments above %{AMOUNT} are not supported. Please order directly at the reception." product_in_use: "This product have already been ordered" slug_already_used: "is already used" + birthday_less_than_18_years_ago: "Birthday must be under 18 years ago" coupon: code_format_error: "only caps letters, numbers, and dashes are allowed" apipie: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5331975c3..092b677cd 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -49,6 +49,7 @@ fr: gateway_amount_too_large: "Les paiements supérieurs à %{AMOUNT} ne sont pas pris en charge. Merci de passer commande directement à l'accueil." product_in_use: "Ce produit a déjà été commandé" slug_already_used: "est déjà utilisée" + birthday_less_than_18_years_ago: "l'age devez avoir au moins 18 ans." coupon: code_format_error: "seules les majuscules, chiffres et tirets sont autorisés" apipie: