12 KiB
Pre-Registration Feature
هدف
پیادهسازی سیستم پیش ثبتنام برای دکتر و کلینیک:
- روی صفحه اصلی (
/) دو دکمه باشد:- ورود به پنل →
/admin(برای کسانی که قبلاً ثبتنام شدهاند) - ثبت نام → فرم پیش ثبتنام (فقط برای دکتر و کلینیک)
- ورود به پنل →
- دکتر/کلینیک فرم را پر میکند
- ادمین در پنل درخواستها را میبیند و تأیید یا رد میکند
- پس از تأیید: کاربر و entity مربوطه ساخته میشود و نامکاربری+پسورد از طریق SMS ارسال میشود
قابلیتها
قابلیت ۱ — Entity و Migration: PreRegistration
ایجاد Entity جدید src/Auth/Entity/PreRegistration.php با فیلدهای:
id(int, PK)uuid(string, unique)type(string:independent_doctor|doctor_with_clinic|clinic_manager) — نوع حساب (توضیح در پایین)name(string, 255) — نام کاملmobile(string, 20) — شماره موبایلinfo(text, nullable) — توضیحات / اطلاعات اضافه (تخصص، سابقه، ...)status(string:pending|approved|rejected) — default:pendingadminNote(text, nullable) — یادداشت ادمین هنگام رد کردنcreatedAt(int) — Unix timestampupdatedAt(int) — Unix timestamp
بعد از ایجاد Entity: حتماً doctrine:migrations:diff و doctrine:migrations:migrate اجرا شود.
قابلیت ۲ — Backend: Public Pre-Registration Endpoint
POST /api/v1/pre-registration — endpoint عمومی (بدون auth)
فایل: src/Auth/Controller/PreRegistrationController.php (class جدید، extends BaseController)
Request body:
{
"type": "independent_doctor", // independent_doctor | doctor_with_clinic | clinic_manager — الزامی
"name": "دکتر احمدی", // الزامی
"mobile": "09121234567", // الزامی
"info": "متخصص داخلی، ۱۰ سال سابقه" // اختیاری
}
مفهوم type و نقشهای دریافتی پس از تأیید:
| مقدار | متن نمایشی در فرم | نقشها | entity ساخته میشود |
|---|---|---|---|
independent_doctor |
«دکتر هستم» | ROLE_DOCTOR |
Doctor |
doctor_with_clinic |
«دکتر هستم و کلینیک دارم و با چند دکتر همکاری میکنم» | ROLE_DOCTOR + ROLE_CLINIC |
Doctor + Clinic |
clinic_manager |
«تنها مدیر کلینیک هستم» | ROLE_CLINIC |
Clinic |
Validation:
typeباید یکی ازindependent_doctor،doctor_with_clinic، یاclinic_managerباشدmobileباید ۱۰ تا ۱۵ کاراکتر باشد- اگر همین موبایل با status=pending در جدول وجود داشت → خطا:
DUPLICATE_REQUESTبا پیام "درخواست ثبتنام شما در حال بررسی است"
Response موفق:
{ "success": true, "data": { "uuid": "...", "status": "pending" } }
امنیت: این endpoint باید در security.yaml به عنوان عمومی (public) تعریف شود.
قابلیت ۳ — Backend: Admin Pre-Registration Management Endpoints
در فایل src/Auth/Controller/PreRegistrationController.php متدهای زیر اضافه شوند:
GET /api/v1/admin/pre-registrations — لیست درخواستها
#[IsGranted('ROLE_ADMIN')]- Query params:
page,limit,status(pending|approved|rejected|all — default: pending) - DQL array hydration، برگرداندن: uuid, type, name, mobile, info, status, admin_note, created_at
- Return:
$this->paginated(...)
POST /api/v1/admin/pre-registrations/{uuid}/approve — تأیید درخواست
#[IsGranted('ROLE_ADMIN')]- اقدامات هنگام تأیید:
- بررسی اینکه status=pending باشد (در غیر این صورت خطا:
ALREADY_PROCESSED) - یک پسورد تصادفی ۸ کاراکتری بسازید (حروف+عدد)
- اگر user با این mobile وجود ندارد → User جدید بسازید با این mobile و پسورد
- اگر user وجود دارد → پسورد را reset کنید
- بر اساس
typeentity و نقشها را بساز:independent_doctor:- Doctor entity بساز (firstName/lastName از split نام؛ اگر یک کلمه بود همه firstName)
- به user بده:
ROLE_DOCTOR
doctor_with_clinic:- Doctor entity بساز (مانند بالا)
- Clinic entity بساز با
name=نام درخواست - به user بده:
ROLE_DOCTOR+ROLE_CLINIC
clinic_manager:- Clinic entity بساز با
name=نام درخواست - به user بده:
ROLE_CLINICدر همه حالتها قبل از ساخت entity بررسی کن که قبلاً وجود نداشته باشد (جلوگیری از duplicate)
- Clinic entity بساز با
statusرا رویapprovedوupdatedAtرا update کنید- یک SMS بفرستید از طریق سرویس SMS موجود در پروژه (کلاس
App\Sms\) با متن: "به کلینیک پرو خوش آمدید! شمارهکاربری: {mobile} | رمز عبور: {password} | لینک ورود: https://clinic-pro.ddev.site/admin"
- بررسی اینکه status=pending باشد (در غیر این صورت خطا:
- Response:
$this->success(['message' => 'تأیید شد و اطلاعات ورود ارسال گردید'])
POST /api/v1/admin/pre-registrations/{uuid}/reject — رد درخواست
#[IsGranted('ROLE_ADMIN')]- Request body:
{ "note": "توضیح دلیل رد" }— اختیاری statusراrejectedکن،adminNoteرا ذخیره کن- Response:
$this->success(['message' => 'درخواست رد شد'])
قابلیت ۴ — Frontend Homepage: دو دکمه در header و hero
فایل: templates/public/home.html.twig
۴الف — جایگزینی کامل <div class="nav-right">:
کل بلوک زیر را حذف کن:
<div class="nav-right">
<button class="nav-ico search" ...>...</button>
<button class="nav-burger nav-toggle" id="navToggle" ...>...</button>
<a href="#download" class="nav-burger" ...>...</a>
</div>
و به جای آن این بلوک را قرار بده:
<div class="nav-right">
<button class="nav-burger nav-toggle" id="navToggle" aria-label="منو">
<svg viewBox="0 0 24 24" fill="none"><path d="M4 7h16M4 12h16M4 17h16" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
</button>
<button class="btn btn-ghost" id="openRegModal" style="font-size:13px;padding:8px 20px">ثبت نام</button>
<a href="/admin" class="btn btn-blue" style="font-size:13px;padding:8px 20px;text-decoration:none">ورود به پنل</a>
</div>
توجه: دکمه hamburger (navToggle) را نگه دار چون JS موبایل به آن وابسته است. دکمه search و لینک download را حذف کن.
۴ب — دکمه ثبت نام در hero:
در <div class="hero-actions reveal d2"> دکمه "دانلود رایگان" را به "ثبت نام" تغییر بده.
توجه: از class="btn btn-coral" با id="openRegModal2" استفاده کن (id متفاوت از header) و در JS هر دو را به modal وصل کن:
<button class="btn btn-coral" id="openRegModal2">ثبت نام دکتر / کلینیک</button>
<a href="#contact" class="btn btn-blue">تماس با ما</a>
۴ج — Modal پیش ثبتنام:
یک modal HTML در انتهای <main> اضافه کن (قبل از </main>) با:
- overlay با id=
regOverlay - modal box با فرم شامل:
- انتخاب نوع حساب — سه کارت قابل کلیک (radio-style) با آیکون و توضیح، به صورت عمودی (stacked):
independent_doctor→ عنوان: «دکتر هستم» — زیرعنوان: «مطب شخصی دارم، به تنهایی کار میکنم»doctor_with_clinic→ عنوان: «دکتر هستم و کلینیک دارم» — زیرعنوان: «با چند دکتر در یک کلینیک همکاری میکنم»clinic_manager→ عنوان: «مدیر کلینیک هستم» — زیرعنوان: «مدیریت کلینیک را دارم، خودم دکتر نیستم» کارت انتخابشده با border رنگی و پسزمینه روشن هایلایت شود
- input: نام کامل
- input: شماره موبایل (dir=ltr)
- textarea: توضیحات اختیاری (تخصص، آدرس، ...)
- دکمه ارسال (disable تا زمانی که type انتخاب نشده)
- انتخاب نوع حساب — سه کارت قابل کلیک (radio-style) با آیکون و توضیح، به صورت عمودی (stacked):
- CSS inline برای modal (همانند استایلهای موجود در صفحه: رنگها
--blue,--coral, border-radius مشابه) - JS inline:
- باز/بسته شدن modal با کلیک روی هر دو دکمه (
openRegModalدر header وopenRegModal2در hero) و کلیک روی overlay - ارسال فرم با
fetch('/api/v1/pre-registration', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({...}) }) - نمایش پیام موفقیت یا خطا
- بعد از موفقیت: بستن modal و نمایش پیام "درخواست شما ثبت شد. پس از بررسی، اطلاعات ورود از طریق SMS ارسال میشود."
- باز/بسته شدن modal با کلیک روی هر دو دکمه (
قابلیت ۵ — Frontend Admin Panel: صفحه مدیریت درخواستها
۵الف — ایجاد assets/admin/pages/PreRegistrationsPage.tsx:
useQueryباqueryKey: ['pre-registrations', page, statusFilter]- API:
GET /api/v1/admin/pre-registrations?page=...&status=... - جدول با ستونها: نام، موبایل، نوع (badge با رنگبندی: «دکتر» آبی برای
independent_doctor/ «دکتر + کلینیک» بنفش برایdoctor_with_clinic/ «مدیر کلینیک» نارنجی برایclinic_manager)، وضعیت badge، تاریخ درخواست، ستون اقدامات - Filter tabs: همه | در انتظار | تأیید شده | رد شده
- برای هر ردیف با status=pending: دکمههای تأیید (سبز) و رد (قرمز)
- کلیک تأیید → mutation به
POST /api/v1/admin/pre-registrations/{uuid}/approve→ toast موفقیت - کلیک رد → یک prompt کوچک (یا یک inline textarea در row) برای وارد کردن دلیل → mutation به
POST /api/v1/admin/pre-registrations/{uuid}/reject→ toast - بعد از هر mutation:
invalidateQueries(['pre-registrations'])
۵ب — اضافه کردن به Sidebar.tsx:
در بخش admin داخل buildSections، در section «مدیریت» یک آیتم اضافه کن:
{ to: '/admin/pre-registrations', icon: ClipboardDocumentCheckIcon, label: 'درخواستهای ثبتنام' }
(import ClipboardDocumentCheckIcon از @heroicons/react/24/outline)
۵ج — اضافه کردن route به App.tsx:
import PreRegistrationsPage from './pages/PreRegistrationsPage';
// ...
<Route path="pre-registrations" element={<RoleRoute roles={['admin']}><PreRegistrationsPage /></RoleRoute>} />
نکات اجرایی
امنیت
- endpoint عمومی
POST /api/v1/pre-registrationرا درconfig/packages/security.yamlداخلpublic_endpointsfirewall اضافه کن - endpoint های admin با
#[IsGranted('ROLE_ADMIN')]محافظت شوند
SMS
- کلاس SMS موجود را در پروژه پیدا کن (
src/Sms/) و از آن استفاده کن - اگر ارسال SMS با خطا مواجه شد، عملیات approve کامل شود (rollback نشود) — فقط یک log warning بزن
پسورد تصادفی
از bin2hex(random_bytes(4)) برای تولید پسورد ۸ کاراکتری hex استفاده کن.
مستندسازی
بعد از اتمام هر قابلیت:
docs/api/auth.mdرا برای endpoint های جدید pre-registration بهروزرسانی کنdocs/api/admin.mdرا برای endpoint های admin بهروزرسانی کن