chore: auth flow cleanup and verification
This commit is contained in:
@@ -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'll send you a verification code to
|
||||
reset your password.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -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't have an account?{" "}
|
||||
<Link
|
||||
href="/signup"
|
||||
className="text-[#eb3779] hover:underline"
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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's security
|
||||
key. No password needed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'd like to sign up
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user