The account settings page had all user-facing strings (profile labels, password form placeholders, validation messages, button text) hardcoded in English. Replace them with i18n translation keys so the page renders correctly when the locale is set to Chinese. Fixed #2710
142 lines
4.4 KiB
TypeScript
142 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { LogOutIcon } from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { fetch, getCsrfHeaders } from "@/core/api/fetcher";
|
|
import { useAuth } from "@/core/auth/AuthProvider";
|
|
import { parseAuthError } from "@/core/auth/types";
|
|
import { useI18n } from "@/core/i18n/hooks";
|
|
|
|
import { SettingsSection } from "./settings-section";
|
|
|
|
export function AccountSettingsPage() {
|
|
const { user, logout } = useAuth();
|
|
const { t } = useI18n();
|
|
const [currentPassword, setCurrentPassword] = useState("");
|
|
const [newPassword, setNewPassword] = useState("");
|
|
const [confirmPassword, setConfirmPassword] = useState("");
|
|
const [message, setMessage] = useState("");
|
|
const [error, setError] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const handleChangePassword = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError("");
|
|
setMessage("");
|
|
|
|
if (newPassword !== confirmPassword) {
|
|
setError(t.settings.account.passwordMismatch);
|
|
return;
|
|
}
|
|
if (newPassword.length < 8) {
|
|
setError(t.settings.account.passwordTooShort);
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
const res = await fetch("/api/v1/auth/change-password", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...getCsrfHeaders(),
|
|
},
|
|
body: JSON.stringify({
|
|
current_password: currentPassword,
|
|
new_password: newPassword,
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const data = await res.json();
|
|
const authError = parseAuthError(data);
|
|
setError(authError.message);
|
|
return;
|
|
}
|
|
|
|
setMessage(t.settings.account.passwordChangedSuccess);
|
|
setCurrentPassword("");
|
|
setNewPassword("");
|
|
setConfirmPassword("");
|
|
} catch {
|
|
setError(t.settings.account.networkError);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<SettingsSection title={t.settings.account.profileTitle}>
|
|
<div className="space-y-2">
|
|
<div className="grid grid-cols-[max-content_max-content] items-center gap-4">
|
|
<span className="text-muted-foreground text-sm">
|
|
{t.settings.account.email}
|
|
</span>
|
|
<span className="text-sm font-medium">{user?.email ?? "—"}</span>
|
|
<span className="text-muted-foreground text-sm">
|
|
{t.settings.account.role}
|
|
</span>
|
|
<span className="text-sm font-medium capitalize">
|
|
{user?.system_role ?? "—"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection
|
|
title={t.settings.account.changePasswordTitle}
|
|
description={t.settings.account.changePasswordDescription}
|
|
>
|
|
<form onSubmit={handleChangePassword} className="max-w-sm space-y-3">
|
|
<Input
|
|
type="password"
|
|
placeholder={t.settings.account.currentPassword}
|
|
value={currentPassword}
|
|
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
required
|
|
/>
|
|
<Input
|
|
type="password"
|
|
placeholder={t.settings.account.newPassword}
|
|
value={newPassword}
|
|
onChange={(e) => setNewPassword(e.target.value)}
|
|
required
|
|
minLength={8}
|
|
/>
|
|
<Input
|
|
type="password"
|
|
placeholder={t.settings.account.confirmNewPassword}
|
|
value={confirmPassword}
|
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
required
|
|
minLength={8}
|
|
/>
|
|
{error && <p className="text-sm text-red-500">{error}</p>}
|
|
{message && <p className="text-sm text-green-500">{message}</p>}
|
|
<Button type="submit" variant="outline" size="sm" disabled={loading}>
|
|
{loading
|
|
? t.settings.account.updating
|
|
: t.settings.account.updatePassword}
|
|
</Button>
|
|
</form>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection title="" description="">
|
|
<Button
|
|
variant="destructive"
|
|
size="sm"
|
|
onClick={logout}
|
|
className="gap-2"
|
|
>
|
|
<LogOutIcon className="size-4" />
|
|
{t.settings.account.signOut}
|
|
</Button>
|
|
</SettingsSection>
|
|
</div>
|
|
);
|
|
}
|