Skip to content

Commit

Permalink
Feat/#42/api login (#49)
Browse files Browse the repository at this point in the history
* fix: 후기 페이지 서버에서 주어지는 데이터에 맞게 props 변경

* feat: recoil, recoil-persist 패키지 다운로드

* feat: 후기 페이지 API 연결

* feat: 후기 페이지 API 연결

* feat: axios interceptor를 사용해서 API 요청 전 토큰 재발급

* feat: 리코일 사용을 위해 설정

* feat: 헤더에 로그인 버튼 추가

* feat: 로그인을 위한 함수 구현

* feat: 로그인 성공시 사용자의 정보를 recoil에 저장

* feat: 후기 페이지 API 연결
  • Loading branch information
jk6722 authored Nov 20, 2023
1 parent d6406cd commit 95583b2
Show file tree
Hide file tree
Showing 14 changed files with 369 additions and 177 deletions.
52 changes: 51 additions & 1 deletion package-lock.json

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

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"js-cookie": "^3.0.5",
"moment": "^2.29.4",
"quill-image-resize-module-react": "^3.0.0",
"react": "^18.2.0",
Expand All @@ -18,9 +19,12 @@
"react-icons": "^4.11.0",
"react-query": "^3.39.3",
"react-quill": "^2.0.0",
"react-router-dom": "^6.16.0"
"react-router-dom": "^6.16.0",
"recoil": "^0.7.7",
"recoil-persist": "^5.1.0"
},
"devDependencies": {
"@types/js-cookie": "^3.0.6",
"@types/node": "^20.8.9",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
Expand Down
22 changes: 22 additions & 0 deletions src/apis/board/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Axios from '..';

export const fetchBoardData =
(boardType: string, filter: string, page: number, size: number) => () => {
if (boardType === 'review') {
return Axios.get('/reviews/category', {
params: {
category: filter === '전체' ? null : filter,
page: page - 1,
size,
},
});
} else if (boardType === 'archive') {
return Axios.get('/archives/category', {
params: {
category: filter,
page: page - 1,
size,
},
});
}
};
78 changes: 76 additions & 2 deletions src/apis/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,81 @@
import axios from 'axios';
import axios, { AxiosResponse } from 'axios';
import Cookies from 'js-cookie';
import { onLoginSuccess } from '@/pages/login/functions';

const Axios = axios.create();
Axios.defaults.baseURL = 'http://52.78.13.36'; // 서버 URL
const ServerURL = 'https://www.tvmaker.shop'; // 서버 URL
Axios.defaults.baseURL = ServerURL;
Axios.defaults.withCredentials = true;

// 요청 전 access 토큰 만료되었는지 확인
Axios.interceptors.request.use(
async config => {
const expireToken = localStorage.getItem('expireToken');
const expireTime = expireToken
? new Date(expireToken as string)
: undefined;
const currentTime = new Date().getTime();
const refreshToken = Cookies.get('refreshToken');

if (expireTime && refreshToken) {
// 이전에 로그인한 적이 있음
if (currentTime > expireTime.getTime()) {
// 만료되었으면
const res = await axios.get(`${ServerURL}/auth/refresh`, {
params: {
refreshToken,
},
});
const { accessToken: newAccessToken, refreshToken: newRefreshToken } =
res?.data?.result;

// header에 accessToken 세팅
Axios.defaults.headers.common[
'Authorization'
] = `Bearer ${newAccessToken}`;
config.headers.Authorization = `Bearer ${newAccessToken}`;

// 갱신된 만료 시간 localStorage에 저장
const newExpireTime = new Date(currentTime + 1 * 60 * 60 * 1000); // 유효시간 1시간
localStorage.setItem('expireToken', newExpireTime.toString());

Cookies.remove('refreshToken');
Cookies.set('refreshToken', newRefreshToken, { expires: 1 });
}
}
return config;
},
error => Promise.reject(error),
);

// 응답 전 accessToken이 만료되어 발생한 에러라면 갈아끼우고 다시 요청
Axios.interceptors.response.use(
(response: AxiosResponse) => {
return response;
},
async error => {
if (error.response?.status === 401) {
// accessToken이 없어서 에러가 발생한 상황
const refreshToken = Cookies.get('refreshToken');
if (refreshToken) {
// refreshToken이 있다면, 이미 로그인된 사용자이므로
const res = await axios.get(`${ServerURL}/auth/refresh`, {
params: { refreshToken },
});
// 기존에 있던 refreshToken은 지우고
Cookies.remove('refreshToken');
// 로그인 성공 로직 실행
onLoginSuccess(res, null);
return Axios(error.config);
} else {
// refreshToken이 없다는 건 로그인을 해야 한다는 것
if (window.confirm('로그인이 필요한 서비스입니다.')) {
window.location.href = '/login';
}
}
}
return error;
},
);

export default Axios;
73 changes: 57 additions & 16 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import { Link } from 'react-router-dom';
import { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import { useRecoilState } from 'recoil';

import { HeaderData, UserDropdown } from '@/constants/Header';
import { B1, H3 } from '@/style/fonts/StyledFonts';
import UserIcon from '@/assets/icons/user-icon.svg';
import RoundedButton from '../Button/RoundedButton';
import { UserAtom } from '@/recoil/LoginAtom';

const Header = () => {
const [isLogined, setIsLogined] = useState<boolean>(false);
const [userInfo, setUserInfo] = useRecoilState(UserAtom);

const navigate = useNavigate();

const handleLogout = () => {
// logout
// 저장하고 있던 사용자 정보 초기화
console.log(userInfo);
setUserInfo({
id: -1,
email: '',
phoneNumber: '',
name: '',
nickName: '',
});
setTimeout(() => {
setIsLogined(false);
}, 1000);
};

const handleToLogin = () => {
navigate('/login');
};

useEffect(() => {
if (userInfo?.id != -1) setIsLogined(true);
}, [userInfo]);

return (
<Container>
<InnerContainer>
Expand All @@ -32,22 +62,33 @@ const Header = () => {

{/* 헤더 우측 (유저 아이콘) */}
<NavBarContainer>
<NavBar>
<img src={UserIcon} alt="user" />
<Dropdown>
{UserDropdown.map(({ title, link }, index) => (
<li key={index} className={`${title}`}>
<Link to={link} state={{ filter: title }}>
<B1 $fontColor="#15191D">{title}</B1>
</Link>
{isLogined ? (
<NavBar>
<img src={UserIcon} alt="user" />
<Dropdown>
{UserDropdown.map(({ title, link }, index) => (
<li key={index} className={`${title}`}>
<Link to={link} state={{ filter: title }}>
<B1 $fontColor="#15191D">{title}</B1>
</Link>
</li>
))}
<Seperator />
<li onClick={handleLogout}>
<B1 $fontColor="#15191D">{'로그아웃'}</B1>
</li>
))}
<Seperator />
<li onClick={handleLogout}>
<B1 $fontColor="#15191D">{'로그아웃'}</B1>
</li>
</Dropdown>
</NavBar>
</Dropdown>
</NavBar>
) : (
<RoundedButton
$buttonColor="#35393D"
$buttonWidth="109px"
$buttonHeight="40px"
$hoverTextColor="#fff"
onClick={handleToLogin}
children={<B1 $fontColor="#fff">로그인</B1>}
/>
)}
</NavBarContainer>
</InnerContainer>
</Container>
Expand Down
5 changes: 4 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import { QueryClient, QueryClientProvider } from 'react-query';
import { RecoilRoot } from 'recoil';

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<RecoilRoot>
<App />
</RecoilRoot>
</QueryClientProvider>
</React.StrictMode>,
);
Loading

0 comments on commit 95583b2

Please sign in to comment.