SEO + AEO

This commit is contained in:
2025-09-04 10:35:41 +02:00
parent 871836f497
commit bccaefedb3
29 changed files with 5951 additions and 116 deletions

View File

@@ -23,6 +23,7 @@ interface PasswordOptions {
}
export function PasswordGenerator() {
const [mounted, setMounted] = useState(false)
const [password, setPassword] = useState('')
const [showPassword, setShowPassword] = useState(true)
const [copied, setCopied] = useState(false)
@@ -35,8 +36,9 @@ export function PasswordGenerator() {
excludeSimilar: false,
})
// Load settings from localStorage on mount
// Mount and load settings from localStorage
useEffect(() => {
setMounted(true)
const savedOptions = localStorage.getItem('passmaster-settings')
if (savedOptions) {
try {
@@ -50,8 +52,30 @@ export function PasswordGenerator() {
// Save settings to localStorage when options change
useEffect(() => {
localStorage.setItem('passmaster-settings', JSON.stringify(options))
}, [options])
if (mounted) {
localStorage.setItem('passmaster-settings', JSON.stringify(options))
}
}, [options, mounted])
// Prevent hydration mismatch
if (!mounted) {
return (
<div className="card max-w-2xl mx-auto">
<div className="animate-pulse">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/4 mb-2"></div>
<div className="h-12 bg-gray-200 dark:bg-gray-700 rounded mb-4"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/3 mb-2"></div>
<div className="h-8 bg-gray-200 dark:bg-gray-700 rounded mb-4"></div>
<div className="space-y-3">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/2"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/3"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-2/3"></div>
</div>
<div className="h-12 bg-gray-200 dark:bg-gray-700 rounded mt-6"></div>
</div>
</div>
)
}
const handleGenerate = () => {
const newPassword = generatePassword(options)
@@ -81,10 +105,10 @@ export function PasswordGenerator() {
}
const getStrengthLevel = (entropy: number) => {
if (entropy < 40) return { level: 'Weak', color: 'strength-weak', bg: 'bg-red-500' }
if (entropy < 60) return { level: 'OK', color: 'strength-ok', bg: 'bg-yellow-500' }
if (entropy < 80) return { level: 'Strong', color: 'strength-strong', bg: 'bg-blue-500' }
return { level: 'Excellent', color: 'strength-excellent', bg: 'bg-green-500' }
if (entropy < 40) return { level: 'Schwach', color: 'strength-weak', bg: 'bg-red-500' }
if (entropy < 60) return { level: 'Mittel', color: 'strength-ok', bg: 'bg-yellow-500' }
if (entropy < 80) return { level: 'Stark', color: 'strength-strong', bg: 'bg-blue-500' }
return { level: 'Sehr Stark', color: 'strength-excellent', bg: 'bg-green-500' }
}
const entropy = password ? calculateEntropy(password) : 0
@@ -96,7 +120,7 @@ export function PasswordGenerator() {
{/* Generated Password */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Generated Password
Generiertes Passwort
</label>
<div className="flex space-x-2">
<div className="flex-1 relative">
@@ -105,13 +129,13 @@ export function PasswordGenerator() {
value={password}
readOnly
className="input-field font-mono text-lg"
placeholder="Click 'Generate Password' to create a secure password"
aria-label="Generated password"
placeholder="Klicken Sie auf 'Passwort generieren' für ein sicheres Passwort"
aria-label="Generiertes Passwort"
/>
<button
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition-colors duration-200"
aria-label={showPassword ? 'Hide password' : 'Show password'}
aria-label={showPassword ? 'Passwort verstecken' : 'Passwort anzeigen'}
>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
@@ -122,7 +146,7 @@ export function PasswordGenerator() {
className="px-4 py-3 bg-primary-600 text-white rounded-md hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-2 transition-colors duration-200"
whileHover={{ scale: password ? 1.05 : 1 }}
whileTap={{ scale: password ? 0.95 : 1 }}
aria-label="Copy password"
aria-label="Passwort kopieren"
>
<AnimatePresence mode="wait">
{copied ? (
@@ -147,7 +171,7 @@ export function PasswordGenerator() {
</motion.div>
)}
</AnimatePresence>
<span>{copied ? 'Copied!' : 'Copy'}</span>
<span>{copied ? 'Kopiert!' : 'Kopieren'}</span>
</motion.button>
</div>
@@ -155,7 +179,7 @@ export function PasswordGenerator() {
{password && (
<div className="mt-4 space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Strength:</span>
<span className="text-gray-600 dark:text-gray-400">Stärke:</span>
<span className={`font-medium ${strength.color.replace('strength-', 'text-')}`}>
{strength.level}
</span>
@@ -170,7 +194,7 @@ export function PasswordGenerator() {
</div>
<div className="flex justify-between text-xs text-gray-500 dark:text-gray-400">
<span>Entropy: {entropy.toFixed(1)} bits</span>
<span>Time to crack: {timeToCrack}</span>
<span>Zeit zum Knacken: {timeToCrack}</span>
</div>
</div>
)}
@@ -181,7 +205,7 @@ export function PasswordGenerator() {
{/* Length Slider */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Password Length: {options.length}
Passwort-Länge: {options.length}
</label>
<input
type="range"
@@ -200,12 +224,12 @@ export function PasswordGenerator() {
{/* Character Options */}
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">Character Types</h3>
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">Zeichentypen</h3>
{[
{ key: 'includeUppercase', label: 'Uppercase (A-Z)' },
{ key: 'includeLowercase', label: 'Lowercase (a-z)' },
{ key: 'includeNumbers', label: 'Numbers (0-9)' },
{ key: 'includeSymbols', label: 'Symbols (!@#$%^&*)' },
{ key: 'includeUppercase', label: 'Großbuchstaben (A-Z)' },
{ key: 'includeLowercase', label: 'Kleinbuchstaben (a-z)' },
{ key: 'includeNumbers', label: 'Zahlen (0-9)' },
{ key: 'includeSymbols', label: 'Symbole (!@#$%^&*)' },
].map(({ key, label }) => (
<label key={key} className="flex items-center space-x-2 cursor-pointer">
<input
@@ -220,7 +244,7 @@ export function PasswordGenerator() {
</div>
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">Options</h3>
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">Optionen</h3>
<label className="flex items-start space-x-2 cursor-pointer">
<input
type="checkbox"
@@ -229,11 +253,11 @@ export function PasswordGenerator() {
className="rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 mt-0.5"
/>
<div className="flex-1">
<span className="text-sm text-gray-700 dark:text-gray-300">Exclude Similar Characters</span>
<span className="text-sm text-gray-700 dark:text-gray-300">Ähnliche Zeichen ausschließen</span>
<div className="flex items-center space-x-1 mt-1">
<Info className="h-3 w-3 text-gray-400" />
<span className="text-xs text-gray-500 dark:text-gray-400">
Excludes 0/O, l/I, 1/I to avoid confusion
Schließt 0/O, l/I, 1/I aus um Verwechslungen zu vermeiden
</span>
</div>
</div>
@@ -250,12 +274,12 @@ export function PasswordGenerator() {
whileTap={{ scale: 0.98 }}
>
<RefreshCw className="h-5 w-5" />
<span>Generate Password</span>
<span>Passwort generieren</span>
</motion.button>
{/* ARIA Live Region for Copy Feedback */}
<div aria-live="polite" className="sr-only">
{copied && 'Password copied to clipboard'}
{copied && 'Passwort in Zwischenablage kopiert'}
</div>
</div>
)