import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Copy, RefreshCw, ShieldCheck, Check, History, Trash2, Settings, Lock } from 'lucide-react'; export default function App() { const [password, setPassword] = useState(''); const [length, setLength] = useState(16); const [includeUpper, setIncludeUpper] = useState(true); const [includeLower, setIncludeLower] = useState(true); const [includeNumbers, setIncludeNumbers] = useState(true); const [includeSymbols, setIncludeSymbols] = useState(true); const [excludeAmbiguous, setExcludeAmbiguous] = useState(false); const [copied, setCopied] = useState(false); const [history, setHistory] = useState([]); const [strength, setStrength] = useState(0); // Character Sets const UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz'; const NUMBERS = '0123456789'; const SYMBOLS = '!@#$%^&*()_+~`|}{[]:;?><,./-='; const AMBIGUOUS = 'Il1O0'; // Calculate Password Strength const calculateStrength = useCallback((pwd) => { let score = 0; if (!pwd) return 0; if (pwd.length > 8) score += 1; if (pwd.length > 12) score += 1; if (pwd.length >= 16) score += 1; if (/[A-Z]/.test(pwd)) score += 1; if (/[a-z]/.test(pwd)) score += 1; if (/[0-9]/.test(pwd)) score += 1; if (/[^A-Za-z0-9]/.test(pwd)) score += 1; return Math.min(score, 4); // Max score 4 (0-4 scale for 5 levels) }, []); const generatePassword = useCallback(() => { let charSet = ''; if (includeLower) charSet += LOWERCASE; if (includeUpper) charSet += UPPERCASE; if (includeNumbers) charSet += NUMBERS; if (includeSymbols) charSet += SYMBOLS; if (excludeAmbiguous) { // Create regex from ambiguous string const regex = new RegExp(`[${AMBIGUOUS}]`, 'g'); charSet = charSet.replace(regex, ''); } if (charSet === '') { setPassword(''); setStrength(0); return; } let generatedPassword = ''; const array = new Uint32Array(length); window.crypto.getRandomValues(array); for (let i = 0; i < length; i++) { generatedPassword += charSet.charAt(array[i] % charSet.length); } setPassword(generatedPassword); setStrength(calculateStrength(generatedPassword)); setCopied(false); // Add to history if not empty if(generatedPassword) { setHistory(prev => { const newHistory = [generatedPassword, ...prev].slice(0, 10); return newHistory; }); } }, [length, includeLower, includeUpper, includeNumbers, includeSymbols, excludeAmbiguous, calculateStrength]); // Initial generation on mount useEffect(() => { generatePassword(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleCopy = (textToCopy) => { if (!textToCopy) return; // Create a temporary textarea element (fallback for iframe restrictions) const textArea = document.createElement("textarea"); textArea.value = textToCopy; // Ensure it's not visible but part of the DOM textArea.style.position = "fixed"; textArea.style.left = "-9999px"; textArea.style.top = "0"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error('Unable to copy', err); } document.body.removeChild(textArea); }; const getStrengthColor = () => { switch (strength) { case 0: return 'bg-red-500'; case 1: return 'bg-orange-500'; case 2: return 'bg-yellow-500'; case 3: return 'bg-blue-500'; case 4: return 'bg-emerald-500'; default: return 'bg-slate-200'; } }; const getStrengthLabel = () => { switch (strength) { case 0: return 'Vulnerable'; case 1: return 'Weak'; case 2: return 'Fair'; case 3: return 'Strong'; case 4: return 'Unbreakable'; default: return ''; } }; return (
Client-Side Password Generator