mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-12 17:35:21 +02:00
feat(i18n): add internationalization support for 2FA setup and error messages
This commit is contained in:
@@ -26,6 +26,7 @@ import { authClient } from "@/lib/auth-client";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Fingerprint, QrCode } from "lucide-react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import QRCode from "qrcode";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -55,6 +56,7 @@ type PinForm = z.infer<typeof PinSchema>;
|
||||
|
||||
export const Enable2FA = () => {
|
||||
const utils = api.useUtils();
|
||||
const { t } = useTranslation("settings");
|
||||
const [data, setData] = useState<TwoFactorSetupData | null>(null);
|
||||
const [backupCodes, setBackupCodes] = useState<string[]>([]);
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
@@ -86,16 +88,18 @@ export const Enable2FA = () => {
|
||||
});
|
||||
|
||||
setStep("verify");
|
||||
toast.success("Scan the QR code with your authenticator app");
|
||||
toast.success(t("settings.2fa.scanQrCode"));
|
||||
} else {
|
||||
throw new Error("No TOTP URI received from server");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Error setting up 2FA",
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("settings.2fa.errorSettingUp")
|
||||
);
|
||||
passwordForm.setError("password", {
|
||||
message: "Error verifying password",
|
||||
message: t("settings.2fa.errorVerifyingPassword"),
|
||||
});
|
||||
} finally {
|
||||
setIsPasswordLoading(false);
|
||||
@@ -111,9 +115,9 @@ export const Enable2FA = () => {
|
||||
if (result.error) {
|
||||
if (result.error.code === "INVALID_TWO_FACTOR_AUTHENTICATION") {
|
||||
pinForm.setError("pin", {
|
||||
message: "Invalid code. Please try again.",
|
||||
message: t("settings.2fa.invalidCode"),
|
||||
});
|
||||
toast.error("Invalid verification code");
|
||||
toast.error(t("settings.2fa.invalidVerificationCode"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,14 +128,14 @@ export const Enable2FA = () => {
|
||||
throw new Error("No response received from server");
|
||||
}
|
||||
|
||||
toast.success("2FA configured successfully");
|
||||
toast.success(t("settings.2fa.success"));
|
||||
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."
|
||||
? t("settings.2fa.connectionError")
|
||||
: error.message;
|
||||
|
||||
pinForm.setError("pin", {
|
||||
@@ -140,9 +144,9 @@ export const Enable2FA = () => {
|
||||
toast.error(errorMessage);
|
||||
} else {
|
||||
pinForm.setError("pin", {
|
||||
message: "Error verifying code",
|
||||
message: t("settings.2fa.errorVerifyingCode"),
|
||||
});
|
||||
toast.error("Error verifying 2FA code");
|
||||
toast.error(t("settings.2fa.errorVerifying2faCode"));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -176,16 +180,16 @@ export const Enable2FA = () => {
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<Fingerprint className="size-4 text-muted-foreground" />
|
||||
Enable 2FA
|
||||
{t("settings.2fa.enable2fa")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>2FA Setup</DialogTitle>
|
||||
<DialogTitle>{t("settings.2fa.title")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{step === "password"
|
||||
? "Enter your password to begin 2FA setup"
|
||||
: "Scan the QR code and verify with your authenticator app"}
|
||||
? t("settings.2fa.enterPassword")
|
||||
: t("settings.2fa.scanQrCodeAndVerify")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -201,16 +205,18 @@ export const Enable2FA = () => {
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormLabel>
|
||||
{t("settings.2fa.password")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter your password"
|
||||
placeholder={t("settings.2fa.enterPasswordPlaceholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Enter your password to enable 2FA
|
||||
{t("settings.2fa.enterPasswordDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -221,7 +227,7 @@ export const Enable2FA = () => {
|
||||
className="w-full"
|
||||
isLoading={isPasswordLoading}
|
||||
>
|
||||
Continue
|
||||
{t("settings.2fa.continue")}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
@@ -238,16 +244,16 @@ export const Enable2FA = () => {
|
||||
<div className="flex flex-col items-center gap-4 p-6 border rounded-lg">
|
||||
<QrCode className="size-5 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">
|
||||
Scan this QR code with your authenticator app
|
||||
{t("settings.2fa.scanQrCode")}
|
||||
</span>
|
||||
<img
|
||||
src={data.qrCodeUrl}
|
||||
alt="2FA QR Code"
|
||||
alt={t("settings.2fa.qrCodeAlt")}
|
||||
className="rounded-lg w-48 h-48"
|
||||
/>
|
||||
<div className="flex flex-col gap-2 text-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Can't scan the QR code?
|
||||
{t("settings.2fa.cantScanQrCode")}
|
||||
</span>
|
||||
<span className="text-xs font-mono bg-muted p-2 rounded">
|
||||
{data.secret}
|
||||
@@ -257,7 +263,9 @@ export const Enable2FA = () => {
|
||||
|
||||
{backupCodes && backupCodes.length > 0 && (
|
||||
<div className="w-full space-y-3 border rounded-lg p-4">
|
||||
<h4 className="font-medium">Backup Codes</h4>
|
||||
<h4 className="font-medium">
|
||||
{t("settings.2fa.backupCodes")}
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{backupCodes.map((code, index) => (
|
||||
<code
|
||||
@@ -269,9 +277,7 @@ export const Enable2FA = () => {
|
||||
))}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Save these backup codes in a secure place. You can use
|
||||
them to access your account if you lose access to your
|
||||
authenticator device.
|
||||
{t("settings.2fa.saveBackupCodes")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -288,7 +294,9 @@ export const Enable2FA = () => {
|
||||
name="pin"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col justify-center items-center">
|
||||
<FormLabel>Verification Code</FormLabel>
|
||||
<FormLabel>
|
||||
{t("settings.2fa.verificationCode")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<InputOTP maxLength={6} {...field}>
|
||||
<InputOTPGroup>
|
||||
@@ -302,7 +310,7 @@ export const Enable2FA = () => {
|
||||
</InputOTP>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Enter the 6-digit code from your authenticator app
|
||||
{t("settings.2fa.enterVerificationCode")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -314,7 +322,7 @@ export const Enable2FA = () => {
|
||||
className="w-full"
|
||||
isLoading={isPasswordLoading}
|
||||
>
|
||||
Enable 2FA
|
||||
{t("settings.2fa.enable2fa")}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
Reference in New Issue
Block a user