mirror of
https://gitlab.w-hs.de/an14051/backerup-website.git
synced 2026-07-27 17:35:29 +00:00
142 lines
4.6 KiB
TypeScript
142 lines
4.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useRef } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
|
|
interface Props {
|
|
from: string
|
|
}
|
|
|
|
export default function LoginForm({ from }: Props) {
|
|
const router = useRouter()
|
|
const [error, setError] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
const usernameRef = useRef<HTMLInputElement>(null)
|
|
const passwordRef = useRef<HTMLInputElement>(null)
|
|
|
|
async function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault()
|
|
setError('')
|
|
setLoading(true)
|
|
|
|
const username = usernameRef.current?.value.trim() ?? ''
|
|
const password = passwordRef.current?.value ?? ''
|
|
|
|
try {
|
|
const res = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password }),
|
|
})
|
|
const data = (await res.json()) as { error?: string }
|
|
if (!res.ok) {
|
|
setError(data.error ?? 'Login failed')
|
|
setLoading(false)
|
|
return
|
|
}
|
|
// Navigate to intended destination — fresh page load picks up the session cookie
|
|
router.push(from || '/browse')
|
|
} catch {
|
|
setError('Network error — please try again')
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-5" noValidate>
|
|
{error && (
|
|
<div
|
|
className="flex items-center gap-2.5 px-4 py-3 rounded-xl text-sm border"
|
|
style={{
|
|
background: 'rgba(239,68,68,0.08)',
|
|
borderColor: 'rgba(239,68,68,0.3)',
|
|
color: '#fca5a5',
|
|
}}
|
|
role="alert"
|
|
>
|
|
<svg viewBox="0 0 20 20" fill="currentColor" className="w-4 h-4 shrink-0" aria-hidden="true">
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-1.5">
|
|
<label htmlFor="username" className="block text-sm font-medium text-slate-300">
|
|
Username
|
|
</label>
|
|
<input
|
|
id="username"
|
|
ref={usernameRef}
|
|
type="text"
|
|
autoComplete="username"
|
|
required
|
|
autoFocus
|
|
className="w-full rounded-xl px-4 py-3 text-sm outline-none transition-all"
|
|
style={{
|
|
background: 'rgba(255,255,255,0.04)',
|
|
border: '1px solid rgba(255,255,255,0.1)',
|
|
color: '#e2e8f0',
|
|
}}
|
|
onFocus={(e) =>
|
|
(e.currentTarget.style.borderColor = 'rgba(34,211,238,0.5)')
|
|
}
|
|
onBlur={(e) =>
|
|
(e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)')
|
|
}
|
|
placeholder="Enter your username"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1.5">
|
|
<label htmlFor="password" className="block text-sm font-medium text-slate-300">
|
|
Password
|
|
</label>
|
|
<input
|
|
id="password"
|
|
ref={passwordRef}
|
|
type="password"
|
|
autoComplete="current-password"
|
|
required
|
|
className="w-full rounded-xl px-4 py-3 text-sm outline-none transition-all"
|
|
style={{
|
|
background: 'rgba(255,255,255,0.04)',
|
|
border: '1px solid rgba(255,255,255,0.1)',
|
|
color: '#e2e8f0',
|
|
}}
|
|
onFocus={(e) =>
|
|
(e.currentTarget.style.borderColor = 'rgba(34,211,238,0.5)')
|
|
}
|
|
onBlur={(e) =>
|
|
(e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)')
|
|
}
|
|
placeholder="Enter your password"
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
className="w-full py-3 rounded-xl font-semibold text-sm text-slate-900 transition-all hover:brightness-110 active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed"
|
|
style={{ background: 'linear-gradient(135deg, #22d3ee, #06b6d4)' }}
|
|
>
|
|
{loading ? (
|
|
<span className="flex items-center justify-center gap-2">
|
|
<svg className="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24" aria-hidden="true">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
</svg>
|
|
Signing in…
|
|
</span>
|
|
) : (
|
|
'Sign in'
|
|
)}
|
|
</button>
|
|
</form>
|
|
)
|
|
}
|