mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-30 19:52:20 +01:00
(wip) create/update user children
This commit is contained in:
parent
5365cbdaba
commit
39178b5d96
@ -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]
|
||||
|
||||
|
@ -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<ChildFormProps> = ({ child, onChange }) => {
|
||||
export const ChildForm: React.FC<ChildFormProps> = ({ child, onChange, onSubmit }) => {
|
||||
const { t } = useTranslation('public');
|
||||
|
||||
const { register, formState } = useForm<Child>({
|
||||
const { register, formState, handleSubmit } = useForm<Child>({
|
||||
defaultValues: child
|
||||
});
|
||||
|
||||
@ -32,7 +35,7 @@ export const ChildForm: React.FC<ChildFormProps> = ({ child, onChange }) => {
|
||||
<div className="info-area">
|
||||
{t('app.public.child_form.child_form_info')}
|
||||
</div>
|
||||
<form>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<FormInput id="first_name"
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
@ -47,6 +50,35 @@ export const ChildForm: React.FC<ChildFormProps> = ({ child, onChange }) => {
|
||||
label={t('app.public.child_form.last_name')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<FormInput id="birthday"
|
||||
register={register}
|
||||
rules={{ required: true, validate: (value) => 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}
|
||||
/>
|
||||
<FormInput id="phone"
|
||||
register={register}
|
||||
formState={formState}
|
||||
label={t('app.public.child_form.phone')}
|
||||
onChange={handleChange}
|
||||
type="tel"
|
||||
/>
|
||||
<FormInput id="email"
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.public.child_form.email')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<div className="actions">
|
||||
<FabButton type="button" onClick={handleSubmit(onSubmit)}>
|
||||
{t('app.public.child_form.save')}
|
||||
</FabButton>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
@ -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<ChildItemProps> = ({ child, onEdit, onDelete })
|
||||
</div>
|
||||
<div>
|
||||
<div>{t('app.public.child_item.birthday')}</div>
|
||||
<div>{child.birthday}</div>
|
||||
<div>{FormatLib.date(child.birthday)}</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<FabButton icon={<i className="fa fa-edit" />} onClick={() => onEdit(child)} className="edit-button" />
|
||||
|
@ -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<ChildModalProps> = ({ child, isOpen, toggleModal }) => {
|
||||
export const ChildModal: React.FC<ChildModalProps> = ({ child, isOpen, toggleModal, onSuccess, onError }) => {
|
||||
const { t } = useTranslation('public');
|
||||
const [data, setData] = useState<Child>(child);
|
||||
console.log(child, data);
|
||||
|
||||
useEffect(() => {
|
||||
setData(child);
|
||||
}, [child]);
|
||||
|
||||
/**
|
||||
* Save the child to the API
|
||||
@ -32,18 +37,12 @@ export const ChildModal: React.FC<ChildModalProps> = ({ 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<ChildModalProps> = ({ child, isOpen, toggleMod
|
||||
isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
closeButton={true}
|
||||
confirmButton={t('app.public.child_modal.save')}
|
||||
onConfirm={handleSaveChild}
|
||||
preventConfirm={isPreventedSaveChild()}>
|
||||
<ChildForm child={child} onChange={handleChildChanged} />
|
||||
confirmButton={false}
|
||||
onConfirm={handleSaveChild} >
|
||||
<ChildForm child={child} onChange={handleChildChanged} onSubmit={handleSaveChild} />
|
||||
</FabModal>
|
||||
);
|
||||
};
|
||||
|
@ -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<ChildrenListProps> = ({ currentUser }) => {
|
||||
export const ChildrenList: React.FC<ChildrenListProps> = ({ currentUser, onError }) => {
|
||||
const { t } = useTranslation('public');
|
||||
|
||||
const [children, setChildren] = useState<Array<Child>>([]);
|
||||
@ -52,10 +53,17 @@ export const ChildrenList: React.FC<ChildrenListProps> = ({ 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 (
|
||||
<section>
|
||||
<header>
|
||||
@ -70,7 +78,7 @@ export const ChildrenList: React.FC<ChildrenListProps> = ({ currentUser }) => {
|
||||
<ChildItem key={child.id} child={child} onEdit={editChild} onDelete={deleteChild} />
|
||||
))}
|
||||
</div>
|
||||
<ChildModal child={child} isOpen={isOpenChildModal} toggleModal={() => setIsOpenChildModal(false)} />
|
||||
<ChildModal child={child} isOpen={isOpenChildModal} toggleModal={() => setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@ -83,4 +91,4 @@ const ChildrenListWrapper: React.FC<ChildrenListProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['currentUser']));
|
||||
Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['currentUser', 'onSuccess', 'onError']));
|
||||
|
@ -22,13 +22,14 @@ type FormInputProps<TFieldValues, TInputType> = FormComponent<TFieldValues> & Ab
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => 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 = <TFieldValues extends FieldValues, TInputType>({ 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<TFieldValues, TInputType>) => {
|
||||
export const FormInput = <TFieldValues extends FieldValues, TInputType>({ 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<TFieldValues, TInputType>) => {
|
||||
const [characterCount, setCharacterCount] = useState<number>(0);
|
||||
|
||||
/**
|
||||
@ -100,7 +101,8 @@ export const FormInput = <TFieldValues extends FieldValues, TInputType>({ id, re
|
||||
disabled={typeof disabled === 'function' ? disabled(id) : disabled}
|
||||
placeholder={placeholder}
|
||||
accept={accept}
|
||||
maxLength={maxLength} />
|
||||
maxLength={maxLength}
|
||||
max={max} />
|
||||
{(type === 'file' && placeholder) && <span className='fab-button is-black file-placeholder'>{placeholder}</span>}
|
||||
{maxLength && <span className='countdown'>{characterCount} / {maxLength}</span>}
|
||||
{addOn && addOnAction && <button aria-label={addOnAriaLabel} type="button" onClick={addOnAction} className={`addon ${addOnClassName || ''} is-btn`}>{addOn}</button>}
|
||||
|
@ -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
|
||||
|
@ -484,6 +484,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"
|
||||
|
@ -488,11 +488,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"
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user