Commit Graph

11 Commits

Author SHA1 Message Date
6801001b64 Phase 3: closing-overlay fix + goodbye-composer dedupe
Customer chat refreshSessionStatus now clears sessionExpired carryover so the
goodbye composer renders correctly when re-opening a closing session from
history. Backend /api/shared/chat/:id/info returns goodbye_submitted_by_me;
both apps suppress the composer for the side that has already submitted and
render an awaiting-banner view instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:43:19 +08:00
f8380163bc Phase 3: session-end UX overhaul + closing-grace cleanup
Promotes the customer-side chat WebSocket to active-session-scoped (driven
by a new `activeSessionProvider`) so home reflects session state in real
time without a per-screen connection. Backend now auto-completes sessions
left in `closing` after a 5-minute grace window so abandoned goodbye flows
don't leave the customer's home permanently locked.

Customer:
- New `activeSessionProvider` (replaces `unread_notifier`) — single source
  of truth for the active session + unread count; polled every 15s.
- Chat WS lifecycle moved to `main.dart` listener on activeSessionProvider.
  Chat screen joins via `connectIfNotConnected`; the new
  `refreshSessionStatus` reconciles flags from the server when re-entering
  an already-connected session (covers missed `sessionClosing`/`sessionExpired`
  WS events).
- Home filters `closing` from the "Sesi Aktif" CTA so a session pending
  goodbye doesn't block "Mulai Curhat".
- Timer-expired UX is a non-dismissible modal (Tutup / Perpanjang) instead
  of an inline bar.
- Early-end goodbye composer gets an amber "Sesi telah ditutup oleh Bestie"
  banner. Goodbye TextEditingController lifted to state so focus changes
  no longer wipe the message.
- Closure provider reset on chat_screen mount to avoid stale
  `ClosureCompleteData` from a previous session leaking into a new view.
- Chat history now lists `closing` sessions with a "Belum ditutup" badge
  that routes to the live chat (goodbye composer) instead of the transcript.

Mitra:
- Same goodbye-controller fix as customer.
- Same chat-history badge + routing for `closing` items.

Backend:
- New `EndedBy.SYSTEM_AUTO_CLOSE` constant.
- `startClosureGraceTimer` extracted in `session-timer.service.js`; wired
  in from `closure.initiateEarlyEnd`, `extension.rejectExtension`, and
  `extension.handleExtensionTimeout`. Cancelled when customer submits
  goodbye.
- Restart recovery (`restoreActiveTimers`) re-arms grace timers and stamps
  any orphaned `closing` rows with `system_auto_close`.
- `getCustomerHistory` / `getMitraHistory` include `closing` alongside
  `completed`; ordering uses `COALESCE(ended_at, created_at)`.

Removed: dead `session_active_screen.dart` (no router entry).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:47:24 +08:00
98156d1e49 Phase 3.4: client_app self-managed auth cutover
Rips firebase_auth; auth talks directly to the new backend endpoints.
Anonymous-first + phone OTP work end-to-end; Google/Apple SDKs are kept
but buttons are hidden behind ENABLE_SOCIAL_AUTH until backend OAuth
credentials are provisioned.

Smoke-tested against the backend via curl:
- anonymous → PATCH display_name → /me
- OTP request (read stub code from backend log) → verify with
  anonymous_customer_id → same customer row preserved, display_name
  preserved, phone added → upgrade confirmed
- refresh rotation + logout → post-logout refresh correctly fails
  REFRESH_INVALID
- Debug APK builds clean

- pubspec: drop firebase_auth; add flutter_secure_storage
- core/auth/auth_bridge.dart: shared mutable state (access token +
  refresh callback + in-flight de-dup) — keepAlive provider
- core/auth/token_storage.dart: flutter_secure_storage wrapper
  (customer_refresh_token key)
- core/auth/social_auth_enabled.dart: const flag from
  --dart-define=ENABLE_SOCIAL_AUTH (default false)
- core/auth/auth_notifier.dart: bootstrap via stored refresh; anonymous
  via /api/shared/auth/anonymous + PATCH display_name; phone OTP via
  /api/client/auth/*; Google + Apple wired (passes anonymous_customer_id
  for upgrade); anonymity config check for ForceRegister state; granular
  error-code mapping
- core/api/api_client.dart: Bearer from bridge + postRaw(skipAuth) for
  auth endpoints + single-retry 401 refresh
- core/chat/chat_notifier.dart + core/pairing/pairing_notifier.dart: WS
  auth frame reads bridge.accessToken
- features/auth/screens/otp_screen.dart: verificationId → otpRequestId
- features/auth/screens/register_screen.dart + force_register_screen.dart:
  Google/Apple buttons gated behind kSocialAuthEnabled; force_register
  drops obsolete linkAccount() (upgrade happens server-side now via
  anonymous_customer_id)
- client_app/CLAUDE.md: Auth section rewritten (was stale on Firebase)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:08:20 +08:00
780cade3db Phase 3.3: topic sensitivity + Phase 3.4: auth foundation
Phase 3.3 — Session Topic Sensitivity (complete):
- Backend: topic_sensitivity column + session_sensitivity_log, sensitivity service
  (flip with one-way-latch + audit), PATCH /api/shared/chat/sessions/:id/topic,
  topic carried in pairing + extension WS payloads, CC filter + sensitive stats
  + per-mitra sensitive columns on activity page
- client_app: TopicSelectionBottomSheet before pricing, topic flows through
  pairing request, silent WS handler for session_topic_updated
- mitra_app: SensitivityBadge + SensitivityTheme + sensitivityConfigProvider,
  overlay badge + yellow accent, chat screen app-bar toggle with configurable
  confirmation + latch, extension card shows current flag, history + transcript
  yellow theme
- control_center: Sensitivitas Topik settings section, topic filter + column
  with inline audit log, sensitive stats dashboard card, mitra activity
  sensitive columns with QC flag

Phase 3.4 — Self-Managed Auth (foundation only):
- Migration: auth_sessions + otp_requests tables, social identity columns on
  customers, password_hash + lockout on control_center_users, OTP + CC lockout
  app_config keys
- New services: password (bcrypt + complexity), token (JWT HS256 + refresh
  rotation, session_id claim pre-wires future Valkey revocation),
  social-identity (Google + Apple JWKS), OTP (Fazpass stub — real API TBD)
- Constants: AuthProvider + OtpChannel
- Middleware, auth route rewrites, WS auth update, Firebase → FCM isolation
  still pending (next chunk); Fazpass docs + Apple Developer setup still
  required before E2E testing

Docs:
- requirement/phase3.3.md, phase3.3-plan.md, phase3.3-testing.md
- requirement/phase3.4.md, phase3.4-plan.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:15:12 +08:00
50d31260dc Fix auth errors, CORS, control center login, and stale session handling
- Mitra auth: parse DioException response for proper error messages
  (ACCOUNT_NOT_FOUND, ACCOUNT_INACTIVE) instead of generic "OTP invalid"
- Backend: add CORS to internal app (port 3001) for control center
- Control center: fix login race condition (wait for AuthContext verify
  before navigating), fix MitraActivityPage fetching paginated data
- Stale session goodbye: both apps detect SESSION_NOT_ACTIVE/409 and
  move to complete state instead of retrying endlessly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:49:57 +08:00
229f889551 Phase 3.1 WS2: FCM fallback Flutter + CC, unread badges, dynamic ping
- Control center: add mitra ping config UI (require ping toggle + interval)
- Mitra app StatusNotifier: honor require_ping and ping_interval_seconds
  from API; skip heartbeat when ping not required
- Both apps: update notification services for FCM deep-linking
  - mitra_app: handle chat_request (open_accept), session_closing
  - client_app: handle session_closing, paired
- Unread badge providers:
  - mitra_app: UnreadSessions provider (polls active-with-unread, badge
    on active sessions button)
  - client_app: UnreadCount provider (polls active-with-unread, badge
    on _ActiveSessionCard)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:29:06 +08:00
fa8c963d92 Phase 3.1: Remove flutter_bloc + equatable, delete old bloc files
- Remove flutter_bloc and equatable dependencies from both apps
- Delete all 10 old bloc files (5 per app)
- Fix 6 remaining screens that used context.read<ApiClient>() from
  flutter_bloc → converted to ConsumerStatefulWidget/ConsumerWidget
  with ref.read(apiClientProvider)
- Both apps now use Riverpod exclusively for state management

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:12:28 +08:00
bc66bbf50a Phase 3.1: Complete client_app Riverpod migration (all blocs)
- Migrate SessionClosureBloc → SessionClosureNotifier (@riverpod)
- Migrate PairingBloc → PairingNotifier (@riverpod, WebSocket + timer)
- Migrate ChatBloc → ChatNotifier (@riverpod, WebSocket + message state)
- Remove all flutter_bloc usage from client_app screens and main.dart
- MultiBlocProvider fully removed from client_app
- All screens now use ConsumerWidget/ConsumerStatefulWidget + ref

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:01:48 +08:00
d15b2f05fc Phase 3.1 WIP: Riverpod migration (client_app Auth + ChatOpening)
- Add phase3.1 requirement and implementation plan docs
- Add Riverpod dependencies to both client_app and mitra_app
- Wrap both app roots with ProviderScope
- Migrate client_app AuthBloc → AuthNotifier (@riverpod annotation)
- Migrate client_app ChatOpeningBloc → chatPricingProvider (FutureProvider)
- Update router to use Riverpod-based auth state for redirects
- Update all auth screens (display name, register, OTP, force register)
- Update home screen and pricing bottom sheet
- Add android:usesCleartextTraffic for dev HTTP access on both apps
- mitra_app prepared with ProviderScope + ApiClient provider (blocs next)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:51:17 +08:00
b0502ac92b Phase 3 testing fixes: Fastify 5, SSE→WebSocket+FCM, enums, security, session lifecycle
- Upgrade Fastify 4→5 with all plugins (@fastify/websocket 11, cors 11, sensible 6)
- Migrate all SSE endpoints to WebSocket + FCM push (mitra chat requests, customer pairing status)
- Add flutter_local_notifications for foreground push notifications with sound
- Add splash screen to both apps (hide auth loading flash)
- Introduce constants/enums across entire codebase (no raw string literals)
- Move price tiers from hardcoded array to app_config DB (data-driven, includes 1-min test tier)
- Add session ownership validation on all shared chat routes
- Add ownership checks on endSession, respondToExtension, requestExtension
- Fix session timer: auto-complete expired/stale sessions on server restart
- Add 5-min grace period for abandoned closing sessions
- Fix extension flow: proper session_resumed handling, clearExtensionRequest, closure grace timer cleanup
- Fix chat screens: ConnectChat in initState, session status check on connect
- Fix customer expired view: 5-min countdown, closure state priority over expired state
- Fix mitra extension UI: loading spinner, disable buttons, handle EXTENSION_RESOLVED error
- Fix GoRouter navigation consistency (no more Navigator.pushNamed)
- Fix goodbye view keyboard overflow (SingleChildScrollView)
- Add active session card on customer home screen with refresh on navigate back
- Fix PricingBottomSheet extension mode (RequestExtension instead of new pairing)
- Send session_resumed to both parties on extension accept

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 00:17:25 +08:00
b4efcf14c2 Phase 3 scaffold: chat engine (WebSocket, FCM, pricing, timer, extension, history)
- Backend: WebSocket plugin, chat/pricing/timer/extension/closure/notification services
- Client app: ChatBloc, pricing dialog, chat screen with message status, extension/goodbye flow, history
- Mitra app: MitraChatBloc, ExtensionBloc, chat screen, extension accept/reject, history
- Control center: free trial, extension timeout, early end config toggles
- DB migration: chat_messages, session_closures, session_extensions, customer_transactions tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:58:11 +08:00