Skip to content

Commit

Permalink
feat : 자동 로그인 / 로그인 보호 설정 (#277)
Browse files Browse the repository at this point in the history
* fix : 중복 선언 제거

* env

* feat : login.ts 실패 처리 삭제

* refactor : 로그인 페이지 로직 분리 / 커스텀 훅 생성

* feat : 로그아웃 함수 작성

* fix : 경로 수정

* chore : if문 제거

* feat : AuthProvider 작성

* refactor : useUserInfo 훅으로 분리

* chore : 더미데이터 바인딩 해제

* feat : 자동로그인 및 보호라우터 설정

* env

* fix : return Navigate로 변경

* refactor : 중첩된 if문 제거

* Delete StoragePage.stories.tsx

* Delete Storage.stories.tsx

* Delete MyPage.stories.tsx

* fix : 스토리북 쿼리클라이언트 설정

* fix : 쿼리클라이언트 오류 수정
  • Loading branch information
cmlim0070 authored Dec 6, 2024
1 parent 5866fa5 commit 661f2b6
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 83 deletions.
19 changes: 18 additions & 1 deletion .storybook/preview.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import React from 'react';
import '../src/index.css';
import { MemoryRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false // 스토리북에서는 재시도 비활성화
}
}
});

const preview = {
parameters: {
Expand All @@ -10,7 +20,14 @@ const preview = {
}
}
},
decorators: [(Story) => <Story />]
decorators: [
(Story) => (
// @ts-ignore - 스토리북 타입 무시
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
)
]
};

export default preview;
32 changes: 32 additions & 0 deletions src/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ReactNode, useEffect, useState } from 'react';
import { tokenStorage } from './service/auth/tokenStorage';
import { useUserInfo } from './hooks/useUserInfo';

interface AuthLayoutProps {
children: ReactNode;
}

export const AuthProvider = ({ children }: AuthLayoutProps) => {
const [isLoading, setIsLoading] = useState(true);
const { handleGetUserInfo } = useUserInfo();

useEffect(() => {
const initializeAuth = async () => {
const accessToken = tokenStorage.getAccessToken();
if (accessToken === null) {
return;
}
const success = await handleGetUserInfo();
if (success) {
setIsLoading(false);
}
};
initializeAuth();
}, []);

if (isLoading) {
return <div>Loading...</div>;
}

return children;
};
Empty file.
35 changes: 8 additions & 27 deletions src/hooks/useLogin.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
import { useNavigate } from 'react-router-dom';
import { useUserStore } from '@/stores/useUserStore';
import { useToastStore } from '@/hooks/useToastStore';
import { useMutation } from '@tanstack/react-query';
import { login } from '@/service/auth/login';
import { getUserInfo } from '@/service/user/getUserInfo';
import { LoginProps, LoginResponseType } from '@/types/login';
import { tokenStorage } from '@/service/auth/tokenStorage';
import { useUserInfo } from './useUserInfo';

export const useLogin = () => {
const navigate = useNavigate();
const { setUser } = useUserStore();
const { addToast } = useToastStore();

const handleGetUserInfo = async (email: string) => {
const userInfoResponse = await getUserInfo();
if (!userInfoResponse) {
addToast('유저 정보를 불러오지 못했습니다.', 'warning');
return false;
}
const userInfoWithEmail = {
nickname: userInfoResponse.nickname || '알 수 없음',
profileImageUrl: userInfoResponse.profileImageUrl || 'testimg.jpg',
email: email
};
setUser(userInfoWithEmail);
return true;
};
const { handleGetUserInfo } = useUserInfo();

const mutation = useMutation({
mutationFn: ({ email, password }: LoginProps) =>
login({ email, password }),
onSuccess: (response: LoginResponseType, { email }) => {
if (response.isSuccess) {
addToast('로그인 되었습니다.', 'success');
tokenStorage.setAccessToken(response.result.accessToken);
handleGetUserInfo(email);
navigate('/');
} else {
addToast(response.data.message, 'success');
}
onSuccess: (response: LoginResponseType) => {
addToast('로그인 되었습니다.', 'success');
tokenStorage.setAccessToken(response.result.accessToken);

Check failure on line 19 in src/hooks/useLogin.ts

View workflow job for this annotation

GitHub Actions / deploy

'response.result' is possibly 'null'.
handleGetUserInfo();
navigate('/');

},
onError: (error) => {
addToast(error.message, 'warning');
Expand Down
28 changes: 28 additions & 0 deletions src/hooks/useUserInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useToastStore } from '@/hooks/useToastStore';
import { useUserStore } from './../stores/useUserStore';
import { getUserInfo } from '@/service/user/getUserInfo';
import { logout } from './../service/auth/logout';

export const useUserInfo = () => {
const { setUser } = useUserStore();
const { addToast } = useToastStore();

const handleGetUserInfo = async () => {
const userInfoResponse = await getUserInfo();
console.log(userInfoResponse);
if (!userInfoResponse) {
addToast('유저 정보를 불러오지 못했습니다.', 'warning');
logout();
return false;
}
const userInfoWithEmail = {
nickname: userInfoResponse.nickname || '알 수 없음',
profileImageUrl: userInfoResponse.profileImageUrl || 'testimg.jpg',
email: userInfoResponse.email || '알 수 없음'
};
setUser(userInfoWithEmail);
return true;
};

return { handleGetUserInfo };
};
Empty file.
25 changes: 0 additions & 25 deletions src/pages/User/MyPage/MyPage.stories.tsx

This file was deleted.

93 changes: 63 additions & 30 deletions src/router.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createBrowserRouter, Outlet } from 'react-router-dom';
import { ReactNode } from 'react';
import { createBrowserRouter, Outlet, Navigate } from 'react-router-dom';
import { NavigationBar } from '@/components/Common/NavigationBar/NavigationBar';
import {
ErrorPage,
Expand All @@ -21,6 +22,26 @@ import {
StoragePage
} from './pages';
import { Margin } from './components/Common/Margin/Margin';
import { tokenStorage } from './service/auth/tokenStorage';
import { AuthProvider } from './AuthProvider';

type RouteProps = {
children: ReactNode;
};

// 보호 라우트 (로그인 필요)
export const ProtectedRoute = ({ children }: RouteProps) => {
const Token = tokenStorage.getAccessToken();
if (Token === null) {
return <Navigate to="/login" replace />;
}
return <AuthProvider>{children}</AuthProvider>;
};

// 비보호 라우트 (비로그인 접근 가능)
export const PublicRoute = ({ children }: RouteProps) => {
return children;
};

const CommonLayout = () => (
<>
Expand All @@ -40,7 +61,11 @@ const SimpleLayout = () => (
export const router = createBrowserRouter([
{
path: '/',
element: <CommonLayout />,
element: (
<ProtectedRoute>
<CommonLayout />
</ProtectedRoute>
),
errorElement: <ErrorPage />,
children: [
{
Expand All @@ -63,14 +88,13 @@ export const router = createBrowserRouter([
{ path: 'bookmark', element: <StoragePage /> }
]
},
{ path: 'labels', element: <LabelCollectionsPage /> },
{
path: 'profile',
element: <ProfilePage />
path: 'labels',
element: <LabelCollectionsPage />
},
{
path: '/labelcollections',
element: <LabelCollectionsPage />
path: 'profile',
element: <ProfilePage />
},
{
path: '/sent',
Expand All @@ -79,43 +103,52 @@ export const router = createBrowserRouter([
{
path: '/share',
element: <SharePage />
},
{
path: '/letter/:type/:letterId',
element: <LetterDetailPage />
},
{
path: '/letter/reply/:id',
element: <ReplyLetterDetailPage />
},
{
path: '/lottery',
element: <LabelLotteryPage />
},
{
path: '/notification',
element: <NotificationPage />
}
]
},
{
path: '/letter',
element: <SimpleLayout />,
element: (
<ProtectedRoute>
<SimpleLayout />
</ProtectedRoute>
),
children: [
{
path: 'create',
element: <CreateLetterPage />
},
{ path: 'create', element: <CreateLetterPage /> },
{ path: 'select', element: <SelectItemPage /> },
{ path: 'success', element: <SuccessLetterPage /> }
]
},
{
path: '/login',
element: <LoginPage />
element: (
<PublicRoute>
<LoginPage />
</PublicRoute>
)
},
{
path: '/register',
element: <RegisterPage />
},
{
path: '/letter/:type/:letterId',
element: <LetterDetailPage />
},
{
path: '/letter/reply/:id',
element: <ReplyLetterDetailPage />
},
{
path: '/lottery',
element: <LabelLotteryPage />
},
{
path: '/notification',
element: <NotificationPage />
element: (
<PublicRoute>
<RegisterPage />
</PublicRoute>
)
}
]);

0 comments on commit 661f2b6

Please sign in to comment.