Improve blog editor, details modal, and header display of first and last name.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-09 12:28:36 +03:30
co-authored by Cursor
parent 8008ea4db1
commit 27d5cd0186
8 changed files with 979 additions and 45 deletions
+50 -25
View File
@@ -2,13 +2,27 @@
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { ChangeEvent, FormEvent, Suspense, useEffect, useRef, useState } from "react";
import {
ChangeEvent,
FormEvent,
Suspense,
useEffect,
useRef,
useState,
} from "react";
import ClothesShell from "@/components/clothes/ClothesShell";
import CategoryTreeSelect from "@/components/ui/CategoryTreeSelect";
import ImageCropper from "@/components/ui/ImageCropper";
import { FormModal } from "@/components/ui/Modal";
import RichTextArea from "@/components/ui/RichTextArea";
import { CategoryNode } from "@/data/clothes";
import { ApiError, uploadMedia } from "@/lib/api";
import { createBlog, fetchBlog, updateBlog } from "@/lib/blog-api";
import {
createBlog,
fetchBlog,
fetchBlogCategoriesTree,
updateBlog,
} from "@/lib/blog-api";
const DEFAULT_IMAGE = "/images/ahmad-ebadi-r5IzkiO08JU-unsplash.jpg";
@@ -19,8 +33,8 @@ function NewBlogPostForm() {
const fileRef = useRef<HTMLInputElement>(null);
const [title, setTitle] = useState("");
const [date, setDate] = useState(() => new Date().toISOString().slice(0, 10));
const [author, setAuthor] = useState("");
const [categoryId, setCategoryId] = useState("");
const [categoryTree, setCategoryTree] = useState<CategoryNode[]>([]);
const [abstract, setAbstract] = useState("");
const [body, setBody] = useState("");
const [imagePreview, setImagePreview] = useState(DEFAULT_IMAGE);
@@ -39,6 +53,21 @@ function NewBlogPostForm() {
metaTitle.trim() || metaDescription.trim() || metaKeywords.trim(),
);
useEffect(() => {
let cancelled = false;
(async () => {
try {
const cats = await fetchBlogCategoriesTree();
if (!cancelled) setCategoryTree(cats);
} catch {
/* optional */
}
})();
return () => {
cancelled = true;
};
}, []);
useEffect(() => {
if (!editId) return;
let cancelled = false;
@@ -49,8 +78,7 @@ function NewBlogPostForm() {
const post = await fetchBlog(editId);
if (cancelled) return;
setTitle(post.title);
setDate(post.date);
setAuthor(post.author);
setCategoryId(post.categoryId || "");
setAbstract(post.abstract);
setBody(post.body);
setImagePreview(post.image);
@@ -111,10 +139,14 @@ function NewBlogPostForm() {
title: title.trim(),
summary: abstract.trim() || undefined,
body: body || undefined,
author: author.trim() || undefined,
metaTitle: metaTitle.trim() || undefined,
metaDescription: metaDescription.trim() || undefined,
published: true,
...(categoryId
? { categoryId }
: editId
? { categoryId: null }
: {}),
...(imageUrl ? { imageUrl, imageKey } : {}),
};
@@ -145,7 +177,7 @@ function NewBlogPostForm() {
<ClothesShell
eyebrow={editId ? "Edit Post" : "New Post"}
title={editId ? "ویرایش مطلب" : "مطلب جدید"}
description="کاور ۳:۲ را برش دهید و محتوای مطلب را کامل کنید."
description="کاور ۳:۲ را برش دهید و محتوای مطلب را کامل کنید. تاریخ و نویسنده به‌صورت خودکار ثبت می‌شوند."
breadcrumbs={[
{ label: "داشبورد", href: "/" },
{ label: "بلاگ", href: "/blog" },
@@ -206,22 +238,13 @@ function NewBlogPostForm() {
/>
</div>
<div className="clothes-field sm:col-span-4">
<label htmlFor="blog-date">تاریخ</label>
<input
id="blog-date"
type="date"
dir="ltr"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
</div>
<div className="clothes-field sm:col-span-12">
<label htmlFor="blog-author">نویسنده</label>
<input
id="blog-author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
placeholder="نام نویسنده"
<label htmlFor="blog-category">دسته‌بندی</label>
<CategoryTreeSelect
id="blog-category"
nodes={categoryTree}
value={categoryId}
onChange={setCategoryId}
placeholder="انتخاب دسته بلاگ..."
/>
</div>
<div className="clothes-field sm:col-span-12">
@@ -367,7 +390,9 @@ function NewBlogPostForm() {
export default function NewBlogPostPage() {
return (
<Suspense fallback={<p className="p-6 text-sm text-muted">در حال بارگذاری...</p>}>
<Suspense
fallback={<p className="p-6 text-sm text-muted">در حال بارگذاری...</p>}
>
<NewBlogPostForm />
</Suspense>
);