- /login page used useSearchParams() without Suspense boundary - Next.js 16 requires Suspense for useSearchParams during static prerender - Extracted LoginForm component, wrapped in <Suspense> - Removed --webpack flag (default Turbopack build now works) - Build passes cleanly: ✓ Compiled successfully
93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
'use client'
|
|
|
|
import { Suspense, useState } from 'react'
|
|
import { useRouter, useSearchParams } from 'next/navigation'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Label } from '@/components/ui/label'
|
|
import { Loader2, Lock } from 'lucide-react'
|
|
|
|
function LoginForm() {
|
|
const router = useRouter()
|
|
const searchParams = useSearchParams()
|
|
const redirect = searchParams.get('redirect') || '/'
|
|
const [password, setPassword] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState('')
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (!password) return
|
|
setLoading(true)
|
|
setError('')
|
|
try {
|
|
const res = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ password, redirect }),
|
|
})
|
|
const data = await res.json()
|
|
if (!res.ok) {
|
|
setError(data.error || 'Login fehlgeschlagen')
|
|
return
|
|
}
|
|
router.push(data.redirect || '/')
|
|
router.refresh()
|
|
} catch {
|
|
setError('Netzwerkfehler')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="password">Passwort</Label>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder="Admin-Passwort eingeben"
|
|
autoFocus
|
|
autoComplete="current-password"
|
|
/>
|
|
</div>
|
|
{error && (
|
|
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
|
)}
|
|
<Button type="submit" className="w-full gap-2" disabled={loading || !password}>
|
|
{loading && <Loader2 className="size-4 animate-spin" />}
|
|
Anmelden
|
|
</Button>
|
|
</form>
|
|
)
|
|
}
|
|
|
|
export default function LoginPage() {
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
<div className="w-full max-w-sm space-y-6">
|
|
<div className="text-center space-y-2">
|
|
<div className="mx-auto flex size-14 items-center justify-center rounded-xl bg-foreground text-background">
|
|
<Lock className="size-7" />
|
|
</div>
|
|
<h1 className="text-2xl font-semibold tracking-tight">AOS Login</h1>
|
|
<p className="text-sm text-muted-foreground">
|
|
Agency Operating System — dfmpanel.eu
|
|
</p>
|
|
</div>
|
|
|
|
<Suspense fallback={<div className="text-center text-sm text-muted-foreground">Laden…</div>}>
|
|
<LoginForm />
|
|
</Suspense>
|
|
|
|
<p className="text-center text-xs text-muted-foreground">
|
|
Geschützter Bereich — nur für autorisierte Benutzer.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|