কমিট করার পরে নতুন ডিপ্লয় শুরু হয়েছে কি না Vercel-এ দেখে নিন। সেখানে নতুন লগেও এই এরর এলে লগের পুরোটা, বিশেষ করে সময়সহ, আমাকে পাঠান।
16/10/2026
"use client";
import { useActionState, useEffect, useState } from "react";
import Image from "next/image";
import { savePost, type PostState } from "@/app/actions/posts";
import { parseTags } from "@/lib/tags";
import RichEditor from "@/components/rich-editor";
import SeoPanel from "@/components/seo-panel";
import { Code, Eye, Pencil, Wrench } from "lucide-react";
// HTML ট্যাবে লেখা পোস্ট চেনার জন্য একটি র্যাপার ব্যবহার হয়।
// not-prose ক্লাস Tailwind prose এর স্টাইল থেকে পোস্টকে আলাদা রাখে।
const OPEN = '
';
const isRawBody = (b: string) => b.trim().startsWith(OPEN);
const wrapRaw = (h: string) => (isRawBody(h) ? h.trim() : `${OPEN}${h.trim()}
`);
const unwrapRaw = (b: string) => {
const t = b.trim();
return isRawBody(t) ? t.slice(OPEN.length).replace(/<\/div>\s*$/, "").trim() : b;
};
export type PostFormData = {
id?: string; title: string; slug: string; categoryId: string; tags: string; published: boolean; focusKeyword: string; keywords: string;
metaTitle: string; metaDescription: string; coverUrl: string | null; coverAlt: string; downloadUrl: string; body: string;
};
const empty: PostFormData = {
title: "", slug: "", categoryId: "", tags: "", published: true, focusKeyword: "", keywords: "", metaTitle: "", metaDescription: "",
coverUrl: null, coverAlt: "", downloadUrl: "", body: "",
};
const f = "w-full rounded-xl border border-slate-300 bg-white px-3 py-2";
const init: PostState = { msg: "" };
type Tab = "compose" | "html" | "preview";
const isEscapedTable = (s: string) => /<\s*table\b/i.test(s);
const risky = (s: string) => / হয়ে যাওয়া কোড আবার ঠিক করে
function repairEscapedHtml(s: string): string {
return s
.replace(/<\/(div|p)>\s*<(div|p)[^>]*>/gi, "\n")
.replace(/^\s*<(div|p)[^>]*>/i, "")
.replace(/<\/(div|p)>\s*$/i, "")
.replace(//gi, "\n")
.replace(/<\/?a\b[^>]*>/gi, "")
.replace(//g, ">").replace(/"/g, '"')
.replace(/'/g, "'").replace(/ /g, " ").replace(/&/g, "&")
.trim();
}
function L({ t, hint, n, max }: { t: string; hint?: string; n?: number; max?: number }) {
return (
{t}{hint && {hint}}
{max !== undefined && n !== undefined && (
max ? "text-red-600" : n >= max * 0.8 ? "text-orange-600" : "text-slate-500"}`}>{n}/{max}
)}
);
}
export default function PostForm({ post = empty, cats }: { post?: PostFormData; cats: { id: string; name: string }[] }) {
const [state, action, pending] = useActionState(savePost.bind(null, post.id ?? null), init);
const [v, setV] = useState({
title: post.title, slug: post.slug, tags: post.tags, focusKeyword: post.focusKeyword, keywords: post.keywords,
metaTitle: post.metaTitle, metaDescription: post.metaDescription,
});
const startRaw = isRawBody(post.body) || /(startRaw ? "html" : "compose");
const [info, setInfo] = useState({ text: "", headings: [] as string[], words: 0 });
const on = (k: keyof typeof v) => (e: React.ChangeEvent) => setV({ ...v, [k]: e.target.value });
useEffect(() => {
const doc = new DOMParser().parseFromString(html.replace(/<\/(p|h[1-6]|li|blockquote|div)>/gi, " "), "text/html");
const text = (doc.body.textContent ?? "").replace(/\s+/g, " ").trim();
setInfo({ text, headings: Array.from(doc.querySelectorAll("h2,h3,h4")).map((h) => (h.textContent ?? "").trim()), words: text ? text.split(" ").length : 0 });
}, [html]);
const goTab = (next: Tab) => {
if (next === "compose" && raw) {
if (risky(html) && !confirm("Compose এডিটর টেবিল ও স্টাইল মুছে ফেলতে পারে। তবুও Compose-এ যাবেন?")) return;
setRaw(false);
}
setTab(next);
};
const previewDoc =
`` +
`` +
`${html}`;
const tabBtn = (t: Tab, label: string, icon: React.ReactNode) => (
goTab(t)}
className={`inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-semibold transition ${tab === t ? "bg-royal text-white" : "bg-slate-100 text-slate-700 hover:bg-slate-200"}`}
>
{icon}{label}
);
const tagList = parseTags(v.tags, info.text);
return (
{tabBtn("compose", "Compose", )}
{tabBtn("html", "HTML", )}
{tabBtn("preview", "Preview", )}
{isEscapedTable(html) && (
এই পোস্টের টেবিল কোড লেখা হিসেবে সেভ হয়ে গেছে। এক ক্লিকে ঠিক করা যাবে।
{ setHtml(repairEscapedHtml(html)); setRaw(true); setTab("html"); }}
className="inline-flex items-center gap-1.5 rounded-lg bg-amber-600 px-3 py-1.5 font-semibold text-white hover:bg-amber-700"
>
টেবিল কোড ঠিক করুন
)}
{/* সার্ভারে সবসময় এই একটি ফিল্ডই যায়। raw হলে কোড হুবহু র্যাপারসহ সেভ হয় */}
{tab === "compose" && }
{tab === "html" && (
