import React, { useState, useMemo, useRef } from 'react'; import { Key, Send, CheckCircle, AlertCircle, Image as ImageIcon, Bold, Italic, Link as LinkIcon, List, Heading1, Heading2, Upload, X, Copy, Terminal, Eye, Edit3 } from 'lucide-react'; export default function HaravanSimpleBlog() { const [apiKey, setApiKey] = useState(''); const [blogId, setBlogId] = useState(''); const [shopDomain, setShopDomain] = useState(''); const [title, setTitle] = useState(''); const [handle, setHandle] = useState(''); const [content, setContent] = useState(''); const [excerpt, setExcerpt] = useState(''); const [metaTitle, setMetaTitle] = useState(''); const [metaDescription, setMetaDescription] = useState(''); const [tags, setTags] = useState(''); const [author, setAuthor] = useState(''); const [featuredImage, setFeaturedImage] = useState(null); const [images, setImages] = useState([]); const [preview, setPreview] = useState(false); const [loading, setLoading] = useState(false); const [notification, setNotification] = useState(null); const [curlCmd, setCurlCmd] = useState(''); const editorRef = useRef(null); const fileInputRef = useRef(null); const featuredInputRef = useRef(null); const slugify = (text) => text.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '') .replace(/đ/g, 'd').replace(/[^a-z0-9\s-]/g, '') .trim().replace(/\s+/g, '-').slice(0, 70); const handleTitle = (e) => { const v = e.target.value; setTitle(v); if (!handle || handle === slugify(title)) setHandle(slugify(v)); if (!metaTitle) setMetaTitle(v); }; // SEO quick check const seo = useMemo(() => { const wc = content.replace(/<[^>]*>/g, '').split(/\s+/).filter(Boolean).length; const checks = [ { ok: title.length >= 30 && title.length <= 60, t: `Tiêu đề ${title.length}/60 ký tự` }, { ok: metaDescription.length >= 120 && metaDescription.length <= 160, t: `Meta desc ${metaDescription.length}/160` }, { ok: wc >= 300, t: `${wc} từ (tối thiểu 300)` }, { ok: !!featuredImage, t: 'Có ảnh đại diện' }, { ok: / c.ok).length / checks.length) * 100); return { checks, score, wc }; }, [title, content, metaDescription, featuredImage, tags, handle]); const scoreColor = seo.score >= 80 ? 'text-green-600' : seo.score >= 50 ? 'text-yellow-600' : 'text-red-600'; // Editor commands const cmd = (c, v = null) => { document.execCommand(c, false, v); if (editorRef.current) setContent(editorRef.current.innerHTML); }; const insertLink = () => { const url = window.prompt('URL:'); if (url) cmd('createLink', url); }; const readFile = (file) => new Promise(res => { const r = new FileReader(); r.onload = e => res(e.target.result); r.readAsDataURL(file); }); const uploadImages = async (e) => { const files = Array.from(e.target.files); for (const f of files) { const b64 = await readFile(f); const img = { id: Date.now() + Math.random(), name: f.name, base64: b64, alt: f.name.replace(/\.[^.]+$/, '') }; setImages(prev => [...prev, img]); } }; const insertImg = (img) => { const tag = `${img.alt}`; if (editorRef.current) { editorRef.current.focus(); document.execCommand('insertHTML', false, tag); setContent(editorRef.current.innerHTML); } }; const uploadFeatured = async (e) => { const f = e.target.files[0]; if (!f) return; const b64 = await readFile(f); setFeaturedImage({ name: f.name, base64: b64, alt: title || f.name }); }; const buildPayload = () => ({ article: { title, author: author || 'Admin', body_html: content, summary_html: excerpt, tags, handle, published: true, meta_title: metaTitle, meta_description: metaDescription, ...(featuredImage && { image: { attachment: featuredImage.base64.split(',')[1], filename: featuredImage.name } }) } }); const publish = async () => { if (!apiKey || !blogId || !title || !content) { return setNotification({ type: 'error', msg: 'Thiếu API Key, Blog ID, tiêu đề hoặc nội dung!' }); } setLoading(true); setCurlCmd(''); const payload = buildPayload(); const url = `https://apis.haravan.com/com/blogs/${blogId}/articles.json`; // Tạo cURL dự phòng const curl = `curl -X POST "${url}" \\ -H "Authorization: Bearer ${apiKey}" \\ -H "Content-Type: application/json" \\ -d '${JSON.stringify(payload).replace(/'/g, "'\\''")}'`; try { const res = await fetch(url, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); setNotification({ type: 'success', msg: `✓ Đăng thành công! ID: ${data.article?.id || '(xem Haravan Admin)'}` }); } catch (err) { // CORS hoặc lỗi khác → show cURL setCurlCmd(curl); setNotification({ type: 'warn', msg: 'Trình duyệt chặn (CORS). Copy lệnh cURL bên dưới dán vào Terminal/CMD để đăng bài.' }); } finally { setLoading(false); } }; const copyCurl = () => { navigator.clipboard.writeText(curlCmd); setNotification({ type: 'success', msg: 'Đã copy cURL! Mở Terminal/CMD paste vào Enter để đăng.' }); }; const copyPayload = () => { navigator.clipboard.writeText(JSON.stringify(buildPayload(), null, 2)); setNotification({ type: 'success', msg: 'Đã copy JSON payload!' }); }; return (
{/* Header */}

📝 Haravan Blog Tool

Viết & đẩy bài chuẩn SEO nhanh gọn

SEO: {seo.score}/100
{/* Notification */} {notification && (
{notification.type === 'success' ? : } {notification.msg}
)} {/* cURL fallback */} {curlCmd && (
Lệnh cURL — dán vào Terminal/CMD
{curlCmd}
)} {/* Config */}
Cấu hình Haravan
setApiKey(e.target.value)} className="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500" /> setBlogId(e.target.value)} className="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500" /> setAuthor(e.target.value)} className="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500" />
{preview ? ( // Preview mode
{featuredImage && {featuredImage.alt}}

{title || 'Tiêu đề'}

{excerpt &&

{excerpt}

}
Chưa có nội dung

' }} /> {tags && (
{tags.split(',').map((t, i) => ( #{t.trim()} ))}
)}
) : (
{/* Main editor */}
URL slug: setHandle(slugify(e.target.value))} className="flex-1 px-2 py-1 border border-slate-200 rounded text-xs font-mono focus:ring-2 focus:ring-blue-500" />