From 459d9745b990c13de141ade2b151b3c88e72afc0 Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:57:32 +0800 Subject: [PATCH] feat(ui): route persistence, sidebar active fix, header spacing - Persist last visited path via Zustand persist, restore on login/root - Sidebar: exact match for active state (issue detail no longer highlights Issues) - Sidebar header: increase vertical padding - Inbox unread count: simplified to text-xs Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/web/app/(auth)/login/page.tsx | 4 ++- .../(dashboard)/_components/app-sidebar.tsx | 10 +++---- apps/web/app/(dashboard)/layout.tsx | 12 ++++++-- apps/web/app/page.tsx | 20 +++++++++++-- apps/web/features/navigation/index.ts | 1 + apps/web/features/navigation/store.ts | 29 +++++++++++++++++++ 6 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 apps/web/features/navigation/index.ts create mode 100644 apps/web/features/navigation/store.ts diff --git a/apps/web/app/(auth)/login/page.tsx b/apps/web/app/(auth)/login/page.tsx index 6a748e99..b1c2cee2 100644 --- a/apps/web/app/(auth)/login/page.tsx +++ b/apps/web/app/(auth)/login/page.tsx @@ -4,6 +4,7 @@ import { Suspense, useState } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { useAuthStore } from "@/features/auth"; import { useWorkspaceStore } from "@/features/workspace"; +import { useNavigationStore } from "@/features/navigation"; import { api } from "@/shared/api"; import { Card, @@ -40,7 +41,8 @@ function LoginPageContent() { await login(email, name || undefined); const wsList = await api.listWorkspaces(); await hydrateWorkspace(wsList); - router.push(searchParams.get("next") || "/issues"); + const fallback = useNavigationStore.getState().lastPath; + router.push(searchParams.get("next") || fallback); } catch (err) { setError("Login failed. Make sure the server is running."); setSubmitting(false); diff --git a/apps/web/app/(dashboard)/_components/app-sidebar.tsx b/apps/web/app/(dashboard)/_components/app-sidebar.tsx index 1b04cf31..5c531ef1 100644 --- a/apps/web/app/(dashboard)/_components/app-sidebar.tsx +++ b/apps/web/app/(dashboard)/_components/app-sidebar.tsx @@ -73,7 +73,7 @@ export function AppSidebar() { return ( {/* Workspace Switcher */} - +
@@ -181,9 +181,7 @@ export function AppSidebar() { {navItems.map((item) => { - const isActive = - pathname === item.href || - pathname.startsWith(item.href + "/"); + const isActive = pathname === item.href; return ( {item.label} {item.label === "Inbox" && unreadCount > 0 && ( - + {unreadCount > 99 ? "99+" : unreadCount} )} @@ -213,7 +211,7 @@ export function AppSidebar() { -
+
{user.name .split(" ") .map((w) => w[0]) diff --git a/apps/web/app/(dashboard)/layout.tsx b/apps/web/app/(dashboard)/layout.tsx index ca479e0c..c9034958 100644 --- a/apps/web/app/(dashboard)/layout.tsx +++ b/apps/web/app/(dashboard)/layout.tsx @@ -1,8 +1,9 @@ "use client"; import { useEffect } from "react"; -import { useRouter } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; import { MulticaIcon } from "@/components/multica-icon"; +import { useNavigationStore } from "@/features/navigation"; import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar"; import { useAuthStore } from "@/features/auth"; import { useWorkspaceStore } from "@/features/workspace"; @@ -14,6 +15,7 @@ export default function DashboardLayout({ children: React.ReactNode; }) { const router = useRouter(); + const pathname = usePathname(); const user = useAuthStore((s) => s.user); const isLoading = useAuthStore((s) => s.isLoading); const workspace = useWorkspaceStore((s) => s.workspace); @@ -24,6 +26,10 @@ export default function DashboardLayout({ } }, [user, isLoading, router]); + useEffect(() => { + useNavigationStore.getState().onPathChange(pathname); + }, [pathname]); + if (isLoading) { return (
@@ -35,9 +41,9 @@ export default function DashboardLayout({ if (!user || !workspace) return null; return ( - + - {children} + {children} ); } diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index d53fc1c5..179831dd 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,5 +1,21 @@ -import { redirect } from "next/navigation"; +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { useNavigationStore } from "@/features/navigation"; +import { MulticaIcon } from "@/components/multica-icon"; export default function Home() { - redirect("/issues"); + const router = useRouter(); + + useEffect(() => { + const lastPath = useNavigationStore.getState().lastPath; + router.replace(lastPath); + }, [router]); + + return ( +
+ +
+ ); } diff --git a/apps/web/features/navigation/index.ts b/apps/web/features/navigation/index.ts new file mode 100644 index 00000000..166a42f8 --- /dev/null +++ b/apps/web/features/navigation/index.ts @@ -0,0 +1 @@ +export { useNavigationStore } from "./store"; diff --git a/apps/web/features/navigation/store.ts b/apps/web/features/navigation/store.ts new file mode 100644 index 00000000..2dad77db --- /dev/null +++ b/apps/web/features/navigation/store.ts @@ -0,0 +1,29 @@ +"use client"; + +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +const EXCLUDED_PREFIXES = ["/login", "/pair/"]; + +interface NavigationState { + lastPath: string; + onPathChange: (path: string) => void; +} + +export const useNavigationStore = create()( + persist( + (set) => ({ + lastPath: "/issues", + + onPathChange: (path: string) => { + if (!EXCLUDED_PREFIXES.some((prefix) => path.startsWith(prefix))) { + set({ lastPath: path }); + } + }, + }), + { + name: "multica_navigation", + partialize: (state) => ({ lastPath: state.lastPath }), + } + ) +);