Files
halobestie-clone/requirement/phase3.2.md
ramadhan sjamsani 4158fb9432 Phase 3.2 docs + Phase 3.1 testing fixes
- Add phase3.2.md requirement: overlay UX, mitra activity log
- Add phase3.2-plan.md implementation plan
- Fix stale request validation: add GET /:sessionId/status endpoint
- Fix notification tap flow: setIncomingFromNotification + onChatRequestTapped
- IncomingRequestSheet shows stale message instead of auto-dismiss
- Home screen validates on resume, shows immediately on fresh WS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 22:09:25 +08:00

138 lines
6.6 KiB
Markdown

# PRD: Mitra Chat Request Overlay & Pairing UX
# Overview
**Goal:** Reliable incoming chat request experience for Mitra — always visible, non-blocking, works in all app states (foreground, background, killed)
**Success looks like:** When a customer requests a chat, the mitra is always notified and can accept/reject without leaving their current screen. The notification works regardless of whether the app is in foreground, background, or opened from a push notification.
## Background
- Current bottom sheet approach (`showModalBottomSheet`) fails silently when the app is backgrounded
- Flutter UI cannot render when the app is not in foreground
- Push notifications work in background but the in-app response (bottom sheet) doesn't show reliably when the user taps the notification
- The state change from WebSocket gets "consumed" by listeners while backgrounded, so returning to foreground shows stale data
- Need a solution that is non-blocking (doesn't interrupt active chat sessions) and works on any page
# Functional Requirement
## Incoming Chat Request Overlay
### Trigger
- Overlay watches `chatRequestProvider` state — it does NOT depend on any specific page or lifecycle event
- Three sources can trigger the overlay:
1. **WebSocket** — real-time delivery when app is in foreground
2. **FCM notification tap** — user taps push notification, app opens, state is set from notification payload
3. **App resume** — app returns to foreground, validates pending request with backend
### Appearance
- Slides up from the bottom of the screen, like a bottom sheet
- Rounded top corners
- Page behind is partially dimmed to get mitra's attention
- Shows on top of ANY page (home, chat, history, settings, etc.)
### Content
- Session information (duration, etc.)
- Accept button
- Reject button
- Swipe down to close (= ignore, NOT reject)
### Behavior
- **Accept** → overlay dismisses, navigate to chat session with customer
- **Reject** → overlay dismisses, mitra continues what they were doing
- **Swipe down / close** → overlay dismisses, request is ignored (no explicit reject sent to backend)
- **Request cancelled by customer** → overlay updates to show "Permintaan dibatalkan oleh customer"
- **Request accepted by other mitra** → overlay updates to show "Permintaan diterima oleh Bestie lain"
- **Request expired (60s timeout)** → overlay updates to show "Permintaan kedaluwarsa"
- Stale messages do NOT auto-dismiss — mitra must acknowledge by tapping OK or swiping down
### Multiple Requests
- Show one request at a time
- Requests are queued — when current request is resolved (accepted/rejected/ignored/expired), next queued request appears
- Future enhancement: dedicated `/chat-requests` page with full list
## Push Notification (Background/Killed)
### When App is Backgrounded
- Local notification with sound + vibration (already implemented via WebSocket listener)
- FCM push notification as fallback when WebSocket is disconnected
### When Notification is Tapped
- App opens → `chatRequestProvider` state is set from notification payload (`session_id`)
- Overlay appears with the request detail
- Backend validation confirms request is still pending before showing accept/reject
### Stale Notification Handling
- If user taps a notification for an already-resolved request, overlay shows appropriate message ("dibatalkan" / "diterima Bestie lain" / "kedaluwarsa") then auto-dismisses
## Mitra Request Activity Log
### Goal
Track every mitra's response to incoming chat requests for QC measurement — how many accepted, rejected, ignored (expired without action), and how many active sessions they had at the time.
### Logged Data
For each incoming request delivered to a mitra, log:
- `mitra_id` — which mitra received the request
- `session_id` — which chat request
- `response``accepted`, `rejected`, `ignored` (expired without action), `missed` (taken by other mitra before response)
- `response_time_seconds` — how long it took the mitra to respond (null if ignored)
- `active_session_count` — how many active sessions the mitra had at the time of the request
- `notified_at` — when the request was delivered to the mitra
- `responded_at` — when the mitra responded (null if ignored)
### When to Log
All logging is **backend-side and event-driven** — no polling or constant monitoring needed. The frontend only triggers `accepted` and `rejected` through existing API calls.
| Response | Triggered by | Frontend action? |
|---|---|---|
| `accepted` | Backend — when mitra calls `POST /:sessionId/accept` | Yes — mitra taps Accept |
| `rejected` | Backend — when mitra calls `POST /:sessionId/decline` | Yes — mitra taps Reject |
| `missed` | Backend — when `acceptPairingRequest` marks other mitras' notifications (another mitra accepted first) | No — fully backend |
| `ignored` | Backend — when `expirePairingRequest` fires after 60s timeout with no response | No — fully backend |
### Existing Infrastructure
The `chat_request_notifications` table already tracks `notified_at`, `response`, `responded_at`. Current changes needed:
- Distinguish `missed` from `ignored` (currently both stored as `ignored`)
- Add `active_session_count` column — recorded when the notification is created
- `response_time_seconds` can be calculated from `responded_at - notified_at` (no new column needed)
### Control Center
- New dedicated page: Mitra Request Activity
- Dashboard: mitra acceptance rate, average response time, rejection count, ignore count per mitra
- Filter by date range and mitra
- Auto-flagging mitras with high rejection/ignore rate is **out of scope** for this phase (planned for future)
## Technical Implementation
### Overlay Component
- Single `OverlayEntry` managed from `main.dart`
- Wrapped around `MaterialApp.router` in a `Stack`
- Watches `chatRequestProvider` — shows/hides based on state
- Uses `AnimatedPositioned` or `SlideTransition` for bottom-up animation
- `GestureDetector` for swipe-to-dismiss
### State Flow
```
WebSocket ──→
FCM tap ──→ chatRequestProvider ──→ Overlay shows/hides
App resume ──→
```
### No Changes Required
- Backend pairing service (already sends WS + FCM)
- Push notification payload (already contains session_id + type)
- Chat request notifier (already manages state from WS + FCM)
### Cleanup from Phase 3.1
- Remove `showModalBottomSheet` for incoming requests from home screen
- Remove `IncomingRequestSheet` widget
- Remove `didChangeAppLifecycleState` incoming request check from home screen
# Tech Stack
- Flutter `Overlay` / `OverlayEntry` or `Stack` with `AnimatedPositioned`
- Existing Riverpod `chatRequestProvider`
- No backend changes