1
+ 'use client'
2
+
3
+ import { CreatePost , UpdatePost } from '@/utils/profileActions' ;
4
+ import { startTransition , useActionState , useEffect , useState } from 'react'
5
+ import { FaImage } from "react-icons/fa" ;
6
+ import { getBlog } from '@/utils/getBlog' ;
7
+ import Image from 'next/image' ;
8
+
9
+ const initialState : { message : string | null , isCreated : boolean , errors ?: Record < string , string [ ] | undefined > , } = {
10
+ message : null ,
11
+ isCreated : false ,
12
+ errors :{ } ,
13
+ }
14
+
15
+ type placeholderValuesType = {
16
+ id :string ,
17
+ blogCover : string ,
18
+ blogName : string ,
19
+ hook ?: string ,
20
+ desc : string ,
21
+ blogTags : string [ ]
22
+ }
23
+
24
+ const placeholderValues : placeholderValuesType = {
25
+ id :'' ,
26
+ blogCover : '' ,
27
+ blogName : '' ,
28
+ hook : '' ,
29
+ desc : '' ,
30
+ blogTags : [ ]
31
+ }
32
+
33
+ const CreateBlog = ( { params} :{ params :{ blogid :string } } ) => {
34
+ const [ loading , setLoading ] = useState < boolean > ( false ) ;
35
+ const [ selectedTags , setSelectedTags ] = useState < string [ ] > ( [ ] ) ;
36
+ const [ action , setAction ] = useState < string > ( '' ) ;
37
+ const [ previewImage , setPreviewImage ] = useState < string | null > ( null ) ;
38
+ const [ placeholder , setPlaceholder ] = useState < placeholderValuesType > ( placeholderValues ) ;
39
+
40
+ // Use useEffect to fetch the blog id asynchronously
41
+ const [ blogid , setBlogid ] = useState < string | null > ( null ) ;
42
+
43
+ useEffect ( ( ) => {
44
+ const fetchBlogId = async ( ) => {
45
+
46
+ const resolvedBlogId = await params . blogid [ 0 ] ;
47
+ console . log ( resolvedBlogId ) ;
48
+
49
+ setBlogid ( resolvedBlogId ) ;
50
+ } ;
51
+
52
+ fetchBlogId ( ) ;
53
+ } , [ params ] ) ;
54
+ // const blogid = params.blogid?.[0];
55
+ const [ state , formAction ] = useActionState ( blogid ? UpdatePost : CreatePost , initialState ) ;
56
+
57
+ useEffect ( ( ) => {
58
+ const fetchBlog = async ( ) => {
59
+ if ( blogid ) {
60
+ const blog = await getBlog ( blogid ) ;
61
+ const { result } = blog ;
62
+
63
+ if ( result ) {
64
+ const { id, blogCover, blogName, hook, desc, blogTags} = result ;
65
+
66
+ const tagNames = blogTags . map ( ( tagObject : { tag : { name : string } } ) => tagObject . tag . name ) ;
67
+
68
+ setPlaceholder ( { id, blogCover, blogName, hook, desc, blogTags : tagNames } ) ;
69
+ setSelectedTags ( tagNames ) ;
70
+ }
71
+ }
72
+ } ;
73
+ fetchBlog ( ) ;
74
+ } , [ blogid ] ) ;
75
+
76
+
77
+
78
+ const handleTagClick = ( tag : string ) => {
79
+ setSelectedTags ( ( prevTags ) =>
80
+ prevTags . includes ( tag ) ? prevTags . filter ( ( t ) => t !== tag ) : [ ...prevTags , tag ]
81
+ ) ;
82
+ } ;
83
+
84
+ const handleFileChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
85
+ const file = e . target . files ?. [ 0 ] ;
86
+ if ( file ) {
87
+ const reader = new FileReader ( ) ;
88
+ reader . onloadend = ( ) => {
89
+ setPreviewImage ( reader . result as string ) ;
90
+ } ;
91
+ reader . readAsDataURL ( file ) ;
92
+ }
93
+ } ;
94
+
95
+ const handleSubmit = async ( e : React . FormEvent < HTMLElement > ) => {
96
+ e . preventDefault ( ) ;
97
+
98
+ const formData = new FormData ( e . currentTarget as HTMLFormElement ) ;
99
+ formData . append ( 'tags' , JSON . stringify ( selectedTags ) ) ;
100
+ formData . append ( 'action' , action ) ;
101
+
102
+ setLoading ( true ) ;
103
+ startTransition ( ( ) => {
104
+ formAction ( formData ) ;
105
+ setLoading ( false ) ;
106
+ } ) ;
107
+ }
108
+
109
+ useEffect ( ( ) => {
110
+ if ( state ?. isCreated ) {
111
+ setPlaceholder ( placeholderValues ) ;
112
+ setPreviewImage ( null ) ;
113
+ setSelectedTags ( [ ] ) ;
114
+ }
115
+ } , [ state ] )
116
+
117
+ return (
118
+ < div className = 'background p-6 rounded-lg max-w-4xl mb-20' >
119
+ < h1 className = 'text-xl font-bold mb-8' > Create a new blog</ h1 >
120
+ { ( state ?. message ) && < >
121
+ < div className = "flex gap-2" >
122
+ < p className = "success-message text-green-500 mb-4 font-bold" >
123
+ { state ?. message }
124
+ </ p >
125
+ </ div >
126
+ </ > }
127
+ { ( state ?. errors ?. root ) && < >
128
+ < div className = "flex gap-2" >
129
+ < p className = "error-message font-bold mb-4 text-sm" >
130
+ { state ?. errors ?. root }
131
+ </ p >
132
+ </ div >
133
+ </ > }
134
+ < form onSubmit = { handleSubmit } method = 'POST' >
135
+ < p className = 'label mb-3' > Upload blog cover < span className = 'asterik' > *</ span > </ p >
136
+ < label htmlFor = "blogCover" className = 'border-2 border-dashed rounded-lg h-40 flex flex-col gap-2 justify-center items-center cursor-pointer mb-5 secondaryBg' >
137
+ < FaImage className = 'label text-3xl' />
138
+ < p className = 'text-xs' > Upload Blog Cover Image</ p >
139
+ < p className = 'text-xs' > click to browse</ p >
140
+ < input
141
+ type = 'file'
142
+ name = 'blogCover'
143
+ id = 'blogCover'
144
+ className = 'hidden'
145
+ onChange = { handleFileChange }
146
+ />
147
+ </ label >
148
+ < input
149
+ type = "hidden"
150
+ name = "blogId"
151
+ value = { blogid || '' }
152
+ />
153
+ { placeholder . blogCover || previewImage ? (
154
+ < div className = "w-40 h-40 bg-gray-200 mb-5" >
155
+ < Image
156
+ src = { placeholder . blogCover || previewImage || '' }
157
+ alt = "Blog Cover Preview"
158
+ className = "w-full h-full object-cover rounded-lg"
159
+ width = { 160 }
160
+ height = { 160 }
161
+ priority
162
+ />
163
+ </ div >
164
+ ) : null }
165
+ { state . errors && state . errors . blogCover && (
166
+ < p className = "error-message mb-2" > { state . errors . blogCover } </ p >
167
+ ) }
168
+ < div className = "flex flex-col mb-5" >
169
+ < label className = "label" htmlFor = "blogName" > Blog Name < span className = "asterik" > *</ span > </ label >
170
+ < input
171
+ type = "text"
172
+ name = "blogName"
173
+ className = "input value w-full mt-2"
174
+ id = "blogName"
175
+ required
176
+ value = { placeholder . blogName }
177
+ placeholder = 'Eg. Next.js 15 is know released.'
178
+ onChange = { ( e ) => setPlaceholder ( ( prev ) => ( { ...prev , blogName : e . target . value } ) ) }
179
+ />
180
+ { state . errors && state . errors . blogName && (
181
+ < p className = "error-message" > { state . errors . blogName } </ p >
182
+ ) }
183
+ </ div >
184
+ < div className = "flex flex-col mb-5" >
185
+ < label className = "label" htmlFor = "hook" > Hook < span className = "asterik" > *</ span > </ label >
186
+ < input
187
+ type = "text"
188
+ name = "hook"
189
+ className = "input value w-full mt-2"
190
+ id = "hook"
191
+ placeholder = 'Eg. Did you know why next.js is so important? I tell you why'
192
+ required
193
+ value = { placeholder . hook }
194
+ onChange = { ( e ) => setPlaceholder ( ( prev ) => ( { ...prev , hook : e . target . value } ) ) }
195
+ />
196
+ { state . errors && state . errors . hook && (
197
+ < p className = "error-message" > { state . errors . hook } </ p >
198
+ ) }
199
+ </ div >
200
+ < div className = "flex flex-col mb-5" >
201
+ < label className = "label" htmlFor = "desc" >
202
+ Description < span className = "asterik" > *</ span >
203
+ </ label >
204
+ < textarea
205
+ name = "desc"
206
+ id = "desc"
207
+ className = "input value w-full mt-2"
208
+ rows = { 10 }
209
+ required
210
+ placeholder = 'Eg. <h1>What is Next.js.</h1>'
211
+ value = { placeholder . desc }
212
+ onChange = { ( e ) => setPlaceholder ( ( prev ) => ( { ...prev , desc : e . target . value } ) ) }
213
+ />
214
+ { state . errors ?. desc && (
215
+ < p className = "error-message" > { state . errors . desc } </ p >
216
+ ) }
217
+ </ div >
218
+ < p className = 'label mb-3' > Tags < span className = 'asterik' > *</ span > </ p >
219
+ < div className = "myBorder rounded-lg h-24 p-3 gap-2 items-center cursor-pointer mb-5 flex flex-wrap justify-start" >
220
+ { [ 'Design' , 'Research' , 'Technology' , 'Politics' , 'Development' ] . map ( ( tag ) => (
221
+ < span
222
+ key = { tag }
223
+ className = { `tag cursor-pointer ${
224
+ selectedTags . includes ( tag ) ? 'tagSelected' : ''
225
+ } `}
226
+ onClick = { ( ) => handleTagClick ( tag ) }
227
+ >
228
+ { tag }
229
+ </ span >
230
+ ) ) }
231
+ </ div >
232
+ { state . errors ?. tags && (
233
+ < p className = "error-message" > { state . errors . tags } </ p >
234
+ ) }
235
+
236
+ { loading && (
237
+ < div className = "fixed top-0 left-0 w-full h-full inset-0 bg-black opacity-75 flex justify-center items-center z-10" >
238
+ < div className = "spinner-border animate-spin inline-block w-16 h-16 border-4 border-t-4 border-blue-600 rounded-full" > </ div >
239
+ </ div >
240
+ ) }
241
+
242
+ < div className = "flex justify-between mt-10" >
243
+ < button type = "submit" onClick = { ( ) => setAction ( 'cancel' ) } className = "transparentBtn" >
244
+ Cancel
245
+ </ button >
246
+ < div className = "flex gap-7" >
247
+ < button type = "submit" onClick = { ( ) => setAction ( 'draft' ) } className = "secondaryBtn" >
248
+ Save as Draft
249
+ </ button >
250
+ < button type = "submit" onClick = { ( ) => setAction ( 'publish' ) } className = "primaryBtn" >
251
+ { blogid ? 'Update' : 'Publish' }
252
+ </ button >
253
+ </ div >
254
+ </ div >
255
+
256
+ </ form >
257
+ </ div >
258
+ )
259
+ }
260
+
261
+ export default CreateBlog
0 commit comments