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:
co-authored by
Cursor
parent
8008ea4db1
commit
27d5cd0186
+116
-4
@@ -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="متا تگها"
|
||||
|
||||
Reference in New Issue
Block a user