mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-23 16:35:59 +00:00
2531cce0d1
- Port account-settings-page.tsx (change password, change email, logout)
- Wire into settings-dialog.tsx as new "account" section with UserIcon,
rendered first in the section list
- Add i18n keys:
- en-US/zh-CN: settings.sections.account ("Account" / "账号")
- en-US/zh-CN: button.logout ("Log out" / "退出登录")
- types.ts: matching type declarations
133 lines
4.0 KiB
TypeScript
133 lines
4.0 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 { fetchWithAuth, getCsrfHeaders } from "@/core/api/fetcher";
|
|
import { useAuth } from "@/core/auth/AuthProvider";
|
|
import { parseAuthError } from "@/core/auth/types";
|
|
|
|
import { SettingsSection } from "./settings-section";
|
|
|
|
export function AccountSettingsPage() {
|
|
const { user, logout } = useAuth();
|
|
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("New passwords do not match");
|
|
return;
|
|
}
|
|
if (newPassword.length < 8) {
|
|
setError("Password must be at least 8 characters");
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
const res = await fetchWithAuth("/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("Password changed successfully");
|
|
setCurrentPassword("");
|
|
setNewPassword("");
|
|
setConfirmPassword("");
|
|
} catch {
|
|
setError("Network error. Please try again.");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<SettingsSection title="Profile">
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-muted-foreground text-sm">Email</span>
|
|
<span className="text-sm font-medium">{user?.email ?? "—"}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-muted-foreground text-sm">Role</span>
|
|
<span className="text-sm font-medium capitalize">
|
|
{user?.system_role ?? "—"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection title="Change Password">
|
|
<form onSubmit={handleChangePassword} className="max-w-sm space-y-3">
|
|
<Input
|
|
type="password"
|
|
placeholder="Current password"
|
|
value={currentPassword}
|
|
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
required
|
|
/>
|
|
<Input
|
|
type="password"
|
|
placeholder="New password"
|
|
value={newPassword}
|
|
onChange={(e) => setNewPassword(e.target.value)}
|
|
required
|
|
minLength={8}
|
|
/>
|
|
<Input
|
|
type="password"
|
|
placeholder="Confirm new password"
|
|
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 ? "Updating..." : "Update Password"}
|
|
</Button>
|
|
</form>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection title="Session">
|
|
<Button
|
|
variant="destructive"
|
|
size="sm"
|
|
onClick={logout}
|
|
className="gap-2"
|
|
>
|
|
<LogOutIcon className="size-4" />
|
|
Sign Out
|
|
</Button>
|
|
</SettingsSection>
|
|
</div>
|
|
);
|
|
}
|