mirror of
https://github.com/Fasthei/taiji-pda-v0.git
synced 2026-09-26 20:12:27 +00:00
437 lines
17 KiB
TypeScript
437 lines
17 KiB
TypeScript
"use client"
|
|
|
|
import type React from "react"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import Image from "next/image"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import { Card } from "@/components/ui/card"
|
|
import { useLanguage } from "@/contexts/language-context"
|
|
import { Eye, EyeOff, Globe } from "lucide-react"
|
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
|
import { TaijiAPIClient } from "@/lib/api-client"
|
|
import { useToast } from "@/hooks/use-toast"
|
|
|
|
export default function LoginPage() {
|
|
const { language, setLanguage, t } = useLanguage()
|
|
const { toast } = useToast()
|
|
const [isRegisterMode, setIsRegisterMode] = useState(false)
|
|
const [email, setEmail] = useState("")
|
|
const [password, setPassword] = useState("")
|
|
const [confirmPassword, setConfirmPassword] = useState("")
|
|
const [username, setUsername] = useState("")
|
|
const [verificationCode, setVerificationCode] = useState("")
|
|
const [showPassword, setShowPassword] = useState(false)
|
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [isSendingCode, setIsSendingCode] = useState(false)
|
|
const [countdown, setCountdown] = useState(0)
|
|
|
|
// 倒计时效果
|
|
useEffect(() => {
|
|
if (countdown > 0) {
|
|
const timer = setTimeout(() => setCountdown(countdown - 1), 1000)
|
|
return () => clearTimeout(timer)
|
|
}
|
|
}, [countdown])
|
|
|
|
// 发送验证码
|
|
const handleSendVerificationCode = async () => {
|
|
if (!email) {
|
|
toast({
|
|
title: t("错误", "Error"),
|
|
description: t("请先输入邮箱地址", "Please enter your email address first"),
|
|
variant: "destructive",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 简单的邮箱格式验证
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
if (!emailRegex.test(email)) {
|
|
toast({
|
|
title: t("错误", "Error"),
|
|
description: t("请输入有效的邮箱地址", "Please enter a valid email address"),
|
|
variant: "destructive",
|
|
})
|
|
return
|
|
}
|
|
|
|
setIsSendingCode(true)
|
|
try {
|
|
const result = await TaijiAPIClient.sendEmailVerificationCode(email)
|
|
if (result && result.success) {
|
|
toast({
|
|
title: t("发送成功", "Success"),
|
|
description: t("验证码已发送到您的邮箱,请查收", "Verification code has been sent to your email"),
|
|
})
|
|
setCountdown(60) // 60秒倒计时
|
|
} else {
|
|
toast({
|
|
title: t("发送失败", "Failed"),
|
|
description: (result as any)?.message || t("发送验证码失败,请重试", "Failed to send verification code, please try again"),
|
|
variant: "destructive",
|
|
})
|
|
}
|
|
} catch (error: any) {
|
|
console.error("Send verification code error:", error)
|
|
toast({
|
|
title: t("发送失败", "Failed"),
|
|
description: error.message || t("发送验证码失败,请重试", "Failed to send verification code, please try again"),
|
|
variant: "destructive",
|
|
})
|
|
} finally {
|
|
setIsSendingCode(false)
|
|
}
|
|
}
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
// 注册模式验证
|
|
if (isRegisterMode) {
|
|
if (!email) {
|
|
toast({
|
|
title: t("错误", "Error"),
|
|
description: t("邮箱为必填项", "Email is required"),
|
|
variant: "destructive",
|
|
})
|
|
return
|
|
}
|
|
if (!verificationCode) {
|
|
toast({
|
|
title: t("错误", "Error"),
|
|
description: t("请输入邮箱验证码", "Please enter the email verification code"),
|
|
variant: "destructive",
|
|
})
|
|
return
|
|
}
|
|
if (!username) {
|
|
toast({
|
|
title: t("错误", "Error"),
|
|
description: t("用户名为必填项", "Username is required"),
|
|
variant: "destructive",
|
|
})
|
|
return
|
|
}
|
|
if (password.length < 8) {
|
|
toast({
|
|
title: t("错误", "Error"),
|
|
description: t("密码长度至少为8位", "Password must be at least 8 characters"),
|
|
variant: "destructive",
|
|
})
|
|
return
|
|
}
|
|
if (password !== confirmPassword) {
|
|
toast({
|
|
title: t("错误", "Error"),
|
|
description: t("两次输入的密码不一致", "Passwords do not match"),
|
|
variant: "destructive",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
setIsLoading(true)
|
|
|
|
try {
|
|
if (isRegisterMode) {
|
|
// 注册
|
|
const result = await TaijiAPIClient.register(email, password, verificationCode, username, undefined)
|
|
|
|
if (result && result.success) {
|
|
toast({
|
|
title: t("注册成功", "Registration successful"),
|
|
description: t("欢迎加入 Taiji AI 平台", "Welcome to Taiji AI Platform"),
|
|
})
|
|
// 注册成功后自动登录,跳转到首页
|
|
window.location.href = "/"
|
|
} else {
|
|
toast({
|
|
title: t("注册失败", "Registration failed"),
|
|
description: (result as any)?.message || t("注册失败,请重试", "Registration failed, please try again"),
|
|
variant: "destructive",
|
|
})
|
|
}
|
|
} else {
|
|
// 登录
|
|
const result = await TaijiAPIClient.login(email, password, "user")
|
|
|
|
if (result && result.success) {
|
|
toast({
|
|
title: t("登录成功", "Login successful"),
|
|
description: t("欢迎回来", "Welcome back"),
|
|
})
|
|
// Redirect to dashboard on success
|
|
window.location.href = "/"
|
|
} else {
|
|
toast({
|
|
title: t("登录失败", "Login failed"),
|
|
description: (result as any)?.message || t("请检查您的邮箱和密码", "Please check your email and password"),
|
|
variant: "destructive",
|
|
})
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
console.error(isRegisterMode ? "Register error:" : "Login error:", error)
|
|
let errorMessage = error.message || t("网络错误,请稍后重试", "Network error, please try again")
|
|
|
|
// 检查是否是连接错误
|
|
if (errorMessage.includes("无法连接到后端服务") || errorMessage.includes("Failed to fetch")) {
|
|
errorMessage = t(
|
|
"无法连接到后端服务,请确保后端服务正在运行",
|
|
"Cannot connect to backend service, please ensure the backend is running"
|
|
)
|
|
}
|
|
|
|
toast({
|
|
title: isRegisterMode ? t("注册失败", "Registration failed") : t("登录失败", "Login failed"),
|
|
description: errorMessage,
|
|
variant: "destructive",
|
|
})
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background flex items-center justify-center p-4 relative overflow-hidden">
|
|
{/* Video Background */}
|
|
<video
|
|
autoPlay
|
|
loop
|
|
muted
|
|
playsInline
|
|
className="absolute inset-0 w-full h-full object-cover z-0"
|
|
>
|
|
<source src="/13094720609404041750_sample_0.mp4" type="video/mp4" />
|
|
</video>
|
|
|
|
{/* Light overlay for better readability - more transparent */}
|
|
<div className="absolute inset-0 bg-background/20 dark:bg-background/30 z-10" />
|
|
|
|
{/* Language Switcher */}
|
|
<div className="absolute top-6 right-6 z-20">
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="outline" size="sm" className="gap-2 bg-card/60 backdrop-blur-md border-border/50">
|
|
<Globe className="h-4 w-4" />
|
|
{language === "zh" ? "中文" : "English"}
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuItem onClick={() => setLanguage("zh")}>中文</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => setLanguage("en")}>English</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
|
|
<div className="w-full max-w-md relative z-20">
|
|
{/* Logo and Header */}
|
|
<div className="text-center mb-8">
|
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-muted/30 backdrop-blur-sm mb-4 overflow-hidden">
|
|
<Image
|
|
src="/logo.jpg"
|
|
alt="Taiji AI Platform Logo"
|
|
width={64}
|
|
height={64}
|
|
className="object-cover w-full h-full"
|
|
unoptimized
|
|
/>
|
|
</div>
|
|
<h1 className="text-3xl font-bold text-foreground mb-2 drop-shadow-lg">{t("Taiji AI 平台", "Taiji AI Platform")}</h1>
|
|
<p className="text-muted-foreground drop-shadow-md">{t("企业级 AI Agent 赋能平台", "Enterprise AI Agent Empowerment")}</p>
|
|
</div>
|
|
|
|
{/* Login/Register Card - More transparent */}
|
|
<Card className="p-8 bg-card/50 backdrop-blur-md border-border/50 shadow-xl">
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
{/* 邮箱 - 必填 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email">
|
|
{t("邮箱地址", "Email Address")} <span className="text-destructive">*</span>
|
|
</Label>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
placeholder={t("请输入邮箱", "Enter your email")}
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
required
|
|
className="h-11 bg-background/70 backdrop-blur-sm border-border/70"
|
|
/>
|
|
</div>
|
|
|
|
{/* 注册模式下的额外字段 */}
|
|
{isRegisterMode && (
|
|
<>
|
|
{/* 邮箱验证码 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="verificationCode">
|
|
{t("邮箱验证码", "Email Verification Code")} <span className="text-destructive">*</span>
|
|
</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
id="verificationCode"
|
|
type="text"
|
|
placeholder={t("请输入验证码", "Enter verification code")}
|
|
value={verificationCode}
|
|
onChange={(e) => setVerificationCode(e.target.value)}
|
|
required
|
|
className="h-11 flex-1 bg-background/70 backdrop-blur-sm border-border/70"
|
|
maxLength={6}
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={handleSendVerificationCode}
|
|
disabled={isSendingCode || countdown > 0 || !email}
|
|
className="h-11 whitespace-nowrap"
|
|
>
|
|
{isSendingCode
|
|
? t("发送中...", "Sending...")
|
|
: countdown > 0
|
|
? `${countdown}s`
|
|
: t("发送验证码", "Send Code")}
|
|
</Button>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t("验证码将发送到您的邮箱", "Verification code will be sent to your email")}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="username">
|
|
{t("用户名", "Username")} <span className="text-destructive">*</span>
|
|
</Label>
|
|
<Input
|
|
id="username"
|
|
type="text"
|
|
placeholder={t("请输入用户名", "Enter username")}
|
|
value={username}
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
required
|
|
className="h-11 bg-background/70 backdrop-blur-sm border-border/70"
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* 密码 */}
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="password">{t("密码", "Password")}</Label>
|
|
{!isRegisterMode && (
|
|
<Button type="button" variant="link" className="px-0 text-sm text-primary hover:text-primary/80">
|
|
{t("忘记密码?", "Forgot password?")}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<div className="relative">
|
|
<Input
|
|
id="password"
|
|
type={showPassword ? "text" : "password"}
|
|
placeholder={t("请输入密码", "Enter your password")}
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
required
|
|
minLength={8}
|
|
className="h-11 pr-10 bg-background/70 backdrop-blur-sm border-border/70"
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
>
|
|
{showPassword ? (
|
|
<EyeOff className="h-4 w-4 text-muted-foreground" />
|
|
) : (
|
|
<Eye className="h-4 w-4 text-muted-foreground" />
|
|
)}
|
|
</Button>
|
|
</div>
|
|
{isRegisterMode && (
|
|
<p className="text-xs text-muted-foreground">
|
|
{t("密码长度至少为8位", "Password must be at least 8 characters")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 确认密码 - 仅注册模式 */}
|
|
{isRegisterMode && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="confirmPassword">{t("确认密码", "Confirm Password")}</Label>
|
|
<div className="relative">
|
|
<Input
|
|
id="confirmPassword"
|
|
type={showConfirmPassword ? "text" : "password"}
|
|
placeholder={t("请再次输入密码", "Enter password again")}
|
|
value={confirmPassword}
|
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
required
|
|
className="h-11 pr-10 bg-background/70 backdrop-blur-sm border-border/70"
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
|
|
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
>
|
|
{showConfirmPassword ? (
|
|
<EyeOff className="h-4 w-4 text-muted-foreground" />
|
|
) : (
|
|
<Eye className="h-4 w-4 text-muted-foreground" />
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<Button type="submit" className="w-full h-11" disabled={isLoading}>
|
|
{isLoading ? (
|
|
<span className="flex items-center gap-2">
|
|
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
|
{isRegisterMode ? t("注册中...", "Registering...") : t("登录中...", "Signing in...")}
|
|
</span>
|
|
) : (
|
|
isRegisterMode ? t("注册", "Sign Up") : t("登录", "Sign In")
|
|
)}
|
|
</Button>
|
|
</form>
|
|
|
|
{/* 切换登录/注册 */}
|
|
<div className="mt-6 text-center">
|
|
<Button
|
|
type="button"
|
|
variant="link"
|
|
className="text-sm"
|
|
onClick={() => {
|
|
setIsRegisterMode(!isRegisterMode)
|
|
setPassword("")
|
|
setConfirmPassword("")
|
|
setUsername("")
|
|
setVerificationCode("")
|
|
setCountdown(0)
|
|
}}
|
|
>
|
|
{isRegisterMode
|
|
? t("已有账号?立即登录", "Already have an account? Sign in")
|
|
: t("没有账号?立即注册", "Don't have an account? Sign up")}
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Footer */}
|
|
<div className="mt-8 text-center text-xs text-muted-foreground drop-shadow-md">
|
|
{t("© 2025 Taiji AI Platform. 保留所有权利。", "© 2025 Taiji AI Platform. All rights reserved.")}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|