Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: axios 세팅 #13

Merged
merged 10 commits into from
Jul 18, 2023
2 changes: 2 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@
"@typescript-eslint/method-signature-style": [ // 특정 메서드 syntax 사용해서 시행
"error"
],
"@typescript-eslint/no-explicit-any": "off",
"unused-imports/no-unused-imports": [ // 사용하지 않는 es6 모듈 import를 찾아 제거
"error"
],
"react/jsx-indent-props": [ // JSX에서 props 들여쓰기 유효성 검사
"error",
"first"
],
"no-restricted-globals": "off",
"react/jsx-curly-newline": "off", // jsx curly 내에서 일관된 줄 바꿈 적용
"react/jsx-one-expression-per-line": "off", // JSX에서 한 줄에 하나의 표현식으로 제한
"react/jsx-props-no-spreading": "off", // JSX props 확산 방지
Expand Down
Empty file removed images/icon/kakao.svg
Empty file.
22 changes: 11 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="images/icon/menu/feel-cat.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>하루한냥</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<link rel='icon' type='image/svg+xml' href='images/icon/menu/feel-cat.svg' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>하루한냥</title>
</head>
<body>
<div id='root'></div>
<script type='module' src='/src/main.tsx'></script>
</body>
</html>
27 changes: 21 additions & 6 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@types/qs": "^6.9.7",
"axios": "^1.4.0",
"qs": "^6.11.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.0",
Expand Down
65 changes: 65 additions & 0 deletions src/api/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import qs from 'qs';
import { API_TIMEOUT, BASE_URL } from '@lib/const/config';
import { ACCESS_TOKEN } from '@lib/const/localstorage';

type APIResponse<T = unknown> = {
success: boolean;
msg: string;
data?: T;
};

export const client = axios.create({
baseURL: BASE_URL,
timeout: API_TIMEOUT,
timeoutErrorMessage: '서버 요청 시간 초과',
paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'comma' }),
});

const handleHeadersWithAccessToken = (config: AxiosRequestConfig): InternalAxiosRequestConfig => {
const accessToken = localStorage.getItem(ACCESS_TOKEN) || '';
config.headers = {
...config.headers,
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};

return config as InternalAxiosRequestConfig;
};

client.interceptors.request.use(handleHeadersWithAccessToken);
client.interceptors.response.use(
(response: AxiosResponse) => response,
(error: AxiosError) => Promise.reject(error),
);

export const http = {
get: function get<Response = unknown>(url: string, config?: AxiosRequestConfig) {
return client.get<APIResponse<Response>>(url, config).then((res) => res.data);
},
post: function post<Response = unknown, Request = any>(url: string, data?: Request, config?: AxiosRequestConfig) {
return client.post<APIResponse<Response>>(url, data, config).then((res) => res.data);
},
put: function put<Response = unknown, Request = any>(url: string, data?: Request, config?: AxiosRequestConfig) {
return client.put<APIResponse<Response>>(url, data, config).then((res) => res.data);
},
patch: function patch<Response = unknown, Request = any>(url: string, data?: Request, config?: AxiosRequestConfig) {
return client.patch<APIResponse<Response>>(url, data, config).then((res) => res.data);
},
delete: function del<Response = unknown>(url: string, config?: AxiosRequestConfig) {
return client.delete<APIResponse<Response>>(url, config).then((res) => res.data);
},
};

export const handleAxiosError = (error: any) => {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError;
console.error('Axios Error:', axiosError);
return axiosError.response?.data as APIResponse;
}
console.log('Unknown Error: ', error);
return {
success: false,
msg: '알 수 없는 오류가 발생했습니다.',
};
};
6 changes: 6 additions & 0 deletions src/lib/const/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const BASE_URL = 'http://133.186.144.153:3001/api';
export const API_TIMEOUT = 3000;

const KAKAO_CLIENT_ID = 'a3d08c94732b0c85334d04b474f49873';
const KAKAO_REDIRECT_URL = 'http://localhost:5173/oauth/kakao';
export const KAKAO_AUTH_URL = `https://kauth.kakao.com/oauth/authorize?client_id=${KAKAO_CLIENT_ID}&redirect_uri=${KAKAO_REDIRECT_URL}&response_type=code`;
Comment on lines +4 to +6
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constant를 변수로 잘 분리해주셨네요. 👍

2 changes: 2 additions & 0 deletions src/lib/const/localstorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const ACCESS_TOKEN = 'accessToken';
export const USER = 'user';
1 change: 1 addition & 0 deletions src/lib/const/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export const PATH = {
TIMELINE: '/timeline',
REPORT: '/report',
SETTING: '/setting',
OAUTH_KAKAO: '/oauth/kakao',
};
2 changes: 1 addition & 1 deletion src/lib/types/user.ts → src/lib/types/user.type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type User = {
export type UserType = {
email: string;
password: string;
passwordCheck: string;
Expand Down
6 changes: 1 addition & 5 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,4 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
40 changes: 40 additions & 0 deletions src/pages/AuthKakaoPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useSearchParams } from 'react-router-dom';
import { useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router';
import { PATH } from '@lib/const/path';
import { ACCESS_TOKEN, USER } from '@lib/const/localstorage';
import { handleAxiosError, http } from '../api/http';

export default function AuthKakaoPage() {
const navigate = useNavigate();
const [params] = useSearchParams();
const code = params.get('code');

const handleSigninKakao = useCallback(async () => {
try {
const responseSignIn = await http.post<{ token: string; user: { name: string } }>('/user/oauth/kakao', { code });
const isSuccess = responseSignIn.success;
if (isSuccess && responseSignIn.data) {
const accessToken = responseSignIn.data.token;
const userProfile = {
name: responseSignIn.data.user.name,
};
localStorage.setItem(ACCESS_TOKEN, JSON.stringify(accessToken));
localStorage.setItem(USER, JSON.stringify(userProfile));
navigate(PATH.CALENDAR);
}
} catch (e) {
const error = handleAxiosError(e);
alert(error.msg);
navigate(PATH.HOME);
}
}, [code, navigate]);

useEffect(() => {
if (code) {
handleSigninKakao();
}
}, [code, handleSigninKakao]);

return <div />;
}
8 changes: 7 additions & 1 deletion src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import styleTokenCss from '@ui/styles/styleToken.css';
import { useNavigate } from 'react-router';
import { PATH } from '@lib/const/path';
import SignButton from '@ui/components/SignButton';
import { KAKAO_AUTH_URL } from '@lib/const/config';

export default function HomePage() {
const navigate = useNavigate();
Expand All @@ -12,6 +13,10 @@ export default function HomePage() {
navigate(PATH.SIGN_IN);
};

const handlePageAuthKakao = () => {
location.href = KAKAO_AUTH_URL;
};

return (
<Container>
<TitleContainer>
Expand All @@ -22,15 +27,16 @@ export default function HomePage() {
<ButtonContainer>
<SignButton
text="이메일로 로그인"
onClick={handlePageSignin}
backgroundColor={styleTokenCss.color.secondary}
color={styleTokenCss.color.gray2}
onClick={handlePageSignin}
/>
<SignButton
text="카카오로 로그인"
imgSrc="/images/icon/kakao.svg"
backgroundColor={styleTokenCss.color.kakao}
color={styleTokenCss.color.gray2}
onClick={handlePageAuthKakao}
/>
</ButtonContainer>
</Container>
Expand Down
49 changes: 46 additions & 3 deletions src/pages/SettingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,56 @@
import Header from '@ui/components/layout/Header';
import Body from '@ui/components/layout/Body';
import Menu from '@ui/components/layout/Menu';
import SignButton from '@ui/components/SignButton';
import styleTokenCss from '@ui/styles/styleToken.css';
import styled from '@emotion/styled';
import { useNavigate } from 'react-router';
import { PATH } from '@lib/const/path';

export default function SettingPage() {
const navigate = useNavigate();

const handleClickLogout = () => {
const isLogout = confirm('로그아웃 하시겠습니까?');
if (isLogout) {
localStorage.clear();
navigate(PATH.HOME);
}
};

return (
<>
<Header />
<Body>setting page</Body>
<Title>마이 페이지</Title>
<Container>
<SignButton
text="로그아웃"
backgroundColor={styleTokenCss.color.secondaryActive}
color={styleTokenCss.color.white}
onClick={handleClickLogout}
/>
</Container>
<Menu />
</>
);
}

const Title = styled.h2`
background-color: ${styleTokenCss.color.background};
color: ${styleTokenCss.color.gray2};
font-size: 24px;
font-weight: 600;

display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
width: 100%;
height: 100px;
padding: 35px;
`;

const Container = styled(Body)`
padding: 35px;
justify-content: flex-start;
align-items: center;
overflow-y: auto;
`;
Loading