mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Merge pull request #12 from Nez27/feat/add-user-form
Implement add user form
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
const invalidFormatMessage = (field: string) => {
|
||||
return `Invalid ${field} format`;
|
||||
};
|
||||
|
||||
export { invalidFormatMessage };
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,21 +1,38 @@
|
||||
import { useRef } from 'react';
|
||||
|
||||
// Components
|
||||
import Direction from '../../commons/styles/Direction';
|
||||
import Button from '../../commons/styles/Button';
|
||||
import UserTable from './UserTable';
|
||||
import UserTable from './Table';
|
||||
|
||||
// Styled
|
||||
import { StyledUser, Title } from './styled';
|
||||
import UserDialog from './Dialog';
|
||||
|
||||
const User = () => {
|
||||
return (
|
||||
<StyledUser>
|
||||
<Direction type="horizontal">
|
||||
<Title>List User</Title>
|
||||
<Button>Add user</Button>
|
||||
</Direction>
|
||||
const dialogRef = useRef<HTMLDialogElement>();
|
||||
|
||||
<UserTable />
|
||||
</StyledUser>
|
||||
const openDialog = () => {
|
||||
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} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user