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`; 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; title?: string;
children?: JSX.Element[] | JSX.Element; children?: JSX.Element[] | JSX.Element;
onClose?: () => void; onClose?: () => void;
reload?: boolean;
setReload?: React.Dispatch<React.SetStateAction<boolean>>;
ref?: React.MutableRefObject<HTMLDialogElement | undefined>; 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'; import { TKeyValue, TPropValues, TStateSchema } from '../globals/types';
const VALUE = 'value'; const VALUE = 'value';
@@ -39,7 +39,7 @@ const addValidator = ({ validatorFunc, prop, required = true }: TValidator) => {
required, required,
validator: { validator: {
func: validatorFunc, func: validatorFunc,
error: invalidFormatMessage(prop), error: invalidFormatMsg(prop),
}, },
}; };
}; };
+5 -2
View File
@@ -3,12 +3,15 @@ import { useEffect, useState } from 'react';
// Constants // Constants
import { BASE_URL } from '../constants/path'; import { BASE_URL } from '../constants/path';
export const useFetch = (path: string) => { export const useFetch = (path: string, reload?: boolean) => {
const [data, setData] = useState(null); const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false); const [isPending, setIsPending] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null); const [errorMsg, setErrorMsg] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
// Clear data
setData(null);
const fetchData = async () => { const fetchData = async () => {
setIsPending(true); setIsPending(true);
@@ -31,6 +34,6 @@ export const useFetch = (path: string) => {
}; };
fetchData(); fetchData();
}, [path]); }, [path, reload]);
return { data, isPending, errorMsg }; return { data, isPending, errorMsg };
}; };
+2 -2
View File
@@ -11,7 +11,7 @@ const UserDialog = forwardRef((props, ref) => {
const dialogRef = ref as React.MutableRefObject< const dialogRef = ref as React.MutableRefObject<
HTMLDialogElement | undefined HTMLDialogElement | undefined
>; >;
const { onClose } = props; const { onClose, setReload, reload } = props;
useEffect(() => { useEffect(() => {
if (dialogRef.current) { if (dialogRef.current) {
@@ -31,7 +31,7 @@ const UserDialog = forwardRef((props, ref) => {
return ( return (
<Dialog title={'Add user'} onClose={onClose} ref={dialogRef}> <Dialog title={'Add user'} onClose={onClose} ref={dialogRef}>
<UserForm onClose={onClose!} /> <UserForm onClose={onClose!} reload={reload!} setReload={setReload!} />
</Dialog> </Dialog>
); );
}) as React.FC<IDialogProps>; }) as React.FC<IDialogProps>;
+54 -20
View File
@@ -1,8 +1,9 @@
import { useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import toast from 'react-hot-toast';
// Styled // Styled
import Input from '../../commons/styles/Input'; import Input from '../../commons/styles/Input';
import { useState } from 'react';
import TextArea from '../../commons/styles/TextArea'; import TextArea from '../../commons/styles/TextArea';
// Components // Components
@@ -11,7 +12,12 @@ import FormRow from '../../components/FormRow';
import Button from '../../commons/styles/Button.ts'; import Button from '../../commons/styles/Button.ts';
// Types // Types
import { TKeyValue, TStateSchema, TValidator } from '../../globals/types'; import {
TKeyValue,
TResponse,
TStateSchema,
TValidator,
} from '../../globals/types';
// Hooks // Hooks
import useForm from '../../hooks/useForm'; import useForm from '../../hooks/useForm';
@@ -24,6 +30,11 @@ import {
isValidString, isValidString,
} from '../../helpers/validators'; } from '../../helpers/validators';
import { addValidator } from '../../helpers/utils.ts'; 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)` const FormBtn = styled(Button)`
width: 100%; width: 100%;
@@ -36,23 +47,25 @@ const FormBtn = styled(Button)`
interface IUserFormProp { interface IUserFormProp {
onClose: () => void; 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); const [reset, setReset] = useState(true);
// Define your state schema // Define your state schema
const stateSchema: TStateSchema = { const stateSchema: TStateSchema = {
fullName: { value: '', error: '' }, name: { value: '', error: '' },
identifiedCode: { value: '', error: '' }, identifiedCode: { value: '', error: '' },
phone: { value: '', error: '' }, phone: { value: '', error: '' },
room: { value: '', error: '' }, roomId: { value: '', error: '' },
address: { value: '', error: '' }, address: { value: '', error: '' },
}; };
// prettier-ignore // prettier-ignore
const stateValidatorSchema: TValidator = { const stateValidatorSchema: TValidator = {
fullName: addValidator({ name: addValidator({
validatorFunc: isValidString, validatorFunc: isValidString,
prop: 'full name' prop: 'full name'
}), }),
@@ -64,7 +77,7 @@ const UserForm = ({ onClose }: IUserFormProp) => {
validatorFunc: isValidPhoneNumber, validatorFunc: isValidPhoneNumber,
prop: 'phone number', prop: 'phone number',
}), }),
room: addValidator({ roomId: addValidator({
validatorFunc: isValidNumber, validatorFunc: isValidNumber,
prop: 'room number' prop: 'room number'
}), }),
@@ -75,10 +88,31 @@ const UserForm = ({ onClose }: IUserFormProp) => {
}; };
// Submit form // Submit form
const onSubmitForm = (state: TKeyValue) => { const onSubmitForm = async (state: TKeyValue) => {
alert(JSON.stringify(state, null, 2)); 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(); onClose();
onResetForm();
}; };
// prettier-ignore // prettier-ignore
@@ -97,10 +131,10 @@ const UserForm = ({ onClose }: IUserFormProp) => {
// prettier-ignore // prettier-ignore
const { const {
fullName, name,
identifiedCode, identifiedCode,
phone, phone,
room, roomId,
address address
} = values; } = values;
@@ -117,15 +151,15 @@ const UserForm = ({ onClose }: IUserFormProp) => {
label="Full Name" label="Full Name"
error={ error={
// prettier-ignore // prettier-ignore
errors.fullName && dirty.fullName ? errors.name && dirty.name ?
(errors.fullName as string) (errors.name as string)
: '' : ''
} }
> >
<Input <Input
type="text" type="text"
name="fullName" name="name"
value={fullName as string} value={name as string}
onChange={handleOnChange} onChange={handleOnChange}
/> />
</FormRow> </FormRow>
@@ -167,15 +201,15 @@ const UserForm = ({ onClose }: IUserFormProp) => {
label="Room" label="Room"
error={ error={
// prettier-ignore // prettier-ignore
errors.room && dirty.room ? errors.roomId && dirty.roomId ?
(errors.room as string) (errors.roomId as string)
: '' : ''
} }
> >
<Input <Input
type="text" type="text"
name="room" name="roomId"
value={room as string} value={roomId as string}
onChange={handleOnChange} onChange={handleOnChange}
/> />
</FormRow> </FormRow>
+10 -3
View File
@@ -1,3 +1,5 @@
import { useEffect, useState } from 'react';
// Components // Components
import { HiSquare2Stack } from 'react-icons/hi2'; import { HiSquare2Stack } from 'react-icons/hi2';
import { HiTrash } from 'react-icons/hi'; import { HiTrash } from 'react-icons/hi';
@@ -15,7 +17,6 @@ import { useFetch } from '../../hooks/useFetch';
// Styled // Styled
import Spinner from '../../commons/styles/Spinner'; import Spinner from '../../commons/styles/Spinner';
import { useEffect, useState } from 'react';
type TUserModal = { user: TUser }; type TUserModal = { user: TUser };
@@ -53,13 +54,19 @@ const UserRow = ({ user }: TUserModal) => {
); );
}; };
const UserTable = () => { interface IUserTable {
const { data, isPending, errorMsg } = useFetch('users'); reload: boolean;
}
const UserTable = ({ reload }: IUserTable) => {
const { data, isPending, errorMsg } = useFetch('users', reload);
const [users, setUsers] = useState<TUser[]>([]); const [users, setUsers] = useState<TUser[]>([]);
useEffect(() => { useEffect(() => {
if (data) { if (data) {
setUsers(data); setUsers(data);
} else {
setUsers([]);
} }
if (errorMsg) { if (errorMsg) {
+9 -3
View File
@@ -1,4 +1,4 @@
import { useRef } from 'react'; import { useRef, useState } from 'react';
// Components // Components
import Direction from '../../commons/styles/Direction'; import Direction from '../../commons/styles/Direction';
@@ -11,6 +11,7 @@ import UserDialog from './Dialog';
const User = () => { const User = () => {
const dialogRef = useRef<HTMLDialogElement>(); const dialogRef = useRef<HTMLDialogElement>();
const [reload, setReload] = useState(true);
const openDialog = () => { const openDialog = () => {
dialogRef.current?.showModal(); dialogRef.current?.showModal();
@@ -28,10 +29,15 @@ const User = () => {
<Button onClick={openDialog}>Add user</Button> <Button onClick={openDialog}>Add user</Button>
</Direction> </Direction>
<UserTable /> <UserTable reload={reload} />
</StyledUser> </StyledUser>
<UserDialog onClose={closeDialog} ref={dialogRef} /> <UserDialog
onClose={closeDialog}
ref={dialogRef}
setReload={setReload}
reload={reload}
/>
</> </>
); );
}; };