Files
clinicpro/.claude/prompt/admin-pwa.md
T
2026-06-13 13:28:39 +03:30

9.2 KiB
Raw Blame History

Admin Panel PWA

پنل ادمین (/admin) را به یک Progressive Web App تبدیل کن تا کاربران بتوانند آن را روی دستگاه خود نصب کنند و آفلاین هم shell اولیه را ببینند.


وضعیت فعلی پروژه

  • Twig template: templates/admin/index.html.twig — فایل HTML ورودی پنل ادمین
  • Webpack Encore با addEntry('admin', './assets/admin/index.tsx')
  • Output path: public/build/
  • Public path: /build
  • آیکون‌های موجود: public/favicon.ico و public/favicon.png
  • دامنه local: https://clinic-pro.ddev.site

قابلیت‌های ۱ تا ۴ قبلاً اجرا شده‌اند:

  • public/manifest.json موجود است
  • public/sw.js موجود است
  • ثبت SW در assets/admin/index.tsx انجام شده
  • assets/admin/components/ui/PwaInstallBanner.tsx موجود است (برای کاربران logged-in)

قابلیت‌های مورد نیاز

قابلیت ۱ — Web App Manifest

فایل public/manifest.json بساز با این مشخصات:

{
  "name": "ClinicPro Admin",
  "short_name": "ClinicPro",
  "description": "پنل مدیریت کلینیک پرو",
  "start_url": "/admin",
  "scope": "/admin",
  "display": "standalone",
  "orientation": "portrait-primary",
  "theme_color": "#6366f1",
  "background_color": "#f8fafc",
  "lang": "fa",
  "dir": "rtl",
  "icons": [
    { "src": "/favicon.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
    { "src": "/favicon.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
  ]
}
  • رنگ theme_color را از CSS variable --primary پروژه بگیر (مقدار واقعی را از assets/admin/styles.css بخوان)
  • در templates/admin/index.html.twig این تگ‌ها را به <head> اضافه کن:
    <link rel="manifest" href="/manifest.json">
    <meta name="theme-color" content="#6366f1">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="default">
    <meta name="apple-mobile-web-app-title" content="ClinicPro">
    <link rel="apple-touch-icon" href="/favicon.png">
    

قابلیت ۲ — Service Worker (Cache Shell)

یک service worker در public/sw.js بنویس. بدون workbox — native Service Worker API:

استراتژی:

  • install event: کش کردن shell (فایل‌های ضروری برای render اولیه)
  • activate event: پاک کردن کش‌های قدیمی
  • fetch event:
    • درخواست‌های API (/api/) → فقط Network (هرگز کش نکن، اگر آفلاین بود خطا بده)
    • فایل‌های build (/build/) → Cache First (سریع‌تر، stale-while-revalidate)
    • navigation (/admin*) → Network First با fallback به shell کش‌شده

فایل‌هایی که در install کش می‌شوند:

/admin
/manifest.json
/favicon.png

فایل‌های /build/ را در install کش نکن (hash در نام آن‌هاست و هر deploy عوض می‌شود) — آن‌ها را در fetch با Cache First مدیریت کن.

Cache name شامل version باشد تا activate بتواند قدیمی‌ها را پاک کند:

const CACHE_NAME = 'clinicpro-admin-v1';
const BUILD_CACHE = 'clinicpro-build-v1';

قابلیت ۳ — ثبت Service Worker در React

در assets/admin/index.tsx (فایل entry point) بعد از ReactDOM.render / createRoot:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .catch(() => { /* silent fail in dev */ });
  });
}

نکته: registration باید فقط در production یا به‌صورت همیشگی باشد — در هر دو حالت کار می‌کند چون sw.js در /public است و مستقیم serve می‌شود.


قابلیت ۴ — Install Prompt Banner (اختیاری اما مطلوب)

یک کامپوننت کوچک React در assets/admin/components/ui/PwaInstallBanner.tsx بساز:

  • beforeinstallprompt event را listen می‌کند
  • اگر prompt در دسترس بود یک banner کوچک در پایین صفحه نشان می‌دهد:
    [آیکون] ClinicPro را نصب کنید — دسترسی سریع‌تر   [نصب] [×]
    
  • بعد از dismiss در localStorage ذخیره کن (pwa-dismissed) تا دوباره نشان داده نشود
  • در assets/admin/App.tsx یا AdminLayout این کامپوننت را اضافه کن

طراحی: از CSS variables پروژه استفاده کن (--surface, --border, --primary, --text). RTL رعایت شود.


تست پس از هر قابلیت

# Build
ddev exec yarn dev

# بررسی manifest
curl https://clinic-pro.ddev.site/manifest.json

# بررسی sw.js
curl https://clinic-pro.ddev.site/sw.js | head -5

# بررسی Twig
curl https://clinic-pro.ddev.site/admin | grep manifest

تست PWA در Chrome:

  1. مرورگر Chrome → https://clinic-pro.ddev.site/admin
  2. DevTools → Application → Service Workers → باید registered باشد
  3. DevTools → Application → Manifest → باید parse شده و installable باشد
  4. آیکون نصب در address bar باید ظاهر شود

قابلیت ۵ — نمایش پرامپت نصب در صفحه لاگین

در صفحه لاگین (assets/admin/pages/LoginPage.tsx) یک بخش نصب اپلیکیشن اضافه کن که همیشه قابل مشاهده باشد — نه فقط وقتی beforeinstallprompt فایر شده.

منطق نمایش:

  • اگر app قبلاً نصب شده (window.matchMedia('(display-mode: standalone)').matches) → این بخش را نشان نده
  • اگر pwa-dismissed در localStorage بود → این بخش را نشان نده
  • در غیر این صورت همیشه نشان بده

دو حالت:

۱. وقتی beforeinstallprompt در دسترس است (Chrome/Edge desktop و Android):

  • دکمه «نصب اپلیکیشن» نشان بده
  • با کلیک روی دکمه، native install prompt مرورگر را باز کن

۲. وقتی beforeinstallprompt در دسترس نیست (Safari iOS، Firefox، یا قبل از fire شدن event):

  • یک راهنمای متنی نشان بده:
    • iOS: «در Safari: دکمه Share را بزن، سپس «Add to Home Screen» را انتخاب کن»
    • سایر: «در منوی مرورگر گزینه «Install app» یا «Add to Home Screen» را انتخاب کن»

طراحی:

یک card زیبا زیر فرم لاگین (یا بالای آن در موبایل) با این ظاهر:

┌─────────────────────────────────────────┐
│  [آیکون ClinicPro]                       │
│  ClinicPro را نصب کنید                  │
│  دسترسی سریع‌تر — بدون نیاز به مرورگر  │
│                                          │
│  [دکمه نصب / راهنما]    [بعداً]         │
└─────────────────────────────────────────┘
  • از CSS variables پروژه استفاده کن (--primary-soft, --primary-700, --surface, --border)
  • RTL رعایت شود
  • دکمه «بعداً» مثل قابلیت ۴ عمل کند: localStorage.setItem('pwa-dismissed', '1') و بخش را مخفی کند
  • کامپوننت جداگانه بساز: assets/admin/components/ui/PwaLoginCard.tsx
  • در LoginPage.tsx این کامپوننت را import و زیر card اصلی فرم لاگین قرار بده

نکته مهم: این کامپوننت مستقل از PwaInstallBanner است — banner برای کاربران logged-in است، این card برای صفحه لاگین است. منطق beforeinstallprompt را در یک custom hook مشترک (assets/admin/hooks/usePwaInstall.ts) بگذار تا هر دو کامپوننت از آن استفاده کنند و event را دو بار capture نکنند.


محدودیت‌ها و نکات

  • workbox اضافه نکن — native API کافی است و dependency غیرضروری اضافه نمی‌کند
  • Webpack Encore را تغییر نده — sw.js مستقیم در public/ می‌نشیند و نیازی به bundle ندارد
  • DDEV HTTPS: mkcert باید قبلاً نصب شده باشد (mkcert -install)؛ اگر نبود service worker register نمی‌شود
  • Scope: /admin — service worker فقط این path را intercept می‌کند، /api/ را تحت تأثیر قرار نمی‌دهد مگر از داخل scope فراخوانی شود
  • آیکون‌ها: اگر favicon.png کوچک بود (زیر 192px)، یک آیکون مناسب در public/icons/ بساز یا از همان استفاده کن — Chrome حتی با آیکون کوچک هم install prompt نشان می‌دهد