Skip to content

Commit 20f44a9

Browse files
committed
feat: 작성한 글 조회
1 parent 274bdc3 commit 20f44a9

4 files changed

Lines changed: 120 additions & 1 deletion

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import PostDetail from '@/components/write/PostDetail';
2+
3+
type Props = {
4+
params: Promise<{ postId: string }>;
5+
};
6+
7+
const Page = async ({ params }: Props) => {
8+
const { postId } = await params;
9+
return <PostDetail postId={postId} />;
10+
};
11+
12+
export default Page;

components/write/Editor.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import type { MDEditorProps } from '@uiw/react-md-editor';
66
import SaveButton from '@/components/write/SaveButton';
77
import { createPost } from '@/services/write/post.service';
88
import { auth } from '@/lib/firebase';
9+
import { useRouter } from 'next/navigation';
910

1011
const MDEditor = dynamic<MDEditorProps>(() => import('@uiw/react-md-editor'), {
1112
ssr: false,
1213
});
1314

1415
const Editor = () => {
1516
const [value, setValue] = useState<string>('');
17+
const router = useRouter();
1618

1719
const onClickCancel = () => {
1820
setValue('');
@@ -33,6 +35,8 @@ const Editor = () => {
3335
const id = await createPost(user.uid, value);
3436
alert('저장 완료!');
3537
console.log('postId:', id);
38+
39+
router.push(`/write/${id}`);
3640
} catch (e) {
3741
console.error(e);
3842
alert('저장 실패');

components/write/PostDetail.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use client';
2+
3+
import dynamic from 'next/dynamic';
4+
import { useEffect, useState } from 'react';
5+
import { useRouter } from 'next/navigation';
6+
import { auth } from '@/lib/firebase';
7+
import { fetchMyPost } from '@/services/write/post.service';
8+
import type { Post } from '@/services/write/post.service'; // 같은 파일에 타입 넣었다면 거기서 export한 Post 쓰세요
9+
10+
const Markdown = dynamic(
11+
() => import('@uiw/react-md-editor').then((mod) => mod.default.Markdown),
12+
{ ssr: false }
13+
);
14+
15+
const PostDetail = ({ postId }: { postId: string }) => {
16+
const router = useRouter();
17+
const [data, setData] = useState<Post | null>(null);
18+
const [loading, setLoading] = useState(true);
19+
20+
useEffect(() => {
21+
const unsub = auth.onAuthStateChanged(async (user) => {
22+
if (!user) {
23+
router.replace('/landing');
24+
return;
25+
}
26+
27+
const post = await fetchMyPost(user.uid, postId);
28+
setData(post);
29+
setLoading(false);
30+
});
31+
32+
return () => unsub();
33+
}, [postId, router]);
34+
35+
if (loading) return <div className="min-h-screen p-6">로딩중...</div>;
36+
if (!data) return <div className="min-h-screen p-6">글이 없습니다.</div>;
37+
38+
return (
39+
<div className="mx-auto min-h-screen max-w-3xl p-6" data-color-mode="light">
40+
<h1 className="mb-4 text-xl font-semibold">
41+
{data.title || '제목 없음'}
42+
</h1>
43+
<Markdown source={data.content} />
44+
</div>
45+
);
46+
};
47+
48+
export default PostDetail;

services/write/post.service.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1-
import { collection, addDoc, serverTimestamp } from 'firebase/firestore';
1+
import {
2+
doc,
3+
getDoc,
4+
collection,
5+
addDoc,
6+
serverTimestamp,
7+
Timestamp,
8+
} from 'firebase/firestore';
29
import { db } from '@/lib/firebase';
310

11+
export type PostData = {
12+
title: string;
13+
content: string;
14+
createdAt: Timestamp | null;
15+
updatedAt: Timestamp | null;
16+
};
17+
18+
export type Post = PostData & { id: string };
19+
420
export async function createPost(uid: string, content: string, title?: string) {
521
const postsCol = collection(db, 'users', uid, 'posts');
622
const docRef = await addDoc(postsCol, {
@@ -11,3 +27,42 @@ export async function createPost(uid: string, content: string, title?: string) {
1127
});
1228
return docRef.id;
1329
}
30+
31+
/** 런타임 최소 검증(안전하게 any 제거) */
32+
function isRecord(v: unknown): v is Record<string, unknown> {
33+
return typeof v === 'object' && v !== null;
34+
}
35+
36+
function parsePostData(raw: unknown): PostData | null {
37+
if (!isRecord(raw)) return null;
38+
39+
const content = raw.content;
40+
if (typeof content !== 'string') return null; // content는 필수
41+
42+
const title = typeof raw.title === 'string' ? raw.title : '';
43+
44+
const createdAt = raw.createdAt instanceof Timestamp ? raw.createdAt : null;
45+
46+
const updatedAt = raw.updatedAt instanceof Timestamp ? raw.updatedAt : null;
47+
48+
return { title, content, createdAt, updatedAt };
49+
}
50+
51+
export async function fetchMyPost(
52+
uid: string | null | undefined,
53+
postId: string | null | undefined
54+
): Promise<Post | null> {
55+
// ✅ 여기서 uid/postId 실물 확인
56+
console.log('[fetchMyPost] path =', { uid, postId });
57+
58+
if (!uid || !postId) return null;
59+
60+
const ref = doc(db, 'users', uid, 'posts', postId);
61+
const snap = await getDoc(ref);
62+
if (!snap.exists()) return null;
63+
64+
const parsed = parsePostData(snap.data());
65+
if (!parsed) return null;
66+
67+
return { id: snap.id, ...parsed };
68+
}

0 commit comments

Comments
 (0)