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 thumbnail +
+ + +
+
제목
+
{project.projectName}
+
+
+
연도
+
{project.year}
+
+
+
참가팀명
+
{project.teamName}
+
+
+
참여자
+
{project.participants.join(", ")}
+
+
+
지도교수
+
{project.professorName.join(", ")}
+
+
+ + + {project.tags.map((label: string, index: number) => ( + + ))} + + + + {isThumbup ? : } + 좋아요 + + + {isInterest ? : } + 관심프로젝트 등록 + + +
+
+ + + + +
+ 작품 영상 + +