Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post-Detail-Page-퍼블리싱 #14

Merged
merged 10 commits into from
Nov 7, 2023
2 changes: 1 addition & 1 deletion client/src/app/@Modal/(.)post/[userId]/[postId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const mockData = {

const page = () => {
return (
<ModalWrapper>
<ModalWrapper disableBox>
<PostDetail {...mockData} />
</ModalWrapper>
);
jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
8 changes: 8 additions & 0 deletions client/src/app/@Modal/layout.tsx
Original file line number Diff line number Diff line change
@@ -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;
}
jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions client/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
9 changes: 9 additions & 0 deletions client/src/app/search/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const SearchPage = ({
searchParams,
}: {
searchParams?: { [key: string]: string | string[] | undefined };
}) => {
return <div>{searchParams?.keyword}</div>;
};

export default SearchPage;
jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions client/src/components/ModalWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const ModalWrapper = ({ children, disableBox }: ModalInterface) => {
>
{
<Box
onClick={(e) => e.stopPropagation()}
sx={{
bgcolor: disableBox ? undefined : "background.paper",
p: disableBox ? 0 : 4,
jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
164 changes: 79 additions & 85 deletions client/src/components/post/PostCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { POST_DETAIL } from "@/const/clientPath";

import { PostInterface } from "@/types/post/PostInterface";

import { MoreVertOutlined } from "@mui/icons-material";
Expand All @@ -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,
Expand All @@ -25,100 +27,92 @@ const PostCard = ({
tags,
id,
}: PostInterface) => {
const openPostDetailPage = useOpenPostDetailPage();
const hasImage = useMemo(() => image.length !== 0, [image]);

return (
<Link href={`${POST_DETAIL(userId, id)}`}>
<Card sx={{ display: "flex", gap: 2, p: 2 }}>
<Avatar
sx={{ bgcolor: "secondary.main" }}
sizes="40"
src={userImage}
data-testid="avatar"
<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%",
}}
>
{userImage || userId[0].toUpperCase()}
</Avatar>
<Box>
{/* Header */}
<Box
data-testid="mui-header"
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
px: 0,
width: "100%",
gap: 1,
alighItems: "center",
}}
>
<Box
sx={{
display: "flex",
flexDirection: "row",
gap: 1,
alighItems: "center",
}}
>
<Typography sx={{ fontWeight: "bold" }}>{nickname}</Typography>
<Typography>{`@${userId}`}</Typography>
<Typography>{createdAt}</Typography>
</Box>

<ButtonBase aria-label="settings" sx={{ p: 0 }}>
<MoreVertOutlined />
</ButtonBase>
{/* 타이틀 */}
<Typography sx={{ fontWeight: "bold" }}>{nickname}</Typography>
<Typography>{`@${userId}`}</Typography>
<Typography>{createdAt}</Typography>
</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" }}
/>
)}
{/* 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>
<ButtonBase aria-label="settings" sx={{ p: 0 }}>
<MoreVertOutlined />
</ButtonBase>
</Box>
</Card>
</Link>

<CardContent sx={{ px: 0 }}>
{/* Contents */}
<Typography
variant="body1"
className={hasImage ? "line-clamp-2" : "line-clamp-5"}
onClick={() => openPostDetailPage(userId, id)}
>
{content}
</Typography>

{/* Hash tags */}
<PostHashTagList tags={tags} />
</CardContent>

{/* image */}
{hasImage && (
<CardMedia
data-testid="postImg"
component="img"
height="142"
onClick={() => openPostDetailPage(userId, id)}
image={image[0]}
alt={`${userId}의 포스트`}
sx={{ borderRadius: 2, bgcolor: "background.default" }}
/>
)}
{/* CTA */}
<CardActions sx={{ px: 0, justifyContent: "end", gap: 2 }}>
<ButtonBase data-testid="commentBtn" aria-label="share">
<Typography onClick={() => openPostDetailPage(userId, id)}>
댓글
</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>
);
};

jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
55 changes: 54 additions & 1 deletion client/src/components/post/PostDetail.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -10,6 +22,47 @@ const PostDetail = ({
tags,
id,
}: PostInterface) => {
return <>{userId}</>;
return (
<Card sx={{ p: 4 }}>
<CardHeader
avatar={
<Avatar
sx={{ bgcolor: "secondary.main" }}
src={userImage}
data-testid="avatar"
>
{userImage || userId[0].toUpperCase()}
</Avatar>
}
title={`${userId} ${nickname}`}
subheader={createdAt}
sx={{ p: 0 }}
/>
<CardContent sx={{ px: 0 }}>
<Typography variant="body1">{content}</Typography>
<PostHashTagList tags={tags} />
</CardContent>
{/* 이미지 */}
{image.length !== 0 && (
<CardMedia
data-testid="postImg"
component="img"
height="360px"
image={image[0]}
alt={`${userId}의 포스트`}
sx={{ borderRadius: 2, bgcolor: "background.default" }}
/>
)}
<CardActions sx={{ justifyContent: "end", gap: 2, px: 0 }}>
<ButtonBase data-testid="likeBtn" aria-label="add to favorites">
<Typography>좋아요</Typography>
</ButtonBase>
<ButtonBase data-testid="shareBtn" aria-label="share">
<Typography>공유하기</Typography>
</ButtonBase>
</CardActions>
<TextField label="댓글" fullWidth></TextField>
</Card>
);
};
export default PostDetail;
jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
40 changes: 40 additions & 0 deletions client/src/components/post/PostHashtagList.tsx
Original file line number Diff line number Diff line change
@@ -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 && (
<Box
data-testid="tags"
sx={{
pt: 2,
display: "flex",
flexDirection: "row",
gap: "8px",
}}
{...others}
>
{tags.map((tag) => (
<Link href={SEARCH_BY_KEYWORD(tag)} key={tag}>
<Typography
component={"span"}
variant={"label"}
color="text.secondary"
>
{`#${tag}`}
</Typography>
</Link>
))}
</Box>
)}
</>
);
};

export default PostHashTagList;
jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
17 changes: 14 additions & 3 deletions client/src/const/clientPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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를 리턴
Expand All @@ -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}`;
};

jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
15 changes: 15 additions & 0 deletions client/src/hooks/useOpenPostDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -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;
};
jobkaeHenry marked this conversation as resolved.
Show resolved Hide resolved
Loading