Skip to content

Commit 47b0f98

Browse files
committed
collapse redundant settings routes into one
1 parent 25656b3 commit 47b0f98

3 files changed

Lines changed: 149 additions & 155 deletions

File tree

apps/web/src/routeTree.gen.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ const SettingsRouteRoute = SettingsRouteRouteImport.update({
3737
id: '/settings',
3838
path: '/settings',
3939
getParentRoute: () => rootRouteImport,
40-
} as any).lazy(() =>
41-
import('./routes/settings/route.lazy').then((d) => d.Route),
42-
)
40+
} as any)
4341
const ChatRouteRoute = ChatRouteRouteImport.update({
4442
id: '/chat',
4543
path: '/chat',

apps/web/src/routes/settings/route.lazy.tsx

Lines changed: 0 additions & 151 deletions
This file was deleted.

apps/web/src/routes/settings/route.tsx

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1-
import { createFileRoute } from "@tanstack/react-router";
1+
import {
2+
createFileRoute,
3+
Link,
4+
Outlet,
5+
useNavigate,
6+
useRouterState,
7+
} from "@tanstack/react-router";
8+
import { useEffect, useMemo, useState } from "react";
9+
import { AppShellSkeleton } from "@/web/components/skeletons/app-skeleton";
10+
import {
11+
Sheet,
12+
SheetContent,
13+
SheetDescription,
14+
SheetHeader,
15+
SheetTitle,
16+
} from "@/web/components/ui/sheet";
17+
import { useIsMobile } from "@/web/hooks/use-mobile";
18+
import { useUserSettings } from "@/web/hooks/use-user-settings";
19+
import { SETTINGS_NAV_ITEMS } from "@/web/lib/constants";
220
import { requireAuthenticated } from "@/web/lib/route-guards";
21+
import { cn } from "@/web/utils/cn";
322
import { SettingsError } from "./-components/settings-error";
423

524
export const Route = createFileRoute("/settings")({
@@ -10,4 +29,132 @@ export const Route = createFileRoute("/settings")({
1029
});
1130
},
1231
errorComponent: SettingsError,
32+
component: SettingsLayout,
33+
pendingComponent: AppShellSkeleton,
1334
});
35+
36+
function SettingsLayout() {
37+
const navigate = useNavigate();
38+
const location = useRouterState({ select: (state) => state.location });
39+
const isMobile = useIsMobile();
40+
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
41+
42+
const activePath = location.pathname ?? "";
43+
44+
// Use shared settings hook
45+
const settingsQuery = useUserSettings();
46+
47+
const navigationItems = useMemo(() => {
48+
return SETTINGS_NAV_ITEMS.map((item) => {
49+
const isActive =
50+
activePath === item.to || activePath.startsWith(`${item.to}/`);
51+
return { ...item, isActive };
52+
});
53+
}, [activePath]);
54+
55+
useEffect(() => {
56+
const pathname = location.pathname ?? "";
57+
if (pathname === "/settings" || pathname === "/settings/") {
58+
void navigate({ to: "/settings/profile", replace: true });
59+
}
60+
}, [location.pathname, navigate]);
61+
62+
useEffect(() => {
63+
const handler = () => setMobileSidebarOpen(true);
64+
window.addEventListener(
65+
"better-chat:open-settings",
66+
handler as EventListener,
67+
);
68+
return () =>
69+
window.removeEventListener(
70+
"better-chat:open-settings",
71+
handler as EventListener,
72+
);
73+
}, []);
74+
75+
useEffect(() => {
76+
if (!isMobile) {
77+
setMobileSidebarOpen(false);
78+
}
79+
}, [isMobile]);
80+
81+
const renderNavigation = (onNavigate?: () => void) => (
82+
<div className="space-y-2">
83+
{navigationItems.map((item) => (
84+
<Link
85+
key={item.to}
86+
to={item.to}
87+
className={cn(
88+
"block border px-3 py-3 transition",
89+
item.isActive
90+
? "border-primary bg-primary/10 text-primary"
91+
: "border-transparent bg-muted/40 text-foreground hover:border-muted hover:bg-muted",
92+
)}
93+
preload="intent"
94+
onClick={() => onNavigate?.()}
95+
>
96+
<div className="font-semibold text-sm">{item.label}</div>
97+
<p className="text-muted-foreground text-xs">{item.description}</p>
98+
</Link>
99+
))}
100+
</div>
101+
);
102+
103+
return (
104+
<div className="max-w-[100vw] overflow-x-hidden px-2 pt-20 sm:px-4">
105+
{isMobile && (
106+
<Sheet open={mobileSidebarOpen} onOpenChange={setMobileSidebarOpen}>
107+
<SheetContent
108+
side="left"
109+
className="w-[min(90vw,18rem)] border-r-0 p-0 sm:max-w-xs"
110+
>
111+
<SheetHeader className="sr-only">
112+
<SheetTitle>Settings</SheetTitle>
113+
<SheetDescription>
114+
Navigate between settings sections.
115+
</SheetDescription>
116+
</SheetHeader>
117+
<div className="flex h-full flex-col gap-4 overflow-hidden bg-card p-4">
118+
<div className="space-y-3">
119+
<h2 className="font-semibold text-muted-foreground text-sm uppercase tracking-wide">
120+
Settings
121+
</h2>
122+
</div>
123+
<div className="-mx-1 flex-1 overflow-y-auto px-1">
124+
{renderNavigation(() => setMobileSidebarOpen(false))}
125+
</div>
126+
</div>
127+
</SheetContent>
128+
</Sheet>
129+
)}
130+
<div
131+
className={cn(
132+
"mx-auto flex min-h-[calc(100svh-5rem-1.5rem)] w-full min-w-0 gap-2 px-1 sm:gap-4 sm:px-0 md:min-h-[calc(100svh-5rem-0.5rem)]",
133+
settingsQuery.data?.chatWidth === "comfortable"
134+
? "max-w-7xl"
135+
: "max-w-5xl",
136+
)}
137+
>
138+
<aside className="relative hidden w-64 shrink-0 md:block">
139+
<div className="sticky top-20 flex h-[calc(100svh-5rem-1.5rem)] flex-col overflow-hidden border bg-card p-3 shadow-sm sm:p-4 md:h-[calc(100svh-5rem-0.5rem)]">
140+
<div className="mb-4">
141+
<h2 className="font-semibold text-muted-foreground text-sm uppercase tracking-wide">
142+
Settings
143+
</h2>
144+
</div>
145+
<div className="-mx-1 flex-1 overflow-y-auto px-1">
146+
{renderNavigation()}
147+
</div>
148+
</div>
149+
</aside>
150+
<section className="min-w-0 flex-1 basis-0">
151+
<div className="sticky top-20 flex h-[calc(100svh-5rem-1.5rem)] max-w-[100vw] flex-col overflow-hidden border bg-card shadow-sm md:h-[calc(100svh-5rem-0.5rem)]">
152+
<div className="flex-1 overflow-y-auto px-4 py-6 sm:px-6">
153+
<Outlet />
154+
</div>
155+
</div>
156+
</section>
157+
</div>
158+
</div>
159+
);
160+
}

0 commit comments

Comments
 (0)