From b67ec699bba54f6bd3765b4f236b7bddbc0d8adf Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Sun, 22 Dec 2024 20:39:08 +0900 Subject: [PATCH 1/6] =?UTF-8?q?style:=20=ED=99=94=EB=A9=B4=EC=9D=B4=20?= =?UTF-8?q?=EC=A2=81=EC=9D=84=EB=95=8C=EB=8A=94=20=EC=95=88=EB=B3=B4?= =?UTF-8?q?=EC=9D=B4=EA=B2=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/post/detail/PostTOC.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/entities/post/detail/PostTOC.tsx b/app/entities/post/detail/PostTOC.tsx index 8039f90..a36d814 100644 --- a/app/entities/post/detail/PostTOC.tsx +++ b/app/entities/post/detail/PostTOC.tsx @@ -12,7 +12,7 @@ const PostTOC = ({ postContent }: { postContent: string }) => { }; return ( -
+

πŸ“Œ Table of Contents

    {parseHeadings(postContent).map((heading) => { From d9cae98a28f690ba0f26e42e764c26750acf45f3 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Sun, 22 Dec 2024 20:48:07 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20BlogForm=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=84=ED=95=B4=EC=A3=BC=EB=8D=98=20props=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/write/page.tsx | 27 +------------------------- app/entities/post/write/BlogForm.tsx | 29 ++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/app/admin/write/page.tsx b/app/admin/write/page.tsx index 8bbb55a..e5f09b6 100644 --- a/app/admin/write/page.tsx +++ b/app/admin/write/page.tsx @@ -1,35 +1,10 @@ -'use client'; import BlogForm from '@/app/entities/post/write/BlogForm'; -import axios from 'axios'; -import { PostBody } from '@/app/types/Post'; -import { useRouter, useSearchParams } from 'next/navigation'; -import useToast from '@/app/hooks/useToast'; const BlogWritePage = () => { - const router = useRouter(); - const params = useSearchParams(); - const postId = params.get('postId'); - const toast = useToast(); - - const postBlog = async (post: PostBody) => { - try { - const response = await axios.post('/api/posts', post); - const data = await response.data; - console.log('κΈ€ λ°œν–‰ κ²°κ³Ό', data); - if (response.status === 201) { - toast.success('글이 μ„±κ³΅μ μœΌλ‘œ λ°œν–‰λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'); - router.push('/posts'); - } - } catch (e) { - toast.error('κΈ€ λ°œν–‰ 쀑 였λ₯˜ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.'); - console.error('κΈ€ λ°œν–‰ 쀑 였λ₯˜ λ°œμƒ', e); - } - }; - return (

    κΈ€ μž‘μ„±

    - +
    ); }; diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index a7ad1b5..c56058f 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -3,25 +3,22 @@ import '@uiw/react-md-editor/markdown-editor.css'; import '@uiw/react-markdown-preview/markdown.css'; import { useEffect, useState } from 'react'; -import * as commands from '@uiw/react-md-editor/commands'; import dynamic from 'next/dynamic'; import Link from 'next/link'; -import LoadingIndicator from '@/app/entities/common/Loading/LoadingIndicator'; + import { PostBody } from '@/app/types/Post'; import { StaticImport } from 'next/dist/shared/lib/get-img-props'; import LoadingSpinner from '@/app/entities/common/Loading/LoadingSpinner'; import axios from 'axios'; import useToast from '@/app/hooks/useToast'; import { useBlockNavigate } from '@/app/hooks/useBlockNavigate'; +import { useRouter, useSearchParams } from 'next/navigation'; const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false }); -interface BlogFormProps { - postBlog: (post: PostBody) => Promise; - postId: string | null; -} - -const BlogForm = ({ postBlog, postId }: BlogFormProps) => { +const BlogForm = () => { + const params = useSearchParams(); + const postId = params.get('postId'); const [submitLoading, setSubmitLoading] = useState(false); const [title, setTitle] = useState(''); const [subTitle, setSubTitle] = useState(''); @@ -30,6 +27,7 @@ const BlogForm = ({ postBlog, postId }: BlogFormProps) => { const [thumbnailImage, setThumbnailImage] = useState(); const [errors, setErrors] = useState([]); const toast = useToast(); + const router = useRouter(); const buttonStyle = `font-bold py-2 px-4 rounded mr-2 disabled:bg-opacity-75 `; const NICKNAME = '개발자 μ„œμ •μš°'; @@ -50,6 +48,21 @@ const BlogForm = ({ postBlog, postId }: BlogFormProps) => { thumbnailImage, }; + const postBlog = async (post: PostBody) => { + try { + const response = await axios.post('/api/posts', post); + const data = await response.data; + console.log('κΈ€ λ°œν–‰ κ²°κ³Ό', data); + if (response.status === 201) { + toast.success('글이 μ„±κ³΅μ μœΌλ‘œ λ°œν–‰λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'); + router.push('/posts'); + } + } catch (e) { + toast.error('κΈ€ λ°œν–‰ 쀑 였λ₯˜ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.'); + console.error('κΈ€ λ°œν–‰ 쀑 였λ₯˜ λ°œμƒ', e); + } + }; + const validatePost = ( post: PostBody ): { isValid: boolean; errors: string[] } => { From 78164bb504da5fbb2a8d394731f8d4bb6b809251 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Sun, 22 Dec 2024 20:59:14 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=EA=B8=80=20=EC=88=98=EC=A0=95=20AP?= =?UTF-8?q?I=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/posts/[slug]/route.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/api/posts/[slug]/route.ts b/app/api/posts/[slug]/route.ts index 7fcaf25..059a503 100644 --- a/app/api/posts/[slug]/route.ts +++ b/app/api/posts/[slug]/route.ts @@ -36,10 +36,14 @@ export async function PUT( await dbConnect(); const body = await req.json(); - const updatedPost = await Post.findByIdAndUpdate(params.slug, body, { - new: true, - runValidators: true, - }).lean(); + const updatedPost = await Post.findOneAndUpdate( + { slug: params.slug }, + body, + { + new: true, + runValidators: true, + } + ).lean(); if (!updatedPost) { return Response.json( From 20cf076f220bf2845a750e7226334b7da81e854c Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Sun, 22 Dec 2024 20:59:36 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=EA=B8=80=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=ED=95=A0=EB=95=8C,=20searchparams=20postId=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20slug=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/posts/page.tsx | 6 +++--- app/entities/post/write/BlogForm.tsx | 32 +++++++++++++++++++++------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/admin/posts/page.tsx b/app/admin/posts/page.tsx index f96efbd..5b84e04 100644 --- a/app/admin/posts/page.tsx +++ b/app/admin/posts/page.tsx @@ -28,9 +28,9 @@ const AdminPostListPage = () => { setLoading(false); }; - const handleEdit = (postId: string) => { + const handleEdit = (slug: string) => { toast.success('κΈ€ μˆ˜μ • νŽ˜μ΄μ§€λ‘œ μ΄λ™ν•©λ‹ˆλ‹€.'); - router.push(`/admin/write?postId=${postId}`); + router.push(`/admin/write?slug=${slug}`); }; const handleDeleteClick = (postId: string) => { @@ -68,7 +68,7 @@ const AdminPostListPage = () => { handleEdit(post._id)} + handleEdit={() => handleEdit(post.slug)} handleDelete={() => handleDeleteClick(post._id)} /> ))} diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index c56058f..30d76dc 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -2,10 +2,8 @@ import '@uiw/react-md-editor/markdown-editor.css'; import '@uiw/react-markdown-preview/markdown.css'; import { useEffect, useState } from 'react'; - import dynamic from 'next/dynamic'; import Link from 'next/link'; - import { PostBody } from '@/app/types/Post'; import { StaticImport } from 'next/dist/shared/lib/get-img-props'; import LoadingSpinner from '@/app/entities/common/Loading/LoadingSpinner'; @@ -18,7 +16,7 @@ const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false }); const BlogForm = () => { const params = useSearchParams(); - const postId = params.get('postId'); + const slug = params.get('slug'); const [submitLoading, setSubmitLoading] = useState(false); const [title, setTitle] = useState(''); const [subTitle, setSubTitle] = useState(''); @@ -34,10 +32,10 @@ const BlogForm = () => { useBlockNavigate({ title, content: content || '' }); useEffect(() => { - if (postId) { + if (slug) { getPostDetail(); } - }, [postId]); + }, [slug]); const postBody: PostBody = { title, @@ -63,6 +61,19 @@ const BlogForm = () => { } }; + const updatePost = async (post: PostBody) => { + try { + const response = await axios.put(`/api/posts/${slug}`, post); + if (response.status === 200) { + toast.success('글이 μ„±κ³΅μ μœΌλ‘œ μˆ˜μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'); + router.push('/posts'); + } + } catch (e) { + toast.error('κΈ€ μˆ˜μ • 쀑 였λ₯˜ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.'); + console.error('κΈ€ μˆ˜μ • 쀑 였λ₯˜ λ°œμƒ', e); + } + }; + const validatePost = ( post: PostBody ): { isValid: boolean; errors: string[] } => { @@ -114,7 +125,11 @@ const BlogForm = () => { return; } - postBlog(post); + if (slug) { + updatePost(post); + } else { + postBlog(post); + } } catch (e) { console.error('κΈ€ λ°œν–‰ 쀑 였λ₯˜ λ°œμƒ', e); setSubmitLoading(false); @@ -123,8 +138,9 @@ const BlogForm = () => { const getPostDetail = async () => { try { - const response = await axios.get(`/api/posts/${postId}`); + const response = await axios.get(`/api/posts/${slug}`); const data = await response.data; + console.log('κΈ€ 쑰회 κ²°κ³Ό', data); setTitle(data.post.title || 'dd'); setSubTitle(data.post.subTitle); setContent(data.post.content); @@ -182,7 +198,7 @@ const BlogForm = () => { submitHandler(postBody); }} > - {submitLoading ? : postId ? 'κΈ€ μˆ˜μ •' : 'κΈ€ λ°œν–‰'} + {submitLoading ? : slug ? 'κΈ€ μˆ˜μ •' : 'κΈ€ λ°œν–‰'}
From c1df54290035af9f0a29b2800dba40194cd3d7de Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Sun, 22 Dec 2024 21:02:34 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=EA=B8=80=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=8B=9C=EC=97=90=20=EC=8D=B8=EB=84=A4=EC=9D=BC=EC=9D=B4=20?= =?UTF-8?q?=EB=B0=94=EB=80=8C=EB=A9=B4=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/posts/[slug]/route.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/api/posts/[slug]/route.ts b/app/api/posts/[slug]/route.ts index 059a503..d9cc02f 100644 --- a/app/api/posts/[slug]/route.ts +++ b/app/api/posts/[slug]/route.ts @@ -2,6 +2,7 @@ import dbConnect from '@/app/lib/dbConnect'; import Post from '@/app/models/Post'; import { NextRequest } from 'next/server'; +import { getThumbnailInMarkdown } from '@/app/lib/utils/parse'; export async function GET( req: NextRequest, @@ -38,7 +39,7 @@ export async function PUT( const updatedPost = await Post.findOneAndUpdate( { slug: params.slug }, - body, + { ...body, thumbnailImage: getThumbnailInMarkdown(body.content) }, { new: true, runValidators: true, From bfbe0f5dad4027c76bf3a42cb72bbdb5a6c0fbc3 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Sun, 22 Dec 2024 21:05:44 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20=EC=BD=98=EC=86=94=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/post/write/BlogForm.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index 30d76dc..aa45b11 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -50,7 +50,6 @@ const BlogForm = () => { try { const response = await axios.post('/api/posts', post); const data = await response.data; - console.log('κΈ€ λ°œν–‰ κ²°κ³Ό', data); if (response.status === 201) { toast.success('글이 μ„±κ³΅μ μœΌλ‘œ λ°œν–‰λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'); router.push('/posts'); @@ -140,7 +139,6 @@ const BlogForm = () => { try { const response = await axios.get(`/api/posts/${slug}`); const data = await response.data; - console.log('κΈ€ 쑰회 κ²°κ³Ό', data); setTitle(data.post.title || 'dd'); setSubTitle(data.post.subTitle); setContent(data.post.content);