Files
taiji-pda-v0/app/login/page.tsx
T
zhanggangyong 2556dfd6f1 feat: 替换平台logo并移除浏览器标签页标题和图标
- 将平台logo替换为logo.jpg
- 在登录页面和侧边栏使用新的logo图片
- 移除浏览器标签页的标题和图标配置
- 删除旧的图标文件
2026-01-11 18:12:06 +00:00

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>
)
}