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