206 lines
8.4 KiB
TypeScript
206 lines
8.4 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { Shield, Sun, Moon, Download, Menu, X } from 'lucide-react'
|
|
import { useTheme } from 'next-themes'
|
|
import { motion } from 'framer-motion'
|
|
import Link from 'next/link'
|
|
|
|
export function Header() {
|
|
const { theme, setTheme } = useTheme()
|
|
const [mounted, setMounted] = useState(false)
|
|
const [showInstallPrompt, setShowInstallPrompt] = useState(false)
|
|
const [deferredPrompt, setDeferredPrompt] = useState<any>(null)
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
|
|
|
useEffect(() => {
|
|
setMounted(true)
|
|
|
|
// Listen for PWA install prompt
|
|
window.addEventListener('beforeinstallprompt', (e) => {
|
|
e.preventDefault()
|
|
setDeferredPrompt(e)
|
|
setShowInstallPrompt(true)
|
|
})
|
|
}, [])
|
|
|
|
const handleInstallClick = async () => {
|
|
if (deferredPrompt) {
|
|
deferredPrompt.prompt()
|
|
const { outcome } = await deferredPrompt.userChoice
|
|
if (outcome === 'accepted') {
|
|
setShowInstallPrompt(false)
|
|
setDeferredPrompt(null)
|
|
}
|
|
}
|
|
}
|
|
|
|
const toggleTheme = () => {
|
|
setTheme(theme === 'dark' ? 'light' : 'dark')
|
|
}
|
|
|
|
if (!mounted) {
|
|
return (
|
|
<header className="sticky top-0 z-50 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-700">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="flex justify-between items-center h-16">
|
|
<div className="flex items-center space-x-2">
|
|
<Shield className="h-8 w-8 text-primary-600" />
|
|
<span className="text-xl font-bold text-gray-900 dark:text-white">
|
|
PassMaster
|
|
</span>
|
|
</div>
|
|
<div className="hidden md:flex items-center space-x-4">
|
|
<div className="w-20 h-8 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
|
<div className="w-20 h-8 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
|
<div className="w-8 h-8 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<header className="sticky top-0 z-50 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-700">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="flex justify-between items-center h-16">
|
|
{/* Logo */}
|
|
<motion.div
|
|
className="flex items-center space-x-2"
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
>
|
|
<Link href="/" className="flex items-center space-x-2 hover:opacity-80 transition-opacity">
|
|
<Shield className="h-8 w-8 text-primary-600" />
|
|
<span className="text-xl font-bold text-gray-900 dark:text-white">
|
|
PassMaster
|
|
</span>
|
|
</Link>
|
|
</motion.div>
|
|
|
|
{/* Desktop Navigation */}
|
|
<nav className="hidden md:flex items-center space-x-4">
|
|
<Link
|
|
href="/offline"
|
|
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors duration-200 px-3 py-2 rounded-md text-sm font-medium"
|
|
>
|
|
Offline
|
|
</Link>
|
|
<Link
|
|
href="/client-side"
|
|
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors duration-200 px-3 py-2 rounded-md text-sm font-medium"
|
|
>
|
|
Sicherheit
|
|
</Link>
|
|
<Link
|
|
href="/exclude-similar"
|
|
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors duration-200 px-3 py-2 rounded-md text-sm font-medium"
|
|
>
|
|
Lesbarkeit
|
|
</Link>
|
|
<Link
|
|
href="/privacy"
|
|
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors duration-200 px-3 py-2 rounded-md text-sm font-medium"
|
|
>
|
|
Datenschutz
|
|
</Link>
|
|
|
|
{showInstallPrompt && (
|
|
<motion.button
|
|
onClick={handleInstallClick}
|
|
className="btn-secondary flex items-center space-x-2"
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<Download className="h-4 w-4" />
|
|
<span>App installieren</span>
|
|
</motion.button>
|
|
)}
|
|
|
|
<button
|
|
onClick={toggleTheme}
|
|
className="p-2 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200"
|
|
aria-label="Toggle theme"
|
|
>
|
|
{theme === 'dark' ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
|
|
</button>
|
|
</nav>
|
|
|
|
{/* Mobile menu button */}
|
|
<div className="md:hidden">
|
|
<button
|
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
className="p-2 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200"
|
|
aria-label="Toggle mobile menu"
|
|
>
|
|
{mobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile Navigation */}
|
|
{mobileMenuOpen && (
|
|
<motion.div
|
|
className="md:hidden py-4 border-t border-gray-200 dark:border-gray-700"
|
|
initial={{ opacity: 0, height: 0 }}
|
|
animate={{ opacity: 1, height: 'auto' }}
|
|
exit={{ opacity: 0, height: 0 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<div className="flex flex-col space-y-3">
|
|
<Link
|
|
href="/offline"
|
|
className="flex items-center justify-center px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
|
|
onClick={() => setMobileMenuOpen(false)}
|
|
>
|
|
Offline PWA
|
|
</Link>
|
|
<Link
|
|
href="/client-side"
|
|
className="flex items-center justify-center px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
|
|
onClick={() => setMobileMenuOpen(false)}
|
|
>
|
|
Client-seitige Sicherheit
|
|
</Link>
|
|
<Link
|
|
href="/exclude-similar"
|
|
className="flex items-center justify-center px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
|
|
onClick={() => setMobileMenuOpen(false)}
|
|
>
|
|
Ähnliche Zeichen
|
|
</Link>
|
|
<Link
|
|
href="/privacy"
|
|
className="flex items-center justify-center px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
|
|
onClick={() => setMobileMenuOpen(false)}
|
|
>
|
|
Datenschutzerklärung
|
|
</Link>
|
|
|
|
{showInstallPrompt && (
|
|
<button
|
|
onClick={handleInstallClick}
|
|
className="btn-secondary flex items-center justify-center space-x-2"
|
|
>
|
|
<Download className="h-4 w-4" />
|
|
<span>App installieren</span>
|
|
</button>
|
|
)}
|
|
|
|
<button
|
|
onClick={toggleTheme}
|
|
className="flex items-center justify-center space-x-2 p-3 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200"
|
|
>
|
|
{theme === 'dark' ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
|
|
<span>Toggle Theme</span>
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
</header>
|
|
)
|
|
} |