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

201 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` بساز با این مشخصات:
```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>` اضافه کن:
```html
<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 بتواند قدیمی‌ها را پاک کند:
```js
const CACHE_NAME = 'clinicpro-admin-v1';
const BUILD_CACHE = 'clinicpro-build-v1';
```
---
### قابلیت ۳ — ثبت Service Worker در React
در `assets/admin/index.tsx` (فایل entry point) بعد از `ReactDOM.render` / `createRoot`:
```typescript
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 رعایت شود.
---
## تست پس از هر قابلیت
```bash
# 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 نشان می‌دهد