2025-07-28 07:50:50 +08:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { client } from "@/lib/auth-client";
|
|
|
|
|
import { Button } from "@nice/ui/components/button";
|
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@nice/ui/components/card";
|
|
|
|
|
import { Input } from "@nice/ui/components/input";
|
|
|
|
|
import { Label } from "@nice/ui/components/label";
|
2025-07-28 10:32:25 +08:00
|
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@nice/ui/components/tabs";
|
2025-07-28 07:50:50 +08:00
|
|
|
import { toast } from "@nice/ui/components/sonner";
|
|
|
|
|
import { useState } from "react";
|
|
|
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
|
|
|
|
|
|
|
|
export default function SignInPage() {
|
2025-07-28 10:32:25 +08:00
|
|
|
const [loginCredentials, setLoginCredentials] = useState({
|
|
|
|
|
email: "",
|
|
|
|
|
username: "",
|
|
|
|
|
password: ""
|
|
|
|
|
});
|
|
|
|
|
const [registerCredentials, setRegisterCredentials] = useState({
|
|
|
|
|
email: "",
|
|
|
|
|
username: "",
|
|
|
|
|
password: "",
|
|
|
|
|
confirmPassword: "",
|
|
|
|
|
name: ""
|
2025-07-28 07:50:50 +08:00
|
|
|
});
|
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
2025-07-28 10:32:25 +08:00
|
|
|
const [currentTab, setCurrentTab] = useState("login");
|
2025-07-28 07:50:50 +08:00
|
|
|
const router = useRouter();
|
|
|
|
|
const searchParams = useSearchParams();
|
|
|
|
|
// 检查是否是从 OIDC Provider 重定向过来的
|
|
|
|
|
const isOIDCFlow = searchParams.get('response_type') === 'code' || searchParams.get('client_id');
|
|
|
|
|
|
|
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
// 登录处理
|
2025-07-28 07:50:50 +08:00
|
|
|
const handleSignIn = async (e: React.FormEvent) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
|
|
|
|
|
try {
|
2025-07-28 10:32:25 +08:00
|
|
|
const result = await client.signIn.username({
|
|
|
|
|
username: loginCredentials.username || loginCredentials.email,
|
|
|
|
|
password: loginCredentials.password,
|
|
|
|
|
callbackURL: 'http://localhost:3000/dashboard'
|
2025-07-28 07:50:50 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.data) {
|
|
|
|
|
toast.success("登录成功!");
|
|
|
|
|
|
|
|
|
|
// 如果是 OIDC 流程,重定向回原始请求
|
|
|
|
|
if (isOIDCFlow) {
|
|
|
|
|
// 获取所有查询参数并重建 URL
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
const redirectUrl = `http://localhost:3001/api/auth/oauth2/authorize?${params.toString()}`;
|
|
|
|
|
window.location.href = redirectUrl;
|
|
|
|
|
} else {
|
|
|
|
|
router.push('/dashboard');
|
|
|
|
|
}
|
|
|
|
|
} else if (result.error) {
|
|
|
|
|
toast.error(`登录失败: ${result.error.message}`);
|
|
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error("登录失败:", error);
|
|
|
|
|
toast.error(`登录失败: ${error.message || "未知错误"}`);
|
|
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
// 注册处理
|
|
|
|
|
const handleSignUp = async (e: React.FormEvent) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
if (registerCredentials.password !== registerCredentials.confirmPassword) {
|
|
|
|
|
toast.error("密码不匹配");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-28 07:50:50 +08:00
|
|
|
setIsLoading(true);
|
2025-07-28 10:32:25 +08:00
|
|
|
|
2025-07-28 07:50:50 +08:00
|
|
|
try {
|
|
|
|
|
const result = await client.signUp.email({
|
2025-07-28 10:32:25 +08:00
|
|
|
email: registerCredentials.email,
|
|
|
|
|
password: registerCredentials.password,
|
|
|
|
|
name: registerCredentials.name,
|
|
|
|
|
username: registerCredentials.username,
|
|
|
|
|
callbackURL: 'http://localhost:3000/dashboard'
|
2025-07-28 07:50:50 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.data) {
|
2025-07-28 10:32:25 +08:00
|
|
|
toast.success("注册成功!");
|
2025-07-28 07:50:50 +08:00
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
// 如果是 OIDC 流程,重定向回原始请求
|
|
|
|
|
if (isOIDCFlow) {
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
const redirectUrl = `http://localhost:3001/api/auth/oauth2/authorize?${params.toString()}`;
|
|
|
|
|
window.location.href = redirectUrl;
|
2025-07-28 07:50:50 +08:00
|
|
|
} else {
|
2025-07-28 10:32:25 +08:00
|
|
|
router.push('/dashboard');
|
2025-07-28 07:50:50 +08:00
|
|
|
}
|
2025-07-28 10:32:25 +08:00
|
|
|
} else if (result.error) {
|
|
|
|
|
toast.error(`注册失败: ${result.error.message}`);
|
2025-07-28 07:50:50 +08:00
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
2025-07-28 10:32:25 +08:00
|
|
|
console.error("注册失败:", error);
|
|
|
|
|
toast.error(`注册失败: ${error.message || "未知错误"}`);
|
2025-07-28 07:50:50 +08:00
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
|
|
|
|
|
|
2025-07-28 07:50:50 +08:00
|
|
|
return (
|
|
|
|
|
<div className="container mx-auto p-6 max-w-md">
|
2025-07-28 10:32:25 +08:00
|
|
|
<div className="mb-4 text-center">
|
|
|
|
|
<Button
|
|
|
|
|
onClick={() => router.push('/test')}
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
>
|
|
|
|
|
← 返回 OIDC 测试页面
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-07-28 07:50:50 +08:00
|
|
|
<Card>
|
|
|
|
|
<CardHeader className="text-center">
|
2025-07-28 10:32:25 +08:00
|
|
|
<CardTitle className="text-2xl">
|
|
|
|
|
{isOIDCFlow ? "授权登录" : "用户认证"}
|
|
|
|
|
</CardTitle>
|
2025-07-28 07:50:50 +08:00
|
|
|
<CardDescription>
|
|
|
|
|
{isOIDCFlow ? (
|
|
|
|
|
<span>OIDC Provider 授权登录</span>
|
|
|
|
|
) : (
|
2025-07-28 10:32:25 +08:00
|
|
|
<span>登录或注册您的账户</span>
|
2025-07-28 07:50:50 +08:00
|
|
|
)}
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
{isOIDCFlow && (
|
|
|
|
|
<div className="mb-4 p-3 bg-blue-50 dark:bg-blue-950 rounded-md">
|
|
|
|
|
<p className="text-sm text-blue-800 dark:text-blue-200">
|
2025-07-28 10:32:25 +08:00
|
|
|
检测到 OIDC 授权流程。登录或注册后将继续完成授权。
|
2025-07-28 07:50:50 +08:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
<Tabs value={currentTab} onValueChange={setCurrentTab}>
|
|
|
|
|
<TabsList className="grid w-full grid-cols-2">
|
|
|
|
|
<TabsTrigger value="login">登录</TabsTrigger>
|
|
|
|
|
<TabsTrigger value="register">注册</TabsTrigger>
|
|
|
|
|
</TabsList>
|
2025-07-28 07:50:50 +08:00
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
{/* 登录表单 */}
|
|
|
|
|
<TabsContent value="login" className="space-y-4 mt-4">
|
|
|
|
|
<form onSubmit={handleSignIn} className="space-y-4">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="login-email">邮箱或用户名</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="login-email"
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="输入邮箱或用户名"
|
|
|
|
|
value={loginCredentials.email}
|
|
|
|
|
onChange={(e) => setLoginCredentials({ ...loginCredentials, email: e.target.value })}
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2025-07-28 07:50:50 +08:00
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="login-password">密码</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="login-password"
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder="输入密码"
|
|
|
|
|
value={loginCredentials.password}
|
|
|
|
|
onChange={(e) => setLoginCredentials({ ...loginCredentials, password: e.target.value })}
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2025-07-28 07:50:50 +08:00
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
|
|
|
{isLoading ? "登录中..." : "登录"}
|
|
|
|
|
</Button>
|
|
|
|
|
</form>
|
|
|
|
|
</TabsContent>
|
2025-07-28 07:50:50 +08:00
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
{/* 注册表单 */}
|
|
|
|
|
<TabsContent value="register" className="space-y-4 mt-4">
|
|
|
|
|
<form onSubmit={handleSignUp} className="space-y-4">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="register-name">姓名</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="register-name"
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="输入您的姓名"
|
|
|
|
|
value={registerCredentials.name}
|
|
|
|
|
onChange={(e) => setRegisterCredentials({ ...registerCredentials, name: e.target.value })}
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="register-username">用户名</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="register-username"
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="输入用户名"
|
|
|
|
|
value={registerCredentials.username}
|
|
|
|
|
onChange={(e) => setRegisterCredentials({ ...registerCredentials, username: e.target.value })}
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="register-email">邮箱</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="register-email"
|
|
|
|
|
type="email"
|
|
|
|
|
placeholder="输入邮箱地址"
|
|
|
|
|
value={registerCredentials.email}
|
|
|
|
|
onChange={(e) => setRegisterCredentials({ ...registerCredentials, email: e.target.value })}
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="register-password">密码</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="register-password"
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder="输入密码"
|
|
|
|
|
value={registerCredentials.password}
|
|
|
|
|
onChange={(e) => setRegisterCredentials({ ...registerCredentials, password: e.target.value })}
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2025-07-28 07:50:50 +08:00
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="register-confirm-password">确认密码</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="register-confirm-password"
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder="再次输入密码"
|
|
|
|
|
value={registerCredentials.confirmPassword}
|
|
|
|
|
onChange={(e) => setRegisterCredentials({ ...registerCredentials, confirmPassword: e.target.value })}
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2025-07-28 07:50:50 +08:00
|
|
|
|
2025-07-28 10:32:25 +08:00
|
|
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
|
|
|
{isLoading ? "注册中..." : "注册"}
|
|
|
|
|
</Button>
|
|
|
|
|
</form>
|
|
|
|
|
</TabsContent>
|
|
|
|
|
</Tabs>
|
2025-07-28 07:50:50 +08:00
|
|
|
|
|
|
|
|
{isOIDCFlow && (
|
|
|
|
|
<div className="mt-4">
|
|
|
|
|
<Button
|
|
|
|
|
onClick={() => router.push('/test')}
|
|
|
|
|
variant="ghost"
|
|
|
|
|
className="w-full"
|
|
|
|
|
>
|
|
|
|
|
返回测试页面
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|