PWA: installable app (web/win/android/ios) + download/help page + push notifications
CI/CD / CI · dotnet build (push) Successful in 40s
CI/CD / Deploy · hamkadr (push) Successful in 55s

- manifest.webmanifest + service worker (offline shell + push + notificationclick) + PNG icons (192/512/apple) + iOS meta + SW registration → installable everywhere
- /Download page: per-OS install help (web/windows/android/ios), install button (beforeinstallprompt), 'enable notifications' flow, usage guide, Bazaar/TWA note; nav + footer links
- Web Push foundation: WebPushSubscription entity + /push/subscribe (stores), VAPID + push settings in /Admin/Settings, on-device local notification; server broadcast documented (WebPush via Nexus)
- docs/PWA-TWA.md: VAPID keygen, server-push wiring, Bubblewrap→Cafe Bazaar + assetlinks steps
- Verified: manifest/sw/icons served, download page, subscribe stores (200), layout wired

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 11:23:13 +03:30
parent 9a92da42e6
commit a02eb6a985
17 changed files with 1417 additions and 1 deletions
@@ -142,6 +142,28 @@
<p class="muted" style="font-size:12px; margin:4px 0 0;">برای انتخاب موقعیت روی نقشه در فرم ثبت مرکز. بدون کلید، فقط دکمه «موقعیت فعلی من» نمایش داده می‌شود.</p>
</div>
<hr style="border:none; border-top:1px solid var(--line); margin:18px 0;" />
<h3 style="margin-top:0;">اعلان‌ها (Web Push / PWA)</h3>
<div class="filter-group">
<label style="display:flex; align-items:center; gap:8px; font-weight:700;">
<input type="checkbox" name="PushEnabled" value="true" style="width:auto;" checked="@Model.PushEnabled" />
فعال‌سازی اشتراک اعلان مرورگری
</label>
</div>
<div class="filter-group">
<label>VAPID Public Key</label>
<input type="text" name="VapidPublicKey" value="@Model.VapidPublicKey" dir="ltr" />
</div>
<div class="filter-group">
<label>VAPID Private Key</label>
<input type="password" name="VapidPrivateKey" value="@Model.VapidPrivateKey" dir="ltr" />
</div>
<div class="filter-group">
<label>VAPID Subject</label>
<input type="text" name="VapidSubject" value="@Model.VapidSubject" dir="ltr" placeholder="mailto:admin@hamkadr.ir" />
</div>
<p class="muted" style="font-size:12px;">جفت‌کلید VAPID را یک‌بار بساز (web-push). بدون آن، اعلان محلی روی دستگاه کار می‌کند ولی ارسال از سرور نیاز به کلید دارد.</p>
<button type="submit" class="btn btn-accent btn-block btn-lg">ذخیره تنظیمات</button>
</form>
</div>
@@ -37,6 +37,10 @@ public class SettingsModel : PageModel
[BindProperty] public string? SmsTemplate { get; set; }
[BindProperty] public string? SmsSender { get; set; }
[BindProperty] public string? NeshanMapKey { get; set; }
[BindProperty] public bool PushEnabled { get; set; }
[BindProperty] public string? VapidPublicKey { get; set; }
[BindProperty] public string? VapidPrivateKey { get; set; }
[BindProperty] public string? VapidSubject { get; set; }
[TempData] public string? Saved { get; set; }
public async Task OnGetAsync()
@@ -66,6 +70,10 @@ public class SettingsModel : PageModel
SmsTemplate = s.SmsTemplate;
SmsSender = s.SmsSender;
NeshanMapKey = s.NeshanMapKey;
PushEnabled = s.PushEnabled;
VapidPublicKey = s.VapidPublicKey;
VapidPrivateKey = s.VapidPrivateKey;
VapidSubject = s.VapidSubject;
}
public async Task<IActionResult> OnPostAsync()
@@ -96,6 +104,10 @@ public class SettingsModel : PageModel
SmsTemplate = SmsTemplate,
SmsSender = SmsSender,
NeshanMapKey = NeshanMapKey,
PushEnabled = PushEnabled,
VapidPublicKey = VapidPublicKey,
VapidPrivateKey = VapidPrivateKey,
VapidSubject = VapidSubject,
});
Saved = "تنظیمات ذخیره شد.";
return RedirectToPage();