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) <noreply@anthropic.com>
This commit is contained in:
parent
8983a9fefa
commit
459d9745b9
6 changed files with 64 additions and 12 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export function AppSidebar() {
|
|||
return (
|
||||
<Sidebar variant="inset">
|
||||
{/* Workspace Switcher */}
|
||||
<SidebarHeader>
|
||||
<SidebarHeader className="py-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<SidebarMenu className="min-w-0 flex-1">
|
||||
<SidebarMenuItem>
|
||||
|
|
@ -181,9 +181,7 @@ export function AppSidebar() {
|
|||
<SidebarGroupContent>
|
||||
<SidebarMenu className="gap-0.5">
|
||||
{navItems.map((item) => {
|
||||
const isActive =
|
||||
pathname === item.href ||
|
||||
pathname.startsWith(item.href + "/");
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<SidebarMenuItem key={item.href}>
|
||||
<SidebarMenuButton
|
||||
|
|
@ -194,7 +192,7 @@ export function AppSidebar() {
|
|||
<item.icon />
|
||||
<span>{item.label}</span>
|
||||
{item.label === "Inbox" && unreadCount > 0 && (
|
||||
<span className="ml-auto rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-medium text-primary-foreground">
|
||||
<span className="ml-auto text-xs">
|
||||
{unreadCount > 99 ? "99+" : unreadCount}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -213,7 +211,7 @@ export function AppSidebar() {
|
|||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="sm">
|
||||
<div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted text-[9px] font-medium">
|
||||
<div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted text-xs leading-none font-medium">
|
||||
{user.name
|
||||
.split(" ")
|
||||
.map((w) => w[0])
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex h-screen items-center justify-center">
|
||||
|
|
@ -35,9 +41,9 @@ export default function DashboardLayout({
|
|||
if (!user || !workspace) return null;
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<SidebarProvider className="max-h-svh">
|
||||
<AppSidebar />
|
||||
<SidebarInset>{children}</SidebarInset>
|
||||
<SidebarInset className="overflow-hidden">{children}</SidebarInset>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex h-screen items-center justify-center">
|
||||
<MulticaIcon className="size-6" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
1
apps/web/features/navigation/index.ts
Normal file
1
apps/web/features/navigation/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { useNavigationStore } from "./store";
|
||||
29
apps/web/features/navigation/store.ts
Normal file
29
apps/web/features/navigation/store.ts
Normal file
|
|
@ -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<NavigationState>()(
|
||||
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 }),
|
||||
}
|
||||
)
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue