import { useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { BytescaleUploader } from '@/components/BytescaleUploader/BytescaleUploader'; import EditorJS, { OutputData } from '@editorjs/editorjs'; import Header from '@editorjs/header'; import List from '@editorjs/list'; import ImageTool from '@editorjs/image'; import Quote from '@editorjs/quote'; import Table from '@editorjs/table'; import CodeTool from '@editorjs/code'; import Embed from '@editorjs/embed'; import Delimiter from '@editorjs/delimiter'; import axios from 'axios'; import { createBlogApi, updateBlogApi, getBlogByIdApi, uploadImageApi } from '@/api/blog'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; export default function BlogEditorPage() { const baseURL = import.meta.env.VITE_API_URL; const { id } = useParams(); const navigate = useNavigate(); const editorRef = useRef(null); const hasInitialized = useRef(false); const hasRenderedContent = useRef(false); const [title, setTitle] = useState(''); const [writer, setWriter] = useState(''); const [coverImage, setCoverImage] = useState(''); const [loading, setLoading] = useState(false); const isEdit = Boolean(id); useEffect(() => { if (hasInitialized.current) return; hasInitialized.current = true; let editor: EditorJS; const initEditor = async () => { editor = new EditorJS({ holder: 'editorjs', placeholder: 'Write blog content...', tools: { header: { class: Header, inlineToolbar: true, config: { placeholder: 'Enter heading', levels: [1, 2, 3, 4], defaultLevel: 2, }, }, list: { class: List, inlineToolbar: true, config: { defaultStyle: 'unordered', }, }, quote: Quote, table: Table, code: CodeTool, embed: Embed, delimiter: Delimiter, image: { class: ImageTool, config: { uploader: { uploadByFile: async (file: File) => { if (file.size > 5 * 1024 * 1024) { alert('File is too large (Max 5MB)'); return { success: 0, file: { url: '' } }; } const formData = new FormData(); formData.append('file', file); formData.append('folderPath', '/blog'); try { const response = await axios.post(`${baseURL}/upload`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); return { success: 1, file: { url: response.data.fileUrl }, }; } catch (e: any) { console.error('EditorJS Image Upload Error:', e); const errorMessage = e.response?.data?.error || e.message || 'Upload failed'; alert(`Upload Error: ${errorMessage}`); return { success: 0, file: { url: '' }, }; } }, }, }, }, }, }); await editor.isReady; editorRef.current = editor; if (isEdit && id && !hasRenderedContent.current) { try { const res = await getBlogByIdApi(Number(id)); setTitle(res.title); setWriter(res.writer); setCoverImage(res.image || ''); if (res.content) { await editor.blocks.clear(); await editor.render(res.content); hasRenderedContent.current = true; } } catch (err) { console.error(err); } } }; initEditor(); }, [id, isEdit]); const handleSave = async () => { if (!editorRef.current) return; setLoading(true); try { const content: OutputData = await editorRef.current.save(); const payload = { title, writer, image: coverImage, content, isActive: true, }; if (isEdit) { await updateBlogApi(Number(id), payload); } else { await createBlogApi(payload); } navigate('/blog'); } catch (err) { console.error(err); } finally { setLoading(false); } }; return (
{isEdit ? 'Edit Blog' : 'Create Blog'} setTitle(e.target.value)} /> setWriter(e.target.value)} />
setCoverImage(url)} /> {coverImage && ( cover )}
); }