Skip to content

Commit

Permalink
feat: 유저 정보 전역에 저장하는 기능 (#172)
Browse files Browse the repository at this point in the history
* feat(shelter): zustand로 authstore 생성, axiosInterceptor에 토큰 추가하는 로직 추가

* feat(shared): access token 갱신 로직 추가

* feat(shelter): accessToken hook 추가, index에 들어갔을 때 volunteers로 리다이렉트

* fix(shared): import error 해결

* fix(shared): 오타 수정

* chore(shared): react query 의존성 추가

* feat(shelter): 어플 처음 접속했을 시 로그인 상태 확인하는 기능추가

* feat(common): 로그인 했을때 유저 정보 스토어에 저장하는 로직 추가

* feat(shared): 어플 접속시 깜빡이는 현상 해결

* feat(shared): 주석 제거 및 보호소 어플일때만 리다이렉트 하도록 실행

* feat(volunteer): withLogin 컴포넌트 추가

* feat(volunteer): 권한이 있는 페이지 WithLogin으로 보호

* fix(common): 보호소 앱에 중복된 훅 제거, 봉사자 앱에 accessToken 다시 받는 mock api 추가, shard layout의 주석 제거

* rename(volunteer): withLogin 파일 이름 수정
  • Loading branch information
DongjaJ authored Nov 24, 2023
1 parent 776ef48 commit 47a450b
Show file tree
Hide file tree
Showing 16 changed files with 174 additions and 22 deletions.
11 changes: 11 additions & 0 deletions apps/shelter/src/mocks/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,15 @@ export const handlers = [
await delay(200);
return HttpResponse.json({}, { status: 200 });
}),
http.post('/auth/refresh', async () => {
await delay(500);
return HttpResponse.json(
{
accessToken: 'access token',
userId: 1,
role: 'role',
},
{ status: 200 },
);
}),
];
11 changes: 5 additions & 6 deletions apps/shelter/src/mocks/handlers/recruitment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ const DUMMY_RECRUITMENT = {
recruitmentCapacity: 15,
};

const DUMMY_RECRUITMENT_LIST = Array.from(
{ length: 4 },
() => DUMMY_RECRUITMENT,
);

export const handlers = [
http.get('/shelters/recruitments', async () => {
await delay(1000);
Expand All @@ -25,7 +20,11 @@ export const handlers = [
totalElements: 100,
hasNext: true,
},
recruitments: DUMMY_RECRUITMENT_LIST,
recruitments: Array.from({ length: 4 }, () => ({
...DUMMY_RECRUITMENT,
recruitmentId: Math.random(),
shelterId: Math.random(),
})),
},
{ status: 200 },
);
Expand Down
6 changes: 5 additions & 1 deletion apps/shelter/src/pages/signin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import AnimalfriendsLogo from 'shared/assets/image-anifriends-logo.png';
import IoEyeOff from 'shared/assets/IoEyeOff';
import IoEyeSharp from 'shared/assets/IoEyeSharp';
import useToggle from 'shared/hooks/useToggle';
import useAuthStore from 'shared/store/authStore';
import { SigninRequestData } from 'shared/types/apis/auth';
import * as z from 'zod';

Expand All @@ -42,6 +43,7 @@ export default function SigninPage() {
const navigate = useNavigate();
const toast = useToast();
const [isShow, toggleInputShow] = useToggle();
const { setUser } = useAuthStore();
const {
register,
handleSubmit,
Expand All @@ -52,10 +54,12 @@ export default function SigninPage() {
});
const { mutate } = useMutation({
mutationFn: (data: SigninRequestData) => signinShelter(data),
onSuccess: () => {
onSuccess: ({ data: { userId, accessToken } }) => {
setUser({ userId, accessToken });
navigate(`/${PATH.VOLUNTEERS.INDEX}`);
},
onError: (error) => {
setUser(null);
toast({
position: 'top',
description: error.response?.data.message,
Expand Down
13 changes: 13 additions & 0 deletions apps/volunteer/src/components/WithLogin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ReactNode } from 'react';
import { Navigate } from 'react-router-dom';
import useAuthStore from 'shared/store/authStore';

export default function WithLogin({ children }: { children: ReactNode }) {
const { user } = useAuthStore();

if (user) {
children;
}

return <Navigate to="/signin" />;
}
11 changes: 11 additions & 0 deletions apps/volunteer/src/mocks/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,15 @@ export const handlers = [
await delay(200);
return HttpResponse.json({}, { status: 200 });
}),
http.post('/auth/refresh', async () => {
await delay(500);
return HttpResponse.json(
{
accessToken: 'access token',
userId: 1,
role: 'role',
},
{ status: 200 },
);
}),
];
11 changes: 5 additions & 6 deletions apps/volunteer/src/mocks/handlers/recruitment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ const DUMMY_RECRUITMENT = {
shelterImageUrl: 'https://source.unsplash.com/random',
};

const DUMMY_RECRUITMENT_LIST = Array.from(
{ length: 4 },
() => DUMMY_RECRUITMENT,
);

export const handlers = [
http.get('/recruitments', async () => {
await delay(1000);
Expand All @@ -27,7 +22,11 @@ export const handlers = [
totalElements: 100,
hasNext: true,
},
recruitments: DUMMY_RECRUITMENT_LIST,
recruitments: Array.from({ length: 4 }, () => ({
...DUMMY_RECRUITMENT,
recruitmentId: Math.random(),
shelterId: Math.random(),
})),
},
{ status: 200 },
);
Expand Down
6 changes: 5 additions & 1 deletion apps/volunteer/src/pages/signin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import AnimalfriendsLogo from 'shared/assets/image-anifriends-logo.png';
import IoEyeOff from 'shared/assets/IoEyeOff';
import IoEyeSharp from 'shared/assets/IoEyeSharp';
import useToggle from 'shared/hooks/useToggle';
import useAuthStore from 'shared/store/authStore';
import { SigninRequestData } from 'shared/types/apis/auth';
import * as z from 'zod';

Expand All @@ -42,6 +43,7 @@ export default function SigninPage() {
const navigate = useNavigate();
const toast = useToast();
const [isShow, toggleInputShow] = useToggle();
const { setUser } = useAuthStore();
const {
register,
handleSubmit,
Expand All @@ -52,10 +54,12 @@ export default function SigninPage() {
});
const { mutate } = useMutation({
mutationFn: (data: SigninRequestData) => signinVolunteer(data),
onSuccess: () => {
onSuccess: ({ data: { userId, accessToken } }) => {
setUser({ userId, accessToken });
navigate(`/${PATH.VOLUNTEERS.INDEX}`);
},
onError: (error) => {
setUser(null);
toast({
position: 'top',
description: error.response?.data.message,
Expand Down
31 changes: 26 additions & 5 deletions apps/volunteer/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import APP_TYPE from 'shared/constants/appType';
import PAGE_TYPE from 'shared/constants/pageType';
import Layout from 'shared/layout';

import WithLogin from '@/components/WithLogin';
import PATH from '@/constants/path';
import AnimalsPage from '@/pages/animals';
import AnimalsDetailPage from '@/pages/animals/detail';
Expand Down Expand Up @@ -82,7 +83,11 @@ export const router: RouterProviderProps['router'] = createBrowserRouter([
{
id: PAGE_TYPE.MYPAGE,
path: PATH.MYPAGE.INDEX,
element: <MyPage />,
element: (
<WithLogin>
<MyPage />
</WithLogin>
),
},
{
path: PATH.SETTINGS.INDEX,
Expand All @@ -91,12 +96,20 @@ export const router: RouterProviderProps['router'] = createBrowserRouter([
{
id: PAGE_TYPE.SETTINGS_ACCOUNT,
path: PATH.SETTINGS.ACCOUNT,
element: <SettingsAccountPage />,
element: (
<WithLogin>
<SettingsAccountPage />
</WithLogin>
),
},
{
id: PAGE_TYPE.SETTINGS_PASSWORD,
path: PATH.SETTINGS.PASSWORD,
element: <SettingsPasswordPage />,
element: (
<WithLogin>
<SettingsPasswordPage />
</WithLogin>
),
},
],
},
Expand All @@ -111,12 +124,20 @@ export const router: RouterProviderProps['router'] = createBrowserRouter([
{
id: PAGE_TYPE.SHELTERS_REVIEWS_WRITE,
path: PATH.SHELTERS.REVIEWS_WRITE,
element: <SheltersReviewsWritePage />,
element: (
<WithLogin>
<SheltersReviewsWritePage />
</WithLogin>
),
},
{
id: PAGE_TYPE.SHELTERS_REVIEWS_UPDATE,
path: PATH.SHELTERS.REVIEWS_UPDATE,
element: <SheltersReviewsUpdatePage />,
element: (
<WithLogin>
<SheltersReviewsUpdatePage />
</WithLogin>
),
},
],
},
Expand Down
10 changes: 9 additions & 1 deletion packages/shared/apis/axiosInterceptor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { AxiosResponse, InternalAxiosRequestConfig } from 'axios';

export const onRequest = (config: InternalAxiosRequestConfig) => config;
import useAuthStore from '../store/authStore';

const getAccessToken = () =>
`bearer ${useAuthStore.getState().user?.accessToken}`;

export const onRequest = (config: InternalAxiosRequestConfig) => {
config.headers.Authorization = getAccessToken();
return config;
};

export const onErrorRequest = (error: Error) => {
return Promise.reject(error);
Expand Down
5 changes: 5 additions & 0 deletions packages/shared/apis/common/AccessToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { SigninResponseData } from '../../types/apis/auth';
import axiosInstance from '../axiosInstance';

export const getAccessTokenAPI = () =>
axiosInstance.post<SigninResponseData>('/auth/refresh');
24 changes: 24 additions & 0 deletions packages/shared/hooks/useAccessTokenMutation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useMutation } from '@tanstack/react-query';

import { getAccessTokenAPI } from '../apis/common/AccessToken';
import useAuthStore from '../store/authStore';

export default function useAccessTokenMutation() {
const { setUser } = useAuthStore();
return useMutation({
mutationFn: async () => {
const { data } = await getAccessTokenAPI();
return data;
},
onSuccess: ({ accessToken, userId }) => {
setUser({
accessToken,
userId,
});
},
onError: (error) => {
console.warn(error);
setUser(null);
},
});
}
28 changes: 27 additions & 1 deletion packages/shared/layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Box, Container } from '@chakra-ui/react';
import { Outlet } from 'react-router-dom';
import { useEffect } from 'react';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';

import useAccessTokenMutation from '../hooks/useAccessTokenMutation';
import { AppType } from '../types/app';
import BottomNavBar from './BottomNavBar';
import Header from './Header';
Expand All @@ -10,6 +12,30 @@ type LayoutProps = {
};

export default function Layout({ appType }: LayoutProps) {
const navigate = useNavigate();
const { pathname } = useLocation();
const { mutate, isPending } = useAccessTokenMutation();

useEffect(() => {
mutate(undefined, {
onSuccess: () => {
if (appType === 'SHELTER_APP' && pathname === '/') {
navigate('/volunteers');
}
},
onError: (error) => {
console.warn(error);
if (appType === 'SHELTER_APP') {
navigate('/signin');
}
},
});
}, [mutate]);

if (isPending) {
return <p>...로딩중</p>;
}

return (
<Container pos="relative" maxW="container.sm" h="100vh" p={0} centerContent>
<Header appType={appType} />
Expand Down
1 change: 1 addition & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@chakra-ui/react": "^2.8.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@tanstack/react-query": "^5.4.3",
"axios": "^1.6.0",
"framer-motion": "^10.16.4",
"react": "^18.2.0",
Expand Down
23 changes: 23 additions & 0 deletions packages/shared/store/authStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { create } from 'zustand';

type User = {
accessToken: string;
userId: number;
};

interface AuthState {
user: User | null;
}

interface AuthActions {
setUser: (user: User | null) => void;
}

const useAuthStore = create<AuthState & AuthActions>((set) => ({
user: null,
setUser: (user: User | null) => {
set({ user });
},
}));

export default useAuthStore;
2 changes: 1 addition & 1 deletion packages/shared/types/apis/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type ChangePasswordRequestData = {

export type SigninResponseData = {
accessToken: string;
useId: number;
userId: number;
role: string;
};

Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 47a450b

Please sign in to comment.