11'use client' ;
22import '@uiw/react-md-editor/markdown-editor.css' ;
33import '@uiw/react-markdown-preview/markdown.css' ;
4- import { useEffect , useState } from 'react' ;
4+ import { useState } from 'react' ;
55import dynamic from 'next/dynamic' ;
6- import { PostBody } from '@/app/types/Post' ;
7- import { StaticImport } from 'next/dist/shared/lib/get-img-props' ;
8- import axios from 'axios' ;
9- import useToast from '@/app/hooks/useToast' ;
106import { useBlockNavigate } from '@/app/hooks/common/useBlockNavigate' ;
11- import { useRouter , useSearchParams } from 'next/navigation' ;
7+ import { useSearchParams } from 'next/navigation' ;
128import PostWriteButtons from '@/app/entities/post/write/PostWriteButtons' ;
13- import { validatePost } from '@/app/lib/utils/validate/validate' ;
14- import { Series } from '@/app/types/Series' ;
159import Overlay from '@/app/entities/common/Overlay/Overlay' ;
1610import CreateSeriesOverlayContainer from '@/app/entities/series/CreateSeriesOverlayContainer' ;
17- import { getAllSeriesData } from '@/app/entities/series/api/series' ;
1811import UploadImageContainer from '@/app/entities/post/write/UploadImageContainer' ;
19- import useDraft from '@/app/hooks/post/useDraft' ;
2012import PostMetadataForm from '@/app/entities/post/write/PostMetadataForm' ;
13+ import usePost from '@/app/hooks/post/usePost' ;
2114
2215const MDEditor = dynamic ( ( ) => import ( '@uiw/react-md-editor' ) , { ssr : false } ) ;
2316
2417const BlogForm = ( ) => {
2518 const params = useSearchParams ( ) ;
2619 const slug = params . get ( 'slug' ) ;
27- const [ submitLoading , setSubmitLoading ] = useState < boolean > ( false ) ;
28- const [ title , setTitle ] = useState ( '' ) ;
29- const [ subTitle , setSubTitle ] = useState ( '' ) ;
30- const [ content , setContent ] = useState < string | undefined > ( '' ) ;
31- const [ profileImage , setProfileImage ] = useState < string | StaticImport > ( ) ;
32- const [ thumbnailImage , setThumbnailImage ] = useState < string | StaticImport > ( ) ;
33- const [ seriesList , setSeriesList ] = useState < Series [ ] > ( [ ] ) ;
34- const [ seriesId , setSeriesId ] = useState < string > ( ) ;
35- const [ seriesLoading , setSeriesLoading ] = useState ( true ) ;
36- const [ errors , setErrors ] = useState < string [ ] > ( [ ] ) ;
37- const [ tags , setTags ] = useState < string [ ] > ( [ ] ) ;
38- const [ isPrivate , setIsPrivate ] = useState < boolean > ( false ) ;
3920
40- const toast = useToast ( ) ;
41- const router = useRouter ( ) ;
42- const NICKNAME = '개발자 서정우' ;
43- const [ createSeriesOpen , setCreateSeriesOpen ] = useState ( false ) ;
44- // 임시저장 상태
45- const { draft, draftImages, updateDraft, clearDraft } = useDraft ( ) ;
46- // 이미지 상태
47- const [ uploadedImages , setUploadedImages ] = useState < string [ ] > ( [ ] ) ;
48-
49- const postBody : PostBody = {
21+ const {
5022 title,
5123 subTitle,
52- author : NICKNAME ,
53- content : content || '' ,
54- profileImage,
55- thumbnailImage,
56- seriesId : seriesId || '' ,
57- tags : tags ,
58- isPrivate : isPrivate ,
59- } ;
24+ submitLoading,
25+ seriesLoading,
26+ seriesId,
27+ seriesList,
28+ content,
29+ setTitle,
30+ setSubTitle,
31+ setContent,
32+ setSeriesId,
33+ setIsPrivate,
34+ isPrivate,
35+ tags,
36+ setTags,
37+ uploadedImages,
38+ setUploadedImages,
39+ overwriteDraft,
40+ saveToDraft,
41+ clearDraftInStore,
42+ submitHandler,
43+ postBody,
44+ errors,
45+ handleLinkCopy,
46+ } = usePost ( slug || '' ) ;
6047
48+ const [ createSeriesOpen , setCreateSeriesOpen ] = useState ( false ) ;
6149 useBlockNavigate ( { title, content : content || '' } ) ;
6250
63- useEffect ( ( ) => {
64- getSeries ( ) ;
65- } , [ ] ) ;
66-
67- useEffect ( ( ) => {
68- if ( slug ) {
69- getPostDetail ( ) ;
70- }
71- } , [ slug ] ) ;
72-
73- // 시리즈
74- const getSeries = async ( ) => {
75- try {
76- const data = await getAllSeriesData ( ) ;
77- setSeriesList ( data ) ;
78- setSeriesId ( data [ 0 ] . _id ) ;
79- setSeriesLoading ( false ) ;
80- } catch ( e ) {
81- console . error ( '시리즈 조회 중 오류 발생' , e ) ;
82- }
83- } ;
84-
85- // 블로그
86- const postBlog = async ( post : PostBody ) => {
87- try {
88- const response = await axios . post ( '/api/posts' , post ) ;
89- if ( response . status === 201 ) {
90- toast . success ( '글이 성공적으로 발행되었습니다.' ) ;
91- router . push ( '/posts' ) ;
92- }
93- } catch ( e ) {
94- toast . error ( '글 발행 중 오류 발생했습니다.' ) ;
95- console . error ( '글 발행 중 오류 발생' , e ) ;
96- }
97- } ;
98-
99- const updatePost = async ( post : PostBody ) => {
100- try {
101- const response = await axios . put ( `/api/posts/${ slug } ` , post ) ;
102- if ( response . status === 200 ) {
103- toast . success ( '글이 성공적으로 수정되었습니다.' ) ;
104- router . push ( '/posts' ) ;
105- }
106- } catch ( e ) {
107- toast . error ( '글 수정 중 오류 발생했습니다.' ) ;
108- console . error ( '글 수정 중 오류 발생' , e ) ;
109- }
110- } ;
111-
112- // 임시저장 관련 함수
113- const saveToDraft = ( ) => {
114- const { success } = updateDraft ( postBody , uploadedImages ) ;
115- if ( success ) {
116- toast . success ( '임시 저장되었습니다.' ) ;
117- } else {
118- toast . error ( '임시 저장 실패' ) ;
119- }
120- } ;
121-
122- const overwriteDraft = ( ) => {
123- if ( draft !== null ) {
124- if ( confirm ( '임시 저장된 글이 있습니다. 덮어쓰시겠습니까?' ) ) {
125- const { title, content, subTitle, seriesId, isPrivate } = draft ;
126- setTitle ( title || '' ) ;
127- setContent ( content ) ;
128- setSubTitle ( subTitle || '' ) ;
129- setSeriesId ( seriesId ) ;
130- setUploadedImages ( draftImages || [ ] ) ;
131- setIsPrivate ( isPrivate || false ) ;
132- }
133- } else {
134- toast . error ( '임시 저장된 글이 없습니다.' ) ;
135- }
136- } ;
137-
138- const clearDraftInStore = ( ) => {
139- clearDraft ( ) ;
140- toast . success ( '임시 저장이 삭제되었습니다.' ) ;
141- } ;
142-
143- const submitHandler = ( post : PostBody ) => {
144- try {
145- setSubmitLoading ( true ) ;
146- const { isValid, errors } = validatePost ( post ) ;
147- setErrors ( errors ) ;
148- if ( ! isValid ) {
149- toast . error ( '유효성 검사 실패' ) ;
150- console . error ( '유효성 검사 실패' , errors ) ;
151- setSubmitLoading ( false ) ;
152- return ;
153- }
154-
155- if ( slug ) {
156- updatePost ( post ) ;
157- } else {
158- postBlog ( post ) ;
159- }
160- clearDraft ( ) ;
161- } catch ( e ) {
162- console . error ( '글 발행 중 오류 발생' , e ) ;
163- setSubmitLoading ( false ) ;
164- }
165- } ;
166-
167- const getPostDetail = async ( ) => {
168- try {
169- const response = await axios . get ( `/api/posts/${ slug } ` ) ;
170- const data = await response . data ;
171- setTitle ( data . post . title || '' ) ;
172- setSubTitle ( data . post . subTitle ) ;
173- setContent ( data . post . content ) ;
174- setSeriesId ( data . post . seriesId || '' ) ;
175- setTags ( data . post . tags || [ ] ) ;
176- setIsPrivate ( data . post . isPrivate || false ) ;
177- } catch ( e ) {
178- console . error ( '글 조회 중 오류 발생' , e ) ;
179- }
180- } ;
181-
182- const handleLinkCopy = ( image : string ) => {
183- navigator . clipboard . writeText ( image ) ;
184- toast . success ( '이미지 링크가 복사되었습니다.' ) ;
185- } ;
186-
18751 return (
18852 < div className = { 'px-16' } >
18953 < PostMetadataForm
@@ -227,15 +91,7 @@ const BlogForm = () => {
22791 setUploadedImages = { setUploadedImages }
22892 onClick = { handleLinkCopy }
22993 />
230- { errors && (
231- < div className = { 'mt-2' } >
232- { errors . slice ( 0 , 3 ) . map ( ( error , index ) => (
233- < p key = { index } className = { 'text-sm text-red-500' } >
234- { error }
235- </ p >
236- ) ) }
237- </ div >
238- ) }
94+ < ErrorBox errors = { errors } />
23995 < PostWriteButtons
24096 slug = { slug }
24197 postBody = { postBody }
@@ -246,4 +102,18 @@ const BlogForm = () => {
246102 </ div >
247103 ) ;
248104} ;
105+
106+ const ErrorBox = ( { errors } : { errors : string [ ] | null } ) => {
107+ if ( ! errors ) return null ;
108+
109+ return (
110+ < div className = { 'mt-2' } >
111+ { errors . slice ( 0 , 3 ) . map ( ( error , index ) => (
112+ < p key = { index } className = { 'text-sm text-red-500' } >
113+ { error }
114+ </ p >
115+ ) ) }
116+ </ div >
117+ ) ;
118+ } ;
249119export default BlogForm ;
0 commit comments