feat(mobile): swap admin page headers to PageHeader

Mechanical sweep replacing the plain h1+p header markup with the
mobile-aware PageHeader primitive across 12 admin pages: index,
backup, branding, documenso, email, import, invitations, monitoring,
onboarding, reminders, reports, webhooks. Webhooks "Add Webhook"
button preserved via the actions slot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-01 12:57:52 +02:00
parent c405124bc3
commit 71da6e8fdc
12 changed files with 101 additions and 107 deletions

View File

@@ -1,10 +1,9 @@
import { PageHeader } from '@/components/shared/page-header';
export default function BackupManagementPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold text-foreground">Backup Management</h1>
<p className="text-muted-foreground">Manage system backups and restoration</p>
</div>
<PageHeader title="Backup Management" description="Manage system backups and restoration" />
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-12">
<p className="text-lg font-medium text-muted-foreground">Coming in Layer 4</p>
<p className="text-sm text-muted-foreground">

View File

@@ -2,6 +2,7 @@ import {
SettingsFormCard,
type SettingFieldDef,
} from '@/components/admin/shared/settings-form-card';
import { PageHeader } from '@/components/shared/page-header';
const FIELDS: SettingFieldDef[] = [
{
@@ -47,13 +48,10 @@ const FIELDS: SettingFieldDef[] = [
export default function BrandingSettingsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold">Branding</h1>
<p className="text-sm text-muted-foreground">
Logo, primary color, app name, and email header/footer HTML used by the branded auth shell
and outgoing email templates.
</p>
</div>
<PageHeader
title="Branding"
description="Logo, primary color, app name, and email header/footer HTML used by the branded auth shell and outgoing email templates."
/>
<SettingsFormCard
title="Identity"
description="App name, logo, and primary color."

View File

@@ -3,6 +3,7 @@ import {
type SettingFieldDef,
} from '@/components/admin/shared/settings-form-card';
import { DocumensoTestButton } from '@/components/admin/documenso/documenso-test-button';
import { PageHeader } from '@/components/shared/page-header';
const API_FIELDS: SettingFieldDef[] = [
{
@@ -48,13 +49,10 @@ const EOI_FIELDS: SettingFieldDef[] = [
export default function DocumensoSettingsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold">Documenso & EOI</h1>
<p className="text-sm text-muted-foreground">
API credentials and default EOI generation pathway. Use the test-connection button to
verify a saved configuration before relying on it.
</p>
</div>
<PageHeader
title="Documenso & EOI"
description="API credentials and default EOI generation pathway. Use the test-connection button to verify a saved configuration before relying on it."
/>
<SettingsFormCard
title="Documenso API"

View File

@@ -2,6 +2,7 @@ import {
SettingsFormCard,
type SettingFieldDef,
} from '@/components/admin/shared/settings-form-card';
import { PageHeader } from '@/components/shared/page-header';
const FIELDS: SettingFieldDef[] = [
{
@@ -79,13 +80,10 @@ const FIELDS: SettingFieldDef[] = [
export default function EmailSettingsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold">Email Settings</h1>
<p className="text-sm text-muted-foreground">
Per-port outgoing email configuration. SMTP credentials and the From address default to
environment variables when these fields are blank.
</p>
</div>
<PageHeader
title="Email Settings"
description="Per-port outgoing email configuration. SMTP credentials and the From address default to environment variables when these fields are blank."
/>
<SettingsFormCard
title="From address & signature"
description="Identity headers and shared HTML used by system-generated emails."

View File

@@ -1,10 +1,9 @@
import { PageHeader } from '@/components/shared/page-header';
export default function DataImportPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold text-foreground">Data Import</h1>
<p className="text-muted-foreground">Import data from external sources</p>
</div>
<PageHeader title="Data Import" description="Import data from external sources" />
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-12">
<p className="text-lg font-medium text-muted-foreground">Coming in Layer 4</p>
<p className="text-sm text-muted-foreground">

View File

@@ -1,15 +1,13 @@
import { InvitationsManager } from '@/components/admin/invitations/invitations-manager';
import { PageHeader } from '@/components/shared/page-header';
export default function InvitationsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold">Invitations</h1>
<p className="text-sm text-muted-foreground">
Send a single-use invitation to a new CRM user. The recipient sets their own password via
the link in the email.
</p>
</div>
<PageHeader
title="Invitations"
description="Send a single-use invitation to a new CRM user. The recipient sets their own password via the link in the email."
/>
<InvitationsManager />
</div>
);

View File

@@ -1,10 +1,9 @@
import { PageHeader } from '@/components/shared/page-header';
export default function OnboardingPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold text-foreground">Onboarding</h1>
<p className="text-muted-foreground">Guided setup for new port configurations</p>
</div>
<PageHeader title="Onboarding" description="Guided setup for new port configurations" />
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-12">
<p className="text-lg font-medium text-muted-foreground">Coming in Layer 4</p>
<p className="text-sm text-muted-foreground">

View File

@@ -20,6 +20,7 @@ import {
} from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { PageHeader } from '@/components/shared/page-header';
interface AdminSection {
href: string;
@@ -165,13 +166,10 @@ export default async function AdminLandingPage({
const { portSlug } = await params;
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold">Administration</h1>
<p className="text-sm text-muted-foreground">
Per-port configuration and system administration. Each card below opens a dedicated
settings page.
</p>
</div>
<PageHeader
title="Administration"
description="Per-port configuration and system administration. Each card below opens a dedicated settings page."
/>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{SECTIONS.map((s) => {
const Icon = s.icon;

View File

@@ -2,6 +2,7 @@ import {
SettingsFormCard,
type SettingFieldDef,
} from '@/components/admin/shared/settings-form-card';
import { PageHeader } from '@/components/shared/page-header';
const DEFAULT_FIELDS: SettingFieldDef[] = [
{
@@ -53,14 +54,10 @@ const DIGEST_FIELDS: SettingFieldDef[] = [
export default function ReminderSettingsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold">Reminders</h1>
<p className="text-sm text-muted-foreground">
Default reminder behaviour for new interests and the optional daily-digest delivery
window. Individual users can still configure their own digest preferences in Notifications
Preferences.
</p>
</div>
<PageHeader
title="Reminders"
description="Default reminder behaviour for new interests and the optional daily-digest delivery window. Individual users can still configure their own digest preferences in Notifications → Preferences."
/>
<SettingsFormCard
title="Defaults for new interests"

View File

@@ -1,10 +1,12 @@
import { PageHeader } from '@/components/shared/page-header';
export default function ScheduledReportsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold text-foreground">Scheduled Reports</h1>
<p className="text-muted-foreground">Configure and manage automated report delivery</p>
</div>
<PageHeader
title="Scheduled Reports"
description="Configure and manage automated report delivery"
/>
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-12">
<p className="text-lg font-medium text-muted-foreground">Coming in Layer 3</p>
<p className="text-sm text-muted-foreground">

View File

@@ -2,6 +2,7 @@
import { useCallback, useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { PageHeader } from '@/components/shared/page-header';
import { Badge } from '@/components/ui/badge';
import {
AlertDialog,
@@ -36,7 +37,11 @@ export default function WebhooksPage() {
const [deleteTarget, setDeleteTarget] = useState<Webhook | null>(null);
const [expandedId, setExpandedId] = useState<string | null>(null);
const [regenerating, setRegenerating] = useState<string | null>(null);
const [newSecret, setNewSecret] = useState<{ webhookId: string; secret: string; masked: string } | null>(null);
const [newSecret, setNewSecret] = useState<{
webhookId: string;
secret: string;
masked: string;
} | null>(null);
const loadWebhooks = useCallback(async () => {
try {
@@ -98,15 +103,20 @@ export default function WebhooksPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-foreground">Webhooks</h1>
<p className="text-muted-foreground">Configure outgoing webhook integrations</p>
</div>
<Button onClick={() => { setEditTarget(null); setFormOpen(true); }}>
<PageHeader
title="Webhooks"
description="Configure outgoing webhook integrations"
actions={
<Button
onClick={() => {
setEditTarget(null);
setFormOpen(true);
}}
>
Add Webhook
</Button>
</div>
}
/>
{loading ? (
<p className="text-sm text-muted-foreground">Loading...</p>
@@ -116,7 +126,13 @@ export default function WebhooksPage() {
<p className="text-sm text-muted-foreground mt-1">
Add a webhook to receive real-time notifications of CRM events.
</p>
<Button className="mt-4" onClick={() => { setEditTarget(null); setFormOpen(true); }}>
<Button
className="mt-4"
onClick={() => {
setEditTarget(null);
setFormOpen(true);
}}
>
Add Webhook
</Button>
</div>
@@ -141,17 +157,16 @@ export default function WebhooksPage() {
</div>
<div className="flex items-center gap-2 shrink-0">
<Button
variant="ghost"
size="sm"
onClick={() => handleToggleActive(webhook)}
>
<Button variant="ghost" size="sm" onClick={() => handleToggleActive(webhook)}>
{webhook.isActive ? 'Disable' : 'Enable'}
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => { setEditTarget(webhook); setFormOpen(true); }}
onClick={() => {
setEditTarget(webhook);
setFormOpen(true);
}}
>
Edit
</Button>
@@ -163,11 +178,7 @@ export default function WebhooksPage() {
>
Delete
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => toggleExpand(webhook.id)}
>
<Button variant="ghost" size="sm" onClick={() => toggleExpand(webhook.id)}>
{expandedId === webhook.id ? 'Collapse' : 'Details'}
</Button>
</div>
@@ -228,18 +239,26 @@ export default function WebhooksPage() {
onSuccess={loadWebhooks}
/>
<AlertDialog open={!!deleteTarget} onOpenChange={(open) => { if (!open) setDeleteTarget(null); }}>
<AlertDialog
open={!!deleteTarget}
onOpenChange={(open) => {
if (!open) setDeleteTarget(null);
}}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Webhook</AlertDialogTitle>
<AlertDialogDescription>
Delete &quot;{deleteTarget?.name}&quot;? This will also delete all delivery history. This action
cannot be undone.
Delete &quot;{deleteTarget?.name}&quot;? This will also delete all delivery history.
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-destructive text-destructive-foreground">
<AlertDialogAction
onClick={handleDelete}
className="bg-destructive text-destructive-foreground"
>
Delete
</AlertDialogAction>
</AlertDialogFooter>

View File

@@ -7,6 +7,7 @@ import { apiFetch } from '@/lib/api/client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { ServiceHealthCard } from './service-health-card';
import { QueueOverview } from './queue-overview';
import { PageHeader } from '@/components/shared/page-header';
import type {
HealthStatus,
QueueStatus,
@@ -17,16 +18,14 @@ import type {
export function SystemMonitoringDashboard() {
const { data: healthData } = useQuery({
queryKey: ['system', 'health'],
queryFn: () =>
apiFetch<{ data: HealthStatus }>('/api/v1/admin/health').then((r) => r.data),
queryFn: () => apiFetch<{ data: HealthStatus }>('/api/v1/admin/health').then((r) => r.data),
staleTime: 30_000,
refetchInterval: 30_000,
});
const { data: queuesData } = useQuery({
queryKey: ['system', 'queues'],
queryFn: () =>
apiFetch<{ data: QueueStatus[] }>('/api/v1/admin/queues').then((r) => r.data),
queryFn: () => apiFetch<{ data: QueueStatus[] }>('/api/v1/admin/queues').then((r) => r.data),
staleTime: 10_000,
refetchInterval: 10_000,
});
@@ -47,11 +46,10 @@ export function SystemMonitoringDashboard() {
return (
<div className="space-y-8">
{/* Page header */}
<div>
<h1 className="text-2xl font-bold text-foreground">System Monitoring</h1>
<p className="text-muted-foreground">Real-time health, queue status and connection tracking</p>
</div>
<PageHeader
title="System Monitoring"
description="Real-time health, queue status and connection tracking"
/>
{/* Service health */}
<section className="space-y-3">
@@ -79,10 +77,7 @@ export function SystemMonitoringDashboard() {
) : (
<div className="flex gap-3">
{[1, 2, 3, 4].map((i) => (
<div
key={i}
className="h-[88px] w-[160px] rounded-xl border bg-card animate-pulse"
/>
<div key={i} className="h-[88px] w-[160px] rounded-xl border bg-card animate-pulse" />
))}
</div>
)}
@@ -124,9 +119,7 @@ export function SystemMonitoringDashboard() {
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">
{queues.reduce((sum, q) => sum + q.active, 0)}
</p>
<p className="text-3xl font-bold">{queues.reduce((sum, q) => sum + q.active, 0)}</p>
</CardContent>
</Card>
</section>
@@ -141,10 +134,7 @@ export function SystemMonitoringDashboard() {
) : (
<div className="grid grid-cols-5 gap-3">
{[1, 2, 3, 4, 5].map((i) => (
<div
key={i}
className="h-[110px] rounded-xl border bg-card animate-pulse"
/>
<div key={i} className="h-[110px] rounded-xl border bg-card animate-pulse" />
))}
</div>
)}
@@ -159,8 +149,7 @@ export function SystemMonitoringDashboard() {
function RecentErrorsPanel() {
const { data: errorsData } = useQuery({
queryKey: ['system', 'errors'],
queryFn: () =>
apiFetch<{ data: RecentError[] }>('/api/v1/admin/errors').then((r) => r.data),
queryFn: () => apiFetch<{ data: RecentError[] }>('/api/v1/admin/errors').then((r) => r.data),
staleTime: 30_000,
refetchInterval: 30_000,
});