feat: update settings navigation to include section parameters for improved user experience

This commit is contained in:
Anish Sarkar 2026-02-21 22:55:54 +05:30
parent c7736aa62c
commit ccf8c063da
9 changed files with 47 additions and 31 deletions

View file

@ -259,7 +259,7 @@ export default function OnboardPage() {
You can add more configurations and customize settings anytime in{" "} You can add more configurations and customize settings anytime in{" "}
<button <button
type="button" type="button"
onClick={() => router.push(`/dashboard/${searchSpaceId}/settings`)} onClick={() => router.push(`/dashboard/${searchSpaceId}/settings?section=general`)}
className="text-violet-500 hover:underline" className="text-violet-500 hover:underline"
> >
Settings Settings

View file

@ -16,7 +16,7 @@ import {
X, X,
} from "lucide-react"; } from "lucide-react";
import { AnimatePresence, motion } from "motion/react"; import { AnimatePresence, motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { PublicChatSnapshotsManager } from "@/components/public-chat-snapshots/public-chat-snapshots-manager"; import { PublicChatSnapshotsManager } from "@/components/public-chat-snapshots/public-chat-snapshots-manager";
@ -248,7 +248,7 @@ function SettingsContent({
{/* Section Header */} {/* Section Header */}
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<motion.div <motion.div
key={activeSection + "-header"} key={`${activeSection}-header`}
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }} exit={{ opacity: 0, y: -10 }}
@ -317,14 +317,26 @@ function SettingsContent({
); );
} }
const VALID_SECTIONS = new Set(settingsNavItems.map((item) => item.id));
const DEFAULT_SECTION = "general";
export default function SettingsPage() { export default function SettingsPage() {
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const searchParams = useSearchParams();
const searchSpaceId = Number(params.search_space_id); const searchSpaceId = Number(params.search_space_id);
const [activeSection, setActiveSection] = useState("general");
const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false);
// Track settings section view const sectionParam = searchParams.get("section");
const activeSection = sectionParam && VALID_SECTIONS.has(sectionParam) ? sectionParam : DEFAULT_SECTION;
const handleSectionChange = useCallback(
(section: string) => {
router.replace(`/dashboard/${searchSpaceId}/settings?section=${section}`, { scroll: false });
},
[router, searchSpaceId]
);
useEffect(() => { useEffect(() => {
trackSettingsViewed(searchSpaceId, activeSection); trackSettingsViewed(searchSpaceId, activeSection);
}, [searchSpaceId, activeSection]); }, [searchSpaceId, activeSection]);
@ -344,7 +356,7 @@ export default function SettingsPage() {
<div className="flex h-full w-full overflow-hidden bg-background md:rounded-xl md:border md:shadow-sm"> <div className="flex h-full w-full overflow-hidden bg-background md:rounded-xl md:border md:shadow-sm">
<SettingsSidebar <SettingsSidebar
activeSection={activeSection} activeSection={activeSection}
onSectionChange={setActiveSection} onSectionChange={handleSectionChange}
onBackToApp={handleBackToApp} onBackToApp={handleBackToApp}
isOpen={isSidebarOpen} isOpen={isSidebarOpen}
onClose={() => setIsSidebarOpen(false)} onClose={() => setIsSidebarOpen(false)}

View file

@ -731,7 +731,7 @@ function CreateInviteDialog({
</code> </code>
<Button variant="outline" size="sm" onClick={copyLink} className="shrink-0"> <Button variant="outline" size="sm" onClick={copyLink} className="shrink-0">
{copiedLink ? ( {copiedLink ? (
<Check className="h-4 w-4 text-emerald-500" /> <Check className="h-4 w-4" />
) : ( ) : (
<Copy className="h-4 w-4" /> <Copy className="h-4 w-4" />
)} )}
@ -902,7 +902,7 @@ function AllInvitesDialog({
{invites.map((invite) => ( {invites.map((invite) => (
<div <div
key={invite.id} key={invite.id}
className="rounded-lg border border-border/40 p-3 space-y-2.5 transition-colors hover:bg-muted/30" className="rounded-lg border border-border/40 p-3 space-y-2.5"
> >
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
@ -929,7 +929,7 @@ function AllInvitesDialog({
</div> </div>
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="ghost" size="icon" className="h-7 w-7 shrink-0 text-destructive hover:text-destructive"> <Button variant="ghost" size="icon" className="h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive">
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" />
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
@ -954,14 +954,16 @@ function AllInvitesDialog({
</AlertDialog> </AlertDialog>
</div> </div>
<div className="flex items-center gap-2 rounded-md bg-muted p-2"> <div className="flex items-center gap-2 rounded-md bg-muted p-2">
<code className="flex-1 min-w-0 text-sm select-all overflow-x-auto whitespace-nowrap"> <div className="flex-1 min-w-0 overflow-x-auto scrollbar-hide">
{typeof window !== "undefined" <code className="text-sm select-all whitespace-nowrap">
? `${window.location.origin}/invite/${invite.invite_code}` {typeof window !== "undefined"
: `/invite/${invite.invite_code}`} ? `${window.location.origin}/invite/${invite.invite_code}`
</code> : `/invite/${invite.invite_code}`}
<Button variant="outline" size="sm" className="shrink-0" onClick={() => copyLink(invite)}> </code>
</div>
<Button variant="ghost" size="sm" className="shrink-0" onClick={() => copyLink(invite)}>
{copiedId === invite.id ? ( {copiedId === invite.id ? (
<Check className="h-4 w-4 text-emerald-500" /> <Check className="h-4 w-4" />
) : ( ) : (
<Copy className="h-4 w-4" /> <Copy className="h-4 w-4" />
)} )}

View file

@ -374,7 +374,7 @@ export const ConnectorIndicator: FC<{ hideTrigger?: boolean }> = ({ hideTrigger
: "You need to configure a Document Summary LLM before adding connectors. This LLM is used to process and summarize documents from your connected sources."} : "You need to configure a Document Summary LLM before adding connectors. This LLM is used to process and summarize documents from your connected sources."}
</p> </p>
<Button asChild size="sm" variant="outline"> <Button asChild size="sm" variant="outline">
<Link href={`/dashboard/${searchSpaceId}/settings`}> <Link href={`/dashboard/${searchSpaceId}/settings?section=models`}>
<Settings className="mr-2 h-4 w-4" /> <Settings className="mr-2 h-4 w-4" />
Go to Settings Go to Settings
</Link> </Link>

View file

@ -156,7 +156,7 @@ const DocumentUploadPopupContent: FC<{
: "You need to configure a Document Summary LLM before uploading files. This LLM is used to process and summarize your uploaded documents."} : "You need to configure a Document Summary LLM before uploading files. This LLM is used to process and summarize your uploaded documents."}
</p> </p>
<Button asChild size="sm" variant="outline"> <Button asChild size="sm" variant="outline">
<Link href={`/dashboard/${searchSpaceId}/settings`}> <Link href={`/dashboard/${searchSpaceId}/settings?section=models`}>
<Settings className="mr-2 h-4 w-4" /> <Settings className="mr-2 h-4 w-4" />
Go to Settings Go to Settings
</Link> </Link>

View file

@ -334,7 +334,7 @@ export function LayoutDataProvider({
const handleSearchSpaceSettings = useCallback( const handleSearchSpaceSettings = useCallback(
(space: SearchSpace) => { (space: SearchSpace) => {
router.push(`/dashboard/${space.id}/settings`); router.push(`/dashboard/${space.id}/settings?section=general`);
}, },
[router] [router]
); );
@ -478,7 +478,7 @@ export function LayoutDataProvider({
); );
const handleSettings = useCallback(() => { const handleSettings = useCallback(() => {
router.push(`/dashboard/${searchSpaceId}/settings`); router.push(`/dashboard/${searchSpaceId}/settings?section=general`);
}, [router, searchSpaceId]); }, [router, searchSpaceId]);
const handleManageMembers = useCallback(() => { const handleManageMembers = useCallback(() => {

View file

@ -4,7 +4,7 @@ import {
ArchiveIcon, ArchiveIcon,
MessageSquare, MessageSquare,
MoreHorizontal, MoreHorizontal,
PencilIcon, PenLine,
RotateCcwIcon, RotateCcwIcon,
Trash2, Trash2,
} from "lucide-react"; } from "lucide-react";
@ -74,7 +74,7 @@ export function ChatListItem({
onRename(); onRename();
}} }}
> >
<PencilIcon className="mr-2 h-4 w-4" /> <PenLine className="mr-2 h-4 w-4" />
<span>{t("rename") || "Rename"}</span> <span>{t("rename") || "Rename"}</span>
</DropdownMenuItem> </DropdownMenuItem>
)} )}

View file

@ -144,7 +144,7 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
onClick={() => router.push(`/dashboard/${params.search_space_id}/settings`)} onClick={() => router.push(`/dashboard/${params.search_space_id}/settings?section=public-links`)}
className="flex items-center justify-center h-8 w-8 rounded-md bg-muted/50 hover:bg-muted transition-colors" className="flex items-center justify-center h-8 w-8 rounded-md bg-muted/50 hover:bg-muted transition-colors"
> >
<Earth className="h-4 w-4 text-muted-foreground" /> <Earth className="h-4 w-4 text-muted-foreground" />

View file

@ -35,12 +35,12 @@ export function PublicChatSnapshotRow({
memberMap, memberMap,
}: PublicChatSnapshotRowProps) { }: PublicChatSnapshotRowProps) {
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const copyTimeoutRef = useRef<ReturnType<typeof setTimeout>>(); const copyTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
const handleCopyClick = useCallback(() => { const handleCopyClick = useCallback(() => {
onCopy(snapshot); onCopy(snapshot);
setCopied(true); setCopied(true);
clearTimeout(copyTimeoutRef.current); if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current);
copyTimeoutRef.current = setTimeout(() => setCopied(false), 2000); copyTimeoutRef.current = setTimeout(() => setCopied(false), 2000);
}, [onCopy, snapshot]); }, [onCopy, snapshot]);
@ -117,12 +117,14 @@ export function PublicChatSnapshotRow({
{/* Public URL selectable fallback for manual copy */} {/* Public URL selectable fallback for manual copy */}
<div className="flex items-center gap-2 rounded-md border border-border/60 bg-muted/30 px-2.5 py-1.5"> <div className="flex items-center gap-2 rounded-md border border-border/60 bg-muted/30 px-2.5 py-1.5">
<p <div className="min-w-0 flex-1 overflow-x-auto scrollbar-hide">
className="min-w-0 flex-1 text-[10px] font-mono text-muted-foreground break-all select-all cursor-text" <p
title={snapshot.public_url} className="text-[10px] font-mono text-muted-foreground whitespace-nowrap select-all cursor-text"
> title={snapshot.public_url}
{snapshot.public_url} >
</p> {snapshot.public_url}
</p>
</div>
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>