Merge pull request #12 from Nez27/feat/add-user-form

Implement add user form
This commit is contained in:
Loi Phan
2023-10-27 14:33:14 +07:00
committed by GitHub
7 changed files with 302 additions and 10 deletions
@@ -0,0 +1,5 @@
const invalidFormatMessage = (field: string) => {
return `Invalid ${field} format`;
};
export { invalidFormatMessage };
+18
View File
@@ -1,3 +1,4 @@
import { invalidFormatMessage } from '../constants/messages';
import { TKeyValue, TPropValues, TStateSchema } from '../globals/types'; import { TKeyValue, TPropValues, TStateSchema } from '../globals/types';
const VALUE = 'value'; const VALUE = 'value';
@@ -27,10 +28,27 @@ const getPropValues = (stateSchema: TStateSchema, prop?: TPropValues) => {
}, {} as TKeyValue); }, {} 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 { export {
isObject, isObject,
isRequired, isRequired,
getPropValues, getPropValues,
addValidator,
VALUE, VALUE,
ERROR, ERROR,
REQUIRED_FIELD_ERROR, REQUIRED_FIELD_ERROR,
+2 -1
View File
@@ -33,7 +33,6 @@ const useForm = (
// Get a local copy of stateSchema // Get a local copy of stateSchema
useEffect(() => { useEffect(() => {
setDisable(true); // Disable button in initial render.
setInitialErrorState(); setInitialErrorState();
}, []); // eslint-disable-line }, []); // eslint-disable-line
@@ -116,8 +115,10 @@ const useForm = (
// Making sure that there's no error in the state // Making sure that there's no error in the state
// before calling the submit callback function // before calling the submit callback function
// and disabled button
if (!validateErrorState()) { if (!validateErrorState()) {
submitFormCallback(values); submitFormCallback(values);
setDisable(true);
} }
}, },
[validateErrorState, submitFormCallback, values], [validateErrorState, submitFormCallback, values],
@@ -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 (
<Dialog title={'Add user'} onClose={onClose} ref={dialogRef}>
<UserForm onClose={onClose!} />
</Dialog>
);
}) as React.FC<IDialogProps>;
export default UserDialog;
+212
View File
@@ -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 (
<Form onSubmit={handleOnSubmit}>
<Input type="hidden" id="id" />
<FormRow
label="Full Name"
error={
// prettier-ignore
errors.fullName && dirty.fullName ?
(errors.fullName as string)
: ''
}
>
<Input
type="text"
name="fullName"
value={fullName as string}
onChange={handleOnChange}
/>
</FormRow>
<FormRow
label="Identified Code"
error={
errors.identifiedCode && dirty.identifiedCode
? (errors.identifiedCode as string)
: ''
}
>
<Input
type="text"
name="identifiedCode"
value={identifiedCode as string}
onChange={handleOnChange}
/>
</FormRow>
<FormRow
label="Phone"
error={
// prettier-ignore
errors.phone && dirty.phone ?
(errors.phone as string)
: ''
}
>
<Input
type="text"
name="phone"
value={phone as string}
onChange={handleOnChange}
/>
</FormRow>
<FormRow
label="Room"
error={
// prettier-ignore
errors.room && dirty.room ?
(errors.room as string)
: ''
}
>
<Input
type="text"
name="room"
value={room as string}
onChange={handleOnChange}
/>
</FormRow>
<FormRow
label="Address"
error={
// prettier-ignore
errors.address && dirty.address ?
(errors.address as string)
: ''
}
>
<TextArea
name="address"
rows={3}
value={address as string}
onChange={handleOnChange}
/>
</FormRow>
<Form.Action>
<FormBtn type="submit" name="submit" disabled={disable}>
Add
</FormBtn>
<FormBtn type="button" styled="secondary" onClick={onClose}>
Close
</FormBtn>
</Form.Action>
</Form>
);
};
export default UserForm;
+26 -9
View File
@@ -1,21 +1,38 @@
import { useRef } from 'react';
// Components // Components
import Direction from '../../commons/styles/Direction'; import Direction from '../../commons/styles/Direction';
import Button from '../../commons/styles/Button'; import Button from '../../commons/styles/Button';
import UserTable from './UserTable'; import UserTable from './Table';
// Styled // Styled
import { StyledUser, Title } from './styled'; import { StyledUser, Title } from './styled';
import UserDialog from './Dialog';
const User = () => { const User = () => {
return ( const dialogRef = useRef<HTMLDialogElement>();
<StyledUser>
<Direction type="horizontal">
<Title>List User</Title>
<Button>Add user</Button>
</Direction>
<UserTable /> const openDialog = () => {
</StyledUser> dialogRef.current?.showModal();
};
const closeDialog = () => {
dialogRef.current?.close();
};
return (
<>
<StyledUser>
<Direction type="horizontal">
<Title>List User</Title>
<Button onClick={openDialog}>Add user</Button>
</Direction>
<UserTable />
</StyledUser>
<UserDialog onClose={closeDialog} ref={dialogRef} />
</>
); );
}; };