import React, { useState, useEffect, useCallback } from 'react'; import { X, RefreshCw, Copy, Check, CheckCheck, Wand2, Sliders } from 'lucide-react'; import { useVault } from '../context/VaultContext.js'; import { generatePassword, generatePassphrase } from '@2password/core'; export const PasswordGeneratorModal: React.FC = () => { const { isPasswordModalOpen, closePasswordModal, onPasswordGeneratedCallback } = useVault(); const [mode, setMode] = useState<'password' | 'passphrase'>('password'); const [length, setLength] = useState(20); const [uppercase, setUppercase] = useState(true); const [lowercase, setLowercase] = useState(true); const [numbers, setNumbers] = useState(true); const [special, setSpecial] = useState(true); const [avoidAmbiguous, setAvoidAmbiguous] = useState(true); const [wordCount, setWordCount] = useState(4); const [separator, setSeparator] = useState('-'); const [generatedValue, setGeneratedValue] = useState(''); const [copied, setCopied] = useState(false); const regenerate = useCallback(() => { if (mode === 'password') { const pwd = generatePassword({ length, uppercase, lowercase, numbers, special, avoidAmbiguous }); setGeneratedValue(pwd); } else { const phrase = generatePassphrase(wordCount, separator); setGeneratedValue(phrase); } setCopied(false); }, [mode, length, uppercase, lowercase, numbers, special, avoidAmbiguous, wordCount, separator]); useEffect(() => { if (isPasswordModalOpen) { regenerate(); } }, [isPasswordModalOpen, regenerate]); const handleCopy = async () => { if (!generatedValue) return; try { await navigator.clipboard.writeText(generatedValue); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { // ignore } }; const handleUsePassword = () => { if (onPasswordGeneratedCallback && generatedValue) { onPasswordGeneratedCallback(generatedValue); } closePasswordModal(); }; const getStrength = () => { if (mode === 'passphrase') { if (wordCount <= 3) return { score: 2, label: 'Fair', color: 'bg-amber-400', textColor: 'text-amber-400' }; if (wordCount === 4) return { score: 3, label: 'Good', color: 'bg-cyan-400', textColor: 'text-cyan-400' }; return { score: 4, label: 'Very Strong', color: 'bg-emerald-400', textColor: 'text-emerald-400' }; } let score = 0; const poolCount = [uppercase, lowercase, numbers, special].filter(Boolean).length; if (length >= 8) score++; if (length >= 14 && poolCount >= 2) score++; if (length >= 18 && poolCount >= 3) score++; if (length >= 24 && poolCount === 4) score++; if (score <= 1) return { score: 1, label: 'Weak', color: 'bg-rose-500', textColor: 'text-rose-400' }; if (score === 2) return { score: 2, label: 'Fair', color: 'bg-amber-400', textColor: 'text-amber-400' }; if (score === 3) return { score: 3, label: 'Strong', color: 'bg-cyan-400', textColor: 'text-cyan-400' }; return { score: 4, label: 'Very Strong', color: 'bg-emerald-400', textColor: 'text-emerald-400' }; }; if (!isPasswordModalOpen) return null; const strength = getStrength(); return (

Generate Password

Cryptographically secure CSPRNG

{generatedValue}
Strength {strength.label}
{[1, 2, 3, 4].map((step) => (
))}
{mode === 'password' ? (
Password Length {length} characters
setLength(Number(e.target.value))} className="w-full accent-cyan-500 cursor-pointer" />
) : (
Number of Words {wordCount} words
setWordCount(Number(e.target.value))} className="w-full accent-cyan-500 cursor-pointer" />
{['-', '_', '.', ' ', '#'].map((sep) => ( ))}
)}
{onPasswordGeneratedCallback ? ( ) : ( )}
); };