chore: auth flow cleanup and verification

This commit is contained in:
2026-04-26 01:45:34 +08:00
parent f60b3b3b08
commit 668610e8fa
11 changed files with 79 additions and 86 deletions
+1 -2
View File
@@ -5,7 +5,6 @@ import { Lock, Loader2, ArrowLeft } from "lucide-react"
import { authClient } from "@/lib/auth-client"
import {
forgotPasswordSchema,
type ForgotPasswordInput,
} from "@/lib/auth/validation"
import Link from "next/link"
import { useRouter } from "next/navigation"
@@ -51,7 +50,7 @@ export default function ForgotPasswordForm() {
Forgot your password?
</h1>
<p className="text-xs text-[#ebe4f1]/50 mt-1 leading-relaxed">
Enter your email and we'll send you a verification code to
Enter your email and we&apos;ll send you a verification code to
reset your password.
</p>
</div>
+2 -3
View File
@@ -1,12 +1,11 @@
"use client"
import { useState, useEffect, useCallback } from "react"
import { useState, useEffect } from "react"
import { Loader2 } from "lucide-react"
import { authClient } from "@/lib/auth-client"
import {
loginEmailSchema,
loginSchema,
type LoginInput,
} from "@/lib/auth/validation"
import SocialButtons from "./social-buttons"
import Link from "next/link"
@@ -221,7 +220,7 @@ export default function LoginForm() {
)}
<p className="text-center text-xs text-[#ebe4f1]/50">
Don't have an account?{" "}
Don&apos;t have an account?{" "}
<Link
href="/signup"
className="text-[#eb3779] hover:underline"
+8 -13
View File
@@ -1,6 +1,6 @@
"use client"
import { useRef, useState, useCallback, useEffect } from "react"
import { useRef, useCallback, useMemo } from "react"
interface OtpInputProps {
length?: number
@@ -17,23 +17,18 @@ export default function OtpInput({
disabled = false,
error,
}: OtpInputProps) {
const [digits, setDigits] = useState<string[]>(
value.split("").concat(Array(length).fill("")).slice(0, length),
const digits = useMemo(
() =>
value
.split("")
.concat(Array(length).fill(""))
.slice(0, length),
[value, length],
)
const refs = useRef<(HTMLInputElement | null)[]>([])
// Sync with external value
useEffect(() => {
const newDigits = value
.split("")
.concat(Array(length).fill(""))
.slice(0, length)
setDigits(newDigits)
}, [value, length])
const updateDigits = useCallback(
(newDigits: string[]) => {
setDigits(newDigits)
onChange(newDigits.join(""))
},
[onChange],
+31 -34
View File
@@ -20,14 +20,11 @@ export default function OtpVerificationStep({
const [error, setError] = useState("")
const [isLoading, setIsLoading] = useState(false)
const [resendTimer, setResendTimer] = useState(300) // 5 minutes
const [canResend, setCanResend] = useState(false)
const canResend = resendTimer <= 0
// Countdown timer
useEffect(() => {
if (resendTimer <= 0) {
setCanResend(true)
return
}
if (resendTimer <= 0) return
const interval = setInterval(() => {
setResendTimer((prev) => prev - 1)
}, 1000)
@@ -40,37 +37,33 @@ export default function OtpVerificationStep({
return `${mins}:${secs.toString().padStart(2, "0")}`
}
const handleVerify = useCallback(async () => {
if (otp.length !== 6) return
const handleVerify = useCallback(
async (otpValue: string) => {
if (otpValue.length !== 6) return
setIsLoading(true)
setError("")
setIsLoading(true)
setError("")
const { error } = await authClient.emailOtp.verifyEmail({
email,
otp,
})
const { error } = await authClient.emailOtp.verifyEmail({
email,
otp: otpValue,
})
setIsLoading(false)
setIsLoading(false)
if (error) {
setError(
error.code === "TOO_MANY_ATTEMPTS"
? "Too many attempts. Please request a new code."
: "Invalid code. Please try again.",
)
return
}
if (error) {
setError(
error.code === "TOO_MANY_ATTEMPTS"
? "Too many attempts. Please request a new code."
: "Invalid code. Please try again.",
)
return
}
onSuccess()
}, [otp, email, onSuccess])
// Auto-submit when all digits entered
useEffect(() => {
if (otp.length === 6) {
handleVerify()
}
}, [otp, handleVerify])
onSuccess()
},
[email, onSuccess],
)
const handleResend = async () => {
setError("")
@@ -79,7 +72,6 @@ export default function OtpVerificationStep({
type: "email-verification",
})
setResendTimer(300)
setCanResend(false)
}
return (
@@ -101,7 +93,12 @@ export default function OtpVerificationStep({
</label>
<OtpInput
value={otp}
onChange={setOtp}
onChange={(value) => {
setOtp(value)
if (value.length === 6) {
handleVerify(value)
}
}}
disabled={isLoading}
error={error}
/>
@@ -126,7 +123,7 @@ export default function OtpVerificationStep({
</div>
<button
onClick={handleVerify}
onClick={() => handleVerify(otp)}
disabled={isLoading || otp.length !== 6}
className="w-full py-2.5 rounded-lg bg-[#eb3779] text-white text-sm font-semibold hover:bg-[#eb3779]/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
+1 -1
View File
@@ -49,7 +49,7 @@ export default function PasskeySetupStep({
Set up a passkey
</h1>
<p className="text-xs text-[#ebe4f1]/50 mt-1 leading-relaxed">
Sign in faster with biometrics or your device's security
Sign in faster with biometrics or your device&apos;s security
key. No password needed.
</p>
</div>
+6 -10
View File
@@ -1,6 +1,6 @@
"use client"
import { useState, useEffect, useCallback } from "react"
import { useState, useEffect } from "react"
import {
Mail,
CheckCircle2,
@@ -10,7 +10,6 @@ import {
import { authClient } from "@/lib/auth-client"
import {
resetPasswordSchema,
type ResetPasswordInput,
} from "@/lib/auth/validation"
import OtpInput from "./otp-input"
import PasswordStrengthMeter from "./password-strength"
@@ -30,14 +29,11 @@ export default function ResetPasswordForm({
const [isLoading, setIsLoading] = useState(false)
const [isSuccess, setIsSuccess] = useState(false)
const [resendTimer, setResendTimer] = useState(300)
const [canResend, setCanResend] = useState(false)
const canResend = resendTimer <= 0
// Countdown timer
useEffect(() => {
if (resendTimer <= 0) {
setCanResend(true)
return
}
if (resendTimer <= 0) return
const interval = setInterval(() => {
setResendTimer((prev) => prev - 1)
}, 1000)
@@ -65,9 +61,10 @@ export default function ResetPasswordForm({
}
setIsLoading(true)
const { error } = await authClient.resetPassword({
newPassword,
const { error } = await authClient.emailOtp.resetPassword({
email,
otp,
password: newPassword,
})
setIsLoading(false)
@@ -87,7 +84,6 @@ export default function ResetPasswordForm({
setError("")
await authClient.emailOtp.requestPasswordReset({ email })
setResendTimer(300)
setCanResend(false)
}
if (isSuccess) {
+2 -2
View File
@@ -3,7 +3,7 @@
import { useState } from "react"
import { Loader2 } from "lucide-react"
import { authClient } from "@/lib/auth-client"
import { signupSchema, type SignupInput } from "@/lib/auth/validation"
import { signupSchema } from "@/lib/auth/validation"
import SocialButtons from "./social-buttons"
import PasswordStrengthMeter from "./password-strength"
import Link from "next/link"
@@ -61,7 +61,7 @@ export default function SignupFormStep({ onSuccess }: SignupFormStepProps) {
Create your account
</h1>
<p className="text-xs text-[#ebe4f1]/50 mt-1">
Choose how you'd like to sign up
Choose how you&apos;d like to sign up
</p>
</div>