Fix PR feedback: Improve Profile section translations and UX
- Fixed password reset translations in Profile section - Moved language selector from TopNavbar to Profile page - Added profile.selectPreferredLanguage translation key - Improved user experience for language preferences
This commit is contained in:
@@ -609,7 +609,8 @@
|
|||||||
"user": "User",
|
"user": "User",
|
||||||
"authMethod": "Authentication Method",
|
"authMethod": "Authentication Method",
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
"external": "External (OIDC)"
|
"external": "External (OIDC)",
|
||||||
|
"selectPreferredLanguage": "Select your preferred language for the interface"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"enterCode": "000000",
|
"enterCode": "000000",
|
||||||
|
|||||||
@@ -609,7 +609,8 @@
|
|||||||
"user": "用户",
|
"user": "用户",
|
||||||
"authMethod": "认证方式",
|
"authMethod": "认证方式",
|
||||||
"local": "本地",
|
"local": "本地",
|
||||||
"external": "外部 (OIDC)"
|
"external": "外部 (OIDC)",
|
||||||
|
"selectPreferredLanguage": "选择您的界面首选语言"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"enterCode": "000000",
|
"enterCode": "000000",
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
import {Input} from "@/components/ui/input.tsx";
|
import {Input} from "@/components/ui/input.tsx";
|
||||||
import {Checkbox} from "@/components/ui/checkbox.tsx";
|
import {Checkbox} from "@/components/ui/checkbox.tsx";
|
||||||
import {Separator} from "@/components/ui/separator.tsx";
|
import {Separator} from "@/components/ui/separator.tsx";
|
||||||
import {LanguageSwitcher} from "@/components/LanguageSwitcher";
|
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
interface TopNavbarProps {
|
interface TopNavbarProps {
|
||||||
@@ -267,8 +266,6 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-center gap-2 flex-1 px-2">
|
<div className="flex items-center justify-center gap-2 flex-1 px-2">
|
||||||
<LanguageSwitcher />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-[30px] h-[30px]"
|
className="w-[30px] h-[30px]"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {Label} from "@/components/ui/label.tsx";
|
|||||||
import {Input} from "@/components/ui/input.tsx";
|
import {Input} from "@/components/ui/input.tsx";
|
||||||
import {Button} from "@/components/ui/button.tsx";
|
import {Button} from "@/components/ui/button.tsx";
|
||||||
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert.tsx";
|
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert.tsx";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
interface PasswordResetProps {
|
interface PasswordResetProps {
|
||||||
userInfo: {
|
userInfo: {
|
||||||
@@ -17,6 +18,7 @@ interface PasswordResetProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PasswordReset({userInfo}: PasswordResetProps) {
|
export function PasswordReset({userInfo}: PasswordResetProps) {
|
||||||
|
const {t} = useTranslation();
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const [resetStep, setResetStep] = useState<"initiate" | "verify" | "newPassword">("initiate");
|
const [resetStep, setResetStep] = useState<"initiate" | "verify" | "newPassword">("initiate");
|
||||||
@@ -35,7 +37,7 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
setResetStep("verify");
|
setResetStep("verify");
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err?.response?.data?.error || err?.message || "Failed to initiate password reset");
|
setError(err?.response?.data?.error || err?.message || t('errors.failedPasswordReset'));
|
||||||
} finally {
|
} finally {
|
||||||
setResetLoading(false);
|
setResetLoading(false);
|
||||||
}
|
}
|
||||||
@@ -60,7 +62,7 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
setResetStep("newPassword");
|
setResetStep("newPassword");
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err?.response?.data?.error || "Failed to verify reset code");
|
setError(err?.response?.data?.error || t('errors.failedVerifyCode'));
|
||||||
} finally {
|
} finally {
|
||||||
setResetLoading(false);
|
setResetLoading(false);
|
||||||
}
|
}
|
||||||
@@ -71,13 +73,13 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
setResetLoading(true);
|
setResetLoading(true);
|
||||||
|
|
||||||
if (newPassword !== confirmPassword) {
|
if (newPassword !== confirmPassword) {
|
||||||
setError("Passwords do not match");
|
setError(t('errors.passwordMismatch'));
|
||||||
setResetLoading(false);
|
setResetLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword.length < 6) {
|
if (newPassword.length < 6) {
|
||||||
setError("Password must be at least 6 characters long");
|
setError(t('errors.weakPassword'));
|
||||||
setResetLoading(false);
|
setResetLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -94,7 +96,7 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
|
|
||||||
setResetSuccess(true);
|
setResetSuccess(true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err?.response?.data?.error || "Failed to complete password reset");
|
setError(err?.response?.data?.error || t('errors.failedCompleteReset'));
|
||||||
} finally {
|
} finally {
|
||||||
setResetLoading(false);
|
setResetLoading(false);
|
||||||
}
|
}
|
||||||
@@ -115,7 +117,7 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
Password
|
Password
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Change your account password
|
{t('profile.changePassword')}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -129,7 +131,7 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
disabled={resetLoading || !userInfo.username.trim()}
|
disabled={resetLoading || !userInfo.username.trim()}
|
||||||
onClick={handleInitiatePasswordReset}
|
onClick={handleInitiatePasswordReset}
|
||||||
>
|
>
|
||||||
{resetLoading ? Spinner : "Send Reset Code"}
|
{resetLoading ? Spinner : t('auth.sendResetCode')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -138,12 +140,11 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
{resetStep === "verify" && (
|
{resetStep === "verify" && (
|
||||||
<>
|
<>
|
||||||
<div className="text-center text-muted-foreground mb-4">
|
<div className="text-center text-muted-foreground mb-4">
|
||||||
<p>Enter the 6-digit code from the docker container logs for
|
<p>{t('auth.enterResetCode')}: <strong>{userInfo.username}</strong></p>
|
||||||
user: <strong>{userInfo.username}</strong></p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="reset-code">Reset Code</Label>
|
<Label htmlFor="reset-code">{t('auth.resetCode')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="reset-code"
|
id="reset-code"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -162,7 +163,7 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
disabled={resetLoading || resetCode.length !== 6}
|
disabled={resetLoading || resetCode.length !== 6}
|
||||||
onClick={handleVerifyResetCode}
|
onClick={handleVerifyResetCode}
|
||||||
>
|
>
|
||||||
{resetLoading ? Spinner : "Verify Code"}
|
{resetLoading ? Spinner : t('auth.verifyCode')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -174,7 +175,7 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
setResetCode("");
|
setResetCode("");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Back
|
{t('common.back')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -183,10 +184,9 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
{resetSuccess && (
|
{resetSuccess && (
|
||||||
<>
|
<>
|
||||||
<Alert className="">
|
<Alert className="">
|
||||||
<AlertTitle>Success!</AlertTitle>
|
<AlertTitle>{t('auth.passwordResetSuccess')}</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
Your password has been successfully reset! You can now log in
|
{t('auth.passwordResetSuccessDesc')}
|
||||||
with your new password.
|
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
</>
|
</>
|
||||||
@@ -195,12 +195,11 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
{resetStep === "newPassword" && !resetSuccess && (
|
{resetStep === "newPassword" && !resetSuccess && (
|
||||||
<>
|
<>
|
||||||
<div className="text-center text-muted-foreground mb-4">
|
<div className="text-center text-muted-foreground mb-4">
|
||||||
<p>Enter your new password for
|
<p>{t('auth.enterNewPassword')}: <strong>{userInfo.username}</strong></p>
|
||||||
user: <strong>{userInfo.username}</strong></p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="new-password">New Password</Label>
|
<Label htmlFor="new-password">{t('auth.newPassword')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="new-password"
|
id="new-password"
|
||||||
type="password"
|
type="password"
|
||||||
@@ -213,7 +212,7 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="confirm-password">Confirm Password</Label>
|
<Label htmlFor="confirm-password">{t('auth.confirmNewPassword')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="confirm-password"
|
id="confirm-password"
|
||||||
type="password"
|
type="password"
|
||||||
@@ -231,7 +230,7 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
disabled={resetLoading || !newPassword || !confirmPassword}
|
disabled={resetLoading || !newPassword || !confirmPassword}
|
||||||
onClick={handleCompletePasswordReset}
|
onClick={handleCompletePasswordReset}
|
||||||
>
|
>
|
||||||
{resetLoading ? Spinner : "Reset Password"}
|
{resetLoading ? Spinner : t('auth.resetPassword')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -244,14 +243,14 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
|
|||||||
setConfirmPassword("");
|
setConfirmPassword("");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Back
|
{t('common.back')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{error && (
|
{error && (
|
||||||
<Alert variant="destructive" className="mt-4">
|
<Alert variant="destructive" className="mt-4">
|
||||||
<AlertTitle>Error</AlertTitle>
|
<AlertTitle>{t('common.error')}</AlertTitle>
|
||||||
<AlertDescription>{error}</AlertDescription>
|
<AlertDescription>{error}</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {getUserInfo} from "@/ui/main-axios.ts";
|
|||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {PasswordReset} from "@/ui/User/PasswordReset.tsx";
|
import {PasswordReset} from "@/ui/User/PasswordReset.tsx";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {LanguageSwitcher} from "@/components/LanguageSwitcher";
|
||||||
|
|
||||||
interface UserProfileProps {
|
interface UserProfileProps {
|
||||||
isTopbarOpen?: boolean;
|
isTopbarOpen?: boolean;
|
||||||
@@ -146,6 +147,16 @@ export function UserProfile({isTopbarOpen = true}: UserProfileProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 pt-6 border-t">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Label>{t('common.language')}</Label>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">{t('profile.selectPreferredLanguage')}</p>
|
||||||
|
</div>
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user