Skip to content

Commit

Permalink
Merge pull request #31 from nakjun12/dev
Browse files Browse the repository at this point in the history
V0.3 최종 발표 전 Main 버전업
  • Loading branch information
zerosial authored Feb 7, 2024
2 parents 8666f4a + 39efff7 commit 464a799
Show file tree
Hide file tree
Showing 32 changed files with 1,178 additions and 667 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.1",
"react-spinners": "^0.13.8",
"swiper": "^11.0.5",
"zustand": "^4.4.7"
},
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

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

20 changes: 0 additions & 20 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,12 @@
import { RouterProvider } from "react-router-dom";
import { routers } from "./router";
import useAuthStore from "./utils/hooks/store/useAuthStore";
import { useEffect } from "react";
import { getUsersMe } from "./api/marketApi";

/**
* App 컴포넌트는 애플리케이션의 루트 컴포넌트입니다.
* RouterProvider를 사용하여 라우터 설정을 적용합니다.
*
* @returns JSX.Element - 라우터 설정이 적용된 App 컴포넌트
*/
function App() {
const { setAccessToken } = useAuthStore();

useEffect(() => {
const checkUserStatus = async () => {
try {
const response = await getUsersMe();
if (response.accessToken) {
setAccessToken(response.accessToken);
}
} catch (error) {
console.error("error:", error);
}
};

checkUserStatus();
}, []);
return <RouterProvider router={routers} />;
}

Expand Down
13 changes: 13 additions & 0 deletions src/api/loader/productLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// productLoader.js
import { getPostById } from "../marketApi";

export const productLoader = async ({ params }) => {
try {
console.log(params.productid, "params.productid", params);
const response = await getPostById(params.productid);
return response.data;
} catch (e) {
console.log(e);
return { error: "Error", message: e.message };
}
};
97 changes: 60 additions & 37 deletions src/api/marketApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,38 +26,59 @@ marketApi.interceptors.request.use(
marketApi.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// 리프레시 토큰 요청에서의 401 오류인지 확인
const isRefreshTokenRequest = originalRequest.url === "/auth/refresh-token";

// 401 인증에러 + 재요청이 아닐때 + refreshToken에 대한 요청이 아닐 떄
if (
error.response.status === 401 &&
!originalRequest._retry &&
!isRefreshTokenRequest
) {
originalRequest._retry = true;
try {
const { data } = await marketApi.post("/auth/refresh-token");
useAuthStore.getState().setAccessToken(data.accessToken);
originalRequest.headers["Authorization"] = `Bearer ${data.accessToken}`;
return marketApi(originalRequest);
} catch (refreshError) {
// 리프레시 토큰 요청 실패 시 로그아웃 처리
// 401에러의 경우 (인증 에러)
if (error.response?.status === 401) {
const isRefreshTokenRequest = originalRequest.url.includes(
"/auth/refresh-token"
);
const originalRequest = error.config;

// Refresh token 에러 (만료 혹은 없거나 비적합)
if (isRefreshTokenRequest) {
useAuthStore.getState().logout();
return Promise.reject(refreshError);
return Promise.reject(error);
}

// 재시도이지 않은 에러
if (!originalRequest._retry) {
// 무한 재요청 방지를 위한 트리거
originalRequest._retry = true;
const newAccessToken = await refreshAccessTokenAndFetchUser();
if (newAccessToken) {
originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`;
return marketApi(originalRequest); // 재요청
}
}
}
return Promise.reject(error);

return Promise.reject(error); // 다른 모든 에러는 여기서 처리
}
);

// 리프래쉬 토큰에 대한 함수
async function refreshAccessTokenAndFetchUser() {
try {
const { data } = await marketApi.post("/auth/refresh-token");
if (data.accessToken) {
useAuthStore.getState().setAccessToken(data.accessToken);
return data.accessToken;
}
} catch (error) {
if (error.response?.status !== 401) {
// 401 만료 이외에 통신 자체 에러일 경우 에러표출
console.error(error);
}
useAuthStore.getState().logout();
return null;
}
}

/**
* 사용자 (user)에 관련된 정보를 이용한 api입니다.
*
*/

// 인증이 필요한 요청
// 인증이 필요한 요청 (헤더 정보 주입)
//const response = await marketApi.get('/protected-route', { requiresAuth: true });

// 인증이 필요하지 않은 요청
Expand Down Expand Up @@ -88,21 +109,8 @@ export const postAuthLogin = async ({ email, password }) => {
useAuthStore.getState().setAccessToken(response.data.accessToken);
return response.data;
};

// 토큰 리프레쉬 (cookie의 refresh토큰 사용 없을 경우 401에러) 삭제예정
export const postRefreshToken = async () => {
const response = marketApi.post("/auth/refresh-token");
useAuthStore.getState().setAccessToken(response.data.accessToken);
return await response.data;
};

//
export const getAuthProfile = async () => {
return await marketApi.get("/auth/profile", { requiresAuth: true });
};

//내 정보 가져오기
export const getUsersMe = async () => {
export const getUserMe = async () => {
return await marketApi.get("/users/me", { requiresAuth: true });
};

Expand Down Expand Up @@ -153,8 +161,23 @@ export const getPostById = (id) => {
};

// 특정 사용자의 게시물 목록 가져오기
export const getPostByUser = (userId) => {
return marketApi.get(`/posts/${userId}/posts`);
export const getPostByUser = ({
page,
limit,
query,
orderBy,
direction,
userId
}) => {
return marketApi.get(`/posts/${userId}/list`, {
params: {
page,
limit,
query,
orderBy,
direction
}
});
};

export default marketApi;
19 changes: 19 additions & 0 deletions src/components/Loading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import { BeatLoader } from "react-spinners";
import styled from "@emotion/styled";

const Loading = () => {
return (
<LoadingWrap>
<BeatLoader />
</LoadingWrap>
);
};
export default Loading;

const LoadingWrap = styled.div`
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`;
2 changes: 2 additions & 0 deletions src/components/WithLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Outlet } from "react-router-dom";
import { SuspenseController } from "./SuspenseController";
import Footer from "./footer";
import Header from "./header";
import { LocationObserver } from "@/utils/LocationObserver";

const DelayedComponent = lazy(() =>
SuspenseController(import("./SuspenseController"), 3000)
Expand All @@ -22,6 +23,7 @@ const Modal = lazy(() => import("./popup/Modal"));
export default function WithLayout() {
return (
<div className="flex flex-col justify-between h-screen">
<LocationObserver />
<Header />
<Suspense fallback={<div>대기중</div>}>
<Modal />
Expand Down
8 changes: 4 additions & 4 deletions src/components/header/components/LoginMenuList.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ export const LoginMenuList = () => {
<ul
tabIndex={0}
className="mt-3 z-[1] p-2 shadow menu menu-sm dropdown-content bg-base-100 rounded-box w-52">
<li>
<div onClick={() => navigate(ROUTES.LOGIN)}>Login</div>
<li onClick={() => navigate(ROUTES.LOGIN)}>
<div>Login</div>
</li>
<li>
<div onClick={() => navigate(ROUTES.JOIN)}>Join</div>
<li onClick={() => navigate(ROUTES.JOIN)}>
<div>Join</div>
</li>
</ul>
</div>
Expand Down
19 changes: 13 additions & 6 deletions src/components/header/components/LogoutMenuList.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { getUserMe } from "@/api/marketApi";
import { ROUTES } from "@/utils/constants/routePaths";
import useAuthStore from "@/utils/hooks/store/useAuthStore";
import { useNavigate } from "react-router-dom";

export const LogoutMenuList = () => {
const { logout } = useAuthStore();
const { logout, userName } = useAuthStore();
const navigate = useNavigate();
return (
<div className="dropdown dropdown-end">
<div
Expand All @@ -18,14 +22,17 @@ export const LogoutMenuList = () => {
<ul
tabIndex={0}
className="mt-3 z-[1] p-2 shadow menu menu-sm dropdown-content bg-base-100 rounded-box w-52">
<li>
<li onClick={() => navigate(ROUTES.PROFILE)}>
<a className="justify-between">
Profile
<span className="badge">New</span>
HI! {userName}
<span className="badge">Profile</span>
</a>
</li>
<li>
<div onClick={() => logout()}>Logout</div>
<li onClick={() => logout()}>
<div>Logout</div>
</li>
<li onClick={() => getUserMe()}>
<div>Me</div>
</li>
</ul>
</div>
Expand Down
32 changes: 18 additions & 14 deletions src/components/popup/Dialog.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { css, Global } from "@emotion/react";
import { useEffect, useRef } from "react";

/**
Expand All @@ -20,18 +20,10 @@ export default function Dialog({ isOpen, onBackdropClick = null, children }) {
const dialogRef = useRef(null);

useEffect(() => {
const body = document.body;

if (isOpen) {
// 대화 상자를 모달로 표시합니다.
dialogRef.current.showModal();
// 스크롤을 방지하기 위해 body의 overflow를 hidden으로 설정합니다.
body.style.overflow = "hidden";
dialogRef.current?.showModal(); // 모달 열기
} else {
// 대화 상자를 닫습니다.
dialogRef.current.close();
// 스크롤을 허용하기 위해 body의 overflow를 unset으로 설정합니다.
body.style.overflow = "unset";
dialogRef.current?.close(); // 모달 닫기
}

// 배경을 클릭했을 때 대화 상자를 닫는 이벤트 핸들러입니다.
Expand All @@ -56,9 +48,12 @@ export default function Dialog({ isOpen, onBackdropClick = null, children }) {
}, [isOpen]);

return (
<dialog ref={dialogRef} css={dialogStyle(isOpen)}>
{children}
</dialog>
<>
<Global styles={globalStyles(isOpen)} />
<dialog ref={dialogRef} css={dialogStyle}>
{children}
</dialog>
</>
);
}

Expand All @@ -77,6 +72,15 @@ const dialogStyle = (isOpen) => css`
/* 여기에 필요한 다른 dialog 스타일을 추가할 수 있습니다 */
`;

const globalStyles = (isOpen) => css`
${isOpen &&
`
body {
overflow: hidden;
}
`}
`;

/**
* 사용법:
* 1. isOpen 상태를 정의하여 모달이 열려 있는지 여부를 제어합니다.
Expand Down
Loading

0 comments on commit 464a799

Please sign in to comment.