From c8522149db34552dd4bd2399815d371af9de0c7f Mon Sep 17 00:00:00 2001 From: Jungu Lee <100949102+jobkaeHenry@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:51:02 +0900 Subject: [PATCH] =?UTF-8?q?Post-Detail-Page-=ED=8D=BC=EB=B8=94=EB=A6=AC?= =?UTF-8?q?=EC=8B=B1=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New : tailwind css 적용 * Refactor : 모달 패딩 제거 * Fix : 모달 컴포넌트 이벤트 버블링 방지 * new: 디테일페이지 퍼블리싱 * new : 클라이언트 패스 추가 * Refactor: 해시태그 컴포넌트 분리 * new : 모달페이지 이동 hooks 추가 * New : 검색페이지 라우트 추가 * Fix : 해시태그 클릭시 모달이 닫히지 않는 버그 수정 * Fix : useRouter Mock 추가 --- client/src/__test__/post/PostCard.test.tsx | 8 + .../@Modal/(.)post/[userId]/[postId]/page.tsx | 2 +- client/src/app/@Modal/layout.tsx | 8 + client/src/app/layout.tsx | 1 + client/src/app/search/page.tsx | 9 + client/src/components/ModalWrapper.tsx | 1 + client/src/components/post/PostCard.tsx | 164 +++++++++--------- client/src/components/post/PostDetail.tsx | 55 +++++- .../src/components/post/PostHashtagList.tsx | 40 +++++ client/src/const/clientPath.ts | 17 +- client/src/hooks/useOpenPostDetailPage.tsx | 15 ++ 11 files changed, 230 insertions(+), 90 deletions(-) create mode 100644 client/src/app/@Modal/layout.tsx create mode 100644 client/src/app/search/page.tsx create mode 100644 client/src/components/post/PostHashtagList.tsx create mode 100644 client/src/hooks/useOpenPostDetailPage.tsx diff --git a/client/src/__test__/post/PostCard.test.tsx b/client/src/__test__/post/PostCard.test.tsx index df38304..bd0e246 100644 --- a/client/src/__test__/post/PostCard.test.tsx +++ b/client/src/__test__/post/PostCard.test.tsx @@ -14,6 +14,14 @@ const mockData = { tags: [], }; +jest.mock("next/navigation", () => ({ + useRouter() { + return { + prefetch: () => null, + }; + }, +})); + describe("버튼 컴포넌트 스펙", () => { beforeEach(() => render()); it("@유저아이디 형태의 헤더가 존재하는지 체크", () => { diff --git a/client/src/app/@Modal/(.)post/[userId]/[postId]/page.tsx b/client/src/app/@Modal/(.)post/[userId]/[postId]/page.tsx index 9bb2c25..58560f7 100644 --- a/client/src/app/@Modal/(.)post/[userId]/[postId]/page.tsx +++ b/client/src/app/@Modal/(.)post/[userId]/[postId]/page.tsx @@ -15,7 +15,7 @@ const mockData = { const page = () => { return ( - + ); diff --git a/client/src/app/@Modal/layout.tsx b/client/src/app/@Modal/layout.tsx new file mode 100644 index 0000000..36d4239 --- /dev/null +++ b/client/src/app/@Modal/layout.tsx @@ -0,0 +1,8 @@ +"use client"; + +import { usePathname } from "next/navigation"; + +export default function Layout({ children }: any) { + const pathname = usePathname(); + return pathname.startsWith("/post/") ? children : null; +} diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index b445a08..5ea2688 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -5,6 +5,7 @@ import OverrideCSS from "@/const/overrideCSS"; import { Box, GlobalStyles } from "@mui/material"; import Pretendard from "~/assets/font/Pretendard"; import NavigationBar from "~/components/NavigationBar"; +import "./globals.css"; export const metadata: Metadata = { title: `${nameOfApp} | ${oneLineMessage}`, diff --git a/client/src/app/search/page.tsx b/client/src/app/search/page.tsx new file mode 100644 index 0000000..56ba6aa --- /dev/null +++ b/client/src/app/search/page.tsx @@ -0,0 +1,9 @@ +const SearchPage = ({ + searchParams, +}: { + searchParams?: { [key: string]: string | string[] | undefined }; +}) => { + return
{searchParams?.keyword}
; +}; + +export default SearchPage; diff --git a/client/src/components/ModalWrapper.tsx b/client/src/components/ModalWrapper.tsx index 3e82ab1..08a5442 100644 --- a/client/src/components/ModalWrapper.tsx +++ b/client/src/components/ModalWrapper.tsx @@ -32,6 +32,7 @@ const ModalWrapper = ({ children, disableBox }: ModalInterface) => { > { e.stopPropagation()} sx={{ bgcolor: disableBox ? undefined : "background.paper", p: disableBox ? 0 : 4, diff --git a/client/src/components/post/PostCard.tsx b/client/src/components/post/PostCard.tsx index c22e48b..a5e0a63 100644 --- a/client/src/components/post/PostCard.tsx +++ b/client/src/components/post/PostCard.tsx @@ -1,5 +1,5 @@ "use client"; -import { POST_DETAIL } from "@/const/clientPath"; + import { PostInterface } from "@/types/post/PostInterface"; import { MoreVertOutlined } from "@mui/icons-material"; @@ -13,7 +13,9 @@ import { Typography, ButtonBase, } from "@mui/material"; -import Link from "next/link"; +import PostHashTagList from "./PostHashtagList"; +import { useOpenPostDetailPage } from "@/hooks/useOpenPostDetailPage"; +import { useMemo } from "react"; const PostCard = ({ image, @@ -25,100 +27,92 @@ const PostCard = ({ tags, id, }: PostInterface) => { + const openPostDetailPage = useOpenPostDetailPage(); + const hasImage = useMemo(() => image.length !== 0, [image]); + return ( - - - + + {userImage || userId[0].toUpperCase()} + + + {/* Header */} + - {userImage || userId[0].toUpperCase()} - - - {/* Header */} - - {nickname} - {`@${userId}`} - {createdAt} - - - - - + {/* 타이틀 */} + {nickname} + {`@${userId}`} + {createdAt} - {/* Contents */} - - {content} - {/* Hash tags */} - {tags?.length > 0 && ( - - {tags.map((tag, i) => ( - - {`#${tag}`} - - ))} - - )} - - {/* image */} - {image.length !== 0 && ( - - )} - {/* CTA */} - - - 댓글 - - - 좋아요 - - - 공유하기 - - + + + - - + + + {/* Contents */} + openPostDetailPage(userId, id)} + > + {content} + + + {/* Hash tags */} + + + + {/* image */} + {hasImage && ( + openPostDetailPage(userId, id)} + image={image[0]} + alt={`${userId}의 포스트`} + sx={{ borderRadius: 2, bgcolor: "background.default" }} + /> + )} + {/* CTA */} + + + openPostDetailPage(userId, id)}> + 댓글 + + + + 좋아요 + + + 공유하기 + + + + ); }; diff --git a/client/src/components/post/PostDetail.tsx b/client/src/components/post/PostDetail.tsx index 87a9ae5..b722855 100644 --- a/client/src/components/post/PostDetail.tsx +++ b/client/src/components/post/PostDetail.tsx @@ -1,4 +1,16 @@ import { PostInterface } from "@/types/post/PostInterface"; +import { + Avatar, + ButtonBase, + Card, + CardActions, + CardContent, + CardHeader, + CardMedia, + TextField, + Typography, +} from "@mui/material"; +import PostHashTagList from "./PostHashtagList"; const PostDetail = ({ image, @@ -10,6 +22,47 @@ const PostDetail = ({ tags, id, }: PostInterface) => { - return <>{userId}; + return ( + + + {userImage || userId[0].toUpperCase()} + + } + title={`${userId} ${nickname}`} + subheader={createdAt} + sx={{ p: 0 }} + /> + + {content} + + + {/* 이미지 */} + {image.length !== 0 && ( + + )} + + + 좋아요 + + + 공유하기 + + + + + ); }; export default PostDetail; diff --git a/client/src/components/post/PostHashtagList.tsx b/client/src/components/post/PostHashtagList.tsx new file mode 100644 index 0000000..07a6fba --- /dev/null +++ b/client/src/components/post/PostHashtagList.tsx @@ -0,0 +1,40 @@ +import { SEARCH_BY_KEYWORD } from "@/const/clientPath"; +import { PostInterface } from "@/types/post/PostInterface"; +import { Box, BoxProps, Typography } from "@mui/material"; +import Link from "next/link"; + +interface TagListInterface extends BoxProps { + tags: PostInterface["tags"]; +} +const PostHashTagList = ({ tags, ...others }: TagListInterface) => { + return ( + <> + {tags?.length > 0 && ( + + {tags.map((tag) => ( + + + {`#${tag}`} + + + ))} + + )} + + ); +}; + +export default PostHashTagList; diff --git a/client/src/const/clientPath.ts b/client/src/const/clientPath.ts index eb532dd..2de5514 100644 --- a/client/src/const/clientPath.ts +++ b/client/src/const/clientPath.ts @@ -14,11 +14,22 @@ export const FORGOTPASSWORD = "/auth/forgot-password" as const; /** * 로그인 했을 경우만 접근 가능한 마이페이지 */ -export const MY_PROFILE = "/user" as const +export const MY_PROFILE = "/user" as const; /** * 술과사전 페이지 라우트 */ -export const WIKI = "/wiki" as const +export const WIKI = "/wiki" as const; +/** + * 검색 페이지 라우트 + */ +export const SEARCH = "/search" as const; +/** + * 키워드를 인자로 받아 쿼리스트링이 추가된 검색페이지 라우트 + * @param keyword + * @returns + */ +export const SEARCH_BY_KEYWORD = (keyword: string) => + `${SEARCH}?keyword=${keyword}`; /** * 유저아이디와 게시글 아이디를 입력받아 /post/@userId/postId 형태의 path를 리턴 @@ -29,7 +40,7 @@ export const WIKI = "/wiki" as const export const POST_DETAIL = (userId: string, postId: string) => { const trimmedUserId = userId.trim(); const trimmedPostId = postId.trim(); - + return `/post/@${trimmedUserId}/${trimmedPostId}`; }; diff --git a/client/src/hooks/useOpenPostDetailPage.tsx b/client/src/hooks/useOpenPostDetailPage.tsx new file mode 100644 index 0000000..171e6f3 --- /dev/null +++ b/client/src/hooks/useOpenPostDetailPage.tsx @@ -0,0 +1,15 @@ +import { POST_DETAIL } from "@/const/clientPath"; +import { useRouter } from "next/navigation"; +import { useCallback } from "react"; + +/** + * userId: string, id: string 의 입력값을 같는 함수를 리턴하는 hooks + * @returns + */ +export const useOpenPostDetailPage = () => { + const router = useRouter(); + const openPostDetailPage = useCallback((userId: string, id: string) => { + router.push(POST_DETAIL(userId, id)); + }, []); + return openPostDetailPage; +};