Skip to content

Commit d8756dd

Browse files
authored
Merge pull request #44 from nettee-space/feature/post-detail
게시글 상세 기능 및 공용 레이아웃 수정
2 parents 4f9a65e + 2d47393 commit d8756dd

File tree

8 files changed

+92
-86
lines changed

8 files changed

+92
-86
lines changed

apps/web/app/layout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ export default function RootLayout({
2020
children: React.ReactNode;
2121
}>) {
2222
return (
23-
<html lang="en" suppressHydrationWarning>
23+
<html lang="ko" suppressHydrationWarning>
2424
<body
2525
className={`${fontSans.variable} ${fontMono.variable} font-sans antialiased`}
2626
>
27-
<Providers>{children}</Providers>
27+
<Providers>
28+
<main className="mx-auto max-w-4xl p-6">{children}</main>
29+
</Providers>
2830
</body>
2931
</html>
3032
);

apps/web/app/page.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@ export default async function Home() {
99

1010
return (
1111
<>
12-
<Button asChild>
13-
<Link href="/posts/write">새 글 작성</Link>
14-
</Button>
12+
<div className="flex flex-col">
13+
<h1 className="mx-8 my-4 text-center text-2xl font-bold">
14+
게시글 목록
15+
</h1>
16+
<div className="flex justify-end">
17+
<Button asChild className="mx-8">
18+
<Link href="/posts/write">새 글 작성</Link>
19+
</Button>
20+
</div>
21+
</div>
1522

1623
<PostInfiniteScroll
1724
postList={data}

apps/web/app/posts/[id]/page.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
1+
import { Button } from '@workspace/ui/components/button';
2+
import Link from 'next/link';
3+
14
import { PostDetail } from '@/features/post/post-detail';
25

3-
// 특정 게시글 페이지 - 게시글 생성 후 넘어가지는지 확인용으로 만든 임시 페이지
4-
export default function PostPage() {
6+
export default async function PostPage({ params }: { params: { id: string } }) {
7+
const id = (await params).id;
8+
59
return (
6-
<main>
7-
<PostDetail />
8-
</main>
10+
<>
11+
<PostDetail params={params} />
12+
<div className="flex flex-col items-end">
13+
<div className="mx-8 flex gap-2">
14+
<Button asChild>
15+
<Link href={'/'}>목록</Link>
16+
</Button>
17+
<Button asChild>
18+
<Link href={`/posts/edit/${id}`}>수정</Link>
19+
</Button>
20+
</div>
21+
</div>
22+
</>
923
);
1024
}

apps/web/app/posts/edit/[id]/page.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,5 @@ export default async function PostEditPage({ params }: PostEditPageProps) {
1515
notFound();
1616
}
1717

18-
return (
19-
<main className="mx-auto max-w-2xl p-6">
20-
<PostForm post={post} />
21-
</main>
22-
);
18+
return <PostForm post={post} />;
2319
}

apps/web/app/posts/write/page.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { PostForm } from '@/features/post/post-editor';
22

33
export default function NewPostPage() {
4-
return (
5-
<main className="mx-auto max-w-2xl p-6">
6-
<PostForm />
7-
</main>
8-
);
4+
return <PostForm />;
95
}
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import { Post } from '@/entities/post';
22
import { formatToLocaleDate } from '@/shared/lib';
33

4-
export const mapPostToViewModel = (post: Post) => ({
5-
...post,
4+
export interface PostViewModel {
5+
id: string;
6+
title: string;
7+
content: string;
8+
author: string;
9+
localeCreatedAt: string;
10+
}
11+
12+
export const mapPostToViewModel = (post: Post): PostViewModel => ({
13+
id: post.id,
14+
title: post.title,
15+
content: post.content,
16+
author: post.author,
617
localeCreatedAt: formatToLocaleDate(post.createdAt),
718
});

apps/web/features/post/post-detail/ui/post-detail.tsx

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,38 @@
1-
'use client';
1+
import { getPostById, mapPostToViewModel } from '@/entities/post';
2+
import { PostViewModel } from '@/entities/post/model/post-view-model';
23

3-
import { Button } from '@workspace/ui/components/button';
4-
import { useParams, useRouter } from 'next/navigation';
5-
import { useEffect, useState } from 'react';
6-
7-
import { getPostById } from '@/entities/post';
8-
import { Post } from '@/entities/post';
9-
import { formatToLocaleDate } from '@/shared/lib';
4+
interface PostDetailProps {
5+
params: { id: string };
6+
}
107

11-
// 특정 게시글 페이지 - 게시글 생성 후 넘어가지는지 확인용으로 만든 임시 컴포넌트
12-
export function PostDetail() {
13-
const { id } = useParams();
14-
const router = useRouter();
15-
const [post, setPost] = useState<Post | null>(null);
16-
const [isLoading, setIsLoading] = useState(true);
17-
const [error, setError] = useState<string | null>(null);
8+
export async function PostDetail({ params }: PostDetailProps) {
9+
let post: PostViewModel | null = null;
10+
let error: string | null = null;
1811

19-
useEffect(() => {
20-
if (!id) return;
12+
const id = (await params).id;
2113

22-
async function fetchPost() {
23-
try {
24-
setIsLoading(true);
25-
const data = await getPostById(id as string);
26-
setPost(data);
27-
} catch (err) {
28-
setError('게시글을 불러오는 중 오류가 발생했습니다.');
29-
console.error(err);
30-
} finally {
31-
setIsLoading(false);
32-
}
33-
}
34-
fetchPost();
35-
}, [id]);
14+
try {
15+
const data = await getPostById(id);
16+
post = mapPostToViewModel(data);
17+
} catch (err) {
18+
console.error(err);
19+
error = '게시글을 불러오는 중 오류가 발생했습니다.';
20+
}
3621

37-
if (isLoading) return <p className="text-center text-gray-500">로딩 중...</p>;
38-
if (error) return <p className="text-center text-red-500">{error}</p>;
39-
if (!post)
40-
return (
41-
<p className="text-center text-gray-500">게시글을 찾을 수 없습니다.</p>
42-
);
22+
if (error) return <p>{error}</p>;
23+
if (!post) return <p>게시글을 찾을 수 없습니다.</p>;
4324

4425
return (
45-
<div className="mx-auto max-w-2xl space-y-4 rounded-lg bg-white p-6 shadow-md">
46-
<h1 className="text-2xl font-bold text-gray-900">{post.title}</h1>
47-
<p className="whitespace-pre-line text-gray-700">{post.content}</p>
48-
<div className="text-sm text-gray-500">
49-
<p>작성자: {post.author}</p>
50-
<p>작성일: {formatToLocaleDate(post.createdAt)}</p>
51-
</div>
52-
<div className="mt-4 flex justify-between">
53-
<Button variant="outline" onClick={() => router.push('/')}>
54-
목록으로
55-
</Button>
56-
<Button onClick={() => router.push(`/posts/edit/${post.id}`)}>
57-
수정하기
58-
</Button>
26+
<div className="mx-8 my-4 rounded border border-gray-400 p-4">
27+
<h3 className="pb-4 font-semibold">{post.title}</h3>
28+
<hr />
29+
<p className="min-h-[30vh] whitespace-pre-wrap pt-4">{post.content}</p>
30+
<br />
31+
<div className="flex flex-col gap-2">
32+
<span className="rounded border border-gray-300 bg-gray-300 px-2 py-0.5 italic">
33+
{post.author}
34+
</span>
35+
<time>{post.localeCreatedAt}</time>
5936
</div>
6037
</div>
6138
);

apps/web/features/post/post-editor/ui/post-form.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export function PostForm({ post }: PostFormProps) {
2222
);
2323

2424
return (
25-
<form action={formAction}>
26-
<h1 className="mb-4 text-xl font-bold">
25+
<form action={formAction} className="mx-8 flex flex-col">
26+
<h1 className="mb-4 text-center text-xl font-bold">
2727
{isEditMode ? '게시글 수정' : '새 게시글 작성'}
2828
</h1>
2929

@@ -43,6 +43,7 @@ export function PostForm({ post }: PostFormProps) {
4343
disabled={isPending}
4444
isTextArea
4545
defaultValue={post?.content || ''}
46+
className="min-h-[30vh]"
4647
/>
4748
<TextField
4849
name="author"
@@ -57,17 +58,19 @@ export function PostForm({ post }: PostFormProps) {
5758
<p className="mt-2 text-sm text-red-500">{actionResult.error}</p>
5859
)}
5960

60-
<Button
61-
type="button"
62-
variant="outline"
63-
onClick={() => router.back()}
64-
disabled={isPending}
65-
>
66-
취소
67-
</Button>
68-
<Button type="submit" disabled={isPending}>
69-
{isPending ? '저장 중...' : '작성하기'}
70-
</Button>
61+
<div className="flex justify-end gap-2">
62+
<Button
63+
type="button"
64+
variant="outline"
65+
onClick={() => router.back()}
66+
disabled={isPending}
67+
>
68+
취소
69+
</Button>
70+
<Button type="submit" disabled={isPending}>
71+
{isPending ? '저장 중...' : '작성하기'}
72+
</Button>
73+
</div>
7174
</form>
7275
);
7376
}

0 commit comments

Comments
 (0)