Files
webmail/hooks/use-admin-navigation.ts
T
Linus Rath 10b4d2a9db feat: rework the admin panel around a settings-style sidebar and one URL per page
The old panel kept its active tab in a store, so every page sat at
/admin, the sidebar duplicated the webmail rail by hand, and each page
was a stack of boxed cards with their own save buttons. Plugin settings
were only reachable through a gear icon in a row of five.

- one catch-all route for the whole panel; pages switch through
  history.pushState, so `/admin/plugins/<id>`, the back button, bookmarks
  and connector links all work without a route transition
- sidebar laid out like the user settings: back to mail, search over
  pages and their sections, two group headings, change password and
  sign out at the bottom; the phone shows a list, then the page
- pages use SettingsSection rows instead of nested cards, with one page
  header, one flash message and one save bar at the bottom (Discard,
  Save, Ctrl+S); the unsaved-changes prompt still guards leaving
- plugins and themes are lists of rows with a toggle each; a row opens
  the extension's own page with its settings, force-on, permissions,
  kept versions and removal
- overview shows stat tiles, server facts and recent activity
- old `/admin?tab=x` links redirect; the tab store is gone and the
  connector registry resolves admin links to the page URL
- `NEXT_TURBOPACK_ROOT` overrides the Turbopack root so `next dev` runs
  from a worktree with a junctioned node_modules
2026-10-20 15:16:05 +02:00

33 lines
1.4 KiB
TypeScript

'use client';
import { useMemo } from 'react';
import { usePathname } from 'next/navigation';
import { withBasePath } from '@/lib/browser-navigation';
import { parseAdminPath, type AdminLocation } from '@/lib/admin/admin-nav';
/**
* Moves the admin panel to another of its pages without a route transition.
*
* The admin tree is one Next route, so a page switch is a history entry and
* nothing else: Next patches `history.pushState`, copies its own bookkeeping
* into the entry when the state passed is null, and updates `usePathname()`.
* Passing the current `history.state` instead would skip that sync, so the
* state here is always null. The back button lands on the previous entry
* the same way (Next's popstate handler restores the URL from the entry).
*/
export function navigateAdmin(path: string, options: { replace?: boolean } = {}): void {
if (typeof window === 'undefined') return;
const url = withBasePath(path);
if (`${window.location.pathname}${window.location.search}` === url && !options.replace) return;
if (options.replace) window.history.replaceState(null, '', url);
else window.history.pushState(null, '', url);
}
const INDEX: AdminLocation = { page: null, detail: null };
/** The page (and detail) the address bar names right now. */
export function useAdminLocation(): AdminLocation {
const pathname = usePathname();
return useMemo(() => parseAdminPath(pathname ?? '') ?? INDEX, [pathname]);
}