mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
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
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import type { ReactNode } from 'react';
|
|
import { ArrowLeft } from '@/components/icons';
|
|
import { cn } from '@/lib/utils';
|
|
import { AdminLink } from './admin-link';
|
|
|
|
interface AdminPageHeaderProps {
|
|
title: ReactNode;
|
|
description?: ReactNode;
|
|
/** Buttons on the end side of the header. */
|
|
actions?: ReactNode;
|
|
/** A link back to the parent page, for detail pages. */
|
|
back?: { href: string; label: string };
|
|
/** Something between the back link and the title, such as an icon. */
|
|
leading?: ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* The header of an admin page. The mobile shell already shows the page name
|
|
* in its top bar, so the title itself is desktop-only there; the
|
|
* description and the actions stay.
|
|
*/
|
|
export function AdminPageHeader({ title, description, actions, back, leading, className }: AdminPageHeaderProps) {
|
|
return (
|
|
<div className={cn('mb-6 space-y-3', className)}>
|
|
{back && (
|
|
<AdminLink
|
|
href={back.href}
|
|
// The phone shell's top bar already has the back arrow.
|
|
className="hidden lg:inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
{back.label}
|
|
</AdminLink>
|
|
)}
|
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
<div className="flex items-start gap-3 min-w-0">
|
|
{leading}
|
|
<div className="min-w-0">
|
|
<h1 className={cn('text-xl font-semibold text-foreground leading-tight', !back && 'hidden lg:block')}>{title}</h1>
|
|
{description && <p className="text-sm text-muted-foreground mt-1">{description}</p>}
|
|
</div>
|
|
</div>
|
|
{actions && <div className="flex flex-wrap items-center gap-2 shrink-0">{actions}</div>}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|