219 lines
7.1 KiB
TypeScript
219 lines
7.1 KiB
TypeScript
"use client"
|
||
|
||
import { useState, useEffect, Suspense } from "react"
|
||
import { signIn, getProviders, useSession } from "next-auth/react"
|
||
import { useRouter, useSearchParams } from "next/navigation"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Input } from "@/components/ui/input"
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||
import { Label } from "@/components/ui/label"
|
||
import { Separator } from "@/components/ui/separator"
|
||
import { Mail, Chrome } from "lucide-react"
|
||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||
import { ThemeToggle } from "@/components/theme-toggle"
|
||
import { CaptchaDialog } from "@/components/captcha-dialog"
|
||
|
||
function SignInContent() {
|
||
const [email, setEmail] = useState("")
|
||
const [isLoading, setIsLoading] = useState(false)
|
||
const [message, setMessage] = useState("")
|
||
const [showCaptcha, setShowCaptcha] = useState(false)
|
||
const { data: session, status } = useSession()
|
||
const router = useRouter()
|
||
const searchParams = useSearchParams()
|
||
|
||
// 检查用户是否已登录,如果已登录则重定向
|
||
useEffect(() => {
|
||
if (status === "authenticated" && session) {
|
||
const callbackUrl = searchParams.get("callbackUrl") || "/"
|
||
router.push(callbackUrl)
|
||
}
|
||
}, [status, session, router, searchParams])
|
||
|
||
// 如果正在加载会话状态,显示loading
|
||
if (status === "loading") {
|
||
return (
|
||
<div className="min-h-screen bg-background transition-colors">
|
||
<div className="absolute top-4 right-4 z-50">
|
||
<ThemeToggle />
|
||
</div>
|
||
<div className="flex items-center justify-center min-h-screen">
|
||
<Card className="w-full max-w-md shadow-lg">
|
||
<CardContent className="flex items-center justify-center p-6">
|
||
<div className="text-center">
|
||
<div className="w-8 h-8 bg-muted rounded-full animate-pulse mx-auto mb-2"></div>
|
||
<p className="text-sm text-muted-foreground">正在检查登录状态...</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 如果已经登录,不显示登录表单(虽然会重定向,但防止闪烁)
|
||
if (status === "authenticated") {
|
||
return null
|
||
}
|
||
|
||
const handleEmailSignInClick = (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
if (!email.trim()) return
|
||
|
||
// 清除之前的消息
|
||
setMessage("")
|
||
|
||
// 显示验证码弹窗
|
||
setShowCaptcha(true)
|
||
}
|
||
|
||
const handleCaptchaVerify = async () => {
|
||
setIsLoading(true)
|
||
setMessage("")
|
||
|
||
try {
|
||
const result = await signIn("nodemailer", {
|
||
email: email.trim(),
|
||
redirect: false,
|
||
})
|
||
|
||
if (result?.error) {
|
||
setMessage("登录失败,请重试")
|
||
} else {
|
||
setMessage("登录链接已发送到您的邮箱,请查收")
|
||
}
|
||
} catch (error) {
|
||
console.error("Email sign in error:", error)
|
||
setMessage("登录失败,请重试")
|
||
} finally {
|
||
setIsLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleCaptchaCancel = () => {
|
||
setMessage("")
|
||
setIsLoading(false)
|
||
}
|
||
|
||
const handleCaptchaOpenChange = (open: boolean) => {
|
||
setShowCaptcha(open)
|
||
if (!open) {
|
||
setIsLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleGoogleSignIn = async () => {
|
||
setIsLoading(true)
|
||
setMessage("")
|
||
try {
|
||
const callbackUrl = searchParams.get("callbackUrl") || "/"
|
||
await signIn("google", { callbackUrl })
|
||
} catch (error) {
|
||
console.error("Google sign in error:", error)
|
||
setMessage("Google登录失败,请重试")
|
||
setIsLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-background transition-colors">
|
||
<div className="absolute top-4 right-4 z-50">
|
||
<ThemeToggle />
|
||
</div>
|
||
<div className="flex items-center justify-center min-h-screen">
|
||
<Card className="w-full max-w-md shadow-lg border">
|
||
<CardHeader className="space-y-1">
|
||
<CardTitle className="text-2xl text-center text-foreground">登录</CardTitle>
|
||
<CardDescription className="text-center text-muted-foreground">
|
||
选择您的登录方式
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<Button
|
||
onClick={handleGoogleSignIn}
|
||
variant="outline"
|
||
className="w-full"
|
||
disabled={isLoading}
|
||
>
|
||
<Chrome className="mr-2 h-4 w-4" />
|
||
使用 Google 登录
|
||
</Button>
|
||
|
||
<div className="relative">
|
||
<div className="absolute inset-0 flex items-center">
|
||
<Separator className="w-full" />
|
||
</div>
|
||
<div className="relative flex justify-center text-xs uppercase">
|
||
<span className="bg-background px-2 text-muted-foreground">
|
||
或者
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<form onSubmit={handleEmailSignInClick} className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="email" className="text-foreground">邮箱地址</Label>
|
||
<Input
|
||
id="email"
|
||
type="email"
|
||
placeholder="输入您的邮箱地址"
|
||
value={email}
|
||
onChange={(e) => setEmail(e.target.value)}
|
||
required
|
||
className="bg-background border-input"
|
||
disabled={isLoading}
|
||
/>
|
||
</div>
|
||
<Button
|
||
type="submit"
|
||
className="w-full"
|
||
disabled={isLoading || !email.trim()}
|
||
>
|
||
<Mail className="mr-2 h-4 w-4" />
|
||
{isLoading ? "发送中..." : "发送登录链接"}
|
||
</Button>
|
||
</form>
|
||
|
||
{message && (
|
||
<Alert className="bg-background border">
|
||
<AlertDescription className="text-foreground">{message}</AlertDescription>
|
||
</Alert>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 验证码弹窗 */}
|
||
<CaptchaDialog
|
||
open={showCaptcha}
|
||
onOpenChange={handleCaptchaOpenChange}
|
||
onVerify={handleCaptchaVerify}
|
||
onCancel={handleCaptchaCancel}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function SignInPage() {
|
||
return (
|
||
<Suspense fallback={
|
||
<div className="min-h-screen bg-background transition-colors">
|
||
<div className="absolute top-4 right-4 z-50">
|
||
<ThemeToggle />
|
||
</div>
|
||
<div className="flex items-center justify-center min-h-screen">
|
||
<Card className="w-full max-w-md shadow-lg">
|
||
<CardContent className="flex items-center justify-center p-6">
|
||
<div className="text-center">
|
||
<div className="w-8 h-8 bg-muted rounded-full animate-pulse mx-auto mb-2"></div>
|
||
<p className="text-sm text-muted-foreground">加载中...</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
}>
|
||
<SignInContent />
|
||
</Suspense>
|
||
)
|
||
}
|