MATIN MOLKARA
SERIES: آموزش Next js — PART 23 OF 28

قسمت بیست و سوم - تمرین عملی ساخت مدیریت کامل پست‌ها در داشبورد ادمین

در این آموزش از شما مخاطبین عزیز دعوت میشه تا دست به کار بشید و تمرین عملی این بخش رو انجام بدید!

تمرین عملی ساخت مدیریت کامل پست‌ها در داشبورد ادمین

تا اینجای مسیر، ما چند بخش مهم از پنل ادمین را کنار هم ساخته‌ایم:

  • احراز هویت ادمین
  • محافظت از مسیرهای داشبورد
  • ساختار کلی پنل مدیریت
  • مدیریت کامل پروژه‌ها در داشبورد

در قسمت قبل، ما ماژول Projects را به‌صورت کامل پیاده‌سازی کردیم؛
یعنی ادمین می‌توانست:

  • لیست پروژه‌ها را ببیند
  • پروژه جدید بسازد
  • پروژه موجود را ویرایش کند
  • پروژه‌ای را حذف کند

یعنی من در این پست:

  • ساختار مسیرها را مشخص می‌کنم
  • فایل‌های لازم را مشخص می‌کنم
  • مسئولیت هر بخش را توضیح می‌دهم
  • نکات مهم پیاده‌سازی را می‌گویم
  • و مسیر حل را کاملاً روشن می‌کنم

اما این بار، بخش اصلی پیاده‌سازی را شما انجام می‌دهید.

هدف این است که فقط کد را تکرار نکنید، بلکه الگوی ساخت یک ماژول مدیریتی کامل را واقعاً یاد بگیرید.


هدف این تمرین چیست؟

در پایان این تمرین، باید بتوانید ماژول کامل مدیریت پست‌ها را در داشبورد ادمین بسازید.

یعنی این قابلیت‌ها را داشته باشید:

  • نمایش لیست پست‌ها در داشبورد
  • ساخت پست جدید
  • ویرایش پست موجود
  • حذف پست
  • اتصال فرم‌ها به API
  • اعتبارسنجی داده‌ها
  • محافظت از صفحات ادمین و endpointهای مدیریتی

اگر قسمت مدیریت پروژه‌ها را کامل انجام داده باشید، این تمرین دقیقاً بهترین فرصت برای تثبیت همان الگو است.


قرار است چه مسیرهایی را بسازید؟

در این تمرین، باید این مسیرها را داشته باشید:

/admin/posts
/admin/posts/new
/admin/posts/[id]/edit

و در بخش API هم این endpointها را:

/api/posts
/api/posts/[id]

اگر به مدیریت پروژه‌ها نگاه کنید، متوجه می‌شوید که ساختار تقریباً یکسان است.
تفاوت اصلی فقط در موجودیت داده است:

  • آن‌جا با project کار می‌کردیم
  • این‌جا با post کار می‌کنیم

از ماژول پروژه‌ها به‌عنوان الگو استفاده کنید

بهترین روش برای انجام این تمرین این است که ماژول projects را الگوی مستقیم خودتان قرار دهید.

جدول زیر مسیر تبدیل را خیلی روشن می‌کند:

ماژول پروژه‌هاماژول پست‌ها
ProjectsTablePostsTable
ProjectFormPostForm
DeleteProjectButtonDeletePostButton
/admin/projects/admin/posts
/admin/projects/new/admin/posts/new
/admin/projects/[id]/edit/admin/posts/[id]/edit
POST /api/projectsPOST /api/posts
PATCH /api/projects/[id]PATCH /api/posts/[id]
DELETE /api/projects/[id]DELETE /api/posts/[id]

اگر این جدول را جلوی خودتان بگذارید، عملاً مسیر پیاده‌سازی کاملاً مشخص می‌شود.


مدل داده پست‌ها را بررسی کنید

قبل از اینکه وارد ساخت صفحه و فرم شوید، اول باید مدل Post خودتان را ببینید.

یک مدل رایج ممکن است چیزی شبیه این باشد:

prisma
model Post {
  id        String   @id @default(cuid())
  title     String
  slug      String   @unique
  excerpt   String?
  content   String
  published Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

ممکن است در پروژه شما فیلدهای بیشتری هم وجود داشته باشد، مثل:

  • coverImage
  • tags
  • readingTime
  • category

اما برای اصل این تمرین، کافی است روی فیلدهای پایه تمرکز کنید:

  • title
  • slug
  • excerpt
  • content
  • published

قبل از شروع، به این سؤال‌ها فکر کنید

برای اینکه این تمرین فقط تبدیل فایل‌ها نباشد، قبل از شروع این سؤال‌ها را از خودتان بپرسید:

  • کدام فیلدها باید در جدول لیست پست‌ها نمایش داده شوند؟
  • کدام فیلدها باید داخل فرم باشند؟
  • آیا excerpt اختیاری است؟
  • آیا slug باید unique باشد؟
  • در صفحه ویرایش، اگر پست پیدا نشد چه باید اتفاقی بیفتد؟
  • بعد از حذف موفق پست، چطور UI را به‌روزرسانی می‌کنید؟

ساختار فایل‌هایی که باید بسازید

برای این تمرین، انتظار می‌رود ساختاری شبیه این داشته باشید:

app/
  admin/
(dashboard)/
posts/
page.tsx
new/
page.tsx
[id]/
edit/
page.tsx

  api/
posts/
route.ts
[id]/
route.ts

components/
  admin/
posts-table.tsx
post-form.tsx
delete-post-button.tsx

lib/
  validations/
post.ts

اگر ساختار پروژه شما کمی متفاوت است، ایرادی ندارد.
مهم این است که این مسئولیت‌ها را در بخش‌های مناسب نگه دارید.


مسئولیت هر فایل چیست؟

در این بخش، دقیقاً مشخص می‌کنیم که هر فایل چه کاری باید انجام دهد.
این قسمت مهم‌ترین نقشه اجرایی تمرین شماست.


1. فایل app/admin/(dashboard)/posts/page.tsx

این فایل باید صفحه اصلی مدیریت پست‌ها باشد.

وظایف آن:

  • فقط برای ادمین قابل دسترسی باشد
  • با Prisma لیست پست‌ها را از دیتابیس بخواند
  • داده‌ها را مرتب‌سازی کند
  • کامپوننت جدول پست‌ها را رندر کند
  • یک لینک برای ساخت پست جدید داشته باشد

نکته

مثل صفحه /admin/projects عمل کنید.
بهتر است فقط فیلدهایی را select کنید که واقعاً برای جدول لازم هستند، مثلاً:

  • id
  • title
  • slug
  • excerpt
  • published
  • updatedAt

2. فایل components/admin/posts-table.tsx

این کامپوننت مسئول نمایش داده‌ها در جدول است.

وظایف آن:

  • اگر پستی وجود نداشت، یک empty state مناسب نمایش دهد
  • برای هر پست، اطلاعات کلیدی را نشان دهد
  • وضعیت انتشار را مشخص کند
  • لینک Edit داشته باشد
  • دکمه Delete داشته باشد

پیشنهاد ستون‌ها

برای شروع، این ستون‌ها کافی هستند:

  • Title
  • Slug
  • Excerpt
  • Status
  • Updated
  • Actions

نکته

اگر excerpt طولانی است، آن را truncate کنید تا جدول مرتب بماند.


3. فایل components/admin/post-form.tsx

این مهم‌ترین بخش تمرین شماست.

باید یک فرم مشترک بسازید که هم برای ساخت و هم برای ویرایش پست استفاده شود.

این فرم باید:

  • در حالت create کار کند
  • در حالت edit کار کند
  • initialValues بگیرد
  • وضعیت submit را مدیریت کند
  • خطاها را نشان دهد
  • بسته به mode درخواست POST یا PATCH بفرستد
  • بعد از موفقیت کاربر را به /admin/posts برگرداند

فیلدهای پایه فرم

برای شروع، این فیلدها را قرار دهید:

  • title
  • slug
  • excerpt
  • content
  • published

نکته

همان الگوی mode: 'create' | 'edit' که در فرم پروژه‌ها داشتید، این‌جا هم کاملاً مناسب است.


4. فایل app/admin/(dashboard)/posts/new/page.tsx

این صفحه وظیفه ساخت پست جدید را دارد.

وظایف آن:

  • ادمین بودن کاربر را بررسی کند
  • عنوان صفحه را نمایش دهد
  • لینک بازگشت به لیست پست‌ها داشته باشد
  • PostForm را در حالت create رندر کند

نکته

این صفحه باید بسیار ساده باشد، چون منطق اصلی داخل PostForm قرار می‌گیرد.


5. فایل app/admin/(dashboard)/posts/[id]/edit/page.tsx

این صفحه برای ویرایش یک پست موجود است.

وظایف آن:

  • ادمین بودن کاربر را بررسی کند
  • id را از مسیر بگیرد
  • پست را از دیتابیس بخواند
  • اگر پست وجود نداشت، notFound() را صدا بزند
  • فرم را با initialValues پر کند

نکته

در این صفحه فقط باید داده لازم برای ویرایش را بخوانید، نه همه فیلدهای ممکن مدل را.


6. فایل components/admin/delete-post-button.tsx

این کامپوننت باید Client Component باشد.

وظایف آن:

  • کلیک کاربر را مدیریت کند
  • قبل از حذف، تأیید بگیرد
  • درخواست DELETE به API بفرستد
  • در صورت موفقیت، جدول را refresh کند
  • در صورت خطا، پیام مناسب نمایش دهد

نکته

اگر در ماژول پروژه‌ها از window.confirm() و router.refresh() استفاده کرده‌اید، این‌جا هم همان الگو کاملاً مناسب است.


7. فایل app/api/posts/route.ts

این فایل مسئول ساخت پست جدید است.

وظایف آن:

  • دسترسی ادمین را بررسی کند
  • request.json() را بخواند
  • داده را با postSchema اعتبارسنجی کند
  • تکراری نبودن slug را بررسی کند
  • پست را در دیتابیس ایجاد کند
  • status code مناسب برگرداند

status codeهای پیشنهادی

  • 201 برای ساخت موفق
  • 400 برای داده نامعتبر
  • 409 برای slug تکراری
  • 500 برای خطای سرور

8. فایل app/api/posts/[id]/route.ts

این فایل باید هم ویرایش و هم حذف را مدیریت کند.

برای PATCH

  • دسترسی ادمین را بررسی کنید
  • داده را اعتبارسنجی کنید
  • وجود پست را بررسی کنید
  • slug تکراری را برای حالت ویرایش کنترل کنید
  • پست را به‌روزرسانی کنید

برای DELETE

  • دسترسی ادمین را بررسی کنید
  • وجود پست را بررسی کنید
  • پست را حذف کنید
  • پاسخ مناسب برگردانید

status codeهای پیشنهادی

  • 200 برای موفقیت
  • 400 برای داده نامعتبر
  • 404 اگر پست پیدا نشد
  • 409 برای تداخل اسلاگ
  • 500 برای خطای سرور

schema اعتبارسنجی پست‌ها

برای اینکه تمرکز تمرین روی CRUD و داشبورد باقی بماند، در این قسمت schema را به‌صورت کامل در اختیار شما می‌گذارم.

ts import { z } from 'zod'

export const postSchema = z.object({ title: z.string().trim().min(1, 'Title is required').max(160), slug: z .string() .trim() .min(1, 'Slug is required') .regex(/^[a-z0-9-]+$/, 'Slug format is invalid'), excerpt: z.string().trim().max(300).optional().or(z.literal('')), content: z.string().trim().min(1, 'Content is required'), published: z.boolean(), })

export type PostInput = z.infer<typeof postSchema>

این schema چه چیزی را تضمین می‌کند؟

  • عنوان خالی نباشد
  • اسلاگ خالی نباشد
  • اسلاگ فرمت قابل‌قبول داشته باشد
  • excerpt در صورت وجود بیش از حد طولانی نباشد
  • محتوا خالی نباشد
  • published مقدار بولی داشته باشد

ترتیب پیاده‌سازی پیشنهادی

اگر می‌خواهید این تمرین را بدون سردرگمی جلو ببرید، این ترتیب بهترین انتخاب است:

مرحله 1

ابتدا postSchema را بسازید.

مرحله 2

صفحه لیست پست‌ها یعنی /admin/posts را پیاده‌سازی کنید.

مرحله 3

کامپوننت PostsTable را بسازید.

مرحله 4

صفحه ساخت پست جدید و فرم PostForm را آماده کنید.

مرحله 5

endpoint ساخت پست یعنی POST /api/posts را پیاده‌سازی کنید.

مرحله 6

صفحه ویرایش پست را بسازید.

مرحله 7

endpoint ویرایش یعنی PATCH /api/posts/[id] را کامل کنید.

مرحله 8

دکمه حذف و endpoint حذف را اضافه کنید.

این ترتیب باعث می‌شود از ساده‌تر به کامل‌تر حرکت کنید.


Hint برای صفحه لیست پست‌ها

اگر نمی‌دانید از کجا شروع کنید، این نکته‌ها راه را روشن می‌کنند:

  • از requireAdmin() استفاده کنید
  • با Prisma از findMany() کمک بگیرید
  • رکوردها را بر اساس createdAt یا updatedAt مرتب کنید
  • نتیجه را به PostsTable پاس بدهید
  • یک دکمه یا لینک برای New Post اضافه کنید

Hint برای جدول پست‌ها

  • posts را به‌عنوان prop دریافت کنید
  • اگر آرایه خالی بود، empty state نمایش دهید
  • برای هر ردیف، Edit و Delete داشته باشید
  • updatedAt را فرمت کنید
  • وضعیت published را با یک badge ساده نشان دهید

Hint برای فرم پست‌ها

  • این کامپوننت باید use client باشد
  • از useState() برای فیلدها استفاده کنید
  • initialValues را برای حالت ویرایش پشتیبانی کنید
  • از یک prop مثل mode استفاده کنید
  • در حالت create درخواست را به /api/posts بفرستید
  • در حالت edit درخواست را به /api/posts/[id] بفرستید
  • بعد از موفقیت از router.push('/admin/posts') و router.refresh() استفاده کنید

Hint برای صفحه ویرایش

  • id را از params بگیرید
  • پست را با findUnique() بخوانید
  • اگر وجود نداشت، notFound() را اجرا کنید
  • داده اولیه را به فرم پاس بدهید

Hint برای حذف پست

  • DeletePostButton باید Client Component باشد پاسخ قبل از حذف از کاربر تأیید بگیرید
  • درخواست DELETE بفرستید
  • اگر پاسخ موفق بود، router.refresh() را صدا بزنید
  • اگر خطا رخ داد، پیام خطا نمایش دهید

Hint برای ساخت پست در API

در handler مربوط به POST این ترتیب را رعایت کنید:

  1. بررسی session ادمین
  2. خواندن body
  3. اعتبارسنجی با safeParse()
  4. بررسی unique بودن slug
  5. ساخت پست با Prisma
  6. برگرداندن پاسخ با status مناسب

Hint برای ویرایش پست در API

در handler مربوط به PATCH به این نکات دقت کنید:

  • اول مطمئن شوید پست موردنظر وجود دارد
  • اگر slug تغییر کرده، بررسی کنید پست دیگری از آن استفاده نکند
  • فقط بعد از این بررسی‌ها عملیات update را انجام دهید

Hint برای حذف پست در API

در handler مربوط به DELETE:

  • اول دسترسی ادمین را چک کنید
  • بعد وجود پست را بررسی کنید
  • اگر پست وجود داشت، آن را حذف کنید
  • اگر وجود نداشت، $404$ برگردانید

چند سؤال مهم برای فکر کردن

قبل از اینکه سراغ کدنویسی بروید، این چند سؤال را برای خودتان پاسخ دهید:

1. چرا باید هم صفحه‌های ادمین و هم APIها محافظت شوند؟

اگر فقط UI محافظت شود ولی API آزاد باشد، چه اتفاقی می‌افتد؟

2. چرا بهتر است فرم ساخت و ویرایش مشترک باشد؟

اگر دو فرم جدا بنویسید، چه مشکلی در نگهداری کد پیش می‌آید؟

3. در حالت ویرایش، چرا بررسی slug باید با دقت بیشتری انجام شود؟

چه فرقی بین ساخت پست جدید و ویرایش یک پست موجود از نظر uniqueness وجود دارد؟

4. اگر کاربر مستقیم آدرس /admin/posts/[id]/edit را باز کند و id معتبر نباشد، چه باید نمایش داده شود؟

5. بعد از حذف موفق پست، چرا router.refresh() انتخاب مناسبی است؟

این سؤال‌ها کمک می‌کنند فقط به خروجی نهایی فکر نکنید، بلکه منطق پشت پیاده‌سازی را هم بفهمید.


چک‌لیست تمرین

اگر این موارد را کامل کرده‌اید، یعنی تمرین را درست انجام داده‌اید:

  • مسیر /admin/posts ساخته شده است
  • لیست پست‌ها از دیتابیس خوانده می‌شود
  • empty state برای نبود پست‌ها دارید
  • صفحه /admin/posts/new کار می‌کند
  • صفحه /admin/posts/[id]/edit داده پست را لود می‌کند
  • فرم مشترک برای create و edit ساخته‌اید
  • endpoint ساخت پست پیاده‌سازی شده است
  • endpoint ویرایش پست پیاده‌سازی شده است
  • endpoint حذف پست پیاده‌سازی شده است
  • slug تکراری کنترل می‌شود
  • همه routeهای ادمین و APIها محافظت شده‌اند
  • بعد از عملیات موفق، UI درست به‌روزرسانی می‌شود

اگر همه این موارد تیک خورده باشند، شما عملاً ماژول مدیریت پست‌ها را کامل کرده‌اید.


این دقیقاً همان چیزی است که در پروژه‌های واقعی هم اتفاق می‌افتد.


این تمرین چرا مهم است؟

شاید در نگاه اول این قسمت فقط تکرار ماژول پروژه‌ها برای پست‌ها به نظر برسد.
اما در عمل، این یکی از مهم‌ترین مراحل یادگیری شماست.

چون اگر بتوانید بدون کپی‌کردن مستقیم، همان الگو را روی یک موجودیت جدید پیاده کنید، یعنی:

  • ساختار App Router را فهمیده‌اید
  • ارتباط بین page و component و API را فهمیده‌اید
  • منطق CRUD در داشبورد را یاد گرفته‌اید
  • و می‌توانید بعداً ماژول‌های دیگری مثل:
    • تجربیات
    • مقالات
    • سرویس‌ها
    • مهارت‌ها
    • گواهی‌نامه‌ها

را هم با همین الگو بسازید


جمع‌بندی

در این قسمت، به‌جای یک پیاده‌سازی کامل آماده، یک تمرین هدایت‌شده برای ساخت ماژول مدیریت پست‌ها در داشبورد ادمین طراحی کردیم.

در این تمرین مشخص شد که:

  • چه مسیرهایی باید ساخته شوند
  • چه فایل‌هایی لازم هستند
  • هر فایل چه مسئولیتی دارد
  • فرم باید چگونه طراحی شود
  • APIها چه رفتاری باید داشته باشند
  • اعتبارسنجی چگونه انجام شود
  • و چه نکاتی برای محافظت و مدیریت خطا مهم هستند

تمرین این قسمت

ماژول کامل مدیریت پست‌ها را با استفاده از الگوی ماژول پروژه‌ها پیاده‌سازی کنید.

اگر خواستید تمرین را کامل‌تر کنید، یکی از این قابلیت‌ها را هم به آن اضافه کنید:

  • تصویر شاخص
  • تگ‌ها
  • زمان مطالعه
  • اطلاعات SEO

این بخش اختیاری است، اما برای عمیق‌تر شدن یادگیری شما بسیار مفید خواهد بود.

// 0 comments
#Next.js#تمرین

نظرات (0)

نظر خود را بنویسید