profit-planet-frontend/src/app/login/components/LoginForm.tsx
seaznCode 4367740652 beautify: login + header
upload: background imgs
2025-10-03 20:48:28 +02:00

387 lines
14 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline'
import { useLogin } from '../hooks/useLogin'
export default function LoginForm() {
const [showPassword, setShowPassword] = useState(false)
const [showBall, setShowBall] = useState(true)
const [formData, setFormData] = useState({
email: '',
password: '',
rememberMe: false
})
const router = useRouter()
const { login, error, setError, loading } = useLogin()
// Responsive ball visibility
useEffect(() => {
const handleResize = () => {
setShowBall(window.innerWidth >= 768)
}
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
// Prevent body scrolling when component mounts
useEffect(() => {
document.body.style.overflow = 'hidden'
document.documentElement.style.overflow = 'hidden'
return () => {
document.body.style.overflow = 'unset'
document.documentElement.style.overflow = 'unset'
}
}, [])
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value, type, checked } = e.target
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}))
setError('') // Clear error when user starts typing
}
const validateForm = (): boolean => {
if (!formData.email.trim()) {
setError('E-Mail-Adresse ist erforderlich')
return false
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
setError('Bitte gib eine gültige E-Mail-Adresse ein')
return false
}
if (!formData.password.trim()) {
setError('Passwort ist erforderlich')
return false
}
if (formData.password.length < 6) {
setError('Passwort muss mindestens 6 Zeichen lang sein')
return false
}
return true
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validateForm()) return
await login({
email: formData.email,
password: formData.password,
rememberMe: formData.rememberMe
})
}
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768
return (
<div
className="w-full flex justify-center items-center relative"
style={{
minHeight: '100vh',
height: '100vh',
overflowY: 'hidden',
overflowX: 'hidden',
paddingTop: isMobile ? '0.25rem' : '5rem',
paddingBottom: isMobile ? '2.5rem' : '2.5rem',
backgroundImage: 'url(/images/misc/marble_bluegoldwhite_BG.jpg)',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
}}
>
<div
className="bg-white rounded-2xl shadow-2xl flex flex-col items-center relative border-t-4 border-[#8D6B1D]"
style={{
width: isMobile ? '98vw' : '40vw',
maxWidth: isMobile ? 'none' : '700px',
minWidth: isMobile ? '0' : '400px',
minHeight: isMobile ? '320px' : '320px',
padding: isMobile ? '0.5rem' : '2rem',
marginTop: isMobile ? '0.5rem' : undefined,
transform: isMobile ? undefined : 'scale(0.85)',
transformOrigin: 'top center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start'
}}
>
{/* Animated Ball - Desktop Only */}
{showBall && !isMobile && (
<div className="absolute -top-16 left-1/2 -translate-x-1/2 w-28 z-20">
<div className="w-28 h-28 rounded-full bg-gradient-to-br from-[#8D6B1D] via-[#A67C20] to-[#C49225] flex items-center justify-center shadow-xl border-4 border-white relative">
<svg className="w-20 h-20 text-[#F4E7D1]" viewBox="0 0 64 64" fill="none">
<circle cx="32" cy="32" r="20" fill="currentColor" />
<ellipse cx="32" cy="38" rx="16" ry="5" fill="#8D6B1D" fillOpacity=".10" />
<ellipse cx="32" cy="26" rx="10" ry="4" fill="#8D6B1D" fillOpacity=".08" />
<circle cx="40" cy="26" r="3" fill="#8D6B1D" fillOpacity=".5" />
</svg>
{/* Orbiting balls */}
<span className="absolute left-1/2 top-1/2 w-0 h-0">
<span className="block absolute animate-orbit-1" style={{ width: 0, height: 0 }}>
<span className="block w-3 h-3 bg-[#8D6B1D] rounded-full shadow-lg" style={{ transform: 'translateX(44px)' }}></span>
</span>
<span className="block absolute animate-orbit-2" style={{ width: 0, height: 0 }}>
<span className="block w-2.5 h-2.5 bg-[#A67C20] rounded-full shadow-md" style={{ transform: 'translateX(-36px)' }}></span>
</span>
</span>
</div>
</div>
)}
{/* Content */}
<div style={{
marginTop: isMobile ? '0.5rem' : '1.5rem',
marginBottom: isMobile ? '1.5rem' : '2rem',
width: '100%',
}}>
<h1
className="mb-2 text-center text-4xl font-extrabold text-[#0F172A] tracking-tight drop-shadow-lg"
style={{
fontSize: isMobile ? '2rem' : undefined,
marginTop: isMobile ? '0.5rem' : undefined,
}}
>
Profit Planet
</h1>
<p
className="mb-8 text-center text-lg text-[#8D6B1D] font-medium"
style={{
fontSize: isMobile ? '0.95rem' : undefined,
marginBottom: isMobile ? '1rem' : undefined,
}}
>
Welcome back! Login to continue.
</p>
<form
className="space-y-7 w-full"
style={{
gap: isMobile ? '0.75rem' : undefined,
}}
onSubmit={handleSubmit}
>
{/* Email Field */}
<div>
<label
htmlFor="email"
className="block text-base font-semibold text-[#0F172A] mb-1"
style={{
fontSize: isMobile ? '0.875rem' : undefined,
marginBottom: isMobile ? '0.25rem' : undefined,
}}
>
E-Mail-Adresse
</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
value={formData.email}
onChange={handleInputChange}
className="appearance-none block w-full px-4 py-3 border border-gray-300 rounded-lg placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#8D6B1D] focus:border-[#8D6B1D] text-base bg-white text-[#0F172A] transition"
style={{
fontSize: isMobile ? '0.875rem' : undefined,
padding: isMobile ? '0.4rem 0.75rem' : undefined,
}}
placeholder="deine@email.com"
required
/>
</div>
{/* Password Field */}
<div>
<label
htmlFor="password"
className="block text-base font-semibold text-[#0F172A] mb-1"
style={{
fontSize: isMobile ? '0.875rem' : undefined,
marginBottom: isMobile ? '0.25rem' : undefined,
}}
>
Passwort
</label>
<div className="relative">
<input
id="password"
name="password"
type={showPassword ? "text" : "password"}
autoComplete="current-password"
value={formData.password}
onChange={handleInputChange}
className="appearance-none block w-full px-4 py-3 pr-12 border border-gray-300 rounded-lg placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#8D6B1D] focus:border-[#8D6B1D] text-base bg-white text-[#0F172A] transition"
style={{
fontSize: isMobile ? '0.875rem' : undefined,
padding: isMobile ? '0.4rem 2.5rem 0.4rem 0.75rem' : '0.75rem 3rem 0.75rem 1rem',
}}
placeholder="Dein Passwort"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-0 pr-3 flex items-center"
>
{showPassword ? (
<EyeSlashIcon className="h-5 w-5 text-gray-400 hover:text-[#8D6B1D] transition-colors" />
) : (
<EyeIcon className="h-5 w-5 text-gray-400 hover:text-[#8D6B1D] transition-colors" />
)}
</button>
</div>
{/* Remember Me & Show Password */}
<div className="mt-2 flex items-center justify-between">
<div className="flex items-center">
<input
id="rememberMe"
name="rememberMe"
type="checkbox"
checked={formData.rememberMe}
onChange={handleInputChange}
className="h-4 w-4 text-[#8D6B1D] border-2 border-gray-300 rounded focus:ring-[#8D6B1D] focus:ring-2"
/>
<label htmlFor="rememberMe" className="ml-2 text-sm text-[#4A4A4A]">
Angemeldet bleiben
</label>
</div>
<div className="flex items-center">
<input
id="show-password"
type="checkbox"
className="h-4 w-4 border-2 border-gray-300 rounded focus:ring-[#8D6B1D] focus:ring-2"
checked={showPassword}
onChange={(e) => setShowPassword(e.target.checked)}
/>
<label htmlFor="show-password" className="ml-2 text-sm text-[#4A4A4A]">
Passwort anzeigen
</label>
</div>
</div>
</div>
{/* Error Message */}
{error && (
<div className="text-red-500 text-sm bg-red-50 border border-red-200 rounded-lg p-3">
{error}
</div>
)}
{/* Submit Button */}
<div>
<button
type="submit"
disabled={loading}
className={`w-full py-3 px-6 rounded-lg shadow-md text-base font-bold text-white transition-all duration-200 transform hover:-translate-y-0.5 ${
loading
? 'bg-gray-400 cursor-not-allowed'
: 'bg-gradient-to-r from-[#8D6B1D] via-[#A67C20] to-[#C49225] hover:from-[#7A5E1A] hover:to-[#B8851F] focus:outline-none focus:ring-2 focus:ring-[#8D6B1D] focus:ring-offset-2'
}`}
style={{
fontSize: isMobile ? '0.9rem' : undefined,
padding: isMobile ? '0.6rem 1rem' : undefined,
}}
>
{loading ? (
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Anmeldung läuft...
</div>
) : (
'Anmelden'
)}
</button>
</div>
{/* Forgot Password */}
<div className="mt-4 flex justify-end">
<button
type="button"
className="text-[#8D6B1D] hover:text-[#7A5E1A] hover:underline text-sm font-medium transition-colors"
onClick={() => router.push("/password-reset")}
>
Passwort vergessen?
</button>
</div>
</form>
{/* Registration Section */}
<div
className="mt-10 w-full"
style={{
marginTop: isMobile ? '1rem' : undefined,
}}
>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-200"></div>
</div>
<div
className="relative flex justify-center text-base"
style={{
fontSize: isMobile ? '0.875rem' : undefined,
}}
>
<a href="/register" className="px-3 bg-white text-[#8D6B1D]">Noch kein Account?</a>
</div>
</div>
<div
className="mt-7 text-center"
style={{
marginTop: isMobile ? '0.75rem' : undefined,
}}
>
<p
className="text-base text-[#4A4A4A]"
style={{
fontSize: isMobile ? '0.8rem' : undefined,
}}
>
Profit Planet is available by invitation only.
</p>
<p
className="text-base text-[#8D6B1D] mt-2"
style={{
fontSize: isMobile ? '0.8rem' : undefined,
}}
>
Contact us for an invitation!
</p>
</div>
</div>
</div>
{/* CSS Animations */}
<style jsx>{`
@keyframes orbit-1 {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes orbit-2 {
0% { transform: rotate(0deg); }
100% { transform: rotate(-360deg); }
}
.animate-orbit-1 {
animation: orbit-1 3s linear infinite;
transform-origin: 0 0;
}
.animate-orbit-2 {
animation: orbit-2 4s linear infinite;
transform-origin: 0 0;
}
`}</style>
</div>
</div>
)
}