"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import { AlertTriangle, ArrowLeft, ArrowRight, FolderUp, ImageUp, Loader2, Save, Send, Trash2 } from "lucide-react"; import { useRouter } from "next/navigation"; import ConfirmAction from "@/components/ConfirmAction"; import Markdown from "@/components/Markdown"; import MarkdownEditor, { type MarkdownDirectionMode } from "@/components/MarkdownEditor"; import { useAuth } from "@/contexts/AuthContext"; import { Link } from "@/lib/router"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/lib/api"; import type * as Types from "@/lib/types"; import { resolveErrorMessage } from "@/lib/utils"; import { useToast } from "@/hooks/use-toast"; type Props = { postId: number | null; }; const emptyForm: Types.PostCreateSchema = { title: "", content: "", excerpt: "", category_id: null, tag_ids: [], writer_ids: [], status: "draft", is_featured: false, seo_title: "", seo_description: "", canonical_url: "", og_title: "", og_description: "", noindex: false, focus_keyword: "", }; export default function AdminBlogEditor({ postId }: Props) { const router = useRouter(); const { user } = useAuth(); const { toast } = useToast(); const featuredInputRef = useRef(null); const [form, setForm] = useState(emptyForm); const [post, setPost] = useState(null); const [categories, setCategories] = useState([]); const [tags, setTags] = useState([]); const [users, setUsers] = useState>([]); const [loading, setLoading] = useState(Boolean(postId)); const [saving, setSaving] = useState(false); const [uploadingFeatured, setUploadingFeatured] = useState(false); const [editorDirection, setEditorDirection] = useState("auto"); const isNew = postId == null; const featuredImage = post?.absolute_featured_image_preview_url || post?.absolute_featured_image_url || post?.featured_image; const canPersistPost = form.title.trim() && form.content.trim(); const canAssignWriters = Boolean(user?.is_staff || user?.is_superuser || user?.can_review_blog_posts); const reviewNote = post?.status === "changes_requested" ? post.review_note?.trim() : ""; useEffect(() => { Promise.all([api.getCategories(), api.getTags()]) .then(([categoryData, tagData]) => { setCategories(categoryData); setTags(tagData); }) .catch(() => undefined); }, []); useEffect(() => { if (!canAssignWriters) return; api.listBlogWriters() .then((data) => setUsers(data)) .catch(() => undefined); }, [canAssignWriters]); useEffect(() => { if (!postId) return; setLoading(true); api.getAdminBlogPost(postId) .then((data) => { setPost(data); setForm({ title: data.title, content: data.content, excerpt: data.excerpt ?? "", category_id: data.category?.id ?? null, tag_ids: data.tags.map((tag) => tag.id), writer_ids: data.writers?.map((writer) => writer.id) ?? [data.author.id], status: data.status as Types.PostCreateSchema["status"], is_featured: data.is_featured, seo_title: data.seo_title ?? "", seo_description: data.seo_description ?? "", canonical_url: data.canonical_url ?? "", og_title: data.og_title ?? "", og_description: data.og_description ?? "", noindex: Boolean(data.noindex), focus_keyword: data.focus_keyword ?? "", }); }) .catch((error) => { toast({ title: "دریافت نوشته ناموفق بود", description: resolveErrorMessage(error, "دوباره تلاش کنید"), variant: "destructive", }); }) .finally(() => setLoading(false)); }, [postId, toast]); const selectedTagIds = useMemo(() => form.tag_ids ?? [], [form.tag_ids]); const selectedWriterIds = useMemo(() => form.writer_ids ?? [], [form.writer_ids]); const updateForm = (key: K, value: Types.PostCreateSchema[K]) => { setForm((prev) => ({ ...prev, [key]: value })); }; const savePost = async () => { setSaving(true); try { const payload = { ...form, status: form.status || "draft" }; const saved = isNew ? await api.createPost(payload) : await api.updatePost(postId, payload); setPost(saved); toast({ title: "نوشته ذخیره شد", variant: "success" }); if (isNew) { router.replace(`/admin/blog/${saved.id}/edit`); } return saved; } catch (error) { toast({ title: "ذخیره ناموفق بود", description: resolveErrorMessage(error, "دوباره تلاش کنید"), variant: "destructive", }); return null; } finally { setSaving(false); } }; const submitForReview = async () => { const saved = await savePost(); if (!saved) return; try { const submitted = await api.submitBlogPost(saved.id); setPost(submitted); updateForm("status", submitted.status as Types.PostCreateSchema["status"]); toast({ title: "برای بررسی ارسال شد", variant: "success" }); } catch (error) { toast({ title: "ارسال ناموفق بود", description: resolveErrorMessage(error, "دوباره تلاش کنید"), variant: "destructive", }); } }; const ensureSavedPost = async () => { if (post?.id) return post; if (!canPersistPost) { toast({ title: "ابتدا نوشته را کامل کنید", description: "برای ذخیره پیش‌نویس و باز کردن مرکز آپلود، عنوان و متن نوشته لازم است.", variant: "destructive", }); return null; } return savePost(); }; const onFeaturedImageChange = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; event.currentTarget.value = ""; if (!file) return; const targetPost = await ensureSavedPost(); if (!targetPost) return; setUploadingFeatured(true); try { const updated = await api.uploadBlogPostFeaturedImage(targetPost.id, file); setPost(updated); toast({ title: "تصویر شاخص به‌روزرسانی شد", variant: "success" }); } catch (error) { toast({ title: "آپلود تصویر شاخص ناموفق بود", description: resolveErrorMessage(error, "دوباره تلاش کنید"), variant: "destructive", }); } finally { setUploadingFeatured(false); } }; const deleteFeaturedImage = async () => { if (!post?.id) return; setUploadingFeatured(true); try { const updated = await api.deleteBlogPostFeaturedImage(post.id); setPost(updated); toast({ title: "تصویر شاخص حذف شد", variant: "success" }); } catch (error) { toast({ title: "حذف تصویر شاخص ناموفق بود", description: resolveErrorMessage(error, "دوباره تلاش کنید"), variant: "destructive", }); } finally { setUploadingFeatured(false); } }; if (loading) { return (
); } return (

{isNew ? "نوشته جدید" : "ویرایش نوشته"}

{form.status || "draft"}

نوشتن مارک‌داون، پیش‌نمایش زنده و تنظیمات انتشار در یک محیط مینیمال.

{reviewNote ? (

این نوشته نیازمند اصلاح است

{reviewNote}

) : null} مشخصات نوشته اطلاعات اصلی، دسته‌بندی، نویسندگان، تصویر شاخص و فایل‌های ضمیمه.
updateForm("title", event.target.value)} className="text-right" />