Files
webmail/components/admin/admin-link.tsx
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

37 lines
1.3 KiB
TypeScript

'use client';
import { forwardRef, type AnchorHTMLAttributes, type MouseEvent } from 'react';
import { withBasePath } from '@/lib/browser-navigation';
import { navigateAdmin } from '@/hooks/use-admin-navigation';
interface AdminLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
/** App-relative admin path, e.g. `/admin/plugins/jitsi-meet`. */
href: string;
replace?: boolean;
}
/**
* A link between admin pages. A real anchor, so it can be opened in a new
* tab and the unsaved-changes guard sees it like any other link; a plain
* click moves within the panel through history.pushState instead of a
* route transition.
*/
export const AdminLink = forwardRef<HTMLAnchorElement, AdminLinkProps>(function AdminLink(
{ href, replace, onClick, target, children, ...rest },
ref,
) {
const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {
onClick?.(event);
if (event.defaultPrevented) return;
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
if (target && target !== '_self') return;
event.preventDefault();
navigateAdmin(href, { replace });
};
return (
<a ref={ref} href={withBasePath(href)} target={target} onClick={handleClick} {...rest}>
{children}
</a>
);
});