Skip to content

Commit

Permalink
포스트-디테일-페이지-인터셉트라우트구현 (#12)
Browse files Browse the repository at this point in the history
* New : 포스트 디테일 페이지 인터셉트 라우트 구현

* New : 포스트 디테일 페이지 인터셉트 라우트 구현

* New : 포스트 디테일 URL 함수로 관리

* Minor : 해시태그 모킹 추가

* Minor : Fixme 추가
  • Loading branch information
jobkaeHenry authored Nov 6, 2023
1 parent 486694b commit fadf6de
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 77 deletions.
1 change: 1 addition & 0 deletions client/src/__test__/post/PostCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const mockData = {
content:
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eos ullam aut minus aliquam quis officia, non dolore omnis, magnam totam tenetur ad harum? Mollitia omnis odit atque blanditiis exercitationem! Voluptatum.",
image: ["https://source.unsplash.com/random?wallpapers"],
tags: [],
};

describe("버튼 컴포넌트 스펙", () => {
Expand Down
24 changes: 24 additions & 0 deletions client/src/app/@Modal/(.)post/[userId]/[postId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import ModalWrapper from "@/components/ModalWrapper";
import PostDetail from "@/components/post/PostDetail";

const mockData = {
id: "123458",
createdAt: "Mon Nov 06 2023 00:13:07",
nickname: "testNick",
userId: "userID",
userImage: "https://source.unsplash.com/random?wallpapers",
content:
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eos ullam aut minus aliquam quis officia, non dolore omnis, magnam totam tenetur ad harum? Mollitia omnis odit atque blanditiis exercitationem! Voluptatum.",
image: ["https://source.unsplash.com/random?wallpapers"],
tags: ["해시태그1", "해시태그2"],
};

const page = () => {
return (
<ModalWrapper>
<PostDetail {...mockData} />
</ModalWrapper>
);
};

export default page;
5 changes: 5 additions & 0 deletions client/src/app/@Modal/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Default = () => {
return null;
};

export default Default;
3 changes: 3 additions & 0 deletions client/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ export const viewport: Viewport = {

export default function RootLayout({
children,
Modal,
}: {
children: React.ReactNode;
Modal: React.ReactNode;
}) {
return (
<html lang="kr" className={Pretendard.className}>
<body>
<ThemeRegistry options={{ key: "mui" }}>
{Modal}
<GlobalStyles styles={OverrideCSS} />
<Box
sx={{
Expand Down
21 changes: 19 additions & 2 deletions client/src/app/post/[userId]/[postId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import PostDetail from "@/components/post/PostDetail";

const mockData = {
id: "123458",
createdAt: "Mon Nov 06 2023 00:13:07",
nickname: "testNick",
userId: "userID",
userImage: "https://source.unsplash.com/random?wallpapers",
content:
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eos ullam aut minus aliquam quis officia, non dolore omnis, magnam totam tenetur ad harum? Mollitia omnis odit atque blanditiis exercitationem! Voluptatum.",
image: ["https://source.unsplash.com/random?wallpapers"],
tags: ["해시태그1", "해시태그2"],
};

const PostDetailPage = ({
params,
}: {
params: { userId: string; postId: string };
}) => {
// FIXME @로 시작되는 경우만 슬라이스 하도록 추후에 고치고, 함수화 해야함
const parsedUserId = params.userId.slice(1, params.userId.length);
const parsedPostId = params.postId.slice(1, params.postId.length);
const parsedPostId = params.postId;
return (
<div>
userId:{parsedUserId}
<br /> postId:{parsedPostId}
postId:{parsedPostId}
<br />
<PostDetail {...{ ...mockData, id: params.userId }} />
</div>
);
};
Expand Down
50 changes: 50 additions & 0 deletions client/src/components/ModalWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";
import { Box, Modal, ModalProps } from "@mui/material";
import { useRouter } from "next/navigation";

import { ReactNode } from "react";

interface ModalInterface
extends Omit<ModalProps, "open" | "onClick" | "children"> {
children?: ReactNode;
/**
* 기본으로 설정된 <Box/> 의 패딩, 배경색 속성을 제거
*/
disableBox?: boolean;
}

/**
* MUI <Modla/>과 <Box/>를 랩핑해놓은 글로벌 모달
*/
const ModalWrapper = ({ children, disableBox }: ModalInterface) => {
const { back } = useRouter();

return (
<Modal
open={true}
onClick={() => back()}
disablePortal
sx={{
alignItems: "center",
justifyContent: "center",
display: "flex",
}}
>
{
<Box
sx={{
bgcolor: disableBox ? undefined : "background.paper",
p: disableBox ? 0 : 4,
maxWidth: "90%",
maxHeight: "90%",
overflowY: "scroll",
}}
>
{children}
</Box>
}
</Modal>
);
};

export default ModalWrapper;
156 changes: 81 additions & 75 deletions client/src/components/post/PostCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";
import { PostInterface } from "@/types/post/PostInterface";
import createPostDetailPath from "@/utils/createPostDetailPath";
import { MoreVertOutlined } from "@mui/icons-material";
import {
Avatar,
Expand All @@ -11,6 +12,7 @@ import {
Typography,
ButtonBase,
} from "@mui/material";
import Link from "next/link";

const PostCard = ({
image,
Expand All @@ -20,98 +22,102 @@ const PostCard = ({
content,
userImage,
tags,
id,
}: PostInterface) => {
return (
<Card sx={{ display: "flex", gap: 2, p: 2 }}>
<Avatar
sx={{ bgcolor: "secondary.main" }}
sizes="40"
src={userImage}
data-testid="avatar"
>
{userImage || userId[0].toUpperCase()}
</Avatar>
<Box>
{/* Header */}
<Box
data-testid="mui-header"
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
px: 0,
width: "100%",
}}
<Link href={createPostDetailPath(userId, id)}>
<Card sx={{ display: "flex", gap: 2, p: 2 }}>
<Avatar
sx={{ bgcolor: "secondary.main" }}
sizes="40"
src={userImage}
data-testid="avatar"
>
{userImage || userId[0].toUpperCase()}
</Avatar>
<Box>
{/* Header */}
<Box
data-testid="mui-header"
sx={{
display: "flex",
flexDirection: "row",
gap: 1,
alighItems: "center",
justifyContent: "space-between",
px: 0,
width: "100%",
}}
>
<Typography sx={{ fontWeight: "bold" }}>{nickname}</Typography>
<Typography>{`@${userId}`}</Typography>
<Typography>{createdAt}</Typography>
</Box>

<ButtonBase aria-label="settings" sx={{ p: 0 }}>
<MoreVertOutlined />
</ButtonBase>
</Box>

{/* Contents */}
<CardContent sx={{ px: 0 }}>
<Typography variant="body1">{content}</Typography>
{/* Hash tags */}
{tags?.length > 0 && (
<Box
data-testid="tags"
sx={{
pt: 2,
display: "flex",
flexDirection: "row",
gap: "8px",
gap: 1,
alighItems: "center",
}}
>
{tags.map((tag, i) => (
<Typography
component={"span"}
variant={"label"}
color="text.secondary"
>
{`#${tag}`}
</Typography>
))}
<Typography sx={{ fontWeight: "bold" }}>{nickname}</Typography>
<Typography>{`@${userId}`}</Typography>
<Typography>{createdAt}</Typography>
</Box>

<ButtonBase aria-label="settings" sx={{ p: 0 }}>
<MoreVertOutlined />
</ButtonBase>
</Box>

{/* Contents */}
<CardContent sx={{ px: 0 }}>
<Typography variant="body1">{content}</Typography>
{/* Hash tags */}
{tags?.length > 0 && (
<Box
data-testid="tags"
sx={{
pt: 2,
display: "flex",
flexDirection: "row",
gap: "8px",
}}
>
{tags.map((tag, i) => (
<Typography
component={"span"}
variant={"label"}
color="text.secondary"
key={i}
>
{`#${tag}`}
</Typography>
))}
</Box>
)}
</CardContent>
{/* image */}
{image.length !== 0 && (
<CardMedia
data-testid="postImg"
component="img"
height="142"
image={image[0]}
alt={`${userId}의 포스트`}
sx={{ borderRadius: 2, bgcolor: "background.default" }}
/>
)}
</CardContent>
{/* image */}
{image.length !== 0 && (
<CardMedia
data-testid="postImg"
component="img"
height="142"
image={image[0]}
alt={`${userId}의 포스트`}
sx={{ borderRadius: 2 }}
/>
)}
{/* CTA */}
<CardActions sx={{ px: 0, justifyContent: "end", gap: 2 }}>
<ButtonBase data-testid="commentBtn" aria-label="share">
<Typography>댓글</Typography>
</ButtonBase>
<ButtonBase data-testid="likeBtn" aria-label="add to favorites">
<Typography>좋아요</Typography>
</ButtonBase>
<ButtonBase data-testid="shareBtn" aria-label="share">
<Typography>공유하기</Typography>
</ButtonBase>
</CardActions>
</Box>
</Card>
{/* CTA */}
<CardActions sx={{ px: 0, justifyContent: "end", gap: 2 }}>
<ButtonBase data-testid="commentBtn" aria-label="share">
<Typography>댓글</Typography>
</ButtonBase>
<ButtonBase data-testid="likeBtn" aria-label="add to favorites">
<Typography>좋아요</Typography>
</ButtonBase>
<ButtonBase data-testid="shareBtn" aria-label="share">
<Typography>공유하기</Typography>
</ButtonBase>
</CardActions>
</Box>
</Card>
</Link>
);
};

Expand Down
15 changes: 15 additions & 0 deletions client/src/components/post/PostDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { PostInterface } from "@/types/post/PostInterface";

const PostDetail = ({
image,
createdAt,
userId,
nickname,
content,
userImage,
tags,
id,
}: PostInterface) => {
return <>{userId}</>;
};
export default PostDetail;
10 changes: 10 additions & 0 deletions client/src/utils/createPostDetailPath.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import createPostDetailPath from "@/utils/createPostDetailPath";

describe("createPostDetailPath 함수 테스트", () => {
it("postId=1 userId=thisUser 가 입력되었을 때 ", () => {
expect(createPostDetailPath("thisUser", "1")).toEqual("/@thisUser/1");
});
it("공백을 자동으로 Trim 하는지 여부", () => {
expect(createPostDetailPath(" thisUser", "1 ")).toEqual("/@thisUser/1");
});
});
14 changes: 14 additions & 0 deletions client/src/utils/createPostDetailPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* 유저아이디와 게시글 아이디를 입력받아 /@userId/postId 형태의 String을 리턴
* @param userId 유저ID
* @param postId 게시글ID
* @returns
*/
const createPostUrl = (userId: string, postId: string) => {
const trimmedUserId = userId.trim();
const trimmedPostId = postId.trim();

return `/@${trimmedUserId}/${trimmedPostId}`;
};

export default createPostUrl;

0 comments on commit fadf6de

Please sign in to comment.