Files
LunaTV/src/app/login/page.tsx
2025-07-17 23:39:13 -07:00

182 lines
6.7 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense, useState } from 'react';
import { useSite } from '@/components/SiteProvider';
import { ThemeToggle } from '@/components/ThemeToggle';
function LoginPageClient() {
const router = useRouter();
const searchParams = useSearchParams();
const [password, setPassword] = useState('');
const [username, setUsername] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { siteName } = useSite();
// 当 STORAGE_TYPE 不为空且不为 localstorage 时,要求输入用户名
const shouldAskUsername =
typeof window !== 'undefined' &&
(window as any).RUNTIME_CONFIG?.STORAGE_TYPE &&
(window as any).RUNTIME_CONFIG?.STORAGE_TYPE !== 'localstorage';
// 是否允许注册
const enableRegister =
typeof window !== 'undefined' &&
Boolean((window as any).RUNTIME_CONFIG?.ENABLE_REGISTER);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError(null);
if (!password || (shouldAskUsername && !username)) return;
try {
setLoading(true);
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
password,
...(shouldAskUsername ? { username } : {}),
}),
});
if (res.ok) {
const redirect = searchParams.get('redirect') || '/';
router.replace(redirect);
} else if (res.status === 401) {
setError('密码错误');
} else {
const data = await res.json().catch(() => ({}));
setError(data.error ?? '服务器错误');
}
} catch (error) {
setError('网络错误,请稍后重试');
} finally {
setLoading(false);
}
};
// 处理注册逻辑
const handleRegister = async () => {
setError(null);
if (!password || !username) return;
try {
setLoading(true);
const res = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (res.ok) {
const redirect = searchParams.get('redirect') || '/';
router.replace(redirect);
} else {
const data = await res.json().catch(() => ({}));
setError(data.error ?? '服务器错误');
}
} catch (error) {
setError('网络错误,请稍后重试');
} finally {
setLoading(false);
}
};
return (
<div className='relative min-h-screen flex items-center justify-center px-4 overflow-hidden'>
<div className='absolute top-4 right-4'>
<ThemeToggle />
</div>
<div className='relative z-10 w-full max-w-md rounded-3xl bg-linear-to-b from-white/90 via-white/70 to-white/40 dark:from-zinc-900/90 dark:via-zinc-900/70 dark:to-zinc-900/40 backdrop-blur-xl shadow-2xl p-10 dark:border dark:border-zinc-800'>
<h1 className='text-green-600 tracking-tight text-center text-3xl font-extrabold mb-8 bg-clip-text drop-shadow-xs'>
{siteName}
</h1>
<form onSubmit={handleSubmit} className='space-y-8'>
{shouldAskUsername && (
<div>
<label htmlFor='username' className='sr-only'>
</label>
<input
id='username'
type='text'
autoComplete='username'
className='block w-full rounded-lg border-0 py-3 px-4 text-gray-900 dark:text-gray-100 shadow-xs ring-1 ring-white/60 dark:ring-white/20 placeholder:text-gray-500 dark:placeholder:text-gray-400 focus:ring-2 focus:ring-green-500 focus:outline-hidden sm:text-base bg-white/60 dark:bg-zinc-800/60 backdrop-blur-sm'
placeholder='输入用户名'
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
)}
<div>
<label htmlFor='password' className='sr-only'>
</label>
<input
id='password'
type='password'
autoComplete='current-password'
className='block w-full rounded-lg border-0 py-3 px-4 text-gray-900 dark:text-gray-100 shadow-xs ring-1 ring-white/60 dark:ring-white/20 placeholder:text-gray-500 dark:placeholder:text-gray-400 focus:ring-2 focus:ring-green-500 focus:outline-hidden sm:text-base bg-white/60 dark:bg-zinc-800/60 backdrop-blur-sm'
placeholder='输入访问密码'
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
{error && (
<p className='text-sm text-red-600 dark:text-red-400'>{error}</p>
)}
{/* 登录 / 注册按钮 */}
{shouldAskUsername && enableRegister ? (
<div className='flex gap-4'>
<button
type='button'
onClick={handleRegister}
disabled={!password || !username || loading}
className='flex-1 inline-flex justify-center rounded-lg bg-blue-600 py-3 text-base font-semibold text-white shadow-lg transition-all duration-200 hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50'
>
{loading ? '注册中...' : '注册'}
</button>
<button
type='submit'
disabled={
!password || loading || (shouldAskUsername && !username)
}
className='flex-1 inline-flex justify-center rounded-lg bg-green-600 py-3 text-base font-semibold text-white shadow-lg transition-all duration-200 hover:from-green-600 hover:to-blue-600 disabled:cursor-not-allowed disabled:opacity-50'
>
{loading ? '登录中...' : '登录'}
</button>
</div>
) : (
<button
type='submit'
disabled={
!password || loading || (shouldAskUsername && !username)
}
className='inline-flex w-full justify-center rounded-lg bg-green-600 py-3 text-base font-semibold text-white shadow-lg transition-all duration-200 hover:from-green-600 hover:to-blue-600 disabled:cursor-not-allowed disabled:opacity-50'
>
{loading ? '登录中...' : '登录'}
</button>
)}
</form>
</div>
</div>
);
}
export default function LoginPage() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LoginPageClient />
</Suspense>
);
}