Skip to content

Commit

Permalink
Merge pull request #738 from codefug/React-이승현-Sprint12
Browse files Browse the repository at this point in the history
  • Loading branch information
Il9 authored Jul 22, 2024
2 parents ff7f23b + 4921433 commit 335d2e0
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 139 deletions.
40 changes: 40 additions & 0 deletions package-lock.json

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

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "tsc && vite build && vite",
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^3.6.0",
"@tanstack/react-query": "^5.51.9",
"@types/js-cookie": "^3.0.6",
"axios": "^1.7.2",
"futil": "^1.76.4",
"lodash": "^4.17.21",
"js-cookie": "^3.0.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.5",
Expand Down
22 changes: 13 additions & 9 deletions src/app/store/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { create } from "zustand";
import { create, StateCreator } from "zustand";
import { persist } from "zustand/middleware";

type User = {
accessToken: string;
};

type Store = {
type UserState = {
user: User | null;
login: (accessToken: string) => void;
logout: () => void;
};

export const useStore = create<Store>()((set) => ({
export const useStoreSlice: StateCreator<UserState> = (set) => ({
user: null,
login: (accessToken: string) => {
localStorage.setItem("accessToken", accessToken);
document.cookie = `accessToken=${localStorage.getItem("accessToken")}`;
return set(() => ({ user: { accessToken } }));
},
login: (accessToken: string) => set(() => ({ user: { accessToken } })),
logout: () => set(() => ({ user: null })),
}));
});

const persistedUserStore = persist<UserState>(useStoreSlice, {
name: "user",
getStorage: () => localStorage,
});

export const useUserStore = create(persistedUserStore);
4 changes: 2 additions & 2 deletions src/pages/login/ui/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useStore } from "@/app/store";
import { useUserStore } from "@/app/store";
import { postSignIn } from "@/shared/api/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { FieldValues, useForm } from "react-hook-form";
Expand All @@ -11,7 +11,7 @@ const schema = z.object({
});
// 네트워크 요청을 보내기 전에 형식 검사
export function LoginPage() {
const { login } = useStore();
const { login } = useUserStore();
const navigate = useNavigate();
const {
register,
Expand Down
4 changes: 0 additions & 4 deletions src/react-app-env.d.ts

This file was deleted.

6 changes: 3 additions & 3 deletions src/router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import {
Route,
} from "react-router-dom";

import App from "../app/App";
import { Items } from "../pages/items";
import { RegisterPage } from "../pages/RegisterPage";
import { ErrorPage } from "../pages/ErrorPage";
import { ProductPage } from "../pages/ProductPage";
import { LoginPage } from "@/pages/login";
import { SignupPage } from "@/pages/signup";
import { LoginPage } from "../pages/login";
import { SignupPage } from "../pages/signup";
import App from "@/app/App";

export const router = createBrowserRouter(
createRoutesFromElements(
Expand Down
61 changes: 50 additions & 11 deletions src/shared/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AxiosError } from "axios";
import { instance } from "./axios";
import { instanceWithoutInterceptors } from "./axios";
import {
GetCommentsProps,
GetDatumProps,
GetProductProps,
PostAuthRefreshToken,
SignInRequestData,
SignInResponseData,
SignUpRequestData,
Expand All @@ -12,6 +13,7 @@ import {
SpecificProductData,
TotalProductsData,
} from "./type";
import Cookies from "js-cookie";

// 나중에 전부 axios로 바꾸기
export async function getDatum({
Expand All @@ -27,8 +29,10 @@ export async function getDatum({
orderBy,
});
if (keyword) searchParams.set("keyword", keyword);
const response = await instance(`/products?`, { params: searchParams });
return response.data as TotalProductsData;
const response = await instanceWithoutInterceptors(`/products?`, {
params: searchParams,
});
return response.data;
} catch (error) {
console.error(error);
alert(error);
Expand All @@ -38,7 +42,9 @@ export async function getDatum({

export const getProduct = async ({ productId }: GetProductProps) => {
try {
const response = await instance(`/products/${productId}`);
const response = await instanceWithoutInterceptors(
`/products/${productId}`
);
const data: SpecificProductData = response.data;
return data;
} catch (error) {
Expand All @@ -56,9 +62,12 @@ export const getComments = async ({
const searchParams = new URLSearchParams({
limit: limit.toString(),
});
const response = await instance(`/products/${productId}/comments`, {
params: searchParams,
});
const response = await instanceWithoutInterceptors(
`/products/${productId}/comments`,
{
params: searchParams,
}
);
const data: SpecificCommentsData = response.data;
return data;
} catch (error) {
Expand All @@ -75,7 +84,7 @@ export const postSignUp = async ({
passwordConfirmation,
}: SignUpRequestData) => {
try {
const response = await instance.post(`/auth/signUp`, {
const response = await instanceWithoutInterceptors.post(`/auth/signUp`, {
email,
nickname,
password,
Expand All @@ -84,21 +93,51 @@ export const postSignUp = async ({
const data: SignUpResponseData = response.data;
return data;
} catch (error) {
console.error(error);
alert(error);
if (error instanceof AxiosError) {
console.log(error.response);
alert(error.response?.data?.message);
throw new Error();
}
throw new Error();
}
};

export const postSignIn = async ({ email, password }: SignInRequestData) => {
try {
const response = await instance.post(`/auth/signIn`, { email, password });
const response = await instanceWithoutInterceptors.post(`/auth/signIn`, {
email,
password,
});
const data: SignInResponseData = response.data;
return data;
} catch (error) {
if (error instanceof AxiosError) {
console.log(error.response);
alert(error.response?.data?.message);
throw new Error();
}
throw new Error();
}
};

export const postAuthRefreshToken = async () => {
try {
const refreshToken = Cookies.get("refreshToken");
if (refreshToken === undefined) {
throw new Error("refreshToken이 없습니다.");
}
const response = await instanceWithoutInterceptors.post(
`/auth/refresh-token`,
{ refreshToken }
);
const data: PostAuthRefreshToken = response.data;
return data;
} catch (error) {
if (error instanceof AxiosError) {
console.log(error.response);
alert(error.response?.data?.message);
throw new Error();
}
throw new Error();
}
};
91 changes: 84 additions & 7 deletions src/shared/api/axios.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,90 @@
import axios from "axios";
import { useUserStore } from "@/app/store";
import { BASE_URL } from "../constants/constants";
import { refreshTokenRotation } from "../util/RTR";

const instance = axios.create({ baseURL: BASE_URL });
import axios, {
AxiosError,
CreateAxiosDefaults,
InternalAxiosRequestConfig,
} from "axios";
import { postAuthRefreshToken } from "./api";

const authInstance = axios.create({ baseURL: BASE_URL });
// 재시도 확인 프로퍼티 설정
interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
_retry?: boolean;
}

const { setAuthHeader } = refreshTokenRotation();
// 기본 설정
const baseConfig: CreateAxiosDefaults = {
baseURL: `${BASE_URL}`,
};

authInstance.interceptors.request.use(setAuthHeader);
// 쿠키 드러내고 인증 필요없는 인스턴스
export const instanceWithoutInterceptors = axios.create(baseConfig);

export { authInstance, instance };
// 인증 필요한 인스턴스
export const instance = axios.create({ ...baseConfig, withCredentials: true });

instance.interceptors.request.use(
// 요청 전에 실행
function (config) {
// 토큰 가져오기
const accessToken = useUserStore.getState().user?.accessToken;

// 헤더에 토큰 추가
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}

// 설정 반환
return config;
},
// 요청 에러 발생 시 실행
function (error) {
return Promise.reject(error);
}
);

instance.interceptors.response.use(
// 응답 성공 시 실행
function (response) {
return response;
},
// 응답 에러 발생 시 실행
async function (error: AxiosError) {
// 에러 정보 가져오기
const originalRequest: CustomAxiosRequestConfig | undefined = error.config;

// 토큰 만료 시 재시도
if (
error.response?.status === 401 &&
originalRequest &&
!originalRequest._retry
) {
originalRequest._retry = true;
try {
// 토큰 재발급
const response = await postAuthRefreshToken();

// 토큰 갱신
useUserStore.setState({
user: { accessToken: response.accessToken },
});

// 헤더에 토큰 추가
originalRequest.headers.Authorization = `Bearer ${response.accessToken}`;

// 재시도
return instance(originalRequest);
} catch (error) {
// 토큰이 만료되었을 때
if (error instanceof AxiosError && error.response?.status === 403) {
// 로그아웃
useUserStore.getState().logout();
return;
}
}
}

return Promise.reject(error);
}
);
4 changes: 4 additions & 0 deletions src/shared/api/type/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,7 @@ export interface SignInResponseData {
createdAt: string;
};
}

export interface PostAuthRefreshToken {
accessToken: string;
}
Loading

0 comments on commit 335d2e0

Please sign in to comment.