finance-calculator/app/auth/signin/page.tsx
2025-06-14 21:34:28 +08:00

219 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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