All files / src/components/auth/ForgotPasswordConfirmation ForgotPasswordConfirmation.tsx

90.9% Statements 20/22
100% Branches 8/8
75% Functions 6/8
95% Lines 19/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100                              2x     26x 26x 26x   26x   26x 26x 3x 3x       26x 3x   3x 3x           26x 26x 26x 1x   25x     26x                                                                                                    
/**
 * ForgotPasswordConfirmation Component (TDD - GREEN Phase)
 * Story 1.2.2: Implement Forgot Password Flow
 */
 
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Typography, Button, Alert, Snackbar } from '@mui/material';
import { CheckCircle } from '@mui/icons-material';
import { useForgotPassword } from '@hooks/useForgotPassword';
 
interface ForgotPasswordConfirmationProps {
  email: string;
}
 
export const ForgotPasswordConfirmation: React.FC<ForgotPasswordConfirmationProps> = ({
  email,
}) => {
  const { t } = useTranslation(['auth', 'common']);
  const [cooldown, setCooldown] = useState(0);
  const [showSuccessToast, setShowSuccessToast] = useState(false);
  // Reuse useForgotPassword for resending - Amplify allows multiple calls to resetPassword
  const { mutate: resendCode, isPending } = useForgotPassword();
 
  useEffect(() => {
    if (cooldown > 0) {
      const timer = setTimeout(() => setCooldown(cooldown - 1), 1000);
      return () => clearTimeout(timer);
    }
  }, [cooldown]);
 
  const handleResend = () => {
    resendCode(email, {
      onSuccess: () => {
        setCooldown(60); // 60-second cooldown
        setShowSuccessToast(true);
      },
    });
  };
 
  // Mask email for display (u***@example.com)
  const maskEmail = (email: string) => {
    const [local, domain] = email.split('@');
    if (local.length === 1) {
      return `${local}@${domain}`;
    }
    return `${local[0]}${'*'.repeat(local.length - 1)}@${domain}`;
  };
 
  return (
    <Box sx={{ maxWidth: 400, mx: 'auto', mt: 8, textAlign: 'center' }}>
      <CheckCircle sx={{ fontSize: 64, color: 'success.main', mb: 2 }} />
 
      <Typography variant="h4" gutterBottom>
        {t('auth:forgot.confirmTitle')}
      </Typography>
 
      <Typography variant="body1" sx={{ mb: 2 }}>
        {t('auth:forgot.confirmMessage')}
      </Typography>
 
      <Typography variant="h6" sx={{ mb: 3, fontWeight: 'bold' }}>
        {maskEmail(email)}
      </Typography>
 
      <Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
        {t('auth:forgot.confirmInstructions')}
      </Typography>
 
      <Button variant="contained" fullWidth href="/auth/login" sx={{ mb: 2 }}>
        {t('auth:forgot.backToLogin')}
      </Button>
 
      <Box sx={{ mt: 3 }}>
        <Typography variant="body2" color="text.secondary">
          {t('auth:forgot.didntReceive')}
        </Typography>
        <Button onClick={handleResend} disabled={cooldown > 0 || isPending} sx={{ mt: 1 }}>
          {cooldown > 0
            ? t('common:status.wait', { seconds: cooldown })
            : t('auth:forgot.resendButton')}
        </Button>
      </Box>
 
      <Alert severity="info" sx={{ mt: 3 }}>
        {t('auth:forgot.checkSpam')}
      </Alert>
 
      {/* Success toast notification */}
      <Snackbar
        open={showSuccessToast}
        autoHideDuration={3000}
        onClose={() => setShowSuccessToast(false)}
        message={t('auth:forgot.resendSuccess')}
        anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
      />
    </Box>
  );
};