Files
tabungin/UX_IMPROVEMENTS.md
dwindown 249f3a9d7d feat: remove OTP gate from transactions, fix categories auth, add implementation plan
- Remove OtpGateGuard from transactions controller (OTP verified at login)
- Fix categories controller to use authenticated user instead of TEMP_USER_ID
- Add comprehensive implementation plan document
- Update .env.example with WEB_APP_URL
- Prepare for admin dashboard development
2025-10-11 14:00:11 +07:00

3.9 KiB

UX Improvements - Email OTP Resend & QR Code Fix

🎯 Improvements Made:

1. Email OTP Resend Button with Timer

Feature: Added a resend button for email OTP with a 30-second cooldown timer.

How it works:

  • When user is on OTP verification page (email tab)
  • Button shows countdown: "Resend in 30s", "Resend in 29s", etc.
  • After 30 seconds, button becomes active: "Resend Code"
  • Click to resend → New OTP sent → Timer resets to 30s

Implementation:

// State management
const [resendTimer, setResendTimer] = useState(30)
const [canResend, setCanResend] = useState(false)

// Countdown timer
useEffect(() => {
  if (resendTimer > 0) {
    const timer = setTimeout(() => setResendTimer(resendTimer - 1), 1000)
    return () => clearTimeout(timer)
  } else {
    setCanResend(true)
  }
}, [resendTimer])

// Resend handler
const handleResendEmail = async () => {
  await axios.post(`${API_URL}/api/otp/email/send`, {}, {
    headers: { Authorization: `Bearer ${tempToken}` }
  })
  setResendTimer(30)
  setCanResend(false)
}

UI:

<Button
  variant="outline"
  onClick={handleResendEmail}
  disabled={!canResend || resendLoading}
>
  {resendLoading ? (
    <>Sending...</>
  ) : canResend ? (
    <>Resend Code</>
  ) : (
    <>Resend in {resendTimer}s</>
  )}
</Button>

2. QR Code Fix After Re-enabling TOTP

Problem: When disabling and re-enabling Google Authenticator, the QR code failed to load.

Root Cause: The QR code state wasn't being cleared when TOTP was disabled, causing stale data.

Fix: Clear QR code and secret when disabling TOTP:

const handleTotpDisable = async () => {
  await axios.post(`${API}/otp/totp/disable`)
  await loadOtpStatus()
  setShowTotpSetup(false)
  
  // Clear QR code and secret when disabling
  setOtpStatus(prev => ({
    ...prev,
    totpSecret: undefined,
    totpQrCode: undefined
  }))
}

Now:

  1. Disable TOTP → QR code and secret cleared
  2. Enable TOTP again → Fresh QR code generated
  3. QR code displays properly

📝 Files Modified:

1. apps/web/src/components/pages/OtpVerification.tsx

  • Added useState for resend timer and loading states
  • Added useEffect for countdown timer
  • Added handleResendEmail() function
  • Added resend button with timer in email OTP tab
  • Imported RefreshCw icon and axios

2. apps/web/src/components/pages/Profile.tsx

  • Updated handleTotpDisable() to clear QR code state
  • Clears totpSecret and totpQrCode when disabling

🧪 Testing:

Test Email OTP Resend:

  1. Login with email/password (has email OTP enabled)
  2. On OTP page, see "Resend in 30s" button (disabled)
  3. Wait for countdown
  4. After 30s, button shows "Resend Code" (enabled)
  5. Click button → New OTP sent
  6. Timer resets to 30s
  7. Check console for new OTP code

Test TOTP QR Code:

  1. Go to Profile page
  2. Setup Google Authenticator → QR code displays
  3. Verify and enable TOTP
  4. Disable TOTP
  5. Setup again → QR code displays properly

User Experience Improvements:

Before:

  • No way to resend email OTP
  • User stuck if email not received
  • QR code broken after re-enabling TOTP

After:

  • Can resend email OTP after 30 seconds
  • Clear countdown timer shows when resend is available
  • QR code works perfectly every time
  • Better user experience overall

🎯 Additional Features:

Resend Button States:

  1. Countdown (0-29s): "Resend in Xs" - Disabled, gray
  2. Ready (30s+): "Resend Code" - Enabled, clickable
  3. Sending: "Sending..." - Disabled, loading spinner
  4. Sent: Timer resets to 30s

Error Handling:

  • If resend fails: Shows error message
  • If verification fails: User can resend
  • Timer persists across tab switches

Both improvements are now live! Test them out! 🚀