Switch the customer app to IRANSans, restyle home featured/newest and cart note sheet, refresh brand icons across apps, and add the PWA install prompt. Co-authored-by: Cursor <cursoragent@cursor.com>
7.0 KiB
PWA install prompt + platform guides
Date: 2026-10-15
App: apps/user-app
Goal
Show a brand-focused centered install modal automatically after first useful visit, with separate Android-web and iOS install guides. Do not nag: if the user dismisses, wait 7 days before offering again (only while not installed). Once installed, never show again.
Decisions (locked)
| Topic | Choice |
|---|---|
| Entry | Auto prompt after short delay (not a permanent settings entry in v1) |
| UI | Centered modal, brand-first, one primary CTA |
| Re-offer | 7 days after dismiss, only if not installed |
| Installed | Never show again |
| iOS / no native prompt | Same modal shell → visual step guide for that platform |
| Guides | Separate copy/steps for Android web vs iOS |
| Style | Follow existing hg-dialog / DialogVant island pattern |
Out of scope
- Admin panel PWA install UX
- Push-notification permission prompts
- Changing
vite-plugin-pwamanifest / SW strategy (already present) - Persistent “Install app” row in profile/menu (can add later)
- Desktop-specific illustrated guide (desktop Chrome may still get native prompt or Android-style fallback text)
Architecture
Three pieces in user-app:
-
usePwaInstall(composable)- Listen for
beforeinstallprompt, stash the event, exposepromptInstall() - Detect installed / standalone (
display-mode: standalone,navigator.standaloneon iOS) - Detect platform for guide content:
ios|android(default non-iOS mobile/desktop to android-style guide) - Read/write persistence; expose
shouldShowPrompt,open(),dismiss(),markInstalled() - After ~4s on eligible routes, set
visibleifshouldShowPrompt
- Listen for
-
InstallPromptModal.vue- Centered
van-dialogwithhg-dialogstyling (or thin wrapper consistent withDialogVant) - Modes:
offer|guide - Offer: brand logo, title, one line, primary CTA, subtle close
- Guide: platform-specific steps; secondary “متوجه شدم” dismisses
- Centered
-
Mount in
App.vue- Sibling of
AppUpdateGate - Skip while route uses
FullPagelayout (login/register and similar)
- Sibling of
Existing SW registration via useRegisterSW stays as-is; this feature only adds install UX.
Show / hide rules
Show only when all are true:
- Not already installed (standalone /
navigator.standalone/ persistedinstalled) dismissedAtis missing, or older than 7 days- Current route is not
meta.layout === 'FullPage' - Initial delay of ~4 seconds after app shell ready has elapsed
- Document is visible (
document.visibilityState === 'visible') — optional soft guard
On dismiss (×, overlay, or “متوجه شدم”):
- Set
pwaInstall.dismissedAt = Date.now() - Close modal; reset mode to
offerfor next open
On successful native install (appinstalled event or user accepted prompt()):
- Set
pwaInstall.installed = true - Clear need to re-offer; never show again
On later visit already in standalone:
- Treat as installed (set
installedif not set); never show
Persistence
localStorage keys (single JSON object preferred, matching cart/appearance patterns):
{
"dismissedAt": null,
"installed": false
}
Key name: pwaInstall (or hg.pwaInstall if namespacing is preferred in implementation).
Constants:
REOFFER_MS = 7 * 24 * 60 * 60 * 1000INITIAL_DELAY_MS = 4000
Modal content
Offer mode (both platforms)
| Element | Copy / asset |
|---|---|
| Media | brand-logo (existing public/assets path) |
| Title | نصب اپلیکیشن هدنا گلد |
| Body | دسترسی سریعتر و تجربهٔ شبیه اپ واقعی |
| Primary CTA | If deferred prompt ready: نصب اپ — else: مشاهده راهنما |
| Close | Small ×; no loud secondary cancel |
CTA behavior:
- Deferred prompt available →
event.prompt(); on accept →markInstalled(); on dismiss of browser UI only → keep ourdismissedAtunset unless user also closed our modal (browser decline alone does not start the 7-day clock; closing our modal does) - No deferred prompt → switch modal to
guidefor detected platform (do not dismiss yet)
Guide — Android web
Title: راهنمای نصب
- منوی مرورگر (⋮) را باز کنید
- گزینهٔ «نصب برنامه» یا «Add to Home screen» را بزنید
- نصب را تأیید کنید
Footer CTA: متوجه شدم → dismiss + 7-day clock
Guide — iOS
Title: راهنمای نصب
- دکمهٔ Share (مربع با فلش رو به بالا) را بزنید
- «Add to Home Screen» / افزودن به صفحهٔ اصلی را انتخاب کنید
- Add را تأیید کنید
Footer CTA: متوجه شدم → dismiss + 7-day clock
Guides use short icon+text rows (no long paragraphs). Illustrations may be simple inline SVG icons (share / menu / home); no new marketing photography required for v1.
Platform detection (guide only)
- iOS: iPhone/iPad/iPod UA, or iPadOS desktop UA with touch — use iOS guide
- Else: Android-web guide (covers Android Chrome without prompt, and as fallback elsewhere)
Native beforeinstallprompt path is independent of guide platform: if the event exists, offer CTA installs directly regardless of guide branch.
Error / edge cases
| Case | Behavior |
|---|---|
prompt() rejects / user cancels browser sheet |
Stay closed or return to offer only if still open; do not set installed; if our modal was closed by user earlier, 7-day rule already applies |
Already installed mid-session (appinstalled) |
Close modal; set installed |
Private mode / localStorage throws |
Fail soft: skip auto-show (no crash); optional in-memory session-only suppress |
| User opens app from home screen | Never show |
Update gate (AppUpdateGate) visible |
Do not stack: keep install modal closed while the update gate is open; schedule or re-check after it closes |
| HTTPS / installability not met | No deferred prompt → auto path still can show offer → guide |
Testing (manual)
- Fresh profile, Android Chrome (installable): after ~4s modal → نصب اپ → browser prompt → accept → never again
- Same, dismiss modal → gone; advance clock / spoof
dismissedAt−8 days → shows again - iOS Safari: after ~4s → مشاهده راهنما → iOS steps → متوجه شدم → 7-day silence
- Standalone launch: no modal
- Login (
FullPage): no modal until leaving that layout and delay elapses on an eligible view
File sketch
apps/user-app/src/composables/usePwaInstall.ts
apps/user-app/src/components/pwa/InstallPromptModal.vue
apps/user-app/src/App.vue (mount modal)
Optional tiny helpers for UA / display-mode can live next to the composable.
Success criteria
- Brand-first centered modal, one primary CTA
- Android and iOS guides are distinct
- Auto once per window of eligibility; 7-day re-offer only if not installed
- Installed / standalone users never see it
- No change to existing SW/update-gate behavior beyond coexistence