@@ -4,38 +4,72 @@ import { Button } from "@/components/ui/button";
44import { toast } from "sonner" ;
55import { emailLoginAction } from "@/actions/email-login" ;
66import { useTranslations } from "next-intl" ;
7+ import { Icons } from "@/components/icons" ; // 导入图标组件
78
89export function EmailLoginForm ( { buttonText } : { buttonText ?: string } ) {
910 const [ email , setEmail ] = useState ( "" ) ;
1011 const [ code , setCode ] = useState ( "" ) ;
1112 const [ cooldown , setCooldown ] = useState ( 0 ) ;
1213 const [ isPending , startTransition ] = useTransition ( ) ;
14+ const [ isSendingCode , setIsSendingCode ] = useState ( false ) ; // 新增:发送验证码加载状态
1315 const t = useTranslations ( "Auth" ) ;
1416
1517 const sendCode = async ( ) => {
1618 if ( ! email ) {
1719 toast . error ( t ( "pleaseEnterEmail" ) ) ;
1820 return ;
1921 }
22+
23+ // 验证邮箱格式
24+ const emailRegex = / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / ;
25+ if ( ! emailRegex . test ( email ) ) {
26+ toast . error ( t ( "invalidEmailFormat" ) ) ;
27+ return ;
28+ }
29+
30+ setIsSendingCode ( true ) ; // 开始发送,显示加载状态
31+
2032 try {
2133 const form = new FormData ( ) ;
2234 form . set ( 'mode' , 'email' ) ;
2335 form . set ( 'email' , email ) ;
24- const res = await fetch ( "/api/auth/send-code" , { method : "POST" , body : form } ) ;
36+
37+ // 添加超时处理
38+ const controller = new AbortController ( ) ;
39+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 10000 ) ; // 10秒超时
40+
41+ const res = await fetch ( "/api/auth/send-email" , {
42+ method : "POST" ,
43+ body : form ,
44+ signal : controller . signal
45+ } ) ;
46+
47+ clearTimeout ( timeoutId ) ;
48+
2549 if ( res . ok ) {
2650 toast . info ( process . env . NODE_ENV === "development" ? t ( "codeSentDev" ) : t ( "codeSent" ) ) ;
2751 setCooldown ( 60 ) ;
2852 const timer = setInterval ( ( ) => {
2953 setCooldown ( ( s ) => {
30- if ( s <= 1 ) { clearInterval ( timer ) ; return 0 ; }
54+ if ( s <= 1 ) {
55+ clearInterval ( timer ) ;
56+ return 0 ;
57+ }
3158 return s - 1 ;
3259 } ) ;
3360 } , 1000 ) ;
3461 } else {
35- toast . error ( t ( "sendFailed" ) ) ;
62+ const errorData = await res . json ( ) . catch ( ( ) => ( { } ) ) ;
63+ toast . error ( errorData . message || t ( "sendFailed" ) ) ;
3664 }
3765 } catch ( e : any ) {
38- toast . error ( `${ t ( "sendFailed" ) } :${ e . message } ` ) ;
66+ if ( e . name === 'AbortError' ) {
67+ toast . error ( t ( "requestTimeout" ) ) ;
68+ } else {
69+ toast . error ( `${ t ( "sendFailed" ) } :${ e . message } ` ) ;
70+ }
71+ } finally {
72+ setIsSendingCode ( false ) ; // 结束发送,隐藏加载状态
3973 }
4074 } ;
4175
@@ -50,7 +84,7 @@ export function EmailLoginForm({ buttonText }: { buttonText?: string }) {
5084
5185 return (
5286 < form onSubmit = { onSubmit } className = "space-y-4" >
53- < div className = "flex flex-col justify-center gap-2 space-y-4" >
87+ < div className = "flex flex-col justify-center gap-2 space-y-4" >
5488 { /* 邮箱 */ }
5589 < div >
5690 < div className = "mb-1 text-sm" > { t ( "email" ) } </ div >
@@ -62,7 +96,8 @@ export function EmailLoginForm({ buttonText }: { buttonText?: string }) {
6296 value = { email }
6397 onChange = { ( e ) => setEmail ( e . target . value ) }
6498 required
65- className = "mx-6 w-full p-1 bg-transparent outline-none border-none focus-visible:border-none focus-visible:ring-0 hover:border-none hover:ring-0"
99+ disabled = { isSendingCode } // 发送时禁用邮箱输入
100+ className = "mx-6 w-full p-1 bg-transparent outline-none border-none focus-visible:border-none focus-visible:ring-0 hover:border-none hover:ring-0 disabled:opacity-50 disabled:cursor-not-allowed"
66101 />
67102 </ div >
68103 </ div >
@@ -77,32 +112,54 @@ export function EmailLoginForm({ buttonText }: { buttonText?: string }) {
77112 type = "text"
78113 value = { code }
79114 onChange = { ( e ) => setCode ( e . target . value ) }
80- className = "mx-6 w-full p-1 bg-transparent outline-none border-none shadow-none focus-visible:border-none focus-visible:ring-0 hover:border-none hover:ring-0"
115+ disabled = { isSendingCode } // 发送时禁用验证码输入
116+ className = "mx-6 w-full p-1 bg-transparent outline-none border-none shadow-none focus-visible:border-none focus-visible:ring-0 hover:border-none hover:ring-0 disabled:opacity-50 disabled:cursor-not-allowed"
81117 />
82118 < Button
83119 type = "button"
84120 variant = "link"
85121 size = "sm"
86122 onClick = { sendCode }
87- disabled = { cooldown > 0 }
88- className = "mr-2 text-primary"
123+ disabled = { cooldown > 0 || isSendingCode || ! email }
124+ className = "mr-2 text-primary min-w-[100px] transition-all duration-300 "
89125 >
90- { cooldown > 0 ? t ( "retryAfter" , { seconds : cooldown } ) : t ( "getCode" ) }
126+ { isSendingCode ? (
127+ < div className = "flex items-center gap-2" >
128+ < Icons . spinner className = "h-4 w-4 animate-spin" />
129+ < span > { t ( "sending" ) } </ span >
130+ </ div >
131+ ) : cooldown > 0 ? (
132+ < div className = "flex items-center gap-2" >
133+ < span > { t ( "retryAfter" , { seconds : cooldown } ) } </ span >
134+ </ div >
135+ ) : (
136+ < div className = "flex items-center gap-2" >
137+ < span > { t ( "getCode" ) } </ span >
138+ </ div >
139+ ) }
91140 </ Button >
92141 </ div >
93142 </ div >
94143
95- { /* 条款提示已移除 */ }
96-
97144 { /* 提交按钮 */ }
98145 < div className = "space-y-4 pb-2" >
99- < Button type = "submit" variant = "default" className = "w-full transition delay-150 duration-300" disabled = { isPending } >
100- { buttonText || t ( "login" ) }
146+ < Button
147+ type = "submit"
148+ variant = "default"
149+ className = "w-full transition delay-150 duration-300"
150+ disabled = { isPending || isSendingCode }
151+ >
152+ { isPending ? (
153+ < div className = "flex items-center gap-2" >
154+ < Icons . spinner className = "h-4 w-4 animate-spin" />
155+ < span > { t ( "loggingIn" ) } </ span >
156+ </ div >
157+ ) : (
158+ buttonText || t ( "login" )
159+ ) }
101160 </ Button >
102161 </ div >
103162 </ div >
104163 </ form >
105164 ) ;
106- }
107-
108-
165+ }
0 commit comments