Skip to content

Commit b4cafa6

Browse files
authored
Merge pull request #36 from arch-spatula/feat/logout
Feat/logout
2 parents 6797152 + 7b3f52a commit b4cafa6

File tree

10 files changed

+113
-28
lines changed

10 files changed

+113
-28
lines changed

src/Components/Navbar/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { Nav, Container, List, ListItem } from './Navbar.style';
33
import { useLogin } from '../../hooks';
44

55
export function Navbar() {
6-
const { token } = useLogin();
7-
return <Nav>{token ? <LoggedInNav /> : <LoggedOutNav />}</Nav>;
6+
const { isLoggedIn } = useLogin();
7+
return <Nav>{isLoggedIn ? <LoggedInNav /> : <LoggedOutNav />}</Nav>;
88
}
99

1010
function LoggedInNav() {

src/api/AxiosClient.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,28 @@ const axiosClient: AxiosInstance = axios.create({
99
},
1010
});
1111

12-
export default axiosClient;
12+
const authClient: AxiosInstance = axios.create({
13+
baseURL: BASE_URL,
14+
headers: {
15+
'Content-Type': 'application/json',
16+
},
17+
});
18+
19+
axiosClient.interceptors.request.use(
20+
(config) => {
21+
const token = localStorage.getItem('accessToken');
22+
23+
const configCopy = { ...config };
24+
if (token)
25+
configCopy.headers.Authorization = `Bearer ${token.slice(
26+
1,
27+
token.length - 1
28+
)}`;
29+
else throw new Error('token이 없습니다.');
30+
31+
return configCopy;
32+
},
33+
(error) => Promise.reject(error)
34+
);
35+
36+
export { axiosClient, authClient };

src/api/authClient.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { AxiosError, AxiosResponse } from 'axios';
2-
import axiosClient from './AxiosClient';
3-
import { API_URLS, BASE_URL } from '../constant/config';
2+
import { authClient } from './AxiosClient';
3+
import { API_URLS } from '../constant/config';
44

55
async function signInAPI(email: string, password: string) {
66
try {
7-
const res: AxiosResponse<{ email: string }> = await axiosClient.post(
8-
BASE_URL + API_URLS.SIGN_IN,
7+
const res: AxiosResponse<{ email: string }> = await authClient.post(
8+
API_URLS.SIGN_IN,
99
{
1010
email,
1111
password,
@@ -21,7 +21,7 @@ async function signInAPI(email: string, password: string) {
2121

2222
async function signUpAPI(email: string, password: string) {
2323
try {
24-
const res = await axiosClient.post(BASE_URL + API_URLS.SIGN_UP, {
24+
const res = await authClient.post(API_URLS.SIGN_UP, {
2525
email,
2626
password,
2727
});

src/api/cardClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AxiosError } from 'axios';
22
import { API_URLS, BASE_URL } from '../constant/config';
3-
import axiosClient from './AxiosClient';
3+
import { axiosClient } from './AxiosClient';
44

55
async function getCardsAPI() {
66
try {

src/constant/config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ export const API_URLS = {
44
CARDS: '/card',
55
SIGN_IN: '/auth/signin',
66
SIGN_UP: '/auth/signup',
7-
};
7+
SIGN_OUT: '/auth/signout',
8+
} as const;
89

910
export const ROUTE_PATHS = {
1011
WELCOME: '/',
@@ -13,4 +14,4 @@ export const ROUTE_PATHS = {
1314
CARDS: '/cards',
1415
DECK: '/deck',
1516
SETTING: '/setting',
16-
};
17+
} as const;

src/hooks/useLogin/index.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
11
import { useAtom } from 'jotai';
2-
import { atomWithStorage } from 'jotai/utils';
2+
import { atomWithStorage, createJSONStorage } from 'jotai/utils';
3+
import { useCallback } from 'react';
34

4-
const tokenAtom = atomWithStorage('token', '');
5+
const session = createJSONStorage(() => sessionStorage);
6+
const accessTokenAtom = atomWithStorage('accessToken', '');
7+
const sessionTokenAtom = atomWithStorage('sessionToken', '', session);
58

69
export function useLogin() {
7-
const [token, setToken] = useAtom(tokenAtom);
10+
const [accessToken, setAccessToken] = useAtom(accessTokenAtom);
11+
const [sessionToken, setSessionToken] = useAtom(sessionTokenAtom);
812

9-
return { token, setToken };
13+
const isLoggedIn = Boolean(accessToken && sessionToken);
14+
15+
const setTokens = useCallback(
16+
(accessToken: string, sessionToken: string) => {
17+
setAccessToken(accessToken);
18+
setSessionToken(sessionToken);
19+
},
20+
[setAccessToken, setSessionToken]
21+
);
22+
23+
const emptyTokens = useCallback(() => {
24+
setAccessToken('');
25+
setSessionToken('');
26+
}, [setAccessToken, setSessionToken]);
27+
28+
return {
29+
isLoggedIn,
30+
setTokens,
31+
emptyTokens,
32+
};
1033
}

src/hooks/useLogin/useLogin.test.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,39 @@ import { renderHook } from '@testing-library/react';
44
import { act } from 'react-dom/test-utils';
55

66
describe('useLogin', () => {
7-
it('returns initial isLoggedIn value as false', () => {
7+
it('should returns initial isLoggedIn value as false', () => {
88
const { result } = renderHook(() => useLogin());
99

10-
expect(result.current.token).toBe('');
10+
expect(result.current.isLoggedIn).toBe(false);
1111
});
1212

13-
it('updates isLoggedIn value when login is called', () => {
14-
const token = 'token1234';
13+
it('should updates isLoggedIn value when login is called', () => {
14+
const accessToken = 'token1234';
15+
const refreshToken = 'token5678';
1516
const { result } = renderHook(() => useLogin());
1617

1718
act(() => {
18-
result.current.setToken(token);
19+
result.current.setTokens(accessToken, refreshToken);
1920
});
2021

21-
expect(result.current.token).toBe(token);
22+
expect(result.current.isLoggedIn).toBe(true);
23+
});
24+
25+
it('should empty both tokens', () => {
26+
const accessToken = 'token1234';
27+
const refreshToken = 'token5678';
28+
const { result } = renderHook(() => useLogin());
29+
30+
act(() => {
31+
result.current.setTokens(accessToken, refreshToken);
32+
});
33+
34+
expect(result.current.isLoggedIn).toBe(true);
35+
36+
act(() => {
37+
result.current.emptyTokens();
38+
});
39+
40+
expect(result.current.isLoggedIn).toBe(false);
2241
});
2342
});

src/pages/Landing/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import { ROUTE_PATHS } from '../../constant/config';
44
import { useEffect } from 'react';
55

66
function Landing() {
7-
const { token } = useLogin();
7+
const { isLoggedIn } = useLogin();
88
const navigate = useNavigate();
99
useEffect(() => {
10-
if (token) {
10+
if (isLoggedIn) {
1111
navigate(ROUTE_PATHS.CARDS);
1212
}
13-
}, [token]);
13+
}, [isLoggedIn]);
1414

1515
return (
1616
<div>

src/pages/Setting/index.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
1+
import { Button } from '../../Components';
2+
import { useLogin } from '../../hooks';
3+
import { useNavigate } from 'react-router-dom';
4+
import { ROUTE_PATHS } from '../../constant/config';
5+
16
function Setting() {
2-
return <div>Setting</div>;
7+
const { emptyTokens } = useLogin();
8+
const navigate = useNavigate();
9+
10+
const handelSignOut = () => {
11+
emptyTokens();
12+
navigate(ROUTE_PATHS.SIGN_IN);
13+
};
14+
15+
return (
16+
<div>
17+
<h1>Setting</h1>
18+
<Button onClick={handelSignOut}>Sign out</Button>
19+
</div>
20+
);
321
}
422

523
export default Setting;

src/pages/SignIn/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function SignIn() {
2828
const navigate = useNavigate();
2929
const [emailError, setEmailError] = useState('');
3030
const [passwordError, setPasswordError] = useState('');
31-
const { setToken } = useLogin();
31+
const { setTokens } = useLogin();
3232

3333
const signIn = async () => {
3434
try {
@@ -42,8 +42,8 @@ function SignIn() {
4242
throw new Error('이메일이 없습니다.');
4343

4444
if (res?.success) {
45-
const { access_token } = res;
46-
if (access_token) setToken(`${access_token}`);
45+
const { access_token, refresh_token } = res;
46+
if (access_token) setTokens(access_token, refresh_token);
4747
navigate(ROUTE_PATHS.CARDS);
4848
}
4949
} catch (error) {

0 commit comments

Comments
 (0)