Adds the Phase 4 requirement docs that align the customer app with the new HaloBestie Figma design dump. - requirement/flow_customer.md: source-of-truth numbered flow (input) - requirement/flow_customer.mermaid.md: 6 mermaid diagrams + Figma cross-ref - requirement/phase4-customer-flow.md: PRD (15 functional sections) - requirement/phase4-customer-flow-plan.md: 10-stage implementation plan - .gitignore: exclude requirement/Figma.zip + extracted Figma/ folder Resolved product decisions: no free trial (replaced by configurable first-session discount), pricing has independent chat/call groups, voice-call mode is chat-with-badge (mitra shares Meet link manually), social login is server-driven via /api/shared/auth-providers, ESP tags are info-only (not used for matching). No code changes; implementation starts at plan stage 0 (design system). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
240 lines
11 KiB
Markdown
240 lines
11 KiB
Markdown
# Customer App Flow — Mermaid Diagrams
|
||
|
||
> Generated from `requirement/flow_customer.md` and cross-checked against the Figma
|
||
> handoff package (`requirement/Figma/` — git-ignored).
|
||
>
|
||
> **Legend (status pulled from a client_app audit, see `phase4-customer-flow.md`):**
|
||
> - 🟢 EXISTS — already shipped in client_app
|
||
> - 🟡 PARTIAL — close, but a key piece (UX, copy, or sub-state) is missing
|
||
> - 🔴 MISSING — no implementation in client_app yet
|
||
|
||
The flow is split into 6 sub-diagrams so each one stays readable. Screen IDs use
|
||
the Figma handoff naming (`S1`, `S6`, `S10`, …); see `Figma/handoff/png/` for the
|
||
909×540 renders and `Figma/screens/*.jsx` for the live source.
|
||
|
||
---
|
||
|
||
## 1. Boot + Home gating
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
S1["S1 · Splash 🟢"] --> Home{"JWT session?"}
|
||
Home -->|"no"| Home1st["Home (1st time)<br/>+ login panel 🟡"]
|
||
Home -->|"yes"| HomeRet["Home (returning)<br/>+ profile panel 🟢"]
|
||
Home1st --> NotifCheck{"OS notif allowed?"}
|
||
HomeRet --> NotifCheck
|
||
NotifCheck -->|"no"| HomeBanner["Home + notif banner 🔴"]
|
||
NotifCheck -->|"yes"| HomeReady["Home (ready) 🟢"]
|
||
HomeBanner --> HomeReady
|
||
|
||
HomeReady --> CTA{"CTA tapped?"}
|
||
CTA -->|"'aku mau curhat'<br/>(1st time)"| NewUser["→ New User flow"]
|
||
CTA -->|"'curhat sama bestie baru'<br/>(returning)"| ReturningUser["→ Returning flow"]
|
||
|
||
classDef missing fill:#ffe5e5,stroke:#c44979
|
||
classDef partial fill:#fff4d6,stroke:#c69b3f
|
||
class HomeBanner missing
|
||
class Home1st partial
|
||
```
|
||
|
||
---
|
||
|
||
## 2. New-User onboarding (verified vs. anonymous)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start["from Home — 'aku mau curhat' 🟢"] --> NameCheck{"call_sign exists?"}
|
||
NameCheck -->|"no"| S2["S2 · Pengisian Nama 🟢"]
|
||
NameCheck -->|"yes"| VerifChoice
|
||
S2 --> VerifChoice["Verif vs Anon Choice Sheet<br/>(VerifChoiceSheet) 🔴"]
|
||
|
||
VerifChoice -->|"verif WA · Rp2k"| ESPa["S5 · ESP screening<br/>(multi-select chips) 🟡"]
|
||
VerifChoice -->|"tanpa verif · Rp5k+"| ESPb["S5 · ESP screening 🟡"]
|
||
|
||
%% Verified path
|
||
ESPa --> USPa["S5b · USP screen 🔴"]
|
||
USPa --> S3a["S3a · WhatsApp input 🟡 (6→4 digit)"]
|
||
S3a --> S3b["S3b · OTP 4-digit 🟡"]
|
||
S3b --> OTPok{"OTP ok?"}
|
||
OTPok -->|"too many retries"| OTPBlock["OTP Blocked Popup 🔴<br/>→ fallback to Anon"]
|
||
OTPBlock --> ESPb
|
||
OTPok -->|"verified"| S6["S6 · Paywall Rp2.000<br/>(12 menit, sekali seumur hidup) 🟡"]
|
||
S6 --> Pay
|
||
|
||
%% Anonymous path
|
||
ESPb --> USPb["S5b · USP screen 🔴"]
|
||
USPb --> PickMethod["Pilih cara curhat<br/>(chat / voice call) 🔴"]
|
||
PickMethod --> PickDuration["Pemilihan harga<br/>(5 durations, full screen) 🟡"]
|
||
PickDuration --> PayMethod["Cara bayar (QRIS-first) 🔴"]
|
||
PayMethod --> Pay
|
||
|
||
%% Shared payment exit
|
||
Pay["Xendit checkout<br/>(QRIS / e-wallet) 🔴"] --> WaitPay["Waiting Payment<br/>(20-min QRIS clock) 🔴"]
|
||
WaitPay --> PayStat{"payment status"}
|
||
PayStat -->|"timeout 20 min"| PayExpired["Pembayaran expired 🔴<br/>→ retry"]
|
||
PayExpired --> Pay
|
||
PayStat -->|"paid"| NotifGate
|
||
|
||
classDef missing fill:#ffe5e5,stroke:#c44979
|
||
classDef partial fill:#fff4d6,stroke:#c69b3f
|
||
class VerifChoice,USPa,USPb,PickMethod,PayMethod,Pay,WaitPay,PayExpired,OTPBlock missing
|
||
class ESPa,ESPb,S3a,S3b,S6,PickDuration partial
|
||
```
|
||
|
||
> **Anchor mismatch:** flow_customer.md numbers ESP/USP under
|
||
> `5.1.2 Verification request (OTP)` for both branches, but Figma puts
|
||
> `VerifChoiceSheet` *before* ESP. The mermaid above follows Figma; reconcile in
|
||
> phase4 spec.
|
||
|
||
---
|
||
|
||
## 3. Pre-pairing → Searching → Match (shared)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
NotifGate["Notif Gate Screen 🔴<br/>(Aktifkan / Nanti Saja)"] --> NotifBranch{"OS allowed?"}
|
||
NotifBranch -->|"no + ask"| EnableNotif["OS settings deeplink"] --> SoftPrompt
|
||
NotifBranch -->|"yes / skipped"| SoftPrompt
|
||
SoftPrompt["S7 · Soft-prompt<br/>(consent + warmup, CTA 'Aku ngerti, Lanjut') 🟡"] --> Blast
|
||
Blast["Blast pair request<br/>S7 · Searching state 🟡"] --> BlastTimer{"5-min timer"}
|
||
BlastTimer -->|"matched"| S9["S9 · Match Found<br/>(bestie name, age, hobi) 🟡"]
|
||
BlastTimer -->|"timeout"| S7Timeout["S7 · Timeout 5 menit 🔴<br/>CTA 'Coba Cari Lagi'<br/>ghost CTA 'Coba cari lagi nanti' → Home"]
|
||
S7Timeout -->|"retry"| Blast
|
||
S7Timeout -->|"home"| HomeRet
|
||
S9 --> S10
|
||
S10["S10 · Chat Room"]
|
||
HomeRet["→ Home (returning)"]
|
||
|
||
classDef missing fill:#ffe5e5,stroke:#c44979
|
||
classDef partial fill:#fff4d6,stroke:#c69b3f
|
||
class NotifGate,S7Timeout missing
|
||
class SoftPrompt,Blast,S9 partial
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Returning-User pairing (lama / baru)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
CTA["'curhat sama bestie baru' 🟢"] --> Choice["Bestie Choice Sheet<br/>(BestieChoiceSheet) 🔴"]
|
||
Choice -->|"bestie yang udah kenal"| HistList["Bestie History List<br/>(BestieHistoryList) 🟢"]
|
||
Choice -->|"bestie baru"| BlastFlow["→ S7 Soft-prompt + Blast<br/>(see diagram 3)"]
|
||
|
||
HistList --> PickBestie["pick bestie"]
|
||
PickBestie --> CheckOnline{"bestie online?"}
|
||
CheckOnline -->|"no"| OfflinePopup["Bestie Offline Popup<br/>(returning variant) 🟢"]
|
||
OfflinePopup -->|"cari bestie lain"| BlastFlow
|
||
OfflinePopup -->|"tanya admin"| AdminSheet["Sheet · tanya admin<br/>(WA / Telegram) 🔴"]
|
||
CheckOnline -->|"yes"| Targeted["Request targeted pair<br/>'Menunggu bestie tertentu' 🟡<br/>(20s countdown overlay)"]
|
||
Targeted --> TargetedRes{"mitra answers?"}
|
||
TargetedRes -->|"accept"| S10["→ S10 Chat Room"]
|
||
TargetedRes -->|"reject / timeout"| OfflinePopup
|
||
|
||
classDef missing fill:#ffe5e5,stroke:#c44979
|
||
classDef partial fill:#fff4d6,stroke:#c69b3f
|
||
class Choice,AdminSheet missing
|
||
class Targeted partial
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Chat Room (S10) — countdown UX
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Enter["enter S10 · Chat Room<br/>(WebSocket open, timer running) 🟡"] --> T3{"3-minutes-left tick"}
|
||
T3 -->|"fired"| Snackbar["S10 · Snackbar reminder<br/>'sisa 3 menit lagi ya' 🔴"]
|
||
Snackbar --> T2
|
||
T3 -->|"not yet"| T2{"2-minutes-left tick"}
|
||
T2 -->|"fired"| LowTime["S10 · Last 2 Minutes<br/>(timer turns danger color) 🔴"]
|
||
LowTime --> Expire
|
||
T2 -->|"not yet"| Expire{"timer hits 0"}
|
||
Expire -->|"fired"| ExpiredBanner["S10 · Floating Expired Banner<br/>'habis nih... mau lanjutin?' 🔴"]
|
||
ExpiredBanner --> CTAExt{"perpanjang CTA?"}
|
||
CTAExt -->|"yes"| TimeUp
|
||
CTAExt -->|"close / ignore"| EndFlow["→ end-session flow"]
|
||
|
||
Expire -->|"not yet · user taps perpanjang"| TimeUp
|
||
TimeUp["Time-up Bottom Sheet<br/>(5 durations · chat/call toggle) 🟡"]
|
||
TimeUp -->|"perpanjang"| AskMitra["Targeted re-pay request<br/>(same mitra, no blast) 🔴"]
|
||
TimeUp -->|"cukup, akhiri sesi"| EndFlow
|
||
|
||
AskMitra --> MitraRes{"mitra approves?"}
|
||
MitraRes -->|"yes + paid"| Enter
|
||
MitraRes -->|"reject"| OfflinePopup["Bestie Offline Popup<br/>(returning variant) 🟢"]
|
||
|
||
classDef missing fill:#ffe5e5,stroke:#c44979
|
||
classDef partial fill:#fff4d6,stroke:#c69b3f
|
||
class Snackbar,LowTime,ExpiredBanner,AskMitra missing
|
||
class Enter,TimeUp partial
|
||
```
|
||
|
||
---
|
||
|
||
## 6. End-of-session sequence (2-step confirm + closing message)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
EndStart["End-session entry<br/>(from S10 or Time-up sheet 'Cukup, Akhiri')"] --> Confirm1["Popup · Konfirmasi Akhiri (1)<br/>'beneran udah cukup?' 🟡"]
|
||
Confirm1 -->|"Gak Jadi, Balik"| TimeUp["Time-up Bottom Sheet 🟡"]
|
||
Confirm1 -->|"Lanjut Akhiri"| Confirm2["Popup · Konfirmasi Akhiri (2)<br/>'mau tinggalin pesan penutup?' 🔴"]
|
||
|
||
Confirm2 -->|"Tulis Pesan Penutup"| ClosingSheet["Pesan Penutup Bottom Sheet<br/>(textarea) 🟡"]
|
||
Confirm2 -->|"Lewati Saja"| ThankYou
|
||
|
||
ClosingSheet -->|"Kirim & Akhiri"| MitraReceipt{"mitra rejects close?"}
|
||
ClosingSheet -->|"Lewat — Langsung Akhiri"| ThankYou
|
||
MitraReceipt -->|"no"| ThankYou
|
||
MitraReceipt -->|"yes (rare)"| OfflinePopup["Bestie Offline Popup 🟢"]
|
||
|
||
ThankYou["S11 · Terima Kasih Udah Cerita 🔴"] --> Home["→ Home (returning) 🟢"]
|
||
|
||
classDef missing fill:#ffe5e5,stroke:#c44979
|
||
classDef partial fill:#fff4d6,stroke:#c69b3f
|
||
class Confirm2,ThankYou missing
|
||
class Confirm1,ClosingSheet,TimeUp partial
|
||
```
|
||
|
||
---
|
||
|
||
## Cross-reference: Figma → flow_customer.md
|
||
|
||
| Figma artifact (file) | Source | flow_customer.md ref |
|
||
|---|---|---|
|
||
| S1 Splash | `screens/onboarding.jsx::S1Splash` | §1 |
|
||
| Home 1st / Returning | `screens/v3.jsx::SHome1st` / `SHomeReturning` + `screens/session.jsx::S12Home` | §2–3 |
|
||
| Notif banner on home | `screens/v3.jsx::HBNotifBanner` | §4.1 |
|
||
| S2 Nama | `screens/onboarding.jsx::S2Name` (and v4 variant) | §5.1.1 |
|
||
| `VerifChoiceSheet` | `screens/v4.jsx::VerifChoiceSheet` | implied between §5.1.1 ↔ §5.1.2 |
|
||
| S5 ESP screening | `screens/onboarding.jsx::S5ESP` | §5.1.2.1.1 / §5.1.2.2.1 |
|
||
| S5b USP | `screens/onboarding.jsx::S5USP` | §5.1.2.1.2 / §5.1.2.2.2 |
|
||
| S3a WA / S3b OTP | `screens/onboarding.jsx::S3Phone` (+ `screens/v4.jsx::S3OTPV4`) | §5.1.2.1.3-4 |
|
||
| `OTPBlockedPopup` | `screens/v4.jsx::OTPBlockedPopup` | (gap — not in flow doc) |
|
||
| S6 Paywall Rp2k | `screens/onboarding.jsx::S6Paywall` | §5.1.2.1.5 |
|
||
| Pilih cara curhat | `screens/v3.jsx::SPickMethod` | §5.1.2.2.3 |
|
||
| Pemilihan harga | `screens/v3.jsx::SPickDuration` (+ `v4::InitialDurationPicker`) | §5.1.2.2.4 |
|
||
| Cara bayar | `screens/extras.jsx::SPaymentMethod` | §5.1.2.2.5 |
|
||
| Waiting Payment | `screens/extras.jsx::SWaitingPayment` | §5.1.4 |
|
||
| Pembayaran expired | `screens/v4.jsx::PaymentExpiredV4` (+ extras `SWaitingPayment expired`) | §5.1.4.1 |
|
||
| Notif Gate (full screen) | `screens/extras.jsx::SNotifGate` (+ `v4::NotifGateV4`) | §5.1.5.1 |
|
||
| S7 Soft-prompt + Searching | `screens/session.jsx::S7Prompt` + `S8Searching` + `screens/v3.jsx::SSearchPrompt` | §5.1.6-8 |
|
||
| S7 Timeout 5 menit | `screens/v3.jsx::SSearchPrompt(state='timeout')` | §5.1.8.2 |
|
||
| S9 Match | `screens/session.jsx::S9Match` (+ `v4::S9MatchV4`) | §5.1.9 |
|
||
| Bestie Choice Sheet | `screens/v4.jsx::BestieChoiceSheet` | §5.2.1 |
|
||
| Bestie History List | `screens/v4.jsx::BestieHistoryList` | §5.2.1.1 |
|
||
| Bestie Offline Popup | `screens/v4.jsx::BestieOfflinePopup` | §5.2.1.1.1 / §5.7.8.1.1.1 |
|
||
| Tanya Admin Sheet | `screens/v3.jsx::HBContactAdminSheet` | §5.2.1.1.1.2 |
|
||
| Menunggu Bestie | `screens/extras.jsx::SWaitingBestie` | §5.2.1.1.2.1 |
|
||
| S10 Chat | `screens/session.jsx::S10Chat` | §5.3 |
|
||
| 3-min Snackbar reminder | `screens/v3.jsx::HBSnackbar` (configured for "sisa 3 menit") | §5.4 |
|
||
| Last-2-min visuals | `screens/session.jsx::S10Chat` (lowTime branch) | §5.5 |
|
||
| Floating expired banner | `screens/v3.jsx::HBChatExpiredBanner` | §5.6 |
|
||
| Time-up Bottom Sheet | `screens/extras.jsx::STimeUpSheet` | §5.7-8 |
|
||
| Confirm akhiri (2 popups) | `screens/v3.jsx::HBConfirmEndPopup` (step 1 + 2) | §5.8.2.1 / §5.8.2.1.1 |
|
||
| Closing Message Sheet | `screens/extras.jsx::SClosingSheet` | §5.8.2.1.1.1 |
|
||
| S11 Thank-you | `screens/session.jsx::S11Post` | §5.8.2.1.1.1.1 |
|
||
|
||
Anything Figma describes that flow_customer.md doesn't mention is captured as a
|
||
gap in `phase4-customer-flow.md` (next-phase doc).
|