Commit Graph

13 Commits

Author SHA1 Message Date
2645bcd0e5 Phase 4 Stage 2: onboarding redesign (client_app + mitra_app)
Verif Choice Sheet on display_name_screen drives the user into either
the verified or anonymous onboarding sub-flow. ESP screen (12 chips,
multi-select, info-only) + USP screen are shared between both branches;
selections persist through to chat_sessions.topics on session start.

OTP-blocked popup (HaloPopup) listens for the four real OTP-rate-limit
error codes (OTP_RATE_LIMIT_PHONE, OTP_RATE_LIMIT_IP, OTP_COOLDOWN,
OTP_ATTEMPTS_EXCEEDED) and drops the user onto the anonymous path with
ESP/USP state preserved.

Auth-providers gating replaces the --dart-define=ENABLE_SOCIAL_AUTH
build flag with server-driven discovery. authProvidersProvider preloads
GET /api/shared/auth-providers at cold start; welcome/register/
force-register screens render Google/Apple buttons only when the
backend reports enabled:true. Falls back to phone-OTP-only when both
providers are off. social_auth_enabled.dart deleted; client_app/CLAUDE.md
updated to reflect the new gating contract.

Mitra app: chat screen renders an ESP chip strip above the first message
bubble when chat_sessions.topics is non-empty.

Backend session.service.js getSessionById SELECTs cs.topics so the mitra
side can read the customer's selected topics.

Maestro flows 02_onboarding_verified.yaml + 03_onboarding_anon.yaml.

Deviation from plan: plan referenced OTP error code 'otp_retry_exhausted';
real codes are OTP_RATE_LIMIT_*/OTP_COOLDOWN/OTP_ATTEMPTS_EXCEEDED -
popup listens for all four. Plan said 'has_paid_first_session'; live
endpoint returns 'has_consulted_before' - used the live field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:23:57 +08:00
4ada7c991a Phase 4 Stage 0: design system foundation (client_app)
- HaloTokens, HaloSpacing, HaloRadius, HaloMotion, HaloShadows (warm palette;
  calm/playful stubbed for phase 5).
- Bundled Bricolage Grotesque, Poppins, JetBrains Mono (~1.2 MB total, OFL).
- haloThemeData() wired into MaterialApp.router with Figma-aligned text
  scale, pill ElevatedButton, 64px input height, 24px-corner BottomSheet,
  dark pill SnackBar.
- Halo* widget primitives: Button, Orb, StepDots, BottomSheet, Popup,
  Snackbar, Chip.
- Dev-only /_theme_preview route gated by --dart-define=THEME_PREVIEW=true
  for visual reference during stages 2-8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:56:00 +08:00
d09e50af55 Phase 3.7: paid pairing flow + returning chat + extension flip
- Backend: payment_sessions + pairing_failures tables; payment.service.js
  and pairing-failure.service.js (new); rewritten pairing.service.js
  (payment-gated blast + targeted "Curhat lagi" + cancel + fallback);
  rewritten extension.service.js (data-driven auto-approve with offline
  safeguard, charge-at-approval); pricing.service.js (extension tiers
  without free trial); mitra-status.service.js (countAvailableMitras
  cached path); 60s sweeper for stale payment sessions
- Backend routes: client.payment.routes, client.mitra-availability.routes,
  internal/failed-pairings.routes; client.chat.routes rewritten for
  payment-gated start + /returning + /cancel + /fallback-to-blast;
  internal/config.routes adds 4 new keys with Valkey invalidate publish
- client_app: mitra-availability poll, payment screen + notifier, pairing
  notifier rewrite (PairingTargetedWaiting + PairingFailed states),
  targeted-waiting overlay + bestie-unavailable dialog, "Curhat lagi"
  CTA, failed-pairing terminal, extension via payment-session
- mitra_app: PairingRequestType enum, returning-chat 20s countdown
  auto-dismiss, extension card "otomatis disetujui" copy
- control_center: 4 new config rows in Settings, Failed Pairings page
  (filter + paginate + action menu), sidebar + route registered
- Test infrastructure: Vitest backend (7/7 pass), Playwright CC (4/4
  pass), Maestro mobile scaffold (CLI install pending)
- Bugs found via Playwright + fixed: LoginPage labels not associated
  with inputs (a11y); backend internal CORS missing PATCH/PUT/DELETE
  in allow-methods (silent settings breakage in browsers since Stage 4)
- Docs: phase3.7.md PRD, phase3.7-plan.md, phase3.7-questions.md (Q&A),
  phase3.7-testing.md (E2E checklist), phase3.7-test-run-2026-05-03.md
  (today's run results)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:02:49 +08:00
d9869bf6af Phase 3.4: customer OTP screen rewrite + lockout UX + bug fixes
OTP screen rewrite: 6 rounded boxes, auto-advance focus, auto-submit on the
6th digit, hardware-backspace on empty boxes (intercepted via Focus.onKeyEvent
since TextField.onChanged doesn't fire on already-empty input), resend
cooldown sourced from /api/shared/config/otp, and an inline error message
under the boxes instead of a SnackBar.

Several bugs fixed inline that surfaced during testing:

- ref.listen inside build() accumulates listeners on every rebuild — the
  resend countdown's per-second setState was piling up duplicate listeners
  so one error triggered N callback fires. Moved to ref.listenManual in
  initState; subscription disposed in dispose().

- RouterNotifier was calling notifyListeners() on every auth state change
  including AsyncError, which rebuilt the Navigator/Scaffold mid-snackbar
  and visually duplicated the error toast. Now skips AsyncError and
  same-data-variant transitions.

- ScaffoldMessenger.showSnackBar from a Riverpod listener callback could
  still render twice even with hideCurrentSnackBar — replaced with an
  inline error widget to sidestep the snackbar machinery entirely.

- register_screen now uses context.go instead of context.push for the
  OTP route, so re-submitting the phone form doesn't stack multiple
  OtpScreen instances with active subscriptions.

Lockout UX: AuthErrorInfo wraps the error message + code + retry_after_seconds
parsed from the backend's structured error response. On rate-limit codes
(OTP_COOLDOWN, OTP_RATE_LIMIT_PHONE, OTP_RATE_LIMIT_IP), the OTP screen
extends "Kirim ulang kode" cooldown to match the server's wait, and the
register screen disables "Kirim OTP" with a "Coba lagi dalam …" countdown.
formatCountdown() in core/constants.dart renders Xd under 90 seconds and
Xm Yd above (clearer than raw seconds for long lockouts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:54:49 +08:00
97d50a8e08 Chat UI redesign, splash screen, and onboarding carousel
- Redesign chat screens (both apps) to match Figma: pink theme with
  doodle pattern background, white app bar with centered name and
  chevron back, rose sender bubbles, white receiver bubbles, entry
  banners, and session-ended bottom bar
- Add splash_chat_hebat.png as native Android splash screen with
  Android 12+ support (values-v31)
- Add Flutter splash screen using splash_chat_hebat.png
- Add onboarding carousel (client_app only): 3 pages with 1s
  auto-advance, last page manual "Mulai" button, first-launch only
- Register image assets in both pubspec.yaml files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 22:05:15 +08:00
212e1e8ac6 Fix auth: auto-create customer, display name flow, OTP auto-verify
- Backend: getOrCreateCustomer with phone fallback for re-login
- Backend: PATCH /api/client/auth/profile for display name update
- Client app: AuthNeedsDisplayNameData state + SetDisplayNameScreen
- Client app: ApiClient.patch method
- Both apps: handle verificationCompleted for auto-verify (test numbers)
- Both apps: skip credential sign-in if already auto-verified
- Remove debug prints from mitra auth + OTP screens
- Fix ChatRequestNotifier.startListening skips when accepting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:22:28 +08:00
1b249e34b0 Fix router redirect breaking OTP flow on both apps
AsyncLoading during OTP request was redirecting from /login to /splash,
bouncing users back to login. Now auth routes stay put during loading —
only redirect to splash from non-auth routes (initial app startup).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:38: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
844d7234e6 Phase 2 refinements: Firebase config, dev environment fixes, phase 3 requirement draft
- Integrated Firebase SDK in both Flutter apps (google-services, firebase_options)
- Fixed auth flow, API client, and pairing/status blocs for dev environment
- Added full Flutter project scaffolds (android, ios, web, etc.)
- Added phase 3 chat engine requirement document
- Added bugreport zip pattern to gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:16:34 +08:00
d668112edd Phase 2 scaffold: mitra online status & pairing logic
Add mitra online/offline status with heartbeat-based auto-offline,
customer-mitra pairing via Valkey pub/sub blast, session management,
and control center dashboard with real-time stats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 23:17:49 +08:00
a7a2a32d27 Phase 1 scaffold: auth for all apps
- Backend: Fastify with two listeners (public + internal), routes, services, DB migration + seed
- client_app: Flutter with BLoC, all auth screens (welcome, display name, register, OTP, force-register)
- mitra_app: Flutter with BLoC, OTP-only login
- control_center: React + Vite, email/password login, mitra/user management, anonymity settings
- Docs: phase1 plan, API contract, client app mockup
- CLAUDE.md and shared memory for all subprojects

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