diff --git a/public/images/_mock/project-poster.png b/public/images/_mock/project-poster.png
new file mode 100644
index 00000000..fefb2c2e
Binary files /dev/null and b/public/images/_mock/project-poster.png differ
diff --git a/public/images/_mock/project-thumbnail.png b/public/images/_mock/project-thumbnail.png
new file mode 100644
index 00000000..89e4f6dc
Binary files /dev/null and b/public/images/_mock/project-thumbnail.png differ
diff --git a/src/app/(user)/projects/[id]/page.module.css b/src/app/(user)/projects/[id]/page.module.css
new file mode 100644
index 00000000..86e46e32
--- /dev/null
+++ b/src/app/(user)/projects/[id]/page.module.css
@@ -0,0 +1,9 @@
+.container {
+ width: 98%;
+ border: 1px solid #efeff0; /* TODO: dark mode 세팅 추가*/
+ background-color: var(--color-background);
+ margin: 10px auto;
+ border-radius: 12px;
+
+ padding: 10px 20px;
+}
diff --git a/src/app/(user)/projects/[id]/page.tsx b/src/app/(user)/projects/[id]/page.tsx
index ba81ea1b..e7b5a301 100644
--- a/src/app/(user)/projects/[id]/page.tsx
+++ b/src/app/(user)/projects/[id]/page.tsx
@@ -1,3 +1,19 @@
-export default function ProjectDetailPage() {
- return Hello, world!;
+import { ProjectDetailInfo, ProjectDetailComment } from "@/components/pages/ProjectDetail";
+import classes from "./page.module.css";
+import { ProjectDetailInquiry } from "@/components/pages/ProjectDetail/ProjectDetailInquiry";
+
+interface Props {
+ params: {
+ id: string;
+ };
+}
+
+export default function ProjectDetailPage({ params: { id } }: Props) {
+ return (
+
+ );
}
diff --git a/src/components/common/CommentBox/CommentBox.module.css b/src/components/common/CommentBox/CommentBox.module.css
index 42156e36..90e84bf8 100644
--- a/src/components/common/CommentBox/CommentBox.module.css
+++ b/src/components/common/CommentBox/CommentBox.module.css
@@ -1,4 +1,4 @@
-@import url("src/theme/fonts/pretendard.css");
+@import url("@/theme/fonts/pretendard.css");
.commentBoxContainer {
display: flex;
diff --git a/src/components/common/CommentBox/CommentBox.tsx b/src/components/common/CommentBox/CommentBox.tsx
index c277f64b..f015c7ff 100644
--- a/src/components/common/CommentBox/CommentBox.tsx
+++ b/src/components/common/CommentBox/CommentBox.tsx
@@ -3,16 +3,17 @@ import classes from "./CommentBox.module.css";
interface CommentBoxProps {
onSubmit?: (comment: string) => void;
+ commentList?: Comment[];
}
-interface Comment {
+export interface Comment {
author: string;
content: string;
}
-export const CommentBox: React.FC = ({ onSubmit }) => {
+export const CommentBox: React.FC = ({ onSubmit, commentList = [] }) => {
const [comment, setComment] = useState("");
- const [comments, setComments] = useState([]);
+ const [comments, setComments] = useState(commentList);
const handleChange = (e: React.ChangeEvent) => {
setComment(e.target.value);
diff --git a/src/components/pages/.gitkeep b/src/components/pages/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/components/pages/ProjectDetail/ProjectDetailComment.tsx b/src/components/pages/ProjectDetail/ProjectDetailComment.tsx
new file mode 100644
index 00000000..b6790a35
--- /dev/null
+++ b/src/components/pages/ProjectDetail/ProjectDetailComment.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { CommentBox, Comment } from "@/components/common/CommentBox/CommentBox";
+import { comments as commentList } from "./_mock/mock-project";
+
+interface Props {
+ projectId: string;
+}
+
+export function ProjectDetailComment({ projectId }: Props) {
+ const [comments, setComments] = useState(commentList);
+
+ useEffect(() => {
+ /**
+ * TODO: 댓글 목록 불러오기
+ */
+ console.log("projectId: ", projectId);
+ }, [projectId]);
+
+ const handleCommentSubmit = (comment: string) => {
+ /**
+ * TODO: 댓글 등록하기
+ */
+ const newComment = { author: "사람", content: comment };
+ setComments([...comments, newComment]);
+ };
+
+ return ;
+}
diff --git a/src/components/pages/ProjectDetail/ProjectDetailInfo.module.css b/src/components/pages/ProjectDetail/ProjectDetailInfo.module.css
new file mode 100644
index 00000000..e96f38d5
--- /dev/null
+++ b/src/components/pages/ProjectDetail/ProjectDetailInfo.module.css
@@ -0,0 +1,115 @@
+.title {
+ font-size: 32px;
+ line-height: 40px;
+ font-weight: 700;
+ padding: 10px 0px;
+}
+
+.description {
+ font-size: 20px;
+ line-height: 25px;
+}
+
+.sectionMiddle {
+ margin-top: 40px;
+ margin-bottom: 15px;
+ gap: 80px;
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ gap: 40px;
+ }
+}
+
+.imageWrapper {
+ position: relative;
+ width: 100%;
+ height: auto;
+ max-width: 635px;
+ aspect-ratio: 635 / 350; /* 이미지 비율 설정 */
+}
+
+.infoContainer {
+ width: 100%;
+ padding-right: 40px;
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ padding-right: 0px;
+ }
+}
+
+.infoContainer > .infoRowGroup {
+ width: 100%;
+}
+
+.infoContainer > .infoRowGroup > .infoRow {
+ display: flex;
+ gap: 10px;
+ width: 100%;
+ line-height: 22px;
+ font-size: 16px;
+}
+
+.infoRow > .firstCol {
+ font-weight: 700;
+ width: 72px;
+ flex-shrink: 0; /* 다른 item이 영역 침범하지 않도록 함 */
+}
+
+.divider {
+ width: 100%;
+}
+
+.btnLabel {
+ padding-left: 5px;
+ font-size: 16px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.addBtn {
+ width: 45%;
+ height: 56px;
+}
+
+.externalBtn {
+ width: 40%;
+ height: 60px;
+ background-color: var(--color-primaryFixedDim);
+ color: #000000;
+
+ font-size: 20px;
+
+ &:hover {
+ background-color: #d1e4ff;
+ color: #000000;
+ }
+
+ @media (max-width: 768px) {
+ width: 80%;
+ }
+}
+
+.sectionBottom {
+ margin-top: 40px;
+}
+
+.posterWrapper {
+ width: 100%;
+ max-width: 794px;
+}
+
+/* ProjectDetailInquiry 컴포넌트에서 사용 */
+
+.SectionInquiry {
+ margin-top: 50px;
+ margin-bottom: 30px;
+}
+
+.SectionInquiry > button {
+ font-size: 20px;
+ height: 56px;
+ margin-top: 20px;
+}
diff --git a/src/components/pages/ProjectDetail/ProjectDetailInfo.tsx b/src/components/pages/ProjectDetail/ProjectDetailInfo.tsx
new file mode 100644
index 00000000..4a764a60
--- /dev/null
+++ b/src/components/pages/ProjectDetail/ProjectDetailInfo.tsx
@@ -0,0 +1,149 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import Image from "next/image";
+import { Flex, Text, AspectRatio, Button, Group, Stack, Divider } from "@mantine/core";
+import classes from "./ProjectDetailInfo.module.css";
+import { project as mockup, ProjectDetailDto } from "./_mock/mock-project";
+import { CardBadge } from "@/components/common/CardBadge";
+import { PrimaryButton } from "@/components/common/Buttons";
+import {
+ IconThumbUp,
+ IconThumbUpFilled,
+ IconBookmark,
+ IconBookmarkFilled,
+} from "@tabler/icons-react";
+import { useRouter } from "next/navigation";
+
+interface Props {
+ projectId: string;
+}
+
+export function ProjectDetailInfo({ projectId }: Props) {
+ const router = useRouter();
+ const [project, setProject] = useState(mockup);
+ const [isThumbup, setIsThumbup] = useState(false);
+ const [isInterest, setIsInterest] = useState(false);
+
+ useEffect(() => {
+ /**
+ * TODO: project 상세정보 불러오기
+ */
+ console.log("projectId: ", projectId);
+ setProject(mockup);
+ setIsThumbup(mockup.isThumbup);
+ setIsInterest(mockup.isInterest);
+ }, [projectId]);
+
+ const handleThumbupClick = () => {
+ /**
+ * TODO: 좋아요 상태 변경하기
+ */
+ setIsThumbup((isThumbup) => !isThumbup);
+ };
+
+ const handleInterestClick = () => {
+ /**
+ * TODO: 관심 여부 상태 변경하기
+ */
+ setIsInterest((isInterest) => !isInterest);
+ };
+
+ const handleProposalClick = () => {
+ router.push("/infodesk/proposals");
+ };
+
+ const handleInquiryClick = () => {
+ router.push("/infodesk/inquries");
+ };
+
+ return (
+ <>
+
+ {project.projectName}
+ {project.description}
+
+
+
+
+
+
+
+
+
제목
+
{project.projectName}
+
+
+
+
참가팀명
+
{project.teamName}
+
+
+
참여자
+
{project.participants.join(", ")}
+
+
+
지도교수
+
{project.professorName.join(", ")}
+
+
+
+
+ {project.tags.map((label: string, index: number) => (
+
+ ))}
+
+
+
+ {isThumbup ? : }
+ 좋아요
+
+
+ {isInterest ? : }
+ 관심프로젝트 등록
+
+
+
+
+
+
+
+
+
+
작품 영상
+
+
+
+
포스터
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/pages/ProjectDetail/ProjectDetailInquiry.tsx b/src/components/pages/ProjectDetail/ProjectDetailInquiry.tsx
new file mode 100644
index 00000000..20ab021e
--- /dev/null
+++ b/src/components/pages/ProjectDetail/ProjectDetailInquiry.tsx
@@ -0,0 +1,21 @@
+"use client";
+
+import { Text } from "@mantine/core";
+import classes from "./ProjectDetailInfo.module.css";
+import { useRouter } from "next/navigation";
+import { PrimaryButton } from "@/components/common/Buttons";
+
+export function ProjectDetailInquiry() {
+ const router = useRouter();
+
+ const handleButtonClick = () => {
+ router.push("/infodesk/inquries");
+ };
+
+ return (
+
+
프로젝트 문의
+
문의 게시판으로 이동
+
+ );
+}
diff --git a/src/components/pages/ProjectDetail/_mock/mock-project.ts b/src/components/pages/ProjectDetail/_mock/mock-project.ts
new file mode 100644
index 00000000..d9037cc0
--- /dev/null
+++ b/src/components/pages/ProjectDetail/_mock/mock-project.ts
@@ -0,0 +1,38 @@
+import { Comment } from "@/components/common/CommentBox/CommentBox";
+
+export interface ProjectDetailDto {
+ // TODO: API 문서 참조하여 변경
+ thumbnailId: number; // 프로젝트 대표 이미지
+ posterId: number; // 프로젝트 포스터
+ projectName: string; // 프로젝트 이름
+ teamName: string; // 참가팀명
+ participants: string[]; // 참여자
+ professorName: string[]; // 지도교수
+ description: string; // 설명
+ tags: string[]; // 프로젝트 기술스택
+ likes?: number; // 좋아요 개수??
+ isThumbup: boolean; // 좋아요 여부
+ isInterest: boolean; // 관심 프로젝트 등록 여부
+ videoUrl: string; // 작품 영상
+ year: number; // 연도
+}
+
+export const project: ProjectDetailDto = {
+ thumbnailId: 0,
+ posterId: 0,
+ projectName: "보안 장비 및 서버 장애 탐지",
+ description: "표준 프로토콜(ICMP, SNMP)과 로그를 이용하여 데이터 전처리 후 서버 상태 시각화",
+ teamName: "스꾸딩",
+ participants: ["김태균"],
+ professorName: ["송상효"],
+ tags: ["보안", "SW공학"],
+ isThumbup: false,
+ isInterest: true,
+ videoUrl: "https://www.youtube.com/embed/NElCk3islhw",
+ year: 2023,
+};
+
+export const comments: Comment[] = [
+ { author: "사람", content: "아이디어가 좋은 것 같아요!" },
+ { author: "사람", content: "응원합니다!" },
+];
diff --git a/src/components/pages/ProjectDetail/index.ts b/src/components/pages/ProjectDetail/index.ts
new file mode 100644
index 00000000..d17612ef
--- /dev/null
+++ b/src/components/pages/ProjectDetail/index.ts
@@ -0,0 +1,2 @@
+export { ProjectDetailInfo } from "./ProjectDetailInfo";
+export { ProjectDetailComment } from "./ProjectDetailComment";