Skip to main content
Glama
phone-signin.tsx7.92 kB
"use client" import { zodResolver } from "@hookform/resolvers/zod" import { Button } from "@repo/ui/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@repo/ui/components/ui/form" import { Input } from "@repo/ui/components/ui/input" import { InputOTP, InputOTPGroup, InputOTPSlot } from "@repo/ui/components/ui/input-otp" import { ArrowRight,Loader2 } from "lucide-react" import { useRouter } from "next/navigation" import { useEffect,useRef, useState } from "react" import { useForm } from "react-hook-form" import { toast } from "sonner" import { z } from "zod" import { authClient } from "@/lib/auth-client" const phoneSchema = z.object({ phone: z.string().min(11, "请输入有效的手机号").max(11, "请输入有效的手机号"), }) const verificationSchema = z.object({ code: z.string().length(6, "验证码必须是6位数字"), }) interface PhoneSigninProps { onSuccess?: () => void } export function PhoneSignin({ onSuccess }: PhoneSigninProps) { const [step, setStep] = useState<"phone" | "verification">("phone") const [phoneNumber, setPhoneNumber] = useState("") const [countdown, setCountdown] = useState(0) const [isLoading, setIsLoading] = useState(false) const router = useRouter(); const otpInputRef = useRef<HTMLInputElement>(null) const phoneForm = useForm<z.infer<typeof phoneSchema>>({ resolver: zodResolver(phoneSchema), defaultValues: { phone: "", }, }) const verificationForm = useForm<z.infer<typeof verificationSchema>>({ resolver: zodResolver(verificationSchema), defaultValues: { code: "", }, }) useEffect(() => { if (step === "verification" && otpInputRef.current) { otpInputRef.current.focus() } }, [step]) const startCountdown = () => { setCountdown(60) const timer = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { clearInterval(timer) return 0 } return prev - 1 }) }, 1000) } const onPhoneSubmit = async (data: z.infer<typeof phoneSchema>) => { try { setIsLoading(true) const { data: result, error } = await authClient.phoneNumber.sendOtp({ phoneNumber: data.phone }); console.warn("[phone-signin] [onPhoneSubmit] result", result, error) if (error && error?.code !== "SUCCESS") { toast.error("发送验证码失败", { description: error?.message }) } else { toast.success("验证码发送成功", { description: "请在手机上查看验证码" }) setPhoneNumber(data.phone) setStep("verification") startCountdown() } } catch (error) { console.error("发送验证码失败:", error) } finally { setIsLoading(false) } } const onVerificationSubmit = async (data: z.infer<typeof verificationSchema>) => { try { setIsLoading(true) const { data: result, error } = await authClient.phoneNumber.verify({ phoneNumber: phoneNumber, code: data.code }); console.warn("[phone-signin] [onVerificationSubmit] result", result, error) if (error && error?.code !== "SUCCESS") { toast.error("验证失败", { description: error?.message }) } else { toast.success("验证成功"); // 验证成功,可以跳转到首页或其他页面 console.log("验证成功"); if (onSuccess) { onSuccess?.() } else { router.push("/"); } } } catch (error) { console.error("验证码验证失败:", error) } finally { setIsLoading(false) } } const resendCode = async () => { try { setIsLoading(true) await authClient.phoneNumber.sendOtp({ phoneNumber }) verificationForm.reset({ code: "" }) startCountdown() if (otpInputRef.current) { otpInputRef.current.focus() } } catch (error) { console.error("重新发送验证码失败:", error) } finally { setIsLoading(false) } } return ( <div className="space-y-4"> {step === "phone" ? ( <Form {...phoneForm}> <form onSubmit={phoneForm.handleSubmit(onPhoneSubmit)} className="space-y-4"> <FormField control={phoneForm.control} name="phone" render={({ field }) => ( <FormItem> <FormLabel>手机号</FormLabel> <FormControl> <Input placeholder="请输入手机号" {...field} className="h-11" maxLength={11} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" className="w-full" disabled={isLoading}> {isLoading ? ( <> <Loader2 className="mr-2 h-4 w-4 animate-spin" /> 发送验证码 </> ) : ( <> 继续 <ArrowRight className="ml-2 h-4 w-4" /> </> )} </Button> </form> </Form> ) : ( <Form {...verificationForm}> <form onSubmit={verificationForm.handleSubmit(onVerificationSubmit)} className="space-y-4"> <div className="text-sm text-muted-foreground mb-4"> 验证码已发送至 <span className="font-medium">{phoneNumber}</span> </div> <FormField control={verificationForm.control} name="code" render={({ field }) => ( <FormItem className="space-y-4"> <FormLabel>验证码</FormLabel> <FormControl> <div className="flex justify-center"> <InputOTP maxLength={6} {...field} value={field.value || ""} onChange={(value) => { field.onChange(value) if (value.length === 6) { verificationForm.handleSubmit(onVerificationSubmit)() } }} ref={otpInputRef} > <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> </div> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" className="w-full" disabled={isLoading}> {isLoading ? ( <> <Loader2 className="mr-2 h-4 w-4 animate-spin" /> 验证中 </> ) : ( "登录" )} </Button> <div className="flex justify-between items-center mt-2"> <Button type="button" variant="link" className="p-0 h-auto" onClick={() => setStep("phone")}> 更换手机号 </Button> <Button type="button" variant="link" className="p-0 h-auto" onClick={resendCode} disabled={countdown > 0 || isLoading} > {countdown > 0 ? `重新发送(${countdown}s)` : "重新发送"} </Button> </div> </form> </Form> )} </div> ) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/metacode0602/open-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server