diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index a15f40b..7c6be6c 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -25,6 +25,7 @@ import { isLinkEnterprise, isLinkVisible, } from '@/lib/layout'; +import { findLastVisitedLinkInLayout, setLastVisitedSection } from '@/lib/lastVisited'; import type { Layout, LayoutItem, LayoutSubItem } from '@/types/schema'; function LucideIcon({ name, className }: { name: string; className?: string }) { @@ -185,6 +186,7 @@ function SidebarSubItem({ item, depth, sectionName, currentPath, navigate, editi if (isLocked) { onUpsell(); } else { + setLastVisitedSection(sectionName, item.viewName); navigate(path); } }} @@ -270,6 +272,7 @@ function SidebarTopItem({ item, sectionName, currentPath, navigate, edition, onU if (isLocked) { onUpsell(); } else { + setLastVisitedSection(sectionName, viewName); navigate(path); } }} @@ -365,7 +368,9 @@ export function Sidebar() { const handleSectionClick = (target: Layout) => { setActiveSection(target.name); const canGet = (prefix: string) => permissions.includes(`${prefix}Get`); + const last = findLastVisitedLinkInLayout(schema, target, edition, canGet, hasPermission); const first = + last ?? findFirstAccessibleLinkInLayout(schema, target, edition, canGet, hasPermission) ?? findFirstVisibleLinkInLayout(schema, target, edition, canGet, hasPermission); if (first) navigate(`/${target.name}/${first}`); diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index 8539617..d04ce8a 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -24,6 +24,7 @@ import { ModeToggle } from '@/components/common/ModeToggle'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; import { findFirstAccessibleLinkInLayout, findFirstVisibleLinkInLayout, visibleLayouts } from '@/lib/layout'; +import { findLastVisitedLinkInLayout } from '@/lib/lastVisited'; import { useUIStore } from '@/stores/uiStore'; import { useAuthStore } from '@/stores/authStore'; import { buildEndSessionUrl, getPostLogoutRedirectUri } from '@/services/auth/oauth'; @@ -152,7 +153,9 @@ export function TopBar() { onClick={() => { setActiveSection(layout.name); const canGet = (prefix: string) => hasObjectPermission(prefix, 'Get'); + const lastLink = findLastVisitedLinkInLayout(schema, layout, edition, canGet, hasPermission); const firstLink = + lastLink ?? findFirstAccessibleLinkInLayout(schema, layout, edition, canGet, hasPermission) ?? findFirstVisibleLinkInLayout(schema, layout, edition, canGet, hasPermission); if (firstLink) { diff --git a/src/lib/lastVisited.ts b/src/lib/lastVisited.ts new file mode 100644 index 0000000..4b01841 --- /dev/null +++ b/src/lib/lastVisited.ts @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +import { isLinkAccessible } from './layout'; +import type { Layout, Schema } from '@/types/schema'; + +type CanGet = (prefix: string) => boolean; +type HasPermission = (permission: string) => boolean; + +const STORAGE_KEY = 'stalwart-last-visited'; + +// Stores the last visited view name for a given top-level section in localStorage. +export function setLastVisitedSection(section: string, viewName: string): void { + try { + const raw = localStorage.getItem(STORAGE_KEY); + const data = raw ? (JSON.parse(raw) as Record) : {}; + data[section] = viewName; + localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); + } catch { + // Ignore storage errors (e.g. private mode). + } +} + +// Retrieves the last visited view name for a section, or null if none is stored. +export function getLastVisitedSection(section: string): string | null { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return null; + const data = JSON.parse(raw) as Record; + return data[section] ?? null; + } catch { + return null; + } +} + +// Returns the stored view name only if it is still accessible in the target layout. +export function findLastVisitedLinkInLayout( + schema: Schema, + layout: Layout, + edition: string, + canGet: CanGet, + hasPermission: HasPermission, +): string | null { + const last = getLastVisitedSection(layout.name); + if (!last) return null; + if (isLinkAccessible(schema, last, edition, canGet, hasPermission)) return last; + return null; +} \ No newline at end of file