Merge pull request #15 from Nez27/feat/implement-add-user-func

Implement add user function
This commit is contained in:
Loi Phan
2023-10-29 22:27:37 +07:00
committed by GitHub
12 changed files with 144 additions and 40 deletions
+3
View File
@@ -0,0 +1,3 @@
const TIME_OUT_SEC = 1;
export { TIME_OUT_SEC };
+8 -2
View File
@@ -1,5 +1,11 @@
const invalidFormatMessage = (field: string) => {
const invalidFormatMsg = (field: string) => {
return `Invalid ${field} format`;
};
export { invalidFormatMessage };
const ADD_SUCCESS = 'Add success';
const errorMsg = (errorCode: number, msg: string) => {
return `Error code: ${errorCode}. Message: ${msg}`;
};
export { invalidFormatMsg, ADD_SUCCESS, errorMsg };
@@ -0,0 +1,6 @@
const STATUS_CODE = {
CREATE: 201,
NOT_FOUND: 404,
};
export { STATUS_CODE };
@@ -24,6 +24,8 @@ interface IDialogProps {
title?: string;
children?: JSX.Element[] | JSX.Element;
onClose?: () => void;
reload?: boolean;
setReload?: React.Dispatch<React.SetStateAction<boolean>>;
ref?: React.MutableRefObject<HTMLDialogElement | undefined>;
}
+13 -1
View File
@@ -30,4 +30,16 @@ type TValidator = {
};
};
export type { TUser, TStateSchema, TKeyValue, TPropValues, TValidator };
type TResponse = {
statusCode: number;
msg: string;
};
export type {
TUser,
TStateSchema,
TKeyValue,
TPropValues,
TValidator,
TResponse,
};
@@ -0,0 +1,25 @@
// Constants
import { BASE_URL } from '../constants/path';
import { TResponse } from '../globals/types';
type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE';
export const sendRequest = async (
path: string,
body: BodyInit | null | undefined,
method: TMethodRequest = 'GET',
): Promise<TResponse> => {
const response = await fetch(BASE_URL + path, {
method,
body,
headers: {
// prettier-ignore
'Accept': 'application/json',
'Content-Type': 'application/json',
},
});
return {
statusCode: response.status,
msg: response.statusText,
};
};
+2 -2
View File
@@ -1,4 +1,4 @@
import { invalidFormatMessage } from '../constants/messages';
import { invalidFormatMsg } from '../constants/messages';
import { TKeyValue, TPropValues, TStateSchema } from '../globals/types';
const VALUE = 'value';
@@ -39,7 +39,7 @@ const addValidator = ({ validatorFunc, prop, required = true }: TValidator) => {
required,
validator: {
func: validatorFunc,
error: invalidFormatMessage(prop),
error: invalidFormatMsg(prop),
},
};
};
+5 -2
View File
@@ -3,12 +3,15 @@ import { useEffect, useState } from 'react';
// Constants
import { BASE_URL } from '../constants/path';
export const useFetch = (path: string) => {
export const useFetch = (path: string, reload?: boolean) => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
useEffect(() => {
// Clear data
setData(null);
const fetchData = async () => {
setIsPending(true);
@@ -31,6 +34,6 @@ export const useFetch = (path: string) => {
};
fetchData();
}, [path]);
}, [path, reload]);
return { data, isPending, errorMsg };
};
+2 -2
View File
@@ -11,7 +11,7 @@ const UserDialog = forwardRef((props, ref) => {
const dialogRef = ref as React.MutableRefObject<
HTMLDialogElement | undefined
>;
const { onClose } = props;
const { onClose, setReload, reload } = props;
useEffect(() => {
if (dialogRef.current) {
@@ -31,7 +31,7 @@ const UserDialog = forwardRef((props, ref) => {
return (
<Dialog title={'Add user'} onClose={onClose} ref={dialogRef}>
<UserForm onClose={onClose!} />
<UserForm onClose={onClose!} reload={reload!} setReload={setReload!} />
</Dialog>
);
}) as React.FC<IDialogProps>;
+59 -25
View File
@@ -1,8 +1,9 @@
import { useState } from 'react';
import styled from 'styled-components';
import toast from 'react-hot-toast';
// Styled
import Input from '../../commons/styles/Input';
import { useState } from 'react';
import TextArea from '../../commons/styles/TextArea';
// Components
@@ -11,7 +12,12 @@ import FormRow from '../../components/FormRow';
import Button from '../../commons/styles/Button.ts';
// Types
import { TKeyValue, TStateSchema, TValidator } from '../../globals/types';
import {
TKeyValue,
TResponse,
TStateSchema,
TValidator,
} from '../../globals/types';
// Hooks
import useForm from '../../hooks/useForm';
@@ -24,6 +30,11 @@ import {
isValidString,
} from '../../helpers/validators';
import { addValidator } from '../../helpers/utils.ts';
import { sendRequest } from '../../helpers/sendRequest.ts';
// Constants
import { STATUS_CODE } from '../../constants/statusCode.ts';
import { ADD_SUCCESS, errorMsg } from '../../constants/messages.ts';
const FormBtn = styled(Button)`
width: 100%;
@@ -36,23 +47,25 @@ const FormBtn = styled(Button)`
interface IUserFormProp {
onClose: () => void;
reload: boolean;
setReload: React.Dispatch<React.SetStateAction<boolean>>;
}
const UserForm = ({ onClose }: IUserFormProp) => {
const UserForm = ({ onClose, reload, setReload }: IUserFormProp) => {
const [reset, setReset] = useState(true);
// Define your state schema
const stateSchema: TStateSchema = {
fullName: { value: '', error: '' },
name: { value: '', error: '' },
identifiedCode: { value: '', error: '' },
phone: { value: '', error: '' },
room: { value: '', error: '' },
roomId: { value: '', error: '' },
address: { value: '', error: '' },
};
// prettier-ignore
const stateValidatorSchema: TValidator = {
fullName: addValidator({
name: addValidator({
validatorFunc: isValidString,
prop: 'full name'
}),
@@ -64,7 +77,7 @@ const UserForm = ({ onClose }: IUserFormProp) => {
validatorFunc: isValidPhoneNumber,
prop: 'phone number',
}),
room: addValidator({
roomId: addValidator({
validatorFunc: isValidNumber,
prop: 'room number'
}),
@@ -75,19 +88,40 @@ const UserForm = ({ onClose }: IUserFormProp) => {
};
// Submit form
const onSubmitForm = (state: TKeyValue) => {
alert(JSON.stringify(state, null, 2));
const onSubmitForm = async (state: TKeyValue) => {
try {
const response = await sendRequest(
'users',
JSON.stringify(state),
'POST',
);
if (response.statusCode === STATUS_CODE.CREATE) {
toast.success(ADD_SUCCESS);
// Reload table data
setReload(!reload);
} else {
toast.error(errorMsg(response.statusCode, response.msg));
}
onResetForm();
} catch (error) {
toast.error(
errorMsg((error as TResponse).statusCode, (error as TResponse).msg),
);
}
onClose();
onResetForm();
};
// prettier-ignore
const {
values,
errors,
dirty,
handleOnChange,
handleOnSubmit,
values,
errors,
dirty,
handleOnChange,
handleOnSubmit,
disable } =
useForm(
stateSchema,
@@ -97,10 +131,10 @@ const UserForm = ({ onClose }: IUserFormProp) => {
// prettier-ignore
const {
fullName,
name,
identifiedCode,
phone,
room,
roomId,
address
} = values;
@@ -117,15 +151,15 @@ const UserForm = ({ onClose }: IUserFormProp) => {
label="Full Name"
error={
// prettier-ignore
errors.fullName && dirty.fullName ?
(errors.fullName as string)
errors.name && dirty.name ?
(errors.name as string)
: ''
}
>
<Input
type="text"
name="fullName"
value={fullName as string}
name="name"
value={name as string}
onChange={handleOnChange}
/>
</FormRow>
@@ -167,15 +201,15 @@ const UserForm = ({ onClose }: IUserFormProp) => {
label="Room"
error={
// prettier-ignore
errors.room && dirty.room ?
(errors.room as string)
errors.roomId && dirty.roomId ?
(errors.roomId as string)
: ''
}
>
<Input
type="text"
name="room"
value={room as string}
name="roomId"
value={roomId as string}
onChange={handleOnChange}
/>
</FormRow>
+10 -3
View File
@@ -1,3 +1,5 @@
import { useEffect, useState } from 'react';
// Components
import { HiSquare2Stack } from 'react-icons/hi2';
import { HiTrash } from 'react-icons/hi';
@@ -15,7 +17,6 @@ import { useFetch } from '../../hooks/useFetch';
// Styled
import Spinner from '../../commons/styles/Spinner';
import { useEffect, useState } from 'react';
type TUserModal = { user: TUser };
@@ -53,13 +54,19 @@ const UserRow = ({ user }: TUserModal) => {
);
};
const UserTable = () => {
const { data, isPending, errorMsg } = useFetch('users');
interface IUserTable {
reload: boolean;
}
const UserTable = ({ reload }: IUserTable) => {
const { data, isPending, errorMsg } = useFetch('users', reload);
const [users, setUsers] = useState<TUser[]>([]);
useEffect(() => {
if (data) {
setUsers(data);
} else {
setUsers([]);
}
if (errorMsg) {
+9 -3
View File
@@ -1,4 +1,4 @@
import { useRef } from 'react';
import { useRef, useState } from 'react';
// Components
import Direction from '../../commons/styles/Direction';
@@ -11,6 +11,7 @@ import UserDialog from './Dialog';
const User = () => {
const dialogRef = useRef<HTMLDialogElement>();
const [reload, setReload] = useState(true);
const openDialog = () => {
dialogRef.current?.showModal();
@@ -28,10 +29,15 @@ const User = () => {
<Button onClick={openDialog}>Add user</Button>
</Direction>
<UserTable />
<UserTable reload={reload} />
</StyledUser>
<UserDialog onClose={closeDialog} ref={dialogRef} />
<UserDialog
onClose={closeDialog}
ref={dialogRef}
setReload={setReload}
reload={reload}
/>
</>
);
};