mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Refactor 2FA enablement flow in Enable2FA component
- Simplified the password submission and verification processes. - Introduced a new state for OTP input, allowing for direct user input. - Updated error handling to provide clearer feedback during the verification process. - Enhanced the user experience by resetting the OTP value when switching steps and modifying the form structure for better clarity.
This commit is contained in:
parent
8fbad8a26e
commit
48cfe66a6b
@ -61,6 +61,79 @@ export const Enable2FA = () => {
|
|||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
const [step, setStep] = useState<"password" | "verify">("password");
|
const [step, setStep] = useState<"password" | "verify">("password");
|
||||||
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
|
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
|
||||||
|
const [otpValue, setOtpValue] = useState("");
|
||||||
|
|
||||||
|
const handleVerifySubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const result = await authClient.twoFactor.verifyTotp({
|
||||||
|
code: otpValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
if (result.error.code === "INVALID_TWO_FACTOR_AUTHENTICATION") {
|
||||||
|
toast.error("Invalid verification code");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.data) {
|
||||||
|
throw new Error("No response received from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("2FA configured successfully");
|
||||||
|
utils.user.get.invalidate();
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
const errorMessage =
|
||||||
|
error.message === "Failed to fetch"
|
||||||
|
? "Connection error. Please check your internet connection."
|
||||||
|
: error.message;
|
||||||
|
|
||||||
|
toast.error(errorMessage);
|
||||||
|
} else {
|
||||||
|
toast.error("Error verifying 2FA code", {
|
||||||
|
description: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const passwordForm = useForm<PasswordForm>({
|
||||||
|
resolver: zodResolver(PasswordSchema),
|
||||||
|
defaultValues: {
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const pinForm = useForm<PinForm>({
|
||||||
|
resolver: zodResolver(PinSchema),
|
||||||
|
defaultValues: {
|
||||||
|
pin: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDialogOpen) {
|
||||||
|
setStep("password");
|
||||||
|
setData(null);
|
||||||
|
setBackupCodes([]);
|
||||||
|
setOtpValue("");
|
||||||
|
passwordForm.reset({
|
||||||
|
password: "",
|
||||||
|
issuer: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isDialogOpen, passwordForm]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (step === "verify") {
|
||||||
|
setOtpValue("");
|
||||||
|
}
|
||||||
|
}, [step]);
|
||||||
|
|
||||||
const handlePasswordSubmit = async (formData: PasswordForm) => {
|
const handlePasswordSubmit = async (formData: PasswordForm) => {
|
||||||
setIsPasswordLoading(true);
|
setIsPasswordLoading(true);
|
||||||
@ -105,75 +178,6 @@ export const Enable2FA = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVerifySubmit = async (formData: PinForm) => {
|
|
||||||
try {
|
|
||||||
const result = await authClient.twoFactor.verifyTotp({
|
|
||||||
code: formData.pin,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
if (result.error.code === "INVALID_TWO_FACTOR_AUTHENTICATION") {
|
|
||||||
pinForm.setError("pin", {
|
|
||||||
message: "Invalid code. Please try again.",
|
|
||||||
});
|
|
||||||
toast.error("Invalid verification code");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw result.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.data) {
|
|
||||||
throw new Error("No response received from server");
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success("2FA configured successfully");
|
|
||||||
utils.user.get.invalidate();
|
|
||||||
setIsDialogOpen(false);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
const errorMessage =
|
|
||||||
error.message === "Failed to fetch"
|
|
||||||
? "Connection error. Please check your internet connection."
|
|
||||||
: error.message;
|
|
||||||
|
|
||||||
pinForm.setError("pin", {
|
|
||||||
message: errorMessage,
|
|
||||||
});
|
|
||||||
toast.error(errorMessage);
|
|
||||||
} else {
|
|
||||||
pinForm.setError("pin", {
|
|
||||||
message: "Error verifying code",
|
|
||||||
});
|
|
||||||
toast.error("Error verifying 2FA code");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const passwordForm = useForm<PasswordForm>({
|
|
||||||
resolver: zodResolver(PasswordSchema),
|
|
||||||
defaultValues: {
|
|
||||||
password: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const pinForm = useForm<PinForm>({
|
|
||||||
resolver: zodResolver(PinSchema),
|
|
||||||
defaultValues: {
|
|
||||||
pin: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isDialogOpen) {
|
|
||||||
setStep("password");
|
|
||||||
setData(null);
|
|
||||||
setBackupCodes([]);
|
|
||||||
passwordForm.reset();
|
|
||||||
pinForm.reset();
|
|
||||||
}
|
|
||||||
}, [isDialogOpen, passwordForm, pinForm]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@ -233,7 +237,8 @@ export const Enable2FA = () => {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Enter your password to enable 2FA
|
Use a custom issuer to identify the service you're
|
||||||
|
authenticating with.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -250,11 +255,7 @@ export const Enable2FA = () => {
|
|||||||
</Form>
|
</Form>
|
||||||
) : (
|
) : (
|
||||||
<Form {...pinForm}>
|
<Form {...pinForm}>
|
||||||
<form
|
<form onSubmit={handleVerifySubmit} className="space-y-6">
|
||||||
id="pin-form"
|
|
||||||
onSubmit={pinForm.handleSubmit(handleVerifySubmit)}
|
|
||||||
className="space-y-6"
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-6 justify-center items-center">
|
<div className="flex flex-col gap-6 justify-center items-center">
|
||||||
{data?.qrCodeUrl ? (
|
{data?.qrCodeUrl ? (
|
||||||
<>
|
<>
|
||||||
@ -306,14 +307,14 @@ export const Enable2FA = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormField
|
<div className="flex flex-col justify-center items-center">
|
||||||
control={pinForm.control}
|
|
||||||
name="pin"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-col justify-center items-center">
|
|
||||||
<FormLabel>Verification Code</FormLabel>
|
<FormLabel>Verification Code</FormLabel>
|
||||||
<FormControl>
|
<InputOTP
|
||||||
<InputOTP maxLength={6} {...field}>
|
maxLength={6}
|
||||||
|
value={otpValue}
|
||||||
|
onChange={setOtpValue}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
<InputOTPGroup>
|
<InputOTPGroup>
|
||||||
<InputOTPSlot index={0} />
|
<InputOTPSlot index={0} />
|
||||||
<InputOTPSlot index={1} />
|
<InputOTPSlot index={1} />
|
||||||
@ -323,19 +324,16 @@ export const Enable2FA = () => {
|
|||||||
<InputOTPSlot index={5} />
|
<InputOTPSlot index={5} />
|
||||||
</InputOTPGroup>
|
</InputOTPGroup>
|
||||||
</InputOTP>
|
</InputOTP>
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Enter the 6-digit code from your authenticator app
|
Enter the 6-digit code from your authenticator app
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
</div>
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
isLoading={isPasswordLoading}
|
isLoading={isPasswordLoading}
|
||||||
|
disabled={otpValue.length !== 6}
|
||||||
>
|
>
|
||||||
Enable 2FA
|
Enable 2FA
|
||||||
</Button>
|
</Button>
|
||||||
|
Loading…
Reference in New Issue
Block a user