feat(housekeeping): deliver system vertical
This commit is contained in:
1 parent
0117b45d74
commit
3788ecd9f1
33 files changed
+4548
-423
No files matched your search
@@ -0,0 +1,165 @@
|
||||
import type { ReactNode } from "react";
|
||||
import {
|
||||
createCorrelationId,
|
||||
fail,
|
||||
type HousekeepingResult,
|
||||
} from "../../../foundation/contracts";
|
||||
import { HousekeepingPageShell } from "../../../foundation/page/housekeeping-page-shell";
|
||||
import { HousekeepingPageState } from "../../../foundation/page/housekeeping-page-state";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import {
|
||||
type SystemConfigurationQueryData,
|
||||
type SystemConfigurationQueryInput,
|
||||
type SystemSettingRow,
|
||||
systemConfigurationQuery,
|
||||
} from "../queries/configuration";
|
||||
|
||||
interface SystemConfigurationPageProps {
|
||||
readonly result?: HousekeepingResult<SystemConfigurationQueryData>;
|
||||
}
|
||||
|
||||
function pageState(
|
||||
kind: "loading" | "empty" | "error",
|
||||
title: string,
|
||||
description: string,
|
||||
) {
|
||||
return (
|
||||
<div data-housekeeping-state={kind}>
|
||||
<HousekeepingPageState
|
||||
state={kind}
|
||||
title={title}
|
||||
description={description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function forbiddenState() {
|
||||
return (
|
||||
<section
|
||||
role="alert"
|
||||
data-housekeeping-state="forbidden"
|
||||
className="rounded-lg border border-[var(--admin-error)] bg-[var(--admin-surface)] p-4"
|
||||
>
|
||||
<h2 className="font-medium text-[var(--admin-text)]">Access denied</h2>
|
||||
<p className="mt-1 text-sm text-[var(--admin-text-muted)]">
|
||||
Your account cannot view system configuration.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function settingList(title: string, rows: readonly SystemSettingRow[]) {
|
||||
return (
|
||||
<section className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4">
|
||||
<h2 className="font-medium text-[var(--admin-text)]">{title}</h2>
|
||||
<dl className="mt-3 divide-y divide-[var(--admin-border)]">
|
||||
{rows.map((row) => (
|
||||
<div
|
||||
key={row.key}
|
||||
className="grid gap-1 py-2 text-sm md:grid-cols-[minmax(12rem,1fr)_2fr]"
|
||||
>
|
||||
<dt className="font-medium text-[var(--admin-text)]">{row.key}</dt>
|
||||
<dd className="break-words text-[var(--admin-text-muted)]">
|
||||
{row.value}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function configurationContent(data: SystemConfigurationQueryData) {
|
||||
return data.kind === "settings" ? (
|
||||
settingList("Website settings", data.settings)
|
||||
) : (
|
||||
<div className="grid gap-4 xl:grid-cols-2">
|
||||
{settingList("Emulator settings", data.settings)}
|
||||
<div className="space-y-2">
|
||||
{settingList("Emulator texts", data.texts)}
|
||||
<p className="text-xs text-[var(--admin-text-muted)]">
|
||||
Showing {data.texts.length} of {data.textsTotal} text entries.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SystemConfigurationPage({
|
||||
result,
|
||||
}: SystemConfigurationPageProps) {
|
||||
let body: ReactNode;
|
||||
if (!result) {
|
||||
body = pageState(
|
||||
"loading",
|
||||
"Loading configuration",
|
||||
"Reading CMS and emulator settings.",
|
||||
);
|
||||
} else if (!result.ok) {
|
||||
body =
|
||||
result.error.code === "FORBIDDEN"
|
||||
? forbiddenState()
|
||||
: pageState(
|
||||
"error",
|
||||
"Configuration unavailable",
|
||||
`Request ${result.correlationId} could not be completed.`,
|
||||
);
|
||||
} else {
|
||||
const data = result.data;
|
||||
const empty =
|
||||
data.kind === "settings"
|
||||
? data.settings.length === 0
|
||||
: data.settings.length === 0 && data.texts.length === 0;
|
||||
if (empty) {
|
||||
body = pageState(
|
||||
"empty",
|
||||
"No configuration entries",
|
||||
"The selected configuration source returned no entries.",
|
||||
);
|
||||
} else {
|
||||
body = (
|
||||
<div
|
||||
data-housekeeping-state={
|
||||
data.partialDependencies.length > 0 ? "partial" : "ready"
|
||||
}
|
||||
className="space-y-4"
|
||||
>
|
||||
{data.partialDependencies.length > 0 ? (
|
||||
<HousekeepingPageState
|
||||
state="partial"
|
||||
partialLabel="Partial data"
|
||||
title="Some configuration data is unavailable"
|
||||
description={`Unavailable: ${data.partialDependencies.join(", ")}`}
|
||||
/>
|
||||
) : null}
|
||||
{configurationContent(data)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<HousekeepingPageShell
|
||||
title="System configuration"
|
||||
description="Review CMS and emulator configuration sources."
|
||||
>
|
||||
{body}
|
||||
</HousekeepingPageShell>
|
||||
);
|
||||
}
|
||||
|
||||
export async function renderSystemConfigurationPage(
|
||||
input: HousekeepingPageInput,
|
||||
) {
|
||||
const queryInput: SystemConfigurationQueryInput | null =
|
||||
input.match.routeId === "system.configuration.settings"
|
||||
? { routeId: "system.configuration.settings" }
|
||||
: input.match.routeId === "system.configuration.emulator"
|
||||
? { routeId: "system.configuration.emulator" }
|
||||
: null;
|
||||
const result = queryInput
|
||||
? await systemConfigurationQuery.run(input.context, queryInput)
|
||||
: fail("NOT_FOUND", "errors.housekeeping.notFound", createCorrelationId());
|
||||
return <SystemConfigurationPage result={result} />;
|
||||
}
|
||||
Reference in new issue
Block a user