125 lines
5.2 KiB
TypeScript
125 lines
5.2 KiB
TypeScript
import { LockKeyhole, ShieldCheck } from 'lucide-react'
|
||
import { useState } from 'react'
|
||
|
||
import { Button } from '@/components/ui/button'
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||
import { Input } from '@/components/ui/input'
|
||
import { Label } from '@/components/ui/label'
|
||
|
||
export function LoginPage({
|
||
submitting,
|
||
localLoginEnabled,
|
||
proxyAuthEnabled,
|
||
onLogin,
|
||
}: {
|
||
submitting: boolean
|
||
localLoginEnabled: boolean
|
||
proxyAuthEnabled: boolean
|
||
onLogin: (payload: { username: string; password: string }) => Promise<void>
|
||
}) {
|
||
const [username, setUsername] = useState('admin')
|
||
const [password, setPassword] = useState('admin123')
|
||
|
||
return (
|
||
<div className="flex min-h-screen items-center justify-center px-4 py-10">
|
||
<div className="grid w-full max-w-5xl gap-6 lg:grid-cols-[1.1fr_0.9fr]">
|
||
<Card className="overflow-hidden border-primary/12 bg-gradient-to-br from-card via-card to-primary/5">
|
||
<CardHeader className="space-y-4">
|
||
<div className="inline-flex w-fit items-center gap-2 rounded-full border border-primary/20 bg-primary/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.28em] text-primary">
|
||
<ShieldCheck className="h-3.5 w-3.5" />
|
||
Termi 后台
|
||
</div>
|
||
<div className="space-y-3">
|
||
<CardTitle className="text-4xl leading-tight">
|
||
将后台从前台中拆分出来,同时保持迭代节奏不掉线。
|
||
</CardTitle>
|
||
<CardDescription className="max-w-xl text-base leading-7">
|
||
当前管理工作统一在这个独立后台中完成,后端专注提供 API、认证与业务规则。
|
||
</CardDescription>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className="grid gap-4 sm:grid-cols-3">
|
||
{[
|
||
['React 应用', '独立后台界面'],
|
||
['shadcn/ui', '统一的组件基础'],
|
||
['Loco API', '后端继续专注数据与规则'],
|
||
].map(([title, description]) => (
|
||
<div
|
||
key={title}
|
||
className="rounded-2xl border border-border/70 bg-background/75 p-4"
|
||
>
|
||
<div className="text-sm font-semibold">{title}</div>
|
||
<p className="mt-2 text-sm leading-6 text-muted-foreground">{description}</p>
|
||
</div>
|
||
))}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-3">
|
||
<span className="flex h-11 w-11 items-center justify-center rounded-2xl border border-primary/20 bg-primary/10 text-primary">
|
||
<LockKeyhole className="h-5 w-5" />
|
||
</span>
|
||
登录管理后台
|
||
</CardTitle>
|
||
<CardDescription>
|
||
{localLoginEnabled
|
||
? '当前登录复用后端管理员账号;如果前面接了 TinyAuth / Pocket ID,也可以直接由反向代理完成 SSO。'
|
||
: proxyAuthEnabled
|
||
? '当前后台已切到代理侧 SSO 模式,请从受保护的后台域名入口进入。'
|
||
: '当前后台未开放本地账号密码登录,请检查部署配置。'}
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{localLoginEnabled ? (
|
||
<form
|
||
className="space-y-5"
|
||
onSubmit={(event) => {
|
||
event.preventDefault()
|
||
void onLogin({ username, password })
|
||
}}
|
||
>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="username">用户名</Label>
|
||
<Input
|
||
id="username"
|
||
value={username}
|
||
onChange={(event) => setUsername(event.target.value)}
|
||
autoComplete="username"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="password">密码</Label>
|
||
<Input
|
||
id="password"
|
||
type="password"
|
||
value={password}
|
||
onChange={(event) => setPassword(event.target.value)}
|
||
autoComplete="current-password"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<Button className="w-full" size="lg" disabled={submitting}>
|
||
{submitting ? '登录中...' : '进入后台'}
|
||
</Button>
|
||
</form>
|
||
) : (
|
||
<div className="space-y-4 rounded-2xl border border-border/70 bg-background/70 p-4 text-sm leading-7 text-muted-foreground">
|
||
<p>推荐通过 Caddy + TinyAuth + Pocket ID 保护整个后台入口。</p>
|
||
<p>如果你已经从受保护的后台域名进入,刷新页面后会自动识别当前 SSO 会话。</p>
|
||
<Button className="w-full" size="lg" onClick={() => window.location.reload()}>
|
||
重新检查会话
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|