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

게시글 상세 기능 및 공용 레이아웃 수정 #44

Merged
merged 7 commits into from
Mar 31, 2025
6 changes: 4 additions & 2 deletions apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<html lang="ko" suppressHydrationWarning>
<body
className={`${fontSans.variable} ${fontMono.variable} font-sans antialiased`}
>
<Providers>{children}</Providers>
<Providers>
<main className="mx-auto max-w-4xl p-6">{children}</main>
</Providers>
</body>
</html>
);
Expand Down
13 changes: 10 additions & 3 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ export default async function Home() {

return (
<>
<Button asChild>
<Link href="/posts/write">새 글 작성</Link>
</Button>
<div className="flex flex-col">
<h1 className="mx-8 my-4 text-center text-2xl font-bold">
게시글 목록
</h1>
<div className="flex justify-end">
<Button asChild className="mx-8">
<Link href="/posts/write">새 글 작성</Link>
</Button>
</div>
</div>

<PostInfiniteScroll
postList={data}
Expand Down
24 changes: 19 additions & 5 deletions apps/web/app/posts/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import { Button } from '@workspace/ui/components/button';
import Link from 'next/link';

import { PostDetail } from '@/features/post/post-detail';

// 특정 게시글 페이지 - 게시글 생성 후 넘어가지는지 확인용으로 만든 임시 페이지
export default function PostPage() {
export default async function PostPage({ params }: { params: { id: string } }) {
const id = (await params).id;

return (
<main>
<PostDetail />
</main>
<>
<PostDetail params={params} />
<div className="flex flex-col items-end">
<div className="mx-8 flex gap-2">
<Button asChild>
<Link href={'/'}>목록</Link>
</Button>
<Button asChild>
<Link href={`/posts/edit/${id}`}>수정</Link>
</Button>
</div>
</div>
</>
);
}
6 changes: 1 addition & 5 deletions apps/web/app/posts/edit/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,5 @@ export default async function PostEditPage({ params }: PostEditPageProps) {
notFound();
}

return (
<main className="mx-auto max-w-2xl p-6">
<PostForm post={post} />
</main>
);
return <PostForm post={post} />;
}
6 changes: 1 addition & 5 deletions apps/web/app/posts/write/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { PostForm } from '@/features/post/post-editor';

export default function NewPostPage() {
return (
<main className="mx-auto max-w-2xl p-6">
<PostForm />
</main>
);
return <PostForm />;
}
15 changes: 13 additions & 2 deletions apps/web/entities/post/model/post-view-model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { Post } from '@/entities/post';
import { formatToLocaleDate } from '@/shared/lib';

export const mapPostToViewModel = (post: Post) => ({
...post,
export interface PostViewModel {
id: string;
title: string;
content: string;
author: string;
localeCreatedAt: string;
}

export const mapPostToViewModel = (post: Post): PostViewModel => ({
id: post.id,
title: post.title,
content: post.content,
author: post.author,
localeCreatedAt: formatToLocaleDate(post.createdAt),
});
79 changes: 28 additions & 51 deletions apps/web/features/post/post-detail/ui/post-detail.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,38 @@
'use client';
import { getPostById, mapPostToViewModel } from '@/entities/post';
import { PostViewModel } from '@/entities/post/model/post-view-model';

import { Button } from '@workspace/ui/components/button';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';

import { getPostById } from '@/entities/post';
import { Post } from '@/entities/post';
import { formatToLocaleDate } from '@/shared/lib';
interface PostDetailProps {
params: { id: string };
}

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

useEffect(() => {
if (!id) return;
const id = (await params).id;

async function fetchPost() {
try {
setIsLoading(true);
const data = await getPostById(id as string);
setPost(data);
} catch (err) {
setError('게시글을 불러오는 중 오류가 발생했습니다.');
console.error(err);
} finally {
setIsLoading(false);
}
}
fetchPost();
}, [id]);
try {
const data = await getPostById(id);
post = mapPostToViewModel(data);
} catch (err) {
console.error(err);
error = '게시글을 불러오는 중 오류가 발생했습니다.';
}

if (isLoading) return <p className="text-center text-gray-500">로딩 중...</p>;
if (error) return <p className="text-center text-red-500">{error}</p>;
if (!post)
return (
<p className="text-center text-gray-500">게시글을 찾을 수 없습니다.</p>
);
if (error) return <p>{error}</p>;
if (!post) return <p>게시글을 찾을 수 없습니다.</p>;

return (
<div className="mx-auto max-w-2xl space-y-4 rounded-lg bg-white p-6 shadow-md">
<h1 className="text-2xl font-bold text-gray-900">{post.title}</h1>
<p className="whitespace-pre-line text-gray-700">{post.content}</p>
<div className="text-sm text-gray-500">
<p>작성자: {post.author}</p>
<p>작성일: {formatToLocaleDate(post.createdAt)}</p>
</div>
<div className="mt-4 flex justify-between">
<Button variant="outline" onClick={() => router.push('/')}>
목록으로
</Button>
<Button onClick={() => router.push(`/posts/edit/${post.id}`)}>
수정하기
</Button>
<div className="mx-8 my-4 rounded border border-gray-400 p-4">
<h3 className="pb-4 font-semibold">{post.title}</h3>
<hr />
<p className="min-h-[30vh] whitespace-pre-wrap pt-4">{post.content}</p>
<br />
<div className="flex flex-col gap-2">
<span className="rounded border border-gray-300 bg-gray-300 px-2 py-0.5 italic">
{post.author}
</span>
<time>{post.localeCreatedAt}</time>
</div>
</div>
);
Expand Down
29 changes: 16 additions & 13 deletions apps/web/features/post/post-editor/ui/post-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export function PostForm({ post }: PostFormProps) {
);

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

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

<Button
type="button"
variant="outline"
onClick={() => router.back()}
disabled={isPending}
>
취소
</Button>
<Button type="submit" disabled={isPending}>
{isPending ? '저장 중...' : '작성하기'}
</Button>
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={() => router.back()}
disabled={isPending}
>
취소
</Button>
<Button type="submit" disabled={isPending}>
{isPending ? '저장 중...' : '작성하기'}
</Button>
</div>
</form>
);
}