mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-23 04:19:48 +00:00
Merge pull request #15 from Nez27/feat/implement-add-user-func
Implement add user function
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
const TIME_OUT_SEC = 1;
|
||||||
|
|
||||||
|
export { TIME_OUT_SEC };
|
||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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,19 +88,40 @@ 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
|
||||||
const {
|
const {
|
||||||
values,
|
values,
|
||||||
errors,
|
errors,
|
||||||
dirty,
|
dirty,
|
||||||
handleOnChange,
|
handleOnChange,
|
||||||
handleOnSubmit,
|
handleOnSubmit,
|
||||||
disable } =
|
disable } =
|
||||||
useForm(
|
useForm(
|
||||||
stateSchema,
|
stateSchema,
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user