mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-04-30 05:48:38 +00:00
feat(auth): account settings page + i18n
- 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
This commit is contained in:
parent
f942e4e597
commit
2531cce0d1
@ -0,0 +1,132 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
@ -6,6 +6,7 @@ import {
|
||||
BrainIcon,
|
||||
PaletteIcon,
|
||||
SparklesIcon,
|
||||
UserIcon,
|
||||
WrenchIcon,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
@ -18,6 +19,7 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { AboutSettingsPage } from "@/components/workspace/settings/about-settings-page";
|
||||
import { AccountSettingsPage } from "@/components/workspace/settings/account-settings-page";
|
||||
import { AppearanceSettingsPage } from "@/components/workspace/settings/appearance-settings-page";
|
||||
import { MemorySettingsPage } from "@/components/workspace/settings/memory-settings-page";
|
||||
import { NotificationSettingsPage } from "@/components/workspace/settings/notification-settings-page";
|
||||
@ -27,6 +29,7 @@ import { useI18n } from "@/core/i18n/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type SettingsSection =
|
||||
| "account"
|
||||
| "appearance"
|
||||
| "memory"
|
||||
| "tools"
|
||||
@ -54,6 +57,11 @@ export function SettingsDialog(props: SettingsDialogProps) {
|
||||
|
||||
const sections = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: "account",
|
||||
label: t.settings.sections.account,
|
||||
icon: UserIcon,
|
||||
},
|
||||
{
|
||||
id: "appearance",
|
||||
label: t.settings.sections.appearance,
|
||||
@ -74,6 +82,7 @@ export function SettingsDialog(props: SettingsDialogProps) {
|
||||
{ id: "about", label: t.settings.sections.about, icon: InfoIcon },
|
||||
],
|
||||
[
|
||||
t.settings.sections.account,
|
||||
t.settings.sections.appearance,
|
||||
t.settings.sections.memory,
|
||||
t.settings.sections.tools,
|
||||
@ -124,6 +133,7 @@ export function SettingsDialog(props: SettingsDialogProps) {
|
||||
</nav>
|
||||
<ScrollArea className="h-full min-h-0 rounded-lg border">
|
||||
<div className="space-y-8 p-6">
|
||||
{activeSection === "account" && <AccountSettingsPage />}
|
||||
{activeSection === "appearance" && <AppearanceSettingsPage />}
|
||||
{activeSection === "memory" && <MemorySettingsPage />}
|
||||
{activeSection === "tools" && <ToolSettingsPage />}
|
||||
|
||||
@ -236,6 +236,7 @@ export const enUS: Translations = {
|
||||
reportIssue: "Report a issue",
|
||||
contactUs: "Contact us",
|
||||
about: "About DeerFlow",
|
||||
logout: "Log out",
|
||||
},
|
||||
|
||||
// Conversation
|
||||
@ -320,6 +321,7 @@ export const enUS: Translations = {
|
||||
title: "Settings",
|
||||
description: "Adjust how DeerFlow looks and behaves for you.",
|
||||
sections: {
|
||||
account: "Account",
|
||||
appearance: "Appearance",
|
||||
memory: "Memory",
|
||||
tools: "Tools",
|
||||
|
||||
@ -168,6 +168,7 @@ export interface Translations {
|
||||
reportIssue: string;
|
||||
contactUs: string;
|
||||
about: string;
|
||||
logout: string;
|
||||
};
|
||||
|
||||
// Conversation
|
||||
@ -250,6 +251,7 @@ export interface Translations {
|
||||
title: string;
|
||||
description: string;
|
||||
sections: {
|
||||
account: string;
|
||||
appearance: string;
|
||||
memory: string;
|
||||
tools: string;
|
||||
|
||||
@ -224,6 +224,7 @@ export const zhCN: Translations = {
|
||||
reportIssue: "报告问题",
|
||||
contactUs: "联系我们",
|
||||
about: "关于 DeerFlow",
|
||||
logout: "退出登录",
|
||||
},
|
||||
|
||||
// Conversation
|
||||
@ -305,6 +306,7 @@ export const zhCN: Translations = {
|
||||
title: "设置",
|
||||
description: "根据你的偏好调整 DeerFlow 的界面和行为。",
|
||||
sections: {
|
||||
account: "账号",
|
||||
appearance: "外观",
|
||||
memory: "记忆",
|
||||
tools: "工具",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user