diff --git a/README.md b/README.md
index 5d98b1e5a..dee6ca53f 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,46 @@
-This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+## **기본 요구사항**
-## Getting Started
+**공통**
-First, run the development server:
+- [x] Github에 위클리 미션 PR을 만들어 주세요.
+- [x] React.js 혹은 Next.js를 사용해 진행합니다.
-```bash
-npm run dev
-# or
-yarn dev
-# or
-pnpm dev
-# or
-bun dev
-```
+### **프론트엔드 구현 요구사항**
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+**중고마켓 페이지**
-You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
+- [ ] 디폴트 이미지로 처리한 이미지를 실제 Product Get API에서 가져온 이미지로 변경해 주세요.
+- [ ] 좋아요 순 정렬 기능을 붙여주세요.
+- [ ] 베스트 상품 기능을 추가해 주세요. 베스트 상품은 가장 많이 좋아요를 받은 순으로 PC 기준 최대 4개까지 조회 가능합니다.
-[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
+**상품 등록하기 페이지**
-The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
+- [ ] 상품 이미지 등록 기능을 구현합니다. 파일을 선택해 이미지를 업로드하고, preview를 볼 수 있도록 구현합니다. 이미지는 최대 3개까지만 등록 가능하도록 구현해 주세요.
+- [ ] 동일하게 상품 이미지 수정 기능도 추가합니다.
+- [ ] 상품 등록 성공 시 중고마켓 페이지로 이동해 주세요.
-This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+## **심화 요구사항**
-## Learn More
+**상태코드 (웹 API 관련)**
-To learn more about Next.js, take a look at the following resources:
+- [ ] 프론트엔드에서는 서버 응답의 상태코드에 따라 적절한 사용자 피드백을 제공합니다.
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+**인증**
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+- [ ] 토큰 기반 방식을 사용할 경우, 만료된 액세스 토큰을 새로 발급하는 리프레시 토큰 발급 기능을 구현합니다.(jwt sliding session 적용)
-## Deploy on Vercel
+**OAuth를 활용한 인증**
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+- [ ] 구글 OAuth를 사용하여 회원가입 및 로그인 기능을 구현합니다.
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
+**프로젝트 구조 변경**
+
+- [ ] 프로젝트의 구조와 복잡성을 관리하기 위해 MVC 패턴이나 Layered Architecture와 같은 설계 방식을 적용해 보세요.
+
+**(생략 가능) 자유게시판 게시물 등록**
+
+- [ ] 프론트엔드를 Next.js로 Migration 했을 경우에만 진행해 주세요.
+ - [ ] 게시물 등록 시 이미지 등록 기능을 구현합니다. 파일을 선택해 이미지를 업로드하고, preview를 볼 수 있도록 구현합니다. 이미지는 최대 3개까지만 등록 가능하도록 구현해 주세요.
+- [ ] 게시물 등록 시 필요한 필드(제목, 내용 등)의 유효성 검증하는 미들웨어를 구현합니다.
+- [ ] `multer` 미들웨어를 사용하여 이미지 업로드 API를 구현해 주세요.
+ - [ ] 업로드된 이미지는 서버에 저장하고, 해당 이미지의 경로를 response 객체에 포함해 반환합니다.
diff --git a/api/api.js b/api/api.js
new file mode 100644
index 000000000..254440ab4
--- /dev/null
+++ b/api/api.js
@@ -0,0 +1,97 @@
+// api.js
+import axiosInstance from "../lib/axios";
+
+// 사용자 정보 가져오기
+export const fetchCurrentUser = async () => {
+ const response = await axiosInstance.get("/users/me");
+ return response.data;
+};
+
+// 상품 목록 가져오기
+export const fetchProducts = async (params) => {
+ const response = await axiosInstance.get("/products", { params });
+ return response.data;
+};
+
+// 상품 등록
+export const createProduct = async (productData) => {
+ const response = await axiosInstance.post("/products", productData);
+ return response.data;
+};
+
+// 상품 상세 정보 가져오기
+export const fetchProductById = async (productId) => {
+ const response = await axiosInstance.get(`/products/${productId}`);
+ return response.data;
+};
+
+// 상품 수정
+export const editProduct = async (productId, productData) => {
+ const response = await axiosInstance.patch(
+ `/products/${productId}`,
+ productData
+ );
+ return response.data;
+};
+
+// 상품 삭제
+export const deleteProduct = async (productId) => {
+ const response = await axiosInstance.delete(`/products/${productId}`);
+ return response.data;
+};
+
+// 상품 댓글 목록 가져오기
+export const fetchProductComments = async (productId, params) => {
+ const response = await axiosInstance.get(`/products/${productId}/comments`, {
+ params,
+ });
+ return response.data;
+};
+
+// 상품 댓글 작성
+export const postProductComment = async ({ productId, content }) => {
+ const response = await axiosInstance.post(`/products/${productId}/comments`, {
+ content,
+ });
+ return response.data;
+};
+
+// 댓글 수정
+export const editComment = async (commentId, content) => {
+ const response = await axiosInstance.patch(`/comments/${commentId}`, {
+ content,
+ });
+ return response.data;
+};
+
+// 댓글 삭제
+export const deleteComment = async (commentId) => {
+ const response = await axiosInstance.delete(`/comments/${commentId}`);
+ return response.data;
+};
+
+// 상품 좋아요 추가
+export const postProductFavorite = async (productId) => {
+ const response = await axiosInstance.post(`/products/${productId}/favorite`);
+ return response.data;
+};
+
+// 상품 좋아요 취소
+export const deleteProductFavorite = async (productId) => {
+ const response = await axiosInstance.delete(
+ `/products/${productId}/favorite`
+ );
+ return response.data;
+};
+
+// 회원가입 요청
+export const signUp = async (userData) => {
+ const response = await axiosInstance.post("/auth/signUp", userData);
+ return response.data;
+};
+
+// 로그인 요청
+export const logIn = async (userData) => {
+ const response = await axiosInstance.post("/auth/signIn", userData);
+ return response.data;
+};
diff --git a/components/ArticleList.js b/components/ArticleList.js
index 719bdc939..5f8d18081 100644
--- a/components/ArticleList.js
+++ b/components/ArticleList.js
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
-import axios from "@/pages/api/axios";
+import axios from "@/lib/axios.js";
import Article from "./Article";
import Image from "next/image";
import Link from "next/link";
diff --git a/components/BestArticleList.js b/components/BestArticleList.js
index d81590d2f..bfb13c777 100644
--- a/components/BestArticleList.js
+++ b/components/BestArticleList.js
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
-import axios from "@/pages/api/axios";
+import axios from "@/lib/axios.js";
import BestArticleCard from "./BestArticleCard";
import styles from "./BestArticleList.module.css";
diff --git a/components/CommentList.js b/components/CommentList.js
index 9429942a7..79d5ffead 100644
--- a/components/CommentList.js
+++ b/components/CommentList.js
@@ -2,7 +2,7 @@
import styles from "@/styles/CommentList.module.css";
import { useState } from "react";
import Image from "next/image";
-import axios from "@/pages/api/axios";
+import axios from "@/lib/axios.js";
export default function Comment({ comment }) {
const [isOpen, setIsOpen] = useState(false);
diff --git a/components/CommentOptions.js b/components/CommentOptions.js
new file mode 100644
index 000000000..c2726d421
--- /dev/null
+++ b/components/CommentOptions.js
@@ -0,0 +1,101 @@
+// components/CommentOptions.js
+
+import React, { useState } from "react";
+import Image from "next/image";
+import styles from "./CommentOptions.module.css";
+
+const CommentOptions = ({ comments, onEdit, onDelete }) => {
+ const [editingCommentId, setEditingCommentId] = useState(null);
+ const [editedContent, setEditedContent] = useState("");
+
+ const handleEditClick = (comment) => {
+ setEditingCommentId(comment.id);
+ setEditedContent(comment.content);
+ };
+
+ const handleSaveClick = (commentId) => {
+ onEdit(commentId, editedContent);
+ setEditingCommentId(null);
+ };
+
+ const handleCancelClick = () => {
+ setEditingCommentId(null);
+ setEditedContent("");
+ };
+
+ const [showOptions, setShowOptions] = useState(null);
+
+ const handleCommentOptions = (commentId) => {
+ setShowOptions((prev) => (prev === commentId ? null : commentId));
+ };
+
+ return (
+
+ {comments.map((comment) => (
+
+
+
+
+
+ {editingCommentId === comment.id ? (
+ // 인라인 편집 폼
+
+ ) : (
+ // 댓글 내용 표시
+ <>
+
{comment.content}
+
+ {comment.writer.nickname} -{" "}
+ {new Date(comment.createdAt).toLocaleString()}
+
+
+ {showOptions === comment.id && (
+
+
+
+
+ )}
+
+
+ >
+ )}
+
+
+ ))}
+
+ );
+};
+
+export default CommentOptions;
diff --git a/components/CommentOptions.module.css b/components/CommentOptions.module.css
new file mode 100644
index 000000000..15fce1d45
--- /dev/null
+++ b/components/CommentOptions.module.css
@@ -0,0 +1,93 @@
+/* components/CommentOptions.module.css */
+
+.comments {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ width: 1200px; /* 전체 container 사이즈를 1200으로 설정 */
+ height: 100px; /* 전체 container 사이즈를 100으로 설정 */
+}
+
+.comment {
+ display: flex;
+ align-items: flex-start;
+ border-bottom: 1px solid #eee;
+ padding: 10px 0;
+}
+
+.profileImageContainer {
+ margin-right: 15px;
+}
+
+.profileImage {
+ border-radius: 50%;
+}
+
+.commentContentWrapper {
+ flex-grow: 1;
+}
+
+.commentContent {
+ margin-bottom: 5px;
+}
+
+.commentMeta {
+ font-size: 12px;
+ color: #666;
+ display: flex;
+ align-items: center;
+}
+
+.commentActions {
+ position: relative;
+ margin-left: auto; /* 오른쪽 끝에 위치시키기 위해 auto 사용 */
+}
+
+.commentOptionsButton {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ position: absolute; /* 절대 위치로 변경 */
+ right: 35px; /* 오른쪽 끝에서 20px 왼쪽으로 이동 */
+ bottom: 10px; /* 위쪽으로 10px 이동 */
+}
+
+.optionsPopup {
+ position: absolute;
+ top: 25px;
+ right: 0;
+ width: 139px;
+ background-color: white;
+ border: 1px solid #ddd;
+ z-index: 1000;
+}
+
+.optionsPopup button {
+ width: 100%;
+ padding: 10px 0;
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.optionsPopup button:hover {
+ background-color: #f0f0f0;
+}
+
+/* 인라인 편집 폼 스타일 */
+.editForm {
+ display: flex;
+ flex-direction: column;
+}
+
+.editTextarea {
+ width: 100%;
+ height: 80px;
+ resize: vertical;
+ margin-bottom: 10px;
+}
+
+.editActions button {
+ margin-right: 5px;
+}
diff --git a/components/DeleteConfirmModal.js b/components/DeleteConfirmModal.js
new file mode 100644
index 000000000..21bf018be
--- /dev/null
+++ b/components/DeleteConfirmModal.js
@@ -0,0 +1,25 @@
+import React from "react";
+import Image from "next/image";
+import styles from "./DeleteConfirmModal.module.css";
+import checkIcon from "../public/ic_check.png";
+
+const DeleteConfirmModal = ({ onConfirm, onCancel }) => {
+ return (
+
+
+
+
정말로 상품을 삭제하시겠어요?
+
+
+
+
+
+
+ );
+};
+
+export default DeleteConfirmModal;
diff --git a/components/DeleteConfirmModal.module.css b/components/DeleteConfirmModal.module.css
new file mode 100644
index 000000000..ba60c595a
--- /dev/null
+++ b/components/DeleteConfirmModal.module.css
@@ -0,0 +1,57 @@
+.modalOverlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+}
+
+.modalContent {
+ background-color: white;
+ width: 298px;
+ height: 202px;
+ border-radius: 8px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ padding: 20px;
+}
+
+.modalText {
+ font-size: 16px;
+ text-align: center;
+ margin: 10px 0;
+}
+
+.buttonContainer {
+ display: flex;
+ justify-content: center; /* 중앙 정렬로 변경 */
+ width: 100%;
+ gap: 8px; /* 버튼 사이의 간격을 8px로 설정 */
+}
+
+.cancelButton,
+.confirmButton {
+ width: 88px;
+ height: 48px;
+ border: none;
+ border-radius: 4px;
+ font-size: 16px;
+ cursor: pointer;
+}
+
+.cancelButton {
+ background-color: #f0f0f0;
+ color: #333;
+}
+
+.confirmButton {
+ background-color: #ff5a5f;
+ color: white;
+}
diff --git a/components/Footer.js b/components/Footer.js
index 480d9ae57..ac9bbeb11 100644
--- a/components/Footer.js
+++ b/components/Footer.js
@@ -1,56 +1,68 @@
// components/Footer.js
import React from "react";
import Image from "next/image"; // Next.js의 Image 컴포넌트 사용
+import Link from "next/link"; // Next.js의 Link 컴포넌트 사용
import styles from "./Footer.module.css"; // CSS Module을 사용하여 스타일링
const Footer = () => {
return (