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
+116 -4
View File
@@ -1,6 +1,7 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { FormEvent, useCallback, useEffect, useState } from "react";
import ClothesShell from "@/components/clothes/ClothesShell";
@@ -11,7 +12,7 @@ import IconButton, {
MetaTagsIcon,
TrashIcon,
} from "@/components/ui/IconButton";
import { FormModal } from "@/components/ui/Modal";
import Modal, { FormModal } from "@/components/ui/Modal";
import Pagination, { usePagination } from "@/components/ui/Pagination";
import { BlogPost, formatBlogDate } from "@/data/blog";
import { ApiError } from "@/lib/api";
@@ -40,6 +41,7 @@ export default function BlogListPage() {
const [error, setError] = useState<string | null>(null);
const [draft, setDraft] = useState<Filters>(emptyFilters);
const [applied, setApplied] = useState<Filters>(emptyFilters);
const [detailPost, setDetailPost] = useState<BlogPost | null>(null);
const [metaPost, setMetaPost] = useState<BlogPost | null>(null);
const [metaTitle, setMetaTitle] = useState("");
const [metaDescription, setMetaDescription] = useState("");
@@ -145,6 +147,14 @@ export default function BlogListPage() {
plusHref="/blog/new"
plusLabel="مطلب جدید"
>
<div className="mb-3 flex flex-wrap gap-2">
<Link
href="/blog/categories"
className="border border-line bg-white px-4 py-2 text-sm text-ink transition-colors hover:border-ink"
>
مدیریت دسته‌بندی‌ها
</Link>
</div>
{error ? (
<p className="mb-2 rounded-lg border border-line bg-surface px-4 py-3 text-sm text-ink">
{error}
@@ -217,8 +227,9 @@ export default function BlogListPage() {
{pageItems.map((post, i) => (
<article
key={post.id}
className="blog-card dash-enter"
className="blog-card dash-enter cursor-pointer"
style={{ animationDelay: `${70 + i * 45}ms` }}
onClick={() => setDetailPost(post)}
>
<div className="blog-card-image">
<Image
@@ -244,7 +255,11 @@ export default function BlogListPage() {
{formatBlogDate(post.date)}
</time>
</div>
<p className="mt-1 text-sm text-muted">{post.author}</p>
<p className="mt-1 text-sm text-muted">
{post.categoryName
? `${post.categoryName} · ${post.author}`
: post.author}
</p>
<p className="mt-2 line-clamp-2 text-sm leading-6 text-ink/80">
{post.abstract}
</p>
@@ -262,7 +277,10 @@ export default function BlogListPage() {
) : null}
</div>
<div className="tree-row-actions shrink-0">
<div
className="tree-row-actions shrink-0"
onClick={(e) => e.stopPropagation()}
>
<IconButton
label="ویرایش"
variant="line"
@@ -308,6 +326,100 @@ export default function BlogListPage() {
)}
</ClothesShell>
<Modal
open={Boolean(detailPost)}
title="جزئیات مطلب"
onClose={() => setDetailPost(null)}
wide="full"
footer={
<button
type="button"
className="modal-btn-ghost"
onClick={() => setDetailPost(null)}
>
بستن
</button>
}
>
{detailPost ? (
<div className="grid grid-cols-1 gap-5 lg:grid-cols-[280px_1fr]">
<div className="space-y-3">
<div className="relative mx-auto aspect-[3/2] w-full max-w-[280px] overflow-hidden rounded-md bg-[#f3f3f1] lg:max-w-none">
<Image
src={detailPost.image}
alt={detailPost.title}
fill
unoptimized
sizes="280px"
className="object-cover"
/>
</div>
</div>
<div className="space-y-4 text-start">
<div>
<h3 className="text-xl text-ink sm:text-2xl">
{detailPost.title}
</h3>
<p className="mt-1 text-sm text-muted">
<time dateTime={detailPost.date} dir="ltr">
{formatBlogDate(detailPost.date)}
</time>
{detailPost.author ? ` · ${detailPost.author}` : null}
</p>
</div>
<dl className="grid grid-cols-1 gap-3 text-sm sm:grid-cols-2">
<div>
<dt className="text-xs text-muted">دسته‌بندی</dt>
<dd className="mt-0.5 text-ink">
{detailPost.categoryName || "—"}
</dd>
</div>
<div>
<dt className="text-xs text-muted">نویسنده</dt>
<dd className="mt-0.5 text-ink">
{detailPost.author || "—"}
</dd>
</div>
</dl>
{detailPost.abstract ? (
<div>
<p className="mb-1 text-xs text-muted">خلاصه</p>
<p className="text-sm leading-7 text-ink/90 whitespace-pre-wrap">
{detailPost.abstract}
</p>
</div>
) : null}
{detailPost.body ? (
<div>
<p className="mb-1 text-xs text-muted">متن</p>
<div
className="blog-detail-body text-sm leading-7 text-ink/90"
dangerouslySetInnerHTML={{ __html: detailPost.body }}
/>
</div>
) : null}
{detailPost.tags.length ? (
<div>
<p className="mb-1.5 text-xs text-muted">برچسب‌ها</p>
<div className="flex flex-wrap gap-1.5">
{detailPost.tags.map((tag) => (
<span key={tag} className="tag-chip !rounded-md !py-0.5">
{tag}
</span>
))}
</div>
</div>
) : null}
</div>
</div>
) : null}
</Modal>
<FormModal
open={Boolean(metaPost)}
title="متا تگ‌ها"