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

6.6 KiB

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
  • responseaccepted, 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