feat(housekeeping): localize content route headings
This commit is contained in:
1 parent
43470ebf82
commit
fc050bd3f8
14 files changed
+288
-30
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentBrandPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -118,7 +120,7 @@ export function ContentBrandPage({
|
||||
) : null;
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Brand"
|
||||
title={title ?? "Brand"}
|
||||
description="Manage theme, favicon, and logo assets."
|
||||
result={result}
|
||||
forms={forms}
|
||||
@@ -128,6 +130,7 @@ export function ContentBrandPage({
|
||||
|
||||
export async function renderContentBrandPage(input: HousekeepingPageInput) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Brand");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.brand.theme",
|
||||
params: input.match.params,
|
||||
@@ -135,6 +138,7 @@ export async function renderContentBrandPage(input: HousekeepingPageInput) {
|
||||
});
|
||||
return (
|
||||
<ContentBrandPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { HousekeepingResult } from "../../../foundation/contracts";
|
||||
import type { ContentQueryData } from "../queries/content-queries";
|
||||
|
||||
export interface ContentPageProps {
|
||||
readonly title?: string;
|
||||
readonly context: import("../../../foundation/contracts").HousekeepingCapabilityContext;
|
||||
readonly result?: HousekeepingResult<ContentQueryData>;
|
||||
readonly routeId: import("../routes").ContentRouteId | null;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { expect, it, vi } from "vitest";
|
||||
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
|
||||
import { ok } from "../../../foundation/contracts";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { renderContentBrandPage } from "./brand";
|
||||
import { renderContentEditorialPage } from "./editorial";
|
||||
import { renderContentEngagementPage } from "./engagement";
|
||||
import { renderContentHelpPage } from "./help";
|
||||
import { renderContentLocalizationPage } from "./localization";
|
||||
import { renderContentMediaPage } from "./media";
|
||||
|
||||
vi.mock("../queries/content-queries", () => ({
|
||||
contentQuery: { run: vi.fn() },
|
||||
}));
|
||||
|
||||
const context: HousekeepingCapabilityContext = {
|
||||
actor: { id: 42, username: "operator", rank: 7 },
|
||||
isSuperAdmin: false,
|
||||
has: () => true,
|
||||
hasAny: () => true,
|
||||
hasAll: () => true,
|
||||
};
|
||||
|
||||
const cases = [
|
||||
[
|
||||
"content.editorial.article-create",
|
||||
"/ase-next/content/editorial/articles/new",
|
||||
"editorial",
|
||||
"New article",
|
||||
renderContentEditorialPage,
|
||||
],
|
||||
[
|
||||
"content.media.ad-create",
|
||||
"/ase-next/content/media/ads/new",
|
||||
"media",
|
||||
"New advertisement",
|
||||
renderContentMediaPage,
|
||||
],
|
||||
[
|
||||
"content.engagement.event-create",
|
||||
"/ase-next/content/engagement/events/create",
|
||||
"engagement",
|
||||
"New event",
|
||||
renderContentEngagementPage,
|
||||
],
|
||||
[
|
||||
"content.help.question-create",
|
||||
"/ase-next/content/help/questions/new",
|
||||
"help",
|
||||
"New help article",
|
||||
renderContentHelpPage,
|
||||
],
|
||||
[
|
||||
"content.brand.theme-builder",
|
||||
"/ase-next/content/brand/theme-builder",
|
||||
"brand",
|
||||
"Theme builder",
|
||||
renderContentBrandPage,
|
||||
],
|
||||
[
|
||||
"content.localization.cms",
|
||||
"/ase-next/content/localization/cms",
|
||||
"localization",
|
||||
"CMS translations",
|
||||
renderContentLocalizationPage,
|
||||
],
|
||||
] as const;
|
||||
|
||||
it.each(cases)(
|
||||
"renders localized title for %s",
|
||||
async (routeId, canonicalHref, kind, title, renderPage) => {
|
||||
vi.mocked(contentQuery.run).mockResolvedValue(
|
||||
ok({ kind, items: [], total: 0, partialDependencies: [] }, routeId),
|
||||
);
|
||||
|
||||
const page = await renderPage({
|
||||
context,
|
||||
match: {
|
||||
routeId,
|
||||
domain: "content",
|
||||
params: {},
|
||||
canonicalHref,
|
||||
},
|
||||
translate: (key) => (key === `routes.${routeId}` ? title : key),
|
||||
});
|
||||
const html = renderToStaticMarkup(page);
|
||||
|
||||
expect(html).toContain(`<h1>${title}</h1>`);
|
||||
},
|
||||
);
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentEditorialPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -17,7 +19,7 @@ export function ContentEditorialPage({
|
||||
const canPages = context.has(PERMS.PAGES_EDIT);
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Editorial content"
|
||||
title={title ?? "Editorial content"}
|
||||
description="Manage articles, navigation, tags, and writable boxes."
|
||||
result={result}
|
||||
forms={
|
||||
@@ -96,6 +98,7 @@ export function ContentEditorialPage({
|
||||
|
||||
export async function renderContentEditorialPage(input: HousekeepingPageInput) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Editorial content");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.editorial.articles",
|
||||
params: input.match.params,
|
||||
@@ -103,6 +106,7 @@ export async function renderContentEditorialPage(input: HousekeepingPageInput) {
|
||||
});
|
||||
return (
|
||||
<ContentEditorialPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import {
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentEngagementPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -131,7 +133,7 @@ export function ContentEngagementPage({
|
||||
];
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Engagement"
|
||||
title={title ?? "Engagement"}
|
||||
description="Manage events, polls, and community prefixes."
|
||||
result={result}
|
||||
forms={
|
||||
@@ -414,6 +416,7 @@ export async function renderContentEngagementPage(
|
||||
input: HousekeepingPageInput,
|
||||
) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Engagement");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.engagement.events",
|
||||
params: input.match.params,
|
||||
@@ -421,6 +424,7 @@ export async function renderContentEngagementPage(
|
||||
});
|
||||
return (
|
||||
<ContentEngagementPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentHelpPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -72,7 +74,7 @@ export function ContentHelpPage({
|
||||
) : null;
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Help content"
|
||||
title={title ?? "Help content"}
|
||||
description="Manage help questions and email templates."
|
||||
result={result}
|
||||
forms={forms}
|
||||
@@ -82,6 +84,7 @@ export function ContentHelpPage({
|
||||
|
||||
export async function renderContentHelpPage(input: HousekeepingPageInput) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Help content");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.help.questions",
|
||||
params: input.match.params,
|
||||
@@ -89,6 +92,7 @@ export async function renderContentHelpPage(input: HousekeepingPageInput) {
|
||||
});
|
||||
return (
|
||||
<ContentHelpPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentLocalizationPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -91,7 +93,7 @@ export function ContentLocalizationPage({
|
||||
) : null;
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Localization"
|
||||
title={title ?? "Localization"}
|
||||
description="Manage CMS, client, and emulator translation stores."
|
||||
result={result}
|
||||
forms={forms}
|
||||
@@ -103,6 +105,7 @@ export async function renderContentLocalizationPage(
|
||||
input: HousekeepingPageInput,
|
||||
) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Localization");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.localization.overview",
|
||||
params: input.match.params,
|
||||
@@ -110,6 +113,7 @@ export async function renderContentLocalizationPage(
|
||||
});
|
||||
return (
|
||||
<ContentLocalizationPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentMediaPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -17,7 +19,7 @@ export function ContentMediaPage({
|
||||
const canBanners = context.has(PERMS.BANNERS_EDIT);
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Media"
|
||||
title={title ?? "Media"}
|
||||
description="Manage photos, uploaded media, banners, and advertisements."
|
||||
result={result}
|
||||
forms={
|
||||
@@ -112,6 +114,7 @@ export function ContentMediaPage({
|
||||
|
||||
export async function renderContentMediaPage(input: HousekeepingPageInput) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Media");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.media.photos",
|
||||
params: input.match.params,
|
||||
@@ -119,6 +122,7 @@ export async function renderContentMediaPage(input: HousekeepingPageInput) {
|
||||
});
|
||||
return (
|
||||
<ContentMediaPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
Reference in new issue
Block a user