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>
This commit is contained in:
@@ -1,3 +1,14 @@
|
||||
/// Format a remaining-seconds countdown for display in a button or label.
|
||||
/// - Under 90 seconds: "Xd" (e.g. "60d")
|
||||
/// - 90 seconds and up: "Xm Yd" (e.g. "11m 40d")
|
||||
/// `d` and `m` are Indonesian short forms for detik (second) and menit (minute).
|
||||
String formatCountdown(int totalSeconds) {
|
||||
if (totalSeconds < 90) return '${totalSeconds}d';
|
||||
final minutes = totalSeconds ~/ 60;
|
||||
final seconds = totalSeconds % 60;
|
||||
return '${minutes}m ${seconds}d';
|
||||
}
|
||||
|
||||
/// User types
|
||||
class UserType {
|
||||
static const customer = 'customer';
|
||||
|
||||
Reference in New Issue
Block a user