Polish Hedena brand, home layout, and user-app chrome.
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>
This commit is contained in:
+5
-1
@@ -13,11 +13,15 @@
|
||||
|
||||
### فایلهای لوگو
|
||||
|
||||
منبع اصلی (آیکون بدون متن): [`scripts/brand-assets/hedena-icon.webp`](../scripts/brand-assets/hedena-icon.webp)
|
||||
|
||||
- `apps/user-app/public/img/brand-logo.png` و `.webp`
|
||||
- `apps/user-app/src/assets/img/brand-logo.png`
|
||||
- `apps/user-app/public/img/no-items.png` و `src/assets/img/no-items.png` (placeholder تصویر محصول)
|
||||
- `apps/admin-panel/src/assets/images/logo/brand-icon.png` و `.webp`، `brand-horizontal.png`
|
||||
- آیکونهای PWA و favicon: `apps/*/public/icon-192.png`، `icon-512.png`، `favicon.*`
|
||||
- اسکریپت: [`scripts/generate-brand-icons.py`](../scripts/generate-brand-icons.py)
|
||||
- اسکریپت (ترجیحی): [`scripts/generate-brand-icons.mjs`](../scripts/generate-brand-icons.mjs) — نیاز به `sharp` و `to-ico`
|
||||
- اسکریپت جایگزین: [`scripts/generate-brand-icons.py`](../scripts/generate-brand-icons.py)
|
||||
|
||||
### بعد از تغییر
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Showcase slider — luxury crossfade
|
||||
|
||||
**Status:** approved
|
||||
**Date:** 2026-10-15
|
||||
**Scope:** `apps/user-app` home hero (`ShowcaseSlider.vue`)
|
||||
|
||||
## Intent
|
||||
|
||||
Calm, luxury gold-vitrine motion. Special feeling lives in the **slide change**, not idle fidgeting.
|
||||
|
||||
## Decisions
|
||||
|
||||
| Choice | Decision |
|
||||
|--------|----------|
|
||||
| Vibe | Luxury / soft (not cinematic parallax, not brand wipe) |
|
||||
| Transition | Soft crossfade + subtle settle zoom |
|
||||
| Swipe | Kept; commit settles with the same crossfade (no hard horizontal slide) |
|
||||
| Autoplay | 6s; off under reduced motion |
|
||||
| Dots | Unchanged pill indicator |
|
||||
| Out of scope | Multi-layer parallax, brand-color curtain, home layout changes |
|
||||
|
||||
## Motion spec
|
||||
|
||||
- Stack slides absolutely inside the rounded frame.
|
||||
- Active: `opacity: 1`, art `scale(1)`.
|
||||
- Incoming settle: art eases from `scale(1.03)` → `1`.
|
||||
- Duration ≈ `1.15s`, easing `cubic-bezier(0.22, 0.61, 0.36, 1)` (softer, less snap).
|
||||
- While dragging, preview fade toward next/prev by drag ratio; on release past ~18% width, change index.
|
||||
- `prefers-reduced-motion` / appearance reduced-motion: instant opacity swap, no scale animation, no autoplay.
|
||||
|
||||
## Non-goals
|
||||
|
||||
Invented product prices, extra copy overlays on slides, new dependencies (Swiper, etc.).
|
||||
@@ -0,0 +1,31 @@
|
||||
# Brand logo assets — icon-only mark
|
||||
|
||||
Date: 2026-10-15
|
||||
|
||||
## Goal
|
||||
|
||||
Replace all brand image assets with the new Hedena icon-only mark (gold symbol on black, no wordmark). Keep text brand name as هدنا گلد / Hedena Gold in `brand.ts`.
|
||||
|
||||
## Source
|
||||
|
||||
- Master: icon-only logo (Frame 2) — square, gold on black, no text.
|
||||
- Full wordmark (Frame 1) is not used in this pass.
|
||||
|
||||
## Mapping
|
||||
|
||||
| Role | Targets |
|
||||
|---|---|
|
||||
| App / admin logos | `user-app` `brand-logo.png/.webp` (public + assets); admin `brand-icon.png/.webp`, `brand-horizontal.png` |
|
||||
| Favicon + PWA | `favicon.ico` / `favicon.png`, `icon-192.png`, `icon-512.png` in both apps’ `public/` |
|
||||
| Product no-image | `user-app` `public/img/no-items.png` and `src/assets/img/no-items.png` |
|
||||
| Lazy-load error placeholder | Continues to use `brand-logo.png` via `main.ts` (no code path change) |
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Renaming brand strings in `brand.ts` or SMS env
|
||||
- Empty-state SVG illustrations (cart empty, etc.)
|
||||
- Regenerating `dist/` / API release builds (caller rebuilds as needed)
|
||||
|
||||
## Approach
|
||||
|
||||
Overwrite fixed filenames listed in `docs/BRANDING.md`. Resize the same master for PWA/favicon sizes. No Vue template changes required if paths stay the same.
|
||||
@@ -0,0 +1,176 @@
|
||||
# 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-pwa` manifest / 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`:
|
||||
|
||||
1. **`usePwaInstall`** (composable)
|
||||
- Listen for `beforeinstallprompt`, stash the event, expose `promptInstall()`
|
||||
- Detect installed / standalone (`display-mode: standalone`, `navigator.standalone` on 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 `visible` if `shouldShowPrompt`
|
||||
|
||||
2. **`InstallPromptModal.vue`**
|
||||
- Centered `van-dialog` with `hg-dialog` styling (or thin wrapper consistent with `DialogVant`)
|
||||
- Modes: `offer` | `guide`
|
||||
- Offer: brand logo, title, one line, primary CTA, subtle close
|
||||
- Guide: platform-specific steps; secondary “متوجه شدم” dismisses
|
||||
|
||||
3. **Mount in `App.vue`**
|
||||
- Sibling of `AppUpdateGate`
|
||||
- Skip while route uses `FullPage` layout (login/register and similar)
|
||||
|
||||
Existing SW registration via `useRegisterSW` stays as-is; this feature only adds install UX.
|
||||
|
||||
## Show / hide rules
|
||||
|
||||
Show only when **all** are true:
|
||||
|
||||
1. Not already installed (standalone / `navigator.standalone` / persisted `installed`)
|
||||
2. `dismissedAt` is missing, or older than **7 days**
|
||||
3. Current route is not `meta.layout === 'FullPage'`
|
||||
4. Initial delay of **~4 seconds** after app shell ready has elapsed
|
||||
5. Document is visible (`document.visibilityState === 'visible'`) — optional soft guard
|
||||
|
||||
On dismiss (×, overlay, or “متوجه شدم”):
|
||||
|
||||
- Set `pwaInstall.dismissedAt = Date.now()`
|
||||
- Close modal; reset mode to `offer` for 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 `installed` if not set); never show
|
||||
|
||||
## Persistence
|
||||
|
||||
`localStorage` keys (single JSON object preferred, matching cart/appearance patterns):
|
||||
|
||||
```json
|
||||
{
|
||||
"dismissedAt": null,
|
||||
"installed": false
|
||||
}
|
||||
```
|
||||
|
||||
Key name: `pwaInstall` (or `hg.pwaInstall` if namespacing is preferred in implementation).
|
||||
|
||||
Constants:
|
||||
|
||||
- `REOFFER_MS = 7 * 24 * 60 * 60 * 1000`
|
||||
- `INITIAL_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 our `dismissedAt` unset 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 `guide` for detected platform (do not dismiss yet)
|
||||
|
||||
### Guide — Android web
|
||||
|
||||
Title: راهنمای نصب
|
||||
|
||||
1. منوی مرورگر (⋮) را باز کنید
|
||||
2. گزینهٔ «نصب برنامه» یا «Add to Home screen» را بزنید
|
||||
3. نصب را تأیید کنید
|
||||
|
||||
Footer CTA: متوجه شدم → dismiss + 7-day clock
|
||||
|
||||
### Guide — iOS
|
||||
|
||||
Title: راهنمای نصب
|
||||
|
||||
1. دکمهٔ Share (مربع با فلش رو به بالا) را بزنید
|
||||
2. «Add to Home Screen» / افزودن به صفحهٔ اصلی را انتخاب کنید
|
||||
3. 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)
|
||||
|
||||
1. Fresh profile, Android Chrome (installable): after ~4s modal → نصب اپ → browser prompt → accept → never again
|
||||
2. Same, dismiss modal → gone; advance clock / spoof `dismissedAt` −8 days → shows again
|
||||
3. iOS Safari: after ~4s → مشاهده راهنما → iOS steps → متوجه شدم → 7-day silence
|
||||
4. Standalone launch: no modal
|
||||
5. 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
|
||||
Reference in New Issue
Block a user