All files / src/components/PasswordWithConfirmation PasswordWithConfirmation.tsx

100% Statements 13/13
100% Branches 26/26
100% Functions 7/7
100% Lines 12/12

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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132                                                          12x 12x 12x     12x               12x 12x   12x               1x                             1x                           60x                                         1x                             1x                          
import { useState, useMemo } from 'react';
import TextField from '@mui/material/TextField';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import InputAdornment from '@mui/material/InputAdornment';
import IconButton from '@mui/material/IconButton';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import { useTranslation } from 'react-i18next';
 
export interface PasswordWithConfirmationProps {
  password: string;
  onPasswordChange: (pw: string) => void;
  confirmPassword: string;
  onConfirmChange: (pw: string) => void;
  touched: boolean;
  onBlur: () => void;
}
 
export default function PasswordWithConfirmation({
  password,
  onPasswordChange,
  confirmPassword,
  onConfirmChange,
  touched,
  onBlur,
}: PasswordWithConfirmationProps) {
  const { t } = useTranslation('signup');
  const [showPassword, setShowPassword] = useState(false);
  const [showConfirm, setShowConfirm]   = useState(false);
 
  // critères de robustesse
  const criteria = useMemo(() => ({
    length: password.length >= 15,
    upper:  /[A-Z]/.test(password),
    lower:  /[a-z]/.test(password),
    number: /\d/.test(password),
    special:/[\W_]/.test(password),
  }), [password]);
 
  const pwStrong = Object.values(criteria).every(Boolean);
  const pwsMatch = password === confirmPassword;
 
  return (
    <Box>
      {/* Mot de passe */}
      <TextField
        required fullWidth
        type={showPassword ? 'text' : 'password'}
        label={t('signup.passwordLabel')}
        value={password}
        onChange={e => onPasswordChange(e.target.value)}
        onBlur={onBlur}
        autoComplete="new-password"
        error={touched && !pwStrong}
        helperText={
          touched && !pwStrong
            ? t('signup.hintPasswordCriteria')
            : ''
        }
        slotProps={{
          input: {
            endAdornment: (
              <InputAdornment position="end">
                <IconButton
                  aria-label={showPassword ? t('signup.hidePassword') : t('signup.showPassword')}
                  onClick={() => setShowPassword(!showPassword)}
                  edge="end"
                >
                  {showPassword ? <VisibilityOff /> : <Visibility />}
                </IconButton>
              </InputAdornment>
            ),
          },
        }}
      />
 
      {/* Checklist de critères */}
      <Box sx={{ pl: 1, mt: 1 }}>
        {Object.entries(criteria).map(([key, ok]) => (
          <Box
            key={key}
            sx={{ display: 'flex', alignItems: 'center', mb: 0.5 }}
          >
            {ok
              ? <CheckCircleIcon fontSize="small" sx={{ color: 'success.main', mr: 1 }}/>
              : <RadioButtonUncheckedIcon fontSize="small" sx={{ color: 'text.disabled', mr: 1 }}/>
            }
            <Typography variant="body2">
              {t(`passwordCriteria.${key}`)}
            </Typography>
          </Box>
        ))}
      </Box>
 
      {/* Confirmation */}
      <TextField
        required fullWidth sx={{ mt: 2 }}
        type={showConfirm ? 'text' : 'password'}
        label={t('signup.confirmPasswordLabel')}
        value={confirmPassword}
        onChange={e => onConfirmChange(e.target.value)}
        onBlur={onBlur}
        autoComplete="new-password"
        error={touched && !pwsMatch}
        helperText={
          touched && !pwsMatch
            ? t('signup.hintPasswordsDontMatch')
            : ''
        }
        slotProps={{
          input: {
            endAdornment: (
              <InputAdornment position="end">
                <IconButton
                  aria-label={showConfirm ? t('signup.hidePassword') : t('signup.showPassword')}
                  onClick={() => setShowConfirm(!showConfirm)}
                  edge="end"
                >
                  {showConfirm ? <VisibilityOff /> : <Visibility />}
                </IconButton>
              </InputAdornment>
            ),
          },
        }}
      />
    </Box>
  );
}