Skip to content

Commit 01a4474

Browse files
authored
Merge pull request #160 from Boggle-Boggle/refactor/155
[Refactor/155] 로그인 로직 전체 수정
2 parents b49606a + 5df7d8e commit 01a4474

25 files changed

Lines changed: 324 additions & 505 deletions

src/components/Header.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@ import { IconArrowLeft } from 'components/icons';
66
import IconButton from './refactor/Button/IconButton';
77

88
type HeaderProps = {
9+
prev?: () => void;
910
title?: React.ReactNode;
1011
rightBtn?: React.ReactNode;
1112
withSpacer?: boolean;
1213
};
1314

14-
const Header = ({ title, rightBtn, withSpacer = true }: HeaderProps) => {
15+
const Header = ({ prev, title, rightBtn, withSpacer = true }: HeaderProps) => {
1516
const navigate = useNavigate();
1617

1718
const isTwoRightBtn = isValidElement(rightBtn) && Array.isArray(rightBtn?.props.children);
1819

1920
return (
2021
<>
2122
<div className="fixed z-header flex h-12 w-full max-w-mobile items-center pt-safe-top">
22-
<IconButton onClick={() => navigate(-1)} label="뒤로가기">
23+
<IconButton onClick={prev || (() => navigate(-1))} label="뒤로가기">
2324
<IconArrowLeft className="size-icon-md" />
2425
</IconButton>
2526
{isTwoRightBtn && <div className="w-12" />}

src/components/refactor/Button/IconButton.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
type IconButtonProps = {
22
label: string;
3+
size?: 'small' | 'medium';
34
align?: 'left' | 'right' | 'center';
45
children: React.ReactNode;
56
onClick: React.MouseEventHandler<HTMLButtonElement>;
67
};
78

8-
const IconButton = ({ label, align = 'center', children, onClick }: IconButtonProps) => {
9+
const IconButton = ({ label, size = 'medium', align = 'center', children, onClick }: IconButtonProps) => {
910
const alignClass = `${align === 'left' ? 'justify-start' : align === 'right' ? 'justify-end' : 'justify-center'}`;
11+
const sizeClass = size === 'small' ? 'size-10' : 'size-12';
1012

1113
return (
12-
<button className={`flex size-12 items-center ${alignClass}`} type="button" aria-label={label} onClick={onClick}>
14+
<button
15+
className={`flex items-center ${sizeClass} ${alignClass}`}
16+
type="button"
17+
aria-label={label}
18+
onClick={onClick}
19+
>
1320
{children}
1421
</button>
1522
);

src/components/refactor/CheckBox.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
type CheckBoxProps = {
2+
id: string;
23
size?: 'small' | 'medium';
34
variant?: 'primary' | 'grey';
45
checked?: boolean;
56
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
67
};
78

8-
const CheckBox = ({ size = 'medium', variant = 'primary', checked = false, onChange }: CheckBoxProps) => {
9+
const CheckBox = ({ id, size = 'medium', variant = 'primary', checked = false, onChange }: CheckBoxProps) => {
910
const variantClass =
1011
variant === 'primary'
1112
? 'border-neutral-40 peer-checked:bg-primary '
1213
: 'border-neutral-60 peer-checked:bg-neutral-80 ';
1314
const sizeClass = size === 'small' ? 'size-3 text-body2' : 'size-6 text-title2';
1415

1516
return (
16-
<label htmlFor="checkbox">
17-
<input id="checkbox" type="checkbox" className="peer sr-only" checked={checked} onChange={onChange} />
17+
<label htmlFor={`checkbox-${id}`}>
18+
<input id={`checkbox-${id}`} type="checkbox" className="peer sr-only" checked={checked} onChange={onChange} />
1819
<span
1920
className={`flex items-center justify-center rounded-[4px] border-[1.5px] text-neutral-0 peer-checked:border-transparent ${variantClass} ${sizeClass}`}
2021
>

src/components/refactor/Toast/ToastContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const ToastContainer = () => {
66
const { toasts } = useToastStore();
77

88
return (
9-
<section className="fixed bottom-6 z-toast flex w-full max-w-mobile flex-col px-mobile">
9+
<section className="fixed bottom-[4.5rem] z-toast flex w-full max-w-mobile flex-col px-mobile">
1010
{toasts.map((toast) => {
1111
return (
1212
<div key={toast.id} className="mt-1">

src/components/refactor/Toast/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ const Toast = ({ type, description, title }: ToastProps) => {
3131
);
3232

3333
return (
34-
<div className={`${borderClass} ${isLeaving ? 'animate-fadeOut' : 'animate-fadeIn'} rounded-xl border px-4 py-2`}>
34+
<div
35+
className={`${borderClass} ${isLeaving ? 'animate-fadeOut' : 'animate-fadeIn'} rounded-xl border bg-neutral-0 px-4 py-2`}
36+
>
3537
{title && (
3638
<div className="mb-1 flex items-center text-body2 text-neutral-80">
3739
{icon}

src/constants/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const NICKNAME_RULE = {
2+
MAX: 15,
3+
MIN: 2,
4+
};
5+
6+
export default NICKNAME_RULE;

src/hooks/useInput.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useRef, useState } from 'react';
2+
3+
const useInput = () => {
4+
const [input, setInput] = useState<string>('');
5+
const inputRef = useRef<HTMLInputElement>(null);
6+
7+
const clear = () => {
8+
setInput('');
9+
inputRef.current?.focus();
10+
};
11+
12+
return { input, setInput, inputRef, clear };
13+
};
14+
15+
export default useInput;

src/main.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
22

3-
import React, { Suspense } from 'react';
3+
import { StrictMode, Suspense } from 'react';
44
import ReactDOM from 'react-dom/client';
55
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
66
import { DeviceProvider } from 'stores/useDeviceStore';
@@ -65,7 +65,7 @@ const router = createBrowserRouter([
6565
},
6666
{ path: 'login', element: <Login /> },
6767
{ path: 'signUp', element: <SignUp /> },
68-
{ path: 'oauth/redirect', element: <Auth /> },
68+
{ path: 'oauth', element: <Auth /> },
6969
],
7070
},
7171
]);
@@ -79,13 +79,14 @@ const queryClient = new QueryClient({
7979
});
8080

8181
ReactDOM.createRoot(document.getElementById('root')!).render(
82-
<React.StrictMode>
82+
<StrictMode>
8383
<DeviceProvider>
8484
<QueryClientProvider client={queryClient}>
8585
<Suspense fallback={<Loading />}>
8686
<RouterProvider router={router} />
8787
</Suspense>
8888
</QueryClientProvider>
8989
</DeviceProvider>
90-
</React.StrictMode>,
90+
,
91+
</StrictMode>,
9192
);

src/pages/Auth/index.tsx

Lines changed: 17 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,34 @@
1-
import { useEffect, useReducer } from 'react';
1+
import { useEffect } from 'react';
22
import { useNavigate, useLocation } from 'react-router-dom';
3-
import useAuthStore from 'stores/useAuthStore';
43

5-
import Alert from 'components/Alert';
64
import Loading from 'pages/Loading';
75

8-
import { getAuthorization } from 'services/user';
9-
import CustomError from 'utils/customError';
10-
11-
function isCustomError(error: unknown): error is CustomError {
12-
return (error as CustomError).custom !== undefined;
13-
}
6+
import { refreshToken } from 'services/user';
147

158
const Auth = () => {
169
const location = useLocation();
1710
const navigate = useNavigate();
18-
const login = useAuthStore((state) => state.login);
19-
const logout = useAuthStore((state) => state.logout);
20-
const [isAlertActive, handleAlertActive] = useReducer((prev) => !prev, false);
21-
const [isAccessTokenAlertActive, handleAccessTokenAlertActive] = useReducer((prev) => !prev, false);
22-
const [isWithdrawnAlertActive, handleWithdrawnAlertActive] = useReducer((prev) => !prev, false);
23-
const [isRefreshAlertActive, handleRefreshAlertActive] = useReducer((prev) => !prev, false);
24-
const [isTermAlertActive, handleTermAlertActive] = useReducer((prev) => !prev, false);
2511

2612
useEffect(() => {
27-
const checkAuthorization = async () => {
28-
const queryParam = new URLSearchParams(location.search);
29-
const accessToken = queryParam.get('token');
30-
31-
try {
32-
if (accessToken) {
33-
login(accessToken);
34-
const authorization = await getAuthorization();
35-
36-
if (authorization === 'GUEST') navigate('/signup');
37-
else if (authorization === 'USER') navigate('/');
38-
else if (authorization === 'LIMITED_USER') navigate('/'); // TODO : 권한이 업데이트 됐을 경우 권한이 업데이트 됨! 페이지로
39-
} //
40-
// 엑세스 토큰이 없으면 얼럿 수행
41-
else {
42-
handleAccessTokenAlertActive();
43-
logout();
44-
45-
setTimeout(() => {
46-
navigate('/login');
47-
}, 2500);
48-
}
49-
} catch (error) {
50-
if (isCustomError(error) && error.custom) {
51-
if (error.message === '탈퇴한 회원입니다. 회원가입을 다시 진행해주세요') handleWithdrawnAlertActive();
52-
else if (error.message === '로그인 기한이 만료되었어요. 다시 로그인 해주세요') handleRefreshAlertActive();
53-
else if (error.message === '약관에 동의하지 않았어요. 회원가입을 다시 진행해주세요') handleTermAlertActive();
54-
} //
55-
else {
56-
handleAlertActive();
57-
}
58-
59-
logout();
60-
setTimeout(() => {
61-
navigate('/login');
62-
}, 2500);
13+
const queryParam = new URLSearchParams(location.search);
14+
const userStatus = queryParam.get('status');
15+
16+
const handleAuth = async () => {
17+
// 기존 유저일 경우 refresh를 통해 엑세스 토큰 발급
18+
if (userStatus === 'EXISTING_USER') {
19+
await refreshToken();
20+
navigate('/');
21+
return;
6322
}
64-
};
6523

66-
checkAuthorization();
67-
}, [location.search, navigate, login, logout]);
24+
// 신규 유저일 경우 회원가입 로직
25+
if (userStatus === 'SIGNUP_REQUIRED') navigate('/signup');
26+
};
6827

69-
return (
70-
<>
71-
{isAlertActive && <Alert message="오류가 발생했어요 다시 시도해주세요" onClose={handleAlertActive} />}
72-
{isAccessTokenAlertActive && (
73-
<Alert message="로그인 과정 중 오류가 발생했어요 다시 시도해주세요" onClose={handleAccessTokenAlertActive} />
74-
)}
75-
{isWithdrawnAlertActive && (
76-
<Alert message="탈퇴한 회원입니다. 회원가입을 다시 진행해주세요" onClose={handleWithdrawnAlertActive} />
77-
)}
78-
{isRefreshAlertActive && (
79-
<Alert message="로그인 기한이 만료되었어요. 다시 로그인 해주세요" onClose={handleRefreshAlertActive} />
80-
)}
81-
{isTermAlertActive && (
82-
<Alert message="약관에 동의하지 않았어요. 회원가입을 다시 진행해주세요'" onClose={handleTermAlertActive} />
83-
)}
28+
handleAuth();
29+
});
8430

85-
<Loading />
86-
</>
87-
);
31+
return <Loading />;
8832
};
8933

9034
export default Auth;

src/pages/Home/index.tsx

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,14 @@
1-
import { useQuery } from '@tanstack/react-query';
2-
3-
import { useState } from 'react';
4-
51
import Header from 'components/Header';
62

7-
import useModal from 'hooks/useModal';
8-
import { getBookCase } from 'services/record';
9-
103
import BookCase from './BookCase';
11-
import SelectPeriodModal from './SelectPeriodModal';
124

135
const Home = () => {
14-
const [title, setTitle] = useState<string>('2025년 전체보기');
15-
const [selectedYear, setSelectedYear] = useState<number | null>(25);
16-
const [selectedMonth, setSelectedMonth] = useState<number | null>(13);
17-
18-
const { data: books, refetch } = useQuery({
19-
queryKey: ['book', null, null],
20-
queryFn: () => getBookCase(selectedYear, selectedMonth),
21-
});
22-
23-
const { isOpen, close, open } = useModal();
24-
256
return (
267
<>
27-
<Header
28-
withSpacer={false}
29-
title={`${title} (${books?.length ?? 0})`}
30-
rightBtn={<button aria-label="기간선택" type="button" onClick={open} />}
31-
/>
8+
<Header withSpacer={false} title="타이틀" rightBtn={<button aria-label="기간선택" type="button" />} />
329
<div className="flex h-dvh flex-col items-center justify-center overflow-scroll">
3310
<BookCase />
3411
</div>
35-
{isOpen && (
36-
<SelectPeriodModal
37-
setTitle={setTitle}
38-
selectedYear={selectedYear}
39-
setSelectedYear={setSelectedYear}
40-
selectedMonth={selectedMonth}
41-
setSelectedMonth={setSelectedMonth}
42-
close={close}
43-
fetchBooks={refetch}
44-
/>
45-
)}
4612
</>
4713
);
4814
};

0 commit comments

Comments
 (0)