Skip to content

Commit

Permalink
feat: 로딩처리 및 예외처리 로직계선
Browse files Browse the repository at this point in the history
  • Loading branch information
JoStar33 committed Apr 27, 2024
1 parent b46159b commit 8294980
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 5 deletions.
Binary file added public/images/Spinner200px.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import { AnimatePresence } from 'framer-motion';
import Router from '@/Router';
import React from 'react';
import environment from './environment';
import { loadingState } from './stores/loading';
import Loading from './components/common/Loading';
import DarkBackground from './components/common/DarkBackground';

export default function App() {
const modalWithTextValue = useRecoilValue(modalWithText);
const loadingValue = useRecoilValue(loadingState);
React.useEffect(() => {
console.log(environment.serverUrl);
}, []);
Expand All @@ -26,6 +30,11 @@ export default function App() {
<AnimatePresence>
{modalWithTextValue.type === 'ALERT' && <Modal.Alert />}
{modalWithTextValue.type === 'CONFIRM' && <Modal.Confirm />}
{loadingValue.isLoading && (
<DarkBackground>
<Loading mode="fixed" />
</DarkBackground>
)}
</AnimatePresence>
</Portal>
</Theme>
Expand Down
2 changes: 1 addition & 1 deletion src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as auth from '@/types/auth';

const Auth = {
Post: {
signIn: (body: auth.ISignInRequest) => requests.post('/auth/sign-in', body),
signIn: (body: auth.ISignInRequest) => requests.post<auth.ISignInResponse>('/auth/sign-in', body),
},
};

Expand Down
55 changes: 55 additions & 0 deletions src/components/common/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import styled, { css } from 'styled-components';

interface IProps {
mode: 'block' | 'center' | 'fixed' | 'inline' | 'absolute';
}

export default function Loading({ mode = 'block' }: IProps) {
return (
<S.Loading mode={mode}>
<img src="/public/images/Spinner200px.gif" />
</S.Loading>
);
}

const S = {
Loading: styled.div<{ mode: string }>`
display: flex;
justify-content: center;
align-items: center;
z-index: 20;
width: 100%;
height: 100%;
img {
width: 80px;
height: 80px;
}
${(props) =>
props.mode === 'inline' &&
css`
width: 100%;
`};
${(props) =>
props.mode === 'center' &&
css`
height: 50%;
`};
${(props) =>
props.mode === 'absolute' &&
css`
position: absolute;
top: 0;
height: 100%;
`};
${(props) =>
props.mode === 'fixed' &&
css`
position: fixed;
top: 0;
left: 0;
right: 0;
`};
`,
};
6 changes: 3 additions & 3 deletions src/components/common/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ function Alert() {
return (
<Portal>
<DarkBackground>
<S.Modal variants={modalVariants} animate="visible" initial="hidden">
<S.Modal variants={modalVariants} animate="visible" initial="hidden" exit="hidden">
<div className="modal-wrapper">
<i className="info-icon">
<IoMdInformationCircleOutline size={20} />
<IoMdInformationCircleOutline size={30} fill="#57a8eb" />
</i>
<h3 className="title-text">{modalValue.titleText}</h3>
<div className="desc-text-container">{computedDescTextArr?.map((desc) => <p className="desc-text">{desc}</p>)}</div>
Expand All @@ -60,7 +60,7 @@ function Confirm() {
<S.Modal variants={modalVariants} animate="visible" initial="hidden" exit="hidden">
<div className="modal-wrapper">
<i className="info-icon">
<IoMdInformationCircleOutline size={20} />
<IoMdInformationCircleOutline size={30} fill="#57a8eb" />
</i>
<h3 className="title-text">{modalValue.titleText}</h3>
<div className="desc-text-container">{computedDescTextArr?.map((desc) => <p className="desc-text">{desc}</p>)}</div>
Expand Down
1 change: 1 addition & 0 deletions src/components/layouts/Aside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const S = {
position: fixed;
top: 0px;
background-color: rgba(0, 0, 0, 0.7);
z-index: 4;
.aside-header {
width: 100%;
display: flex;
Expand Down
1 change: 1 addition & 0 deletions src/constants/recoilKeys.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const recoilKeys = {
MODAL: 'MODAL',
MODAL_WITH_TEXT: 'MODAL_WITH_TEXT',
LOADING: 'LOADING',
};

export default recoilKeys;
Empty file removed src/containers/.gitkeep
Empty file.
25 changes: 24 additions & 1 deletion src/containers/SignInContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import Auth from '@/api/auth';
import SignIn from '@/components/sign-in';
import routerPath from '@/constants/routerPath';
import { useErrorHandler } from '@/hooks/useErrorHandler';
import useLoading from '@/hooks/useLoading';
import { ISignInForm } from '@/types/auth';
import { schema } from '@/utils/schema';
import { storage } from '@/utils/storage';
import { yupResolver } from '@hookform/resolvers/yup';
import React from 'react';
import { useForm, SubmitHandler, FormProvider } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';

export default function SignInContainer() {
const { handleError } = useErrorHandler();
const { isLoading, setIsLoading } = useLoading();
const navigate = useNavigate();
const methods = useForm<ISignInForm>({
defaultValues: {
email: '',
Expand All @@ -16,10 +24,25 @@ export default function SignInContainer() {
resolver: yupResolver(schema.signInSchema),
});

const fetchSignIn = async (submitData: ISignInForm) => {
try {
if (isLoading) return;
const response = await Auth.Post.signIn(submitData);
if (response.code !== 200) throw new Error(response.message);
storage.setAccessTokenLocalStorage(response.value.accessToken);
navigate(routerPath.HOME);
} catch (error: unknown) {
handleError(error);
} finally {
setIsLoading(false);
}
};

const onSubmit: SubmitHandler<ISignInForm> = React.useCallback(
async (submitData, event) => {
event?.preventDefault();
Auth.Post.signIn(submitData);
setIsLoading(true);
fetchSignIn(submitData);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[methods],
Expand Down
75 changes: 75 additions & 0 deletions src/hooks/useErrorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { modalState } from '@/stores/modal';
import { DefaultResponse } from '@/types';
import axios, { AxiosError } from 'axios';
import { useResetRecoilState, useSetRecoilState } from 'recoil';

export function useErrorHandler() {
const setModalState = useSetRecoilState(modalState);
const resetModalState = useResetRecoilState(modalState);
const handleError = (error: any) => {
if (axios.isAxiosError(error)) {
return setModalState((prev) => ({
...prev,
type: 'ALERT',
titleText: '오류 발생',
descText: error?.message ? error.message : '잠시 후 다시 시도해주세요',
confirmButtonText: '확인',
onClickConfirm: () => {
resetModalState();
},
}));
}
const axiosError = error as AxiosError<DefaultResponse>;
const isConnectionRefusedError = error.code?.includes('ERR_NETWORK');
const isUnprocessableEntity = axiosError.response?.status === 422;
const isInternalServerError = axiosError.response?.status === 500;

if (isConnectionRefusedError) {
return setModalState((prev) => ({
...prev,
type: 'ALERT',
titleText: '네트워크 통신이 원활하지 않습니다.',
descText: '네트워크 상태를 확인해주세요.',
confirmButtonText: '확인',
onClickConfirm: () => {
resetModalState();
},
}));
}
if (isUnprocessableEntity) {
return setModalState((prev) => ({
...prev,
type: 'ALERT',
titleText: '유효하지 않습니다.',
descText: axiosError.response?.data.message,
confirmButtonText: '확인',
onClickConfirm: () => {
resetModalState();
},
}));
}
if (isInternalServerError) {
return setModalState((prev) => ({
...prev,
type: 'ALERT',
titleText: '오류 발생',
descText: axiosError.message ? axiosError.message : '서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.',
confirmButtonText: '확인',
onClickConfirm: () => {
resetModalState();
},
}));
}
return setModalState((prev) => ({
...prev,
type: 'ALERT',
titleText: '오류 발생',
descText: '잠시 후 다시 시도해주세요.',
confirmButtonText: '확인',
onClickConfirm: () => {
resetModalState();
},
}));
};
return { handleError };
}
15 changes: 15 additions & 0 deletions src/hooks/useLoading.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { loadingState } from '@/stores/loading';
import React from 'react';
import { useRecoilState, useResetRecoilState } from 'recoil';

export default function useLoading() {
const [loadingValue, setLoadingValue] = useRecoilState(loadingState);
const isLoading = loadingValue.isLoading;
const setIsLoading = (value: boolean) => setLoadingValue((prev) => ({ ...prev, isLoading: value }));
const resetLoading = useResetRecoilState(loadingState);
React.useEffect(() => {
resetLoading();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return { isLoading, setIsLoading };
}
13 changes: 13 additions & 0 deletions src/stores/loading.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { atom } from 'recoil';
import recoilKeys from '@/constants/recoilKeys';

interface ILoadingState {
isLoading: boolean;
}

export const loadingState = atom<ILoadingState>({
key: recoilKeys.LOADING,
default: {
isLoading: false,
},
});
17 changes: 17 additions & 0 deletions src/utils/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const ACCESS_TOKEN = 'jwt';

export const storage = {
setAccessTokenLocalStorage: (value: string) => {
localStorage.setItem(ACCESS_TOKEN, value);
},
getAccessTokenLocalStorageItem: (): string | null => {
const accessToken = localStorage.getItem(ACCESS_TOKEN);
if (!accessToken) {
return null;
}
return JSON.parse(accessToken);
},
removeAccessToken: () => {
return localStorage.removeItem(ACCESS_TOKEN);
},
};

0 comments on commit 8294980

Please sign in to comment.