oa-ai/src/app/login/page.tsx

97 lines
4.6 KiB
TypeScript

'use client'
import { useState, Suspense } from 'react'
import { useSearchParams } from 'next/navigation'
function LoginPageContent() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const [showLdapForm, setShowLdapForm] = useState(false)
const searchParams = useSearchParams()
const isLocalMode = searchParams.get('method') === 'local'
async function handleSubmit(e: React.FormEvent) {
e.preventDefault(); setError(''); setLoading(true)
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()
if (!res.ok) { setError(data.error || '登录失败'); return }
window.location.href = '/'
} catch { setError('网络错误') } finally { setLoading(false) }
}
function handleSsoLogin() {
fetch('/api/auth/login/oidc').then(res => {
if (res.status === 200) {
return res.json().then(data => {
if (data.conflict) { window.location.href = '/' }
else { setError(data.error || '登录失败') }
})
}
window.location.href = res.url || '/api/auth/login/oidc'
}).catch(() => { window.location.href = '/api/auth/login/oidc' })
}
const showLocalForm = isLocalMode || showLdapForm
return (
<div className="min-h-screen flex items-center justify-center bg-slate-50 dark:bg-slate-950 px-4">
<div className="w-full max-w-sm bg-white dark:bg-slate-900 rounded-lg border border-blue-200/50 dark:border-blue-500/20 shadow-lg p-8">
<h1 className="text-2xl font-bold text-center mb-6 text-slate-900 dark:text-white"></h1>
{error && <div className="mb-4 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-600 dark:text-red-400 text-sm">{error}</div>}
{!showLocalForm ? (
<>
<button onClick={handleSsoLogin} className="w-full py-2 px-4 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors duration-200 mb-3">
</button>
<p className="text-center text-xs text-slate-400 mb-3"> SSO </p>
<p className="text-center">
<button onClick={() => setShowLdapForm(true)} className="text-xs text-slate-400 hover:text-slate-600 underline">
使 LDAP
</button>
</p>
</>
) : (
<>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"></label>
<input type="text" value={username} onChange={e => setUsername(e.target.value)} placeholder="请输入用户名"
className="w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" required />
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"></label>
<input type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="请输入密码"
className="w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" required />
</div>
<button type="submit" disabled={loading} className="w-full py-2 px-4 rounded-lg bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-medium transition-colors duration-200">{loading ? '登录中...' : '登录'}</button>
</form>
{!isLocalMode && (
<p className="text-center mt-3">
<button onClick={() => setShowLdapForm(false)} className="text-xs text-slate-400 hover:text-slate-600 underline">
</button>
</p>
)}
</>
)}
</div>
</div>
)
}
export default function LoginPage() {
return (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center">...</div>}>
<LoginPageContent />
</Suspense>
)
}