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