Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8b30c7e
[Chore] heroicons, react-hook-form 설치, 의존성 추가
dlcks0601 Dec 13, 2024
1d9c0be
[Feat] 로고 추가
dlcks0601 Dec 13, 2024
021c9ec
[Feat] 회원가입 폼 구현
dlcks0601 Dec 13, 2024
091bd62
[Feat] 로그인 폼 구현
dlcks0601 Dec 13, 2024
c6e3ba1
[Feat] 인풋 폼 구현
dlcks0601 Dec 13, 2024
ecb1752
[Refactor] 필요없는 주석 제거
dlcks0601 Dec 13, 2024
5aa9a5c
[Feat] 비밀번호 가이드 라인 구현
dlcks0601 Dec 13, 2024
47b1a0e
[Feat] 회원가입 페이지 구현
dlcks0601 Dec 13, 2024
e2c9920
[Feat] 로그인 페이지 구현
dlcks0601 Dec 13, 2024
912bba5
[Feat] 회원가입, 로그인 페이지 라우팅 추가
dlcks0601 Dec 13, 2024
e6c4bcd
[Feat] jwt-decode 설치
dlcks0601 Dec 14, 2024
fad712b
[Refactor] 로그인, 회원가입 폼 삭제
dlcks0601 Dec 14, 2024
fe87fc5
[Refactor] 로그인, 회원가입 폼 삭제 후 로그인, 회원가입 페이지로 이동
dlcks0601 Dec 14, 2024
cc4a35c
[Refactor] env 숨김
dlcks0601 Dec 14, 2024
6b9f093
[Feat] 상태관리 라우팅
dlcks0601 Dec 14, 2024
aacc705
[Refactor] 주석 제거
dlcks0601 Dec 14, 2024
18d9c29
[Feat] auth API 모듈 생성 및 기본 기능 구현
dlcks0601 Dec 14, 2024
e4c0621
[Feat] HTTP API 모듈 생성 및 Axios 인스턴스 설정
dlcks0601 Dec 14, 2024
242fe75
[Refactor] Authorization 헤더 문제 - 리팩토링 필요함
dlcks0601 Dec 14, 2024
49090d6
[Feat] 리덕스 상태관리 store 생성
dlcks0601 Dec 14, 2024
b4985a1
[Refactor] 회원가입 api 연결
dlcks0601 Dec 14, 2024
d3dd90a
[Refactor] 로그인 api 연결
dlcks0601 Dec 14, 2024
f862a16
[Feat] 사용자 상태관리 slice 기능 구현
dlcks0601 Dec 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ dist
dist-ssr
*.local

# Environment variables
.env

# Editor directories and files
.vscode/*
!.vscode/extensions.json
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
"build-storybook": "storybook build"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"@reduxjs/toolkit": "^2.4.0",
"@tanstack/react-query": "^5.62.2",
"@types/react-redux": "^7.1.34",
"axios": "^1.7.9",
"jwt-decode": "^4.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.54.1",
"react-redux": "^9.1.2",
"react-router": "^7.0.2",
"styled-components": "^6.1.13"
Expand All @@ -33,6 +36,7 @@
"@storybook/react": "^8.4.7",
"@storybook/react-vite": "^8.4.7",
"@storybook/test": "^8.4.7",
"@types/jwt-decode": "^3.1.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
Expand Down
45 changes: 45 additions & 0 deletions pnpm-lock.yaml

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

12 changes: 7 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import Button from '@/components/atoms/Button';
import { RouterProvider, createBrowserRouter } from 'react-router';
import router from '@/routes/router';
import { Provider } from 'react-redux'; // Redux Provider 임포트
import { store } from '@/store/store'; // 설정된 Redux 스토어 임포트

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -16,10 +17,11 @@ const queryClient = new QueryClient({
const App = () => {
const appRouter = createBrowserRouter(router);
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={appRouter} />
<Button variant='primary'>테스트 버튼</Button>
</QueryClientProvider>
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<RouterProvider router={appRouter} />
</QueryClientProvider>
</Provider>
);
};

Expand Down
70 changes: 70 additions & 0 deletions src/apis/auth.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import httpClient from './http.api'; // Axios 인스턴스 가져오기
import { JoinProps } from '@/pages/JoinPage';
import { LoginProps } from '@/pages/LoginPage';

// 회원가입 요청
export const join = async (userData: JoinProps) => {
try {
const { data, status } = await httpClient.post('/api/users/join', userData);

if (status === 200) {
console.log('회원가입 성공:', data);
return data;
} else if (status === 400 && data.message.includes('입력해주세요')) {
console.error('필수 입력값 없음:', data.message);
throw new Error(data.message);
} else if (status === 400 && data.message.includes('중복된')) {
console.error('중복 오류:', data.message);
throw new Error(data.message);
} else {
throw new Error('알 수 없는 회원가입 에러가 발생했습니다.');
}
} catch (error) {
console.error('회원가입 요청 중 에러 발생:', error);
throw new Error('회원가입 요청에 실패했습니다. 관리자에게 문의하세요.');
}
};

interface LoginResponse {
message: any;
success: boolean;
token?: string;
}

// 로그인 요청
export const login = async (data: LoginProps): Promise<LoginResponse> => {
try {
const { data: responseData, status } = await httpClient.post<LoginResponse>(
'/api/users/login',
data
);

if (status === 200 && responseData.success) {
console.log('로그인 성공:', responseData);
return responseData;
} else if (
status === 400 &&
responseData.message.includes('입력해주세요')
) {
console.error('필수 입력값 없음:', responseData.message);
throw new Error(responseData.message);
} else if (
status === 404 &&
responseData.message.includes('등록되지 않은 이메일')
) {
console.error('등록되지 않은 이메일:', responseData.message);
throw new Error(responseData.message);
} else if (
status === 401 &&
responseData.message.includes('비밀번호가 틀렸습니다')
) {
console.error('비밀번호 오류:', responseData.message);
throw new Error(responseData.message);
} else {
throw new Error('알 수 없는 로그인 에러가 발생했습니다.');
}
} catch (error) {
console.error('로그인 요청 중 에러 발생:', error);
throw new Error('로그인 요청에 실패했습니다. 관리자에게 문의하세요.');
}
};
105 changes: 105 additions & 0 deletions src/apis/http.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { getToken, removeToken } from '@/store/slices/authSlice'; // 토큰 유틸리티 함수 import
import { logout } from '@/store/slices/authSlice';
import { store } from '@/store/store';

const BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3333';
const DEFAULT_TIMEOUT = 30000; // 요청 제한 시간

// Axios 인스턴스 생성 함수
export const createClient = (config?: AxiosRequestConfig): AxiosInstance => {
const axiosInstance = axios.create({
baseURL: BASE_URL,
timeout: DEFAULT_TIMEOUT,
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
...config,
});

// 요청 인터셉터: Authorization 헤더 동적 설정
axiosInstance.interceptors.request.use(
(config) => {
const accessToken = getToken();
if (accessToken) {
if (config.headers && typeof config.headers.set === 'function') {
// AxiosHeaders 객체 처리
config.headers.set('Authorization', `Bearer ${accessToken}`);
} else {
// 일반 객체 초기화
config.headers = {
...config.headers, // 기존 헤더 유지
Authorization: `Bearer ${accessToken}`,
} as any;
}
}
console.log('Request Headers:', config.headers); // 디버깅용 로그
return config;
},
(error) => {
console.error('Request Error:', error);
return Promise.reject(error);
}
);

// 응답 인터셉터: 401 상태 처리
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
console.warn('401 Unauthorized: Logging out user...');
removeToken(); // 토큰 삭제
store.dispatch(logout()); // Redux 상태 초기화
window.location.href = '/login'; // 로그인 페이지로 리다이렉트
return;
}
return Promise.reject(error);
}
);

return axiosInstance;
};

// 기본 Axios 인스턴스 생성
export const httpClient = createClient();

// API 요청 함수
export const authApi = {
join: async (data: { email: string; password: string; nickname: string }) => {
try {
const response = await httpClient.post('/api/signup', data);
return response.data;
} catch (error) {
console.error('Signup API Error:', error);
throw error;
}
},
login: async (data: { email: string; password: string }) => {
try {
const response = await httpClient.post('/api/login', data);
const { token } = response.data;

// 토큰 저장
localStorage.setItem('accessToken', token); // 필요 시 유틸리티 함수 사용 가능
return response.data;
} catch (error) {
console.error('Login API Error:', error);
throw error;
}
},
logout: async () => {
try {
await httpClient.post('/api/logout');
removeToken(); // 토큰 삭제
store.dispatch(logout()); // Redux 상태 초기화
window.location.href = '/login'; // 로그인 페이지로 리다이렉트
} catch (error) {
console.error('Logout API Error:', error);
throw error;
}
},
};

// 기본 Axios 인스턴스 내보내기
export default httpClient;
Loading