Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions frontend/src/app/workspace/settings/about/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { AboutSettingsPage } from "@/components/workspace/settings/about-settings-page";

export default function SettingsAboutRoute() {
return <AboutSettingsPage />;
}
5 changes: 5 additions & 0 deletions frontend/src/app/workspace/settings/account/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { AccountSettingsPage } from "@/components/workspace/settings/account-settings-page";

export default function SettingsAccountRoute() {
return <AccountSettingsPage />;
}
5 changes: 5 additions & 0 deletions frontend/src/app/workspace/settings/general/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { GeneralSettingsPage } from "@/components/workspace/settings/general-settings-page";

export default function SettingsGeneralRoute() {
return <GeneralSettingsPage />;
}
7 changes: 7 additions & 0 deletions frontend/src/app/workspace/settings/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { SettingsShell } from "@/components/workspace/settings/settings-shell";

export default function SettingsLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return <SettingsShell>{children}</SettingsShell>;
}
5 changes: 5 additions & 0 deletions frontend/src/app/workspace/settings/memory/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { MemorySettingsPage } from "@/components/workspace/settings/memory-settings-page";

export default function SettingsMemoryRoute() {
return <MemorySettingsPage />;
}
5 changes: 5 additions & 0 deletions frontend/src/app/workspace/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { redirect } from "next/navigation";

export default function SettingsIndexPage() {
redirect("/workspace/settings/general");
}
14 changes: 14 additions & 0 deletions frontend/src/app/workspace/settings/skills/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use client";

import { useRouter } from "next/navigation";
import { useCallback } from "react";

import { SkillSettingsPage } from "@/components/workspace/settings/skill-settings-page";

export default function SettingsSkillsRoute() {
const router = useRouter();
const handleClose = useCallback(() => {
router.push("/workspace");
}, [router]);
return <SkillSettingsPage onClose={handleClose} />;
}
5 changes: 5 additions & 0 deletions frontend/src/app/workspace/settings/tools/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ToolSettingsPage } from "@/components/workspace/settings/tool-settings-page";

export default function SettingsToolsRoute() {
return <ToolSettingsPage />;
}
8 changes: 2 additions & 6 deletions frontend/src/components/workspace/command-palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,11 @@ import {
import { useI18n } from "@/core/i18n/hooks";
import { useGlobalShortcuts } from "@/hooks/use-global-shortcuts";

import { SettingsDialog } from "./settings";

export function CommandPalette() {
const { t } = useI18n();
const router = useRouter();
const [open, setOpen] = useState(false);
const [shortcutsOpen, setShortcutsOpen] = useState(false);
const [settingsOpen, setSettingsOpen] = useState(false);
const [isMac, setIsMac] = useState(false);

const handleNewChat = useCallback(() => {
Expand All @@ -44,8 +41,8 @@ export function CommandPalette() {

const handleOpenSettings = useCallback(() => {
setOpen(false);
setSettingsOpen(true);
}, []);
router.push("/workspace/settings/general");
}, [router]);

const handleShowShortcuts = useCallback(() => {
setOpen(false);
Expand All @@ -72,7 +69,6 @@ export function CommandPalette() {

return (
<>
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder={t.shortcuts.searchActions} />
<CommandList>
Expand Down
167 changes: 110 additions & 57 deletions frontend/src/components/workspace/settings/account-settings-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useAuth } from "@/core/auth/AuthProvider";
import { parseAuthError } from "@/core/auth/types";
import { useI18n } from "@/core/i18n/hooks";

import { SettingsSection } from "./settings-section";
import { SettingsCard, SettingsRow, SettingsSection } from "./settings-section";

export function AccountSettingsPage() {
const { user, logout } = useAuth();
Expand Down Expand Up @@ -68,73 +68,126 @@ export function AccountSettingsPage() {
}
};

const initial = (
user?.email?.[0] ??
user?.system_role?.[0] ??
"?"
).toUpperCase();

return (
<div className="space-y-8">
<div className="space-y-10">
<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>
<SettingsCard>
<div className="flex items-center gap-4 px-5 py-4">
<div className="bg-primary text-primary-foreground flex size-12 shrink-0 items-center justify-center rounded-full text-base font-semibold">
{initial}
</div>
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-semibold">
{user?.email ?? "—"}
</div>
<div className="text-muted-foreground mt-0.5 text-xs capitalize">
{user?.system_role ?? "—"}
</div>
</div>
</div>
</div>
</SettingsCard>
</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>
<SettingsCard>
<form onSubmit={handleChangePassword}>
<SettingsRow
size="compact"
label={t.settings.account.currentPassword}
control={
<Input
type="password"
placeholder="••••••••"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required
className="w-[260px]"
/>
}
/>
<SettingsRow
size="compact"
label={t.settings.account.newPassword}
control={
<Input
type="password"
placeholder="••••••••"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
minLength={8}
className="w-[260px]"
/>
}
/>
<SettingsRow
size="compact"
label={t.settings.account.confirmNewPassword}
control={
<Input
type="password"
placeholder="••••••••"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
minLength={8}
className="w-[260px]"
/>
}
/>
{(error || message) && (
<div className="px-5 py-2">
{error && <p className="text-sm text-red-500">{error}</p>}
{message && (
<p className="text-sm text-emerald-600 dark:text-emerald-400">
{message}
</p>
)}
</div>
)}
<div className="flex justify-end px-5 py-3">
<Button
type="submit"
variant="default"
size="sm"
disabled={loading}
>
{loading
? t.settings.account.updating
: t.settings.account.updatePassword}
</Button>
</div>
</form>
</SettingsCard>
</SettingsSection>

<SettingsSection title="" description="">
<Button
variant="destructive"
size="sm"
onClick={logout}
className="gap-2"
>
<LogOutIcon className="size-4" />
{t.settings.account.signOut}
</Button>
<SettingsSection title={t.settings.account.sessionTitle}>
<SettingsCard>
<SettingsRow
label={t.settings.account.signOut}
description={t.settings.account.signOutDescription}
control={
<Button
variant="destructive"
size="sm"
onClick={logout}
className="gap-2"
>
<LogOutIcon className="size-4" />
{t.settings.account.signOut}
</Button>
}
/>
</SettingsCard>
</SettingsSection>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { enUS, isLocale, zhCN, type Locale } from "@/core/i18n";
import { useI18n } from "@/core/i18n/hooks";
import { cn } from "@/lib/utils";

import { SettingsSection } from "./settings-section";
import { SettingsCard, SettingsRow, SettingsSection } from "./settings-section";

const languageOptions: { value: Locale; label: string }[] = [
{ value: "en-US", label: enUS.locale.localName },
Expand Down Expand Up @@ -60,7 +59,7 @@ export function AppearanceSettingsPage() {
);

return (
<div className="space-y-8">
<div className="space-y-10">
<SettingsSection
title={t.settings.appearance.themeTitle}
description={t.settings.appearance.themeDescription}
Expand All @@ -81,31 +80,34 @@ export function AppearanceSettingsPage() {
</div>
</SettingsSection>

<Separator />

<SettingsSection
title={t.settings.appearance.languageTitle}
description={t.settings.appearance.languageDescription}
>
<Select
value={locale}
onValueChange={(value) => {
if (isLocale(value)) {
changeLocale(value);
<SettingsSection title={t.settings.appearance.languageTitle}>
<SettingsCard>
<SettingsRow
label={t.settings.appearance.languageTitle}
description={t.settings.appearance.languageDescription}
control={
<Select
value={locale}
onValueChange={(value) => {
if (isLocale(value)) {
changeLocale(value);
}
}}
>
<SelectTrigger className="w-[200px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
{languageOptions.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectContent>
</Select>
}
}}
>
<SelectTrigger className="w-[220px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
{languageOptions.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectContent>
</Select>
/>
</SettingsCard>
</SettingsSection>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";

import { AppearanceSettingsPage } from "./appearance-settings-page";
import { NotificationSettingsPage } from "./notification-settings-page";

export function GeneralSettingsPage() {
return (
<div className="space-y-10">
<AppearanceSettingsPage />
<NotificationSettingsPage />
</div>
);
}
2 changes: 1 addition & 1 deletion frontend/src/components/workspace/settings/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { SettingsDialog } from "./settings-dialog";
export { SettingsShell } from "./settings-shell";
Loading
Loading