All files / src/components PasswordStrengthIndicator.tsx

100% Statements 25/25
100% Branches 28/28
100% Functions 4/4
100% Lines 23/23

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                          2x     35x 35x     35x 175x 165x 107x   4x   14x   3x   76x   5x   5x         35x 33x   4x   7x   1x   19x   1x   1x       35x         175x                                                              
// src/pages/admin/components/PasswordStrengthIndicator.tsx
// src/components/PasswordStrengthIndicator.tsx
import React from 'react';
import zxcvbn from 'zxcvbn';
 
interface PasswordStrengthIndicatorProps {
  password?: string;
}
 
/**
 * A component that visually indicates the strength of a password using zxcvbn.
 * It displays a colored bar and provides feedback to the user.
 */
export const PasswordStrengthIndicator: React.FC<PasswordStrengthIndicatorProps> = ({
  password = '',
}) => {
  const result = zxcvbn(password);
  const score = result.score; // Score from 0 (worst) to 4 (best)
 
  // Function to determine the color of each segment of the strength bar
  const getBarColor = (index: number) => {
    if (password.length === 0) return 'bg-gray-200 dark:bg-gray-600';
    if (index > score) return 'bg-gray-200 dark:bg-gray-600';
    switch (score) {
      case 0:
        return 'bg-red-500';
      case 1:
        return 'bg-red-500';
      case 2:
        return 'bg-orange-500';
      case 3:
        return 'bg-yellow-500';
      case 4:
        return 'bg-green-500';
      default:
        return 'bg-gray-200 dark:bg-gray-600';
    }
  };
 
  // Function to get a human-readable strength label
  const getStrengthLabel = () => {
    switch (score) {
      case 0:
        return 'Very Weak';
      case 1:
        return 'Weak';
      case 2:
        return 'Fair';
      case 3:
        return 'Good';
      case 4:
        return 'Strong';
      default:
        return '';
    }
  };
 
  return (
    <div className="mt-2 space-y-1">
      <div className="flex space-x-1">
        {/* Create 5 segments for the strength bar */}
        {Array.from(Array(5).keys()).map((index) => (
          <div
            key={index}
            className={`h-1.5 flex-1 rounded-full ${getBarColor(index)} transition-colors`}
          ></div>
        ))}
      </div>
      {password.length > 0 && (
        <div className="flex justify-between items-center text-xs">
          <span
            className={`font-bold ${
              score < 2 ? 'text-red-500' : score < 3 ? 'text-orange-500' : 'text-green-500'
            }`}
          >
            {getStrengthLabel()}
          </span>
          {/* Display feedback from zxcvbn if available */}
          {(result.feedback.warning || result.feedback.suggestions.length > 0) && (
            <span className="text-gray-500 dark:text-gray-400 text-right">
              {/* Prioritize the warning over suggestions. */}
              {result.feedback.warning ? (
                <span>{result.feedback.warning}</span>
              ) : (
                <span>{result.feedback.suggestions[0]}</span>
              )}
            </span>
          )}
        </div>
      )}
    </div>
  );
};