diff --git a/apps/backoffice/src/app/features/certificate-requirements/pages/CertificateRequirementsPage.tsx b/apps/backoffice/src/app/features/certificate-requirements/pages/CertificateRequirementsPage.tsx index 8e9414a0a..86352bb97 100644 --- a/apps/backoffice/src/app/features/certificate-requirements/pages/CertificateRequirementsPage.tsx +++ b/apps/backoffice/src/app/features/certificate-requirements/pages/CertificateRequirementsPage.tsx @@ -44,6 +44,7 @@ export function CertificateRequirementsPage() { 'certReq.subtitle', 'Configure the form fields and document uploads applicants must complete for a licence type, including conditions on when a requirement applies.', )} + noMargin /> {isError ? ( diff --git a/apps/backoffice/src/app/features/certification/pages/CertificationPage/index.tsx b/apps/backoffice/src/app/features/certification/pages/CertificationPage/index.tsx index 1c5893ab3..717311b65 100644 --- a/apps/backoffice/src/app/features/certification/pages/CertificationPage/index.tsx +++ b/apps/backoffice/src/app/features/certification/pages/CertificationPage/index.tsx @@ -1,20 +1,9 @@ import { useState } from 'react'; -import { - Stack, - Title, - Group, - Button, - Modal, - Text, - TextInput, - Textarea, - Card, - Alert, -} from '@mantine/core'; +import {Stack, Button, Modal, Text, TextInput, Textarea, Card} from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { useTranslation } from 'react-i18next'; -import { IconPlus, IconInfoCircle } from '@tabler/icons-react'; -import { notify, useErrorHandler, AdvancedTable, useServerTable, ModalFooter } from '@ema-platform/ui'; +import {IconPlus} from '@tabler/icons-react'; +import { AdvancedTable, ErrorState, ModalFooter, notify, PageHeader, useErrorHandler, useServerTable } from '@ema-platform/ui'; import { useGetCertificationsQuery, useCreateCertificationMutation, @@ -120,7 +109,8 @@ export function CertificationPage() { } }; - if (isError) return } color="red" title={t('certification.loadError')} />; + if (isError) + return ; const columns = [ ...certificationColumns(t, locale), @@ -134,17 +124,18 @@ export function CertificationPage() { return ( - -
- {t('certification.title')} - {t('certification.subtitle')} -
- {!showForm && ( - - )} -
+ } onClick={() => setShowForm(true)} size="sm"> + {t('certification.add')} + + ) + } + /> {showForm && ( - {t("configuration.title")} + diff --git a/apps/backoffice/src/app/features/dashboard/pages/DashboardPage/index.tsx b/apps/backoffice/src/app/features/dashboard/pages/DashboardPage/index.tsx index 927ca47a3..41b363b6c 100644 --- a/apps/backoffice/src/app/features/dashboard/pages/DashboardPage/index.tsx +++ b/apps/backoffice/src/app/features/dashboard/pages/DashboardPage/index.tsx @@ -1,17 +1,8 @@ import { useNavigate } from 'react-router-dom'; -import { - Card, - Center, - Container, - Group, - Loader, - SimpleGrid, - Text, - Title, -} from '@mantine/core'; +import {Card, Center, Container, Group, Loader, SimpleGrid, Text} from '@mantine/core'; import { IconChevronRight } from '@tabler/icons-react'; import { useGetAssignedToMeQuery, useGetQueueQuery } from '@ema-platform/api'; -import { AdvancedTable, PageLoader, useServerTable } from '@ema-platform/ui'; +import { AdvancedTable, PageHeader, PageLoader, useServerTable } from '@ema-platform/ui'; import { dashboardQueueColumns } from './columns'; /** @@ -53,12 +44,10 @@ export function DashboardPage() { return ( - - Dashboard - - - Licence applications currently in the system. - + {stats.map((stat) => ( diff --git a/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx b/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx index dbf134fe3..f52b29c91 100644 --- a/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx +++ b/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx @@ -291,7 +291,7 @@ export function ExamDetailPage() {
- {exam.title[locale]} + {exam.title[locale]}
diff --git a/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx b/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx index c34e5975b..cd5d7c192 100644 --- a/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx +++ b/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { Stack, - Title, Group, Button, Modal, @@ -10,7 +9,6 @@ import { TextInput, Textarea, Card, - Alert, Select, NumberInput, Tabs, @@ -35,6 +33,7 @@ import { import type { Exam } from "../../types/exam"; import { examColumns } from "./columns"; import { examActionsColumn } from "./actions"; +import { ErrorState, PageHeader } from '@ema-platform/ui'; function ExamForm({ editing, @@ -416,13 +415,7 @@ export function ExamPage() { }; if (isError) - return ( - } - color="red" - title={t("exam.loadError")} - /> - ); + return ; const columns = [ ...examColumns(t, locale, getCertName, (exam) => navigate(`/exams/${exam.id}`)), @@ -443,26 +436,25 @@ export function ExamPage() { return ( - -
- {t("exam.title")} - - {t("exam.subtitle")} - -
- {!showForm && ( - - - - )} -
+ + + + ) + } + /> {showForm && ( - Items + diff --git a/apps/backoffice/src/app/features/license-register/pages/LicenseRegisterPage/index.tsx b/apps/backoffice/src/app/features/license-register/pages/LicenseRegisterPage/index.tsx index 754d0af80..f57d0c933 100644 --- a/apps/backoffice/src/app/features/license-register/pages/LicenseRegisterPage/index.tsx +++ b/apps/backoffice/src/app/features/license-register/pages/LicenseRegisterPage/index.tsx @@ -1,19 +1,7 @@ import { useState } from 'react'; -import { - Button, - Card, - Container, - Group, - Modal, - Select, - Stack, - Text, - TextInput, - Textarea, - Title, -} from '@mantine/core'; +import {Button, Card, Container, Group, Modal, Select, Stack, Text, TextInput, Textarea} from '@mantine/core'; import { IconSearch } from '@tabler/icons-react'; -import { AdvancedTable, notify, useServerTable } from '@ema-platform/ui'; +import { AdvancedTable, notify, PageHeader, useServerTable } from '@ema-platform/ui'; import { useDateDisplayer } from '@ema-platform/shared'; import { extractErrorMessage, @@ -151,21 +139,19 @@ export function LicenseRegisterPage() { return ( - -
- Licence register - - {data?.total ?? 0} issued licence{(data?.total ?? 0) === 1 ? '' : 's'} - -
- } - value={search} - onChange={(e) => setSearch(e.currentTarget.value)} - w={280} - /> -
+ } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + w={280} + /> + } + /> diff --git a/apps/backoffice/src/app/features/license-review/pages/LicenseQueuePage/index.tsx b/apps/backoffice/src/app/features/license-review/pages/LicenseQueuePage/index.tsx index b8791eb4e..856b30581 100644 --- a/apps/backoffice/src/app/features/license-review/pages/LicenseQueuePage/index.tsx +++ b/apps/backoffice/src/app/features/license-review/pages/LicenseQueuePage/index.tsx @@ -17,7 +17,6 @@ import { Tabs, Text, TextInput, - Title, } from "@mantine/core"; import { useDebouncedValue } from "@mantine/hooks"; import { @@ -72,6 +71,7 @@ import { useAppDispatch, useAppSelector } from "../../../../store/hooks"; import { KEYBOARD_SHORTCUTS, useQueueKeyboard } from "../../useQueueKeyboard"; import { licenseQueueColumns } from "./columns"; import { licenseQueueActionsColumn } from "./actions"; +import { PageHeader } from '@ema-platform/ui'; const PAGE_SIZE = 10; const SEARCH_DEBOUNCE_MS = 300; @@ -501,16 +501,11 @@ export function LicenseQueuePage() { return ( - -
- {queueTitle} - {typeCode && ( - - {t(`nav.type${typeCode}`, { defaultValue: typeCode })} - - )} -
- + {t("queue.export", "Export CSV")} - -
+
+ } + /> {/* Saved views, counted. */}
- {headerName} + {headerName} {app.applicationNumber} diff --git a/apps/backoffice/src/app/features/location/pages/LocationPage.tsx b/apps/backoffice/src/app/features/location/pages/LocationPage.tsx index b2d4f9e3c..e22a16b20 100644 --- a/apps/backoffice/src/app/features/location/pages/LocationPage.tsx +++ b/apps/backoffice/src/app/features/location/pages/LocationPage.tsx @@ -1,23 +1,9 @@ import { useState, useCallback } from 'react'; -import { - Stack, - Title, - Group, - Button, - Paper, - Text, - Grid, - Modal, - ActionIcon, - Tooltip, - Loader, - Center, - Alert, -} from '@mantine/core'; +import {Stack, Group, Button, Paper, Text, Grid, Modal, ActionIcon, Tooltip, Loader, Center, Alert} from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { IconSettings, IconMap, IconInfoCircle, IconPlus } from '@tabler/icons-react'; import { useTranslation } from 'react-i18next'; -import { notify, useErrorHandler, ModalFooter, PageLoader } from '@ema-platform/ui'; +import { ModalFooter, notify, PageHeader, PageLoader, useErrorHandler } from '@ema-platform/ui'; import { LocationTree } from '../components/LocationTree'; import { LocationDetail } from '../components/LocationDetail'; import { LocationForm } from '../components/LocationForm'; @@ -108,9 +94,11 @@ export function LocationPage() { return ( - - {t('location.title')} - + {locationTypes.length > 0 && ( - - )} - + + + + ) + } + /> {showForm && ( -
- {t('result.appeals.title')} - - {t('result.appeals.subtitle')} - -
+ diff --git a/apps/backoffice/src/app/features/result/pages/ResultPage/index.tsx b/apps/backoffice/src/app/features/result/pages/ResultPage/index.tsx index 015ef7e68..054a05cda 100644 --- a/apps/backoffice/src/app/features/result/pages/ResultPage/index.tsx +++ b/apps/backoffice/src/app/features/result/pages/ResultPage/index.tsx @@ -1,39 +1,9 @@ import { useState, useCallback, type ElementType } from 'react'; import { useTranslation } from 'react-i18next'; -import { - Stack, - Title, - Group, - Table, - Badge, - Modal, - Text, - Paper, - Loader, - Center, - Alert, - Select, - SimpleGrid, - Divider, - Button, - ThemeIcon, - TextInput, -} from '@mantine/core'; +import {Stack, Group, Table, Badge, Modal, Text, Paper, Loader, Center, Select, SimpleGrid, Divider, Button, ThemeIcon, TextInput} from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; -import { - IconInfoCircle, - IconUser, - IconCertificate, - IconDeviceFloppy, - IconPlus, - IconClipboardList, - IconCircleCheck, - IconCircleX, - IconChartBar, - IconSearch, - IconSend, -} from '@tabler/icons-react'; -import { notify, BilingualInput, useErrorHandler, AdvancedTable, StatusBadge, useServerTable, ModalFooter } from '@ema-platform/ui'; +import {IconUser, IconCertificate, IconDeviceFloppy, IconPlus, IconClipboardList, IconCircleCheck, IconCircleX, IconChartBar, IconSearch, IconSend} from '@tabler/icons-react'; +import { AdvancedTable, BilingualInput, ErrorState, ModalFooter, notify, PageHeader, StatusBadge, useErrorHandler, useServerTable } from '@ema-platform/ui'; import { useDateDisplayer } from '@ema-platform/shared'; import type { BilingualValue } from '@ema-platform/ui'; import { extractErrorMessage, useLocalized } from '@ema-platform/api'; @@ -279,7 +249,7 @@ export function ResultPage() { } }; - if (isError) return } color="red" title={t('result.loadError')} />; + if (isError) return ; const columns = [ ...resultColumns(t, locale, showDate, getExamTitle), @@ -294,12 +264,12 @@ export function ResultPage() { return ( - -
- {t('result.title')} - {t('result.subtitle')} -
- + - -
+
+ } + /> diff --git a/apps/backoffice/src/app/features/seafarer-document-review/pages/SeafarerDocumentQueuePage.tsx b/apps/backoffice/src/app/features/seafarer-document-review/pages/SeafarerDocumentQueuePage.tsx index 4c9e3c0aa..51460e1f8 100644 --- a/apps/backoffice/src/app/features/seafarer-document-review/pages/SeafarerDocumentQueuePage.tsx +++ b/apps/backoffice/src/app/features/seafarer-document-review/pages/SeafarerDocumentQueuePage.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import {Badge, Container, Select, Text, TextInput, Title} from '@mantine/core'; +import {Badge, Container, Select, Text, TextInput} from '@mantine/core'; import { IconSearch } from '@tabler/icons-react'; import { useDebouncedValue } from '@mantine/hooks'; import { @@ -12,7 +12,7 @@ import { type SeafarerDocumentRow, type SeafarerDocumentStatus, } from '@ema-platform/api'; -import { AdvancedTable, type AdvancedColumn } from '@ema-platform/ui'; +import { AdvancedTable, PageHeader, WaitingFor, type AdvancedColumn } from '@ema-platform/ui'; import { useDateDisplayer } from '@ema-platform/shared'; const PAGE_SIZE = 10; @@ -91,6 +91,17 @@ export function SeafarerDocumentQueuePage({ kind }: { kind: SeafarerDocumentKind {row.original.submittedAt ? showDate(row.original.submittedAt) : '—'} ), }, + { + header: 'Waiting', + accessorKey: 'submittedAt', + size: 90, + cell: ({ row }) => ( + + ), + }, { header: 'Status', accessorKey: 'status', @@ -106,13 +117,10 @@ export function SeafarerDocumentQueuePage({ kind }: { kind: SeafarerDocumentKind return ( - - {SEAFARER_DOCUMENT_KIND_LABELS[kind]} Queue - - - Requests released by an approved seafarer registration: confirm payment, schedule the - collection date, then issue. - + Back to queue - -
- - {kindLabel} — {applicant?.name ?? '—'} - - + {document.requestNumber} @@ -140,9 +123,10 @@ export function SeafarerDocumentReviewPage() { {document.documentNumber} )} - -
- + + } + action={ + {(document.status === 'PAYMENT_PENDING' || document.status === 'PAID') && ( )} - - +
+ } + /> {document.status === 'REJECTED' && ( } title="Rejected" mb="md"> @@ -189,7 +174,7 @@ export function SeafarerDocumentReviewPage() { Seafarer - +
@@ -212,7 +197,7 @@ export function SeafarerDocumentReviewPage() { Payment -
+
@@ -226,7 +211,7 @@ export function SeafarerDocumentReviewPage() { Issuance -
+
diff --git a/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx b/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx index 9e1bff58f..6c9a7f1b5 100644 --- a/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx +++ b/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import {Container, Select, Text, TextInput, Title} from '@mantine/core'; +import {Container, Select, Text, TextInput} from '@mantine/core'; import { IconSearch } from '@tabler/icons-react'; import { useDebouncedValue } from '@mantine/hooks'; import { @@ -11,7 +11,7 @@ import { type SeafarerRegistration, type SeafarerRegistrationStatus, } from '@ema-platform/api'; -import { AdvancedTable, StatusBadge, type AdvancedColumn } from '@ema-platform/ui'; +import { AdvancedTable, PageHeader, StatusBadge, WaitingFor, type AdvancedColumn } from '@ema-platform/ui'; import { useDateDisplayer } from '@ema-platform/shared'; const PAGE_SIZE = 10; @@ -78,6 +78,17 @@ export function SeafarerRegistrationQueuePage() { {row.original.submittedAt ? showDate(row.original.submittedAt) : '—'} ), }, + { + header: 'Waiting', + accessorKey: 'submittedAt', + size: 90, + cell: ({ row }) => ( + + ), + }, { header: 'Status', accessorKey: 'status', @@ -94,13 +105,10 @@ export function SeafarerRegistrationQueuePage() { return ( - - Seafarer Registration Queue - - - Registrations awaiting review. Approval numbers the seafarer and opens their Seaman Book and - BTC applications. - + } onClick={() => navigate('/seafarer-registrations')} mb="xs"> Back to queue - -
- {applicantName(registration)} - + {registration.registrationNumber} @@ -125,9 +109,10 @@ export function SeafarerRegistrationReviewPage() { {registration.seafarerNumber} )} - -
- + + } + action={ + {canDecide && ( <> @@ -147,8 +132,9 @@ export function SeafarerRegistrationReviewPage() { )} - - +
+ } + /> {registration.status === 'RESUBMIT_REQUIRED' && ( } title="Awaiting the applicant's corrections" mb="md"> @@ -168,7 +154,7 @@ export function SeafarerRegistrationReviewPage() { {section.title} -
+
{section.fields .filter((f) => f !== 'passportExpiry' || registration.passportNumber) @@ -193,7 +179,7 @@ export function SeafarerRegistrationReviewPage() { Documents -
+
{slots.map((slot) => { const file = attachments.find((a) => a.documentKey === slot.key)?.files?.[0]; diff --git a/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx b/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx index 5ed329330..f451d101f 100644 --- a/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx +++ b/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx @@ -1,27 +1,8 @@ -import { StatusBadge } from '@ema-platform/ui'; +import { PageHeader, StatusBadge } from '@ema-platform/ui'; import { type StatusTone } from '@ema-platform/shared'; import { useState } from 'react'; import { useApiQuery } from '@ema-platform/api'; -import { - ActionIcon, - Badge, - Button, - Card, - Collapse, - Divider, - Group, - Modal, - Paper, - Select, - SimpleGrid, - Stack, - Table, - Text, - TextInput, - ThemeIcon, - Title, - rem, -} from '@mantine/core'; +import {ActionIcon, Badge, Button, Card, Collapse, Divider, Group, Modal, Paper, Select, SimpleGrid, Stack, Table, Text, TextInput, ThemeIcon, rem} from '@mantine/core'; import { IconBook2, IconCertificate, @@ -171,10 +152,11 @@ export function SeafarerRegistryPage() { return ( -
- Seafarer Registry - Search and view all registered seafarers, their documents, and certificate status -
+ {/* KPIs */} diff --git a/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationFormBuilderPage.tsx b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationFormBuilderPage.tsx index 37b9e3f97..47cac3c33 100644 --- a/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationFormBuilderPage.tsx +++ b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationFormBuilderPage.tsx @@ -336,7 +336,7 @@ export function VesselRegistrationFormBuilderPage() {
- Vessel Registration Form Builder + Vessel Registration Form Builder Add, edit, reorder, or disable fields on the vessel registration form
diff --git a/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationQueuePage/index.tsx b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationQueuePage/index.tsx index f7a504651..0664497b5 100644 --- a/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationQueuePage/index.tsx +++ b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationQueuePage/index.tsx @@ -1,29 +1,12 @@ import { useState } from 'react'; import { Link } from 'react-router-dom'; -import { - Alert, - Badge, - Button, - Card, - Container, - Drawer, - Group, - Loader, - Modal, - Select, - Stack, - Table, - Text, - TextInput, - Textarea, - Title, -} from '@mantine/core'; +import {Alert, Badge, Button, Card, Container, Drawer, Group, Loader, Modal, Select, Stack, Table, Text, TextInput, Textarea} from '@mantine/core'; import { IconAlertTriangle, IconInfoCircle, IconSearch, } from '@tabler/icons-react'; -import { AdvancedTable, notify, useServerTable } from '@ema-platform/ui'; +import { AdvancedTable, notify, PageHeader, useServerTable } from '@ema-platform/ui'; import { useDateDisplayer } from '@ema-platform/shared'; import { extractErrorMessage, @@ -238,26 +221,28 @@ export function VesselRegistrationQueuePage() { return ( - -
- Vessel register - + {data?.total ?? 0} vessel{(data?.total ?? 0) === 1 ? '' : 's'} — pending registrations are reviewed in the{' '} licence queue . - -
- } - value={search} - onChange={(e) => setSearch(e.currentTarget.value)} - w={280} - /> -
+ + } + action={ + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + w={280} + /> + } + /> - -
- Vessel registration report - - {report - ? `Register-wide totals with a ${showDate(report.filters.from)} – ${showDate(report.filters.to)} window on the trends.` - : 'The national vessel register at a glance.'} - -
-
+ + — + + ); + } + + const days = daysSince(since); + + // A decided item keeps its elapsed time visible — useful when reviewing how + // long something took — but never coloured, because nothing is pending. + const tone = done + ? 'neutral' + : days >= slaDays + ? 'danger' + : days >= slaDays * WARNING_RATIO + ? 'pending' + : 'neutral'; + + const label = days === 0 ? 'today' : `${days}d`; + + return ( + + + {label} + + + ); +} diff --git a/libs/ui/src/lib/layout/PageHeader.tsx b/libs/ui/src/lib/layout/PageHeader.tsx index 0e3d9d6de..b544a29cf 100644 --- a/libs/ui/src/lib/layout/PageHeader.tsx +++ b/libs/ui/src/lib/layout/PageHeader.tsx @@ -2,17 +2,54 @@ import { Group, Stack, Text, Title } from '@mantine/core'; import type { ReactNode } from 'react'; interface PageHeaderProps { - title: string; - subtitle?: string; + /** + * The page's name. A `ReactNode` rather than a string because detail pages + * title themselves with the record they are showing, which is often a + * localised or composed value rather than a literal. + */ + title: ReactNode; + subtitle?: ReactNode; + /** + * Identifiers that belong beside the title rather than under it — a + * reference number, a status badge. Rendered inline, small and dimmed. + */ + meta?: ReactNode; /** Right-aligned actions (e.g. a primary button). */ action?: ReactNode; + /** + * Set when the header sits directly inside a gapped ``, which already + * spaces it from what follows — the built-in margin would double that gap. + */ + noMargin?: boolean; } -export function PageHeader({ title, subtitle, action }: PageHeaderProps) { +/** + * The heading every backoffice page starts with. + * + * It exists because 25 pages each rolled their own: eight at `order={2}`, + * seventeen at `order={3}`, with `mb="xs"`, `mb={4}`, `mb="lg"` and nothing at + * all between them. Clicking between two screens moved the title, which is the + * single loudest signal that a product was assembled rather than designed. + * + * `order={2}` is deliberate — it is the page's only `h2`, sitting under the + * app-level `h1`, so the heading outline reads correctly for a screen reader. + * Section headings inside cards stay at `order={4}`/`{5}` and are not this + * component's business. + */ +export function PageHeader({ title, subtitle, meta, action, noMargin }: PageHeaderProps) { return ( - + - {title} + + {title} + {meta} + {subtitle && ( {subtitle}