diff --git a/hotel-management/src/constants/messages.ts b/hotel-management/src/constants/messages.ts new file mode 100644 index 0000000..67eb3df --- /dev/null +++ b/hotel-management/src/constants/messages.ts @@ -0,0 +1,5 @@ +const invalidFormatMessage = (field: string) => { + return `Invalid ${field} format`; +}; + +export { invalidFormatMessage }; diff --git a/hotel-management/src/helpers/utils.ts b/hotel-management/src/helpers/utils.ts index 767a3d0..b5a3c19 100644 --- a/hotel-management/src/helpers/utils.ts +++ b/hotel-management/src/helpers/utils.ts @@ -1,3 +1,4 @@ +import { invalidFormatMessage } from '../constants/messages'; import { TKeyValue, TPropValues, TStateSchema } from '../globals/types'; const VALUE = 'value'; @@ -27,10 +28,27 @@ const getPropValues = (stateSchema: TStateSchema, prop?: TPropValues) => { }, {} as TKeyValue); }; +type TValidator = { + validatorFunc: (value: string) => boolean; + prop: string; + required?: boolean; +}; + +const addValidator = ({ validatorFunc, prop, required = true }: TValidator) => { + return { + required, + validator: { + func: validatorFunc, + error: invalidFormatMessage(prop), + }, + }; +}; + export { isObject, isRequired, getPropValues, + addValidator, VALUE, ERROR, REQUIRED_FIELD_ERROR, diff --git a/hotel-management/src/hooks/useForm.ts b/hotel-management/src/hooks/useForm.ts index 74be96e..fa5e71c 100644 --- a/hotel-management/src/hooks/useForm.ts +++ b/hotel-management/src/hooks/useForm.ts @@ -33,7 +33,6 @@ const useForm = ( // Get a local copy of stateSchema useEffect(() => { - setDisable(true); // Disable button in initial render. setInitialErrorState(); }, []); // eslint-disable-line @@ -116,8 +115,10 @@ const useForm = ( // Making sure that there's no error in the state // before calling the submit callback function + // and disabled button if (!validateErrorState()) { submitFormCallback(values); + setDisable(true); } }, [validateErrorState, submitFormCallback, values], diff --git a/hotel-management/src/pages/User/Dialog.tsx b/hotel-management/src/pages/User/Dialog.tsx new file mode 100644 index 0000000..defe696 --- /dev/null +++ b/hotel-management/src/pages/User/Dialog.tsx @@ -0,0 +1,39 @@ +import { forwardRef, useEffect } from 'react'; + +// Components +import Dialog from '../../components/Dialog'; +import UserForm from './Form'; + +// Interfaces +import { IDialogProps } from '../../globals/interfaces'; + +const UserDialog = forwardRef((props, ref) => { + const dialogRef = ref as React.MutableRefObject< + HTMLDialogElement | undefined + >; + const { onClose } = props; + + useEffect(() => { + if (dialogRef.current) { + dialogRef.current.addEventListener('click', (e: MouseEvent) => { + const dialogDimensions = dialogRef.current!.getBoundingClientRect(); + if ( + e.clientX < dialogDimensions.left || + e.clientX > dialogDimensions.right || + e.clientY < dialogDimensions.top || + e.clientY > dialogDimensions.bottom + ) { + dialogRef.current!.close(); + } + }); + } + }, [dialogRef]); + + return ( + + + + ); +}) as React.FC; + +export default UserDialog; diff --git a/hotel-management/src/pages/User/Form.tsx b/hotel-management/src/pages/User/Form.tsx new file mode 100644 index 0000000..01bf120 --- /dev/null +++ b/hotel-management/src/pages/User/Form.tsx @@ -0,0 +1,212 @@ +import styled from 'styled-components'; + +// Styled +import Input from '../../commons/styles/Input'; +import { useState } from 'react'; +import TextArea from '../../commons/styles/TextArea'; + +// Components +import Form from '../../components/Form'; +import FormRow from '../../components/FormRow'; +import Button from '../../commons/styles/Button.ts'; + +// Types +import { TKeyValue, TStateSchema, TValidator } from '../../globals/types'; + +// Hooks +import useForm from '../../hooks/useForm'; + +// Utils +import { + isValidAddress, + isValidNumber, + isValidPhoneNumber, + isValidString, +} from '../../helpers/validators'; +import { addValidator } from '../../helpers/utils.ts'; + +const FormBtn = styled(Button)` + width: 100%; + + &:disabled, + &[disabled] { + background-color: var(--disabled-btn-color); + } +`; + +interface IUserFormProp { + onClose: () => void; +} + +const UserForm = ({ onClose }: IUserFormProp) => { + const [reset, setReset] = useState(true); + + // Define your state schema + const stateSchema: TStateSchema = { + fullName: { value: '', error: '' }, + identifiedCode: { value: '', error: '' }, + phone: { value: '', error: '' }, + room: { value: '', error: '' }, + address: { value: '', error: '' }, + }; + + // prettier-ignore + const stateValidatorSchema: TValidator = { + fullName: addValidator({ + validatorFunc: isValidString, + prop: 'full name' + }), + identifiedCode: addValidator({ + validatorFunc: isValidNumber, + prop: 'identified code', + }), + phone: addValidator({ + validatorFunc: isValidPhoneNumber, + prop: 'phone number', + }), + room: addValidator({ + validatorFunc: isValidNumber, + prop: 'room number' + }), + address: addValidator({ + validatorFunc: isValidAddress, + prop: 'address' + }), + }; + + // Submit form + const onSubmitForm = (state: TKeyValue) => { + alert(JSON.stringify(state, null, 2)); + onClose(); + onResetForm(); + }; + + // prettier-ignore + const { + values, + errors, + dirty, + handleOnChange, + handleOnSubmit, + disable } = + useForm( + stateSchema, + stateValidatorSchema, + onSubmitForm, + ); + + // prettier-ignore + const { + fullName, + identifiedCode, + phone, + room, + address + } = values; + + // Reset form + const onResetForm = () => { + setReset(!reset); + Object.keys(values).forEach((key) => (values[key] = '')); + }; + + return ( +
+ + + + + + + + + + + + + + + + + + +