Skip to content

Commit

Permalink
Merge pull request #20 from nakjun12/dev
Browse files Browse the repository at this point in the history
V0.2 중간 프로젝트 전 Main 버전업
  • Loading branch information
zerosial authored Jan 24, 2024
2 parents ad854ef + 871050b commit 05bb8df
Show file tree
Hide file tree
Showing 36 changed files with 1,688 additions and 96 deletions.
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM node:18-alpine

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

RUN npm run build

EXPOSE 8080

CMD [ "npm", "run", "preview" ]
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: "3.8"

services:
pinemarket:
build:
context: .
dockerfile: Dockerfile
args:
TAG: "0.1v"
image: "pinemarket:0.1v"
ports:
- "8080:8080"
command: npm run preview
networks:
- pinemarket_api

networks:
pinemarket_api:
external: true
9 changes: 8 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link
rel="preload"
href="https://cdn.jsdelivr.net/gh/Project-Noonnu/[email protected]/Pretendard-Regular.woff"
as="font"
type="font/woff"
crossorigin
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<div id="popup-root"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,22 @@
"preview": "vite preview",
"cypress:open": "cypress open",
"format": "prettier --write \"src/**/*.js\"",
"test": "vitest watch"
"test": "vitest watch",
"docker:build": "docker-compose build",
"docker:start": "docker-compose up -d",
"docker:stop": "docker-compose down"
},
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@heroicons/react": "^2.1.1",
"@tanstack/react-query": "^5.17.9",
"@vitejs/plugin-react": "^4.2.1",
"axios": "^1.6.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.1",
"swiper": "^11.0.5",
"zustand": "^4.4.7"
},
"devDependencies": {
Expand Down
19 changes: 19 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: 19 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
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 컴포넌트는 애플리케이션의 루트 컴포넌트입니다.
Expand All @@ -8,8 +11,23 @@ import { routers } from "./router";
* @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} />;
}

export default App;

125 changes: 101 additions & 24 deletions src/api/marketApi.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,144 @@
import useAuthStore from "@/utils/hooks/store/useAuthStore";
import axios from "axios";

const baseURL = "https://pinemarket-api.vercel.app";
const baseURL = "https://pinemarket.cielui.com";

const marketApi = axios.create({
baseURL
baseURL,
withCredentials: true
});

// 요청 인터셉터: 모든 요청 전에 실행
marketApi.interceptors.request.use(
(config) => {
const { accessToken } = useAuthStore.getState(); // useAuthStore에서 액세스 토큰 가져오기
console.log("accessToken", accessToken);
console.log("config.requiresAuth", config.requiresAuth);
if (config.requiresAuth && accessToken) {
config.headers["Authorization"] = `Bearer ${accessToken}`; // 액세스 토큰이 있으면 요청 헤더에 추가
}
return config;
},
(error) => {
return Promise.reject(error); // 요청 오류 처리
}
);

// 응답 인터셉터: 모든 응답 후에 실행
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) {
// 리프레시 토큰 요청 실패 시 로그아웃 처리
useAuthStore.getState().logout();
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);

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

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

// 인증이 필요하지 않은 요청
//const response = await marketApi.get('/public-route');

// hello + 사용자 이름 반환
export const getHello = (name) => {
return marketApi.get(`/hello/${name}`);
export const getHello = async () => {
return await marketApi.get(`/hello/test`);
};

// 받아온 회원이 입력한 정보로 회원가입
export const postAuthSignup = (userData) => {
return marketApi.post("/auth/signup", userData);
export const postAuthSignup = async ({ email, password, username }) => {
const response = await marketApi.post("/auth/signup", {
email,
password,
username
});
useAuthStore.getState().setAccessToken(response.data.accessToken);
return response.data;
};

// 사용자가 입력한 정보로 로그인
export const postAuthLogin = (loginData) => {
return marketApi.post("/auth/login", loginData);
export const postAuthLogin = async ({ email, password }) => {
const response = await marketApi.post("/auth/login", {
email,
password
});
useAuthStore.getState().setAccessToken(response.data.accessToken);
return response.data;
};

// 토큰 리프레쉬
export const postRefreshToken = (token) => {
return marketApi.post("/auth/refresh-token", token);
// 토큰 리프레쉬 (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 = () => {
return marketApi.get("/auth/profile");
export const getAuthProfile = async () => {
return await marketApi.get("/auth/profile", { requiresAuth: true });
};

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

// 유저 정보 업데이트
export const updateUser = (name) => {
return marketApi.put("/users/update", name);
export const updateUser = async (name) => {
return await marketApi.put("/users/update", name, { requiresAuth: true });
};

// 유저 비밀번호 변경
export const changePassword = (pwData) => {
return marketApi.put("/users/change-password", pwData);
export const changePassword = async (pwData) => {
return await marketApi.put("/users/change-password", pwData, {
requiresAuth: true
});
};

/**
* 게시물 (post)에 관련된 정보를 이용한 api입니다.
*
*/
// 새 게시물 등록
export const postPosts = (postData) => {
return marketApi.post("/posts", postData);
export const postPosts = async (postData) => {
return await marketApi.post("/posts", postData, { requiresAuth: true });
};

// 검색 조건에 따라 게시물 가져오기
export const getPublishedPosts = (page, limit, query, orderBy, direction) => {
return postsApi.get("/posts/published", {

export const getPublishedPosts = ({
page,
limit,
query,
orderBy,
direction
}) => {
return marketApi.get("/posts/published", {
params: {
page,
limit,
Expand Down
15 changes: 9 additions & 6 deletions src/components/WithLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { Outlet } from "react-router-dom";
import { SuspenseController } from "./SuspenseController";
import Footer from "./footer";
import Header from "./header";
// import { Popup } from "./popup";

const DelayedComponent = lazy(() =>
SuspenseController(import("./SuspenseController"), 3000)
);

const Modal = lazy(() => import("./popup/Modal"));
/**
* WithLayout 컴포넌트는 애플리케이션의 공통 레이아웃을 제공합니다.
* 이 컴포넌트는 페이지의 헤더와 푸터를 포함하고, 중앙에는 Outlet을 통해
Expand All @@ -20,18 +21,20 @@ const DelayedComponent = lazy(() =>
*/
export default function WithLayout() {
return (
<>
<div className="flex flex-col justify-between h-screen">
<Header />
{/* <Popup /> */}
<Suspense fallback={<div>대기중</div>}>
<Modal />
{/* <DelayedComponent /> */}
{/* Outlet 컴포넌트는 현재 경로에 매칭되는 중첩된 라우트의 컴포넌트를 렌더링합니다.
이는 React Router의 중첩된 라우팅 구조를 활용하는데 중요한 역할을 합니다.
예시로 router.js routeConfig의 element를 넣습니다.
*/}
<Outlet />
<Footer />
<div className="h-full">
<Outlet />
</div>
</Suspense>
</>
<Footer />
</div>
);
}
Loading

0 comments on commit 05bb8df

Please sign in to comment.