pwa setting

This commit is contained in:
hamed
2026-06-13 13:28:39 +03:30
parent 0b5d017b14
commit d50fb96542
15 changed files with 782 additions and 0 deletions
+200
View File
@@ -0,0 +1,200 @@
# 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 نشان می‌دهد