import React, { useState } from 'react'; import { useParams, useNavigate } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useForm, Controller } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { toast } from 'sonner'; import { CKEditor } from '@ckeditor/ckeditor5-react'; import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import type { Blog, City } from '../types'; import type { PaginatedResponse } from '../lib/api'; import { useAuthStore } from '../stores/authStore'; import { blogFormSchema, blogDefaults, buildBlogPayload } from '../lib/blogForm'; import type { BlogFormData } from '../lib/blogForm'; import PageHeader from '../components/ui/PageHeader'; import SearchableSelect from '../components/ui/SearchableSelect'; import BlogSeoFields from '../components/BlogSeoFields'; import Modal from '../components/ui/Modal'; async function uploadBlogImage(file: File): Promise { const token = useAuthStore.getState().token; const fd = new FormData(); fd.append('file', file); const res = await fetch('/file/upload/clinic_pro/blog/field_image', { method: 'POST', headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: fd, }); const json = await res.json().catch(() => ({})); if (res.status === 401) { useAuthStore.getState().logout(); window.location.replace('/admin/login'); throw new Error('نشست منقضی شده است'); } if (!res.ok) throw new Error(json?.errors?.[0]?.message ?? 'خطا در آپلود تصویر'); return json.data.image_url as string; } export default function BlogFormPage() { const { uuid } = useParams<{ uuid: string }>(); const navigate = useNavigate(); const qc = useQueryClient(); const isEdit = !!uuid; const [uploading, setUploading] = useState(false); const [aiOpen, setAiOpen] = useState(false); const { data, isLoading, isError, error } = useQuery({ queryKey: ['blog', uuid], // اندپوینت ادمین پیش‌نویس و آرشیو را هم برمی‌گرداند؛ اندپوینت عمومی فقط // published است و برای پیش‌نویس ۴۰۴ می‌داد → فرم خالی بدون هیچ پیام خطا. queryFn: () => api.get>(`/api/v1/admin/blog/${uuid}`), enabled: isEdit, retry: false, }); const blog = data?.data?.data; const citiesQuery = useQuery({ queryKey: ['cities-select'], queryFn: () => api.get>('/api/v1/admin/cities?limit=200'), staleTime: 5 * 60_000, }); const cityOptions = (citiesQuery.data?.data ?? []).map((c) => ({ value: c.id, label: c.name })); const { register, handleSubmit, control, watch, setValue, formState: { errors } } = useForm({ resolver: zodResolver(blogFormSchema), defaultValues: blogDefaults(), values: blog ? (blogDefaults(blog) as BlogFormData) : undefined, }); const imageUrl = watch('image_url'); const createMutation = useMutation({ mutationFn: (d: BlogFormData) => api.post>('/api/v1/blog', buildBlogPayload(d)), onSuccess: () => { toast.success('مقاله ذخیره شد'); qc.invalidateQueries({ queryKey: ['blogs'] }); navigate('/admin/blogs'); }, onError: (err: Error) => toast.error(err.message), }); const updateMutation = useMutation({ mutationFn: (d: BlogFormData) => api.patch>(`/api/v1/blog/${uuid}`, buildBlogPayload(d)), onSuccess: () => { toast.success('مقاله بروزرسانی شد'); qc.invalidateQueries({ queryKey: ['blogs'] }); qc.invalidateQueries({ queryKey: ['blog', uuid] }); navigate('/admin/blogs'); }, onError: (err: Error) => toast.error(err.message), }); const saving = createMutation.isPending || updateMutation.isPending; const onSubmit = (d: BlogFormData) => { if (isEdit) updateMutation.mutate(d); else createMutation.mutate(d); }; const onImageSelect = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setUploading(true); try { const url = await uploadBlogImage(file); setValue('image_url', url, { shouldValidate: true }); toast.success('تصویر آپلود شد'); } catch (err) { toast.error((err as Error).message); } finally { setUploading(false); e.target.value = ''; } }; if (isEdit && isLoading) { return (
{Array.from({ length: 5 }).map((_, i) => (
))}
); } // فرم خالیِ قابل‌ثبت نمایش داده نشود: کاربر باید بفهمد مقاله بارگذاری نشده. if (isEdit && (isError || !blog)) { return (

{(error as Error | null)?.message ?? 'مقاله یافت نشد'}

); } return (
setAiOpen(true)}> تولید با هوش مصنوعی } />
{errors.title &&

{errors.title.message}

}