213 lines
6.7 KiB
TypeScript
213 lines
6.7 KiB
TypeScript
/* ============================================================================
|
|
WHAT WE DO - /en/what-we-do and /ar/what-we-do
|
|
----------------------------------------------------------------------------
|
|
Section 2 of docs/SHARPER_Website_Draft_Section_EN_AR.docx: the participatory
|
|
approach, the four connected areas of work, the values that guide them and
|
|
the four principles the approach rests on.
|
|
|
|
Overridden field by field by api::what-we-do-page.what-we-do-page; the draft
|
|
copy below is what renders until an editor fills that in.
|
|
========================================================================= */
|
|
|
|
import { Card } from "~/components/ui/Card";
|
|
import { createTranslator, formatNumber, sectionPath } from "~/i18n";
|
|
import { buildBreadcrumbJsonLd, buildMeta } from "~/lib/seo";
|
|
|
|
import type { Route } from "./+types/what-we-do";
|
|
import { isRichTextBlock, type CmsWhatWeDoPage } from "./_page-kit/cms-types";
|
|
import {
|
|
list,
|
|
loadSingle,
|
|
toImage,
|
|
toStringList,
|
|
} from "./_page-kit/cms.server";
|
|
import { pageCopy } from "./_page-kit/copy";
|
|
import { routeLocale } from "./_page-kit/locale";
|
|
import { RichText } from "./_page-kit/rich-text";
|
|
import {
|
|
InfoPanel,
|
|
PageHero,
|
|
PageSection,
|
|
Prose,
|
|
SectionHeader,
|
|
ValueChips,
|
|
} from "./_page-kit/sections";
|
|
|
|
export async function loader({ params }: Route.LoaderArgs) {
|
|
const locale = routeLocale(params);
|
|
|
|
const page = await loadSingle<CmsWhatWeDoPage>("what-we-do-page", {
|
|
locale,
|
|
populate: {
|
|
hero: { populate: { media: true, ctas: true } },
|
|
workstreams: { populate: { icon: true } },
|
|
principles: true,
|
|
blocks: { on: { "blocks.rich-text": { populate: "*" } } },
|
|
seo: { populate: { ogImage: true } },
|
|
},
|
|
});
|
|
|
|
return {
|
|
locale,
|
|
hero: {
|
|
eyebrow: page?.hero?.eyebrow ?? null,
|
|
headline: page?.hero?.headline ?? null,
|
|
subheading: page?.hero?.subheading ?? null,
|
|
media: toImage(page?.hero?.media, "large") ?? null,
|
|
},
|
|
intro: page?.intro ?? null,
|
|
richTextBlocks: list(page?.blocks)
|
|
.filter(isRichTextBlock)
|
|
.map((block) => ({ id: block.id, body: block.body })),
|
|
workstreams: list(page?.workstreams).map((workstream) => ({
|
|
title: workstream.title,
|
|
description: workstream.description ?? "",
|
|
icon: toImage(workstream.icon, "thumbnail") ?? null,
|
|
})),
|
|
principles: list(page?.principles).map((principle) => ({
|
|
title: principle.title,
|
|
description: principle.description ?? "",
|
|
})),
|
|
guidingValues: toStringList(page?.guidingValues),
|
|
seo: {
|
|
title: page?.seo?.metaTitle ?? null,
|
|
description: page?.seo?.metaDescription ?? null,
|
|
image: toImage(page?.seo?.ogImage, "large") ?? null,
|
|
noIndex: page?.seo?.noIndex ?? false,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function meta({ loaderData, matches }: Route.MetaArgs) {
|
|
const root = matches[0].loaderData;
|
|
const locale = loaderData?.locale ?? root.locale;
|
|
const t = createTranslator(locale);
|
|
const copy = pageCopy(locale).whatWeDo;
|
|
|
|
return buildMeta({
|
|
locale,
|
|
pathname: root.pathname,
|
|
siteUrl: root.siteUrl,
|
|
title: loaderData?.seo.title ?? t("nav.sections.whatWeDo"),
|
|
description: loaderData?.seo.description ?? copy.lead,
|
|
...(loaderData?.seo.image
|
|
? { image: { url: loaderData.seo.image.src, alt: loaderData.seo.image.alt } }
|
|
: {}),
|
|
noIndex: loaderData?.seo.noIndex ?? false,
|
|
jsonLd: buildBreadcrumbJsonLd(root.siteUrl, [
|
|
{ name: t("nav.sections.home"), pathname: sectionPath("home", locale) },
|
|
{
|
|
name: t("nav.sections.whatWeDo"),
|
|
pathname: sectionPath("whatWeDo", locale),
|
|
},
|
|
]),
|
|
});
|
|
}
|
|
|
|
export default function WhatWeDo({ loaderData }: Route.ComponentProps) {
|
|
const { locale, hero, intro, richTextBlocks } = loaderData;
|
|
const t = createTranslator(locale);
|
|
const copy = pageCopy(locale).whatWeDo;
|
|
|
|
const workstreams =
|
|
loaderData.workstreams.length > 0
|
|
? loaderData.workstreams
|
|
: copy.workstreams.map((workstream) => ({ ...workstream, icon: null }));
|
|
|
|
const values =
|
|
loaderData.guidingValues.length > 0 ? loaderData.guidingValues : copy.values;
|
|
|
|
return (
|
|
<>
|
|
<PageHero
|
|
title={hero.headline ?? copy.heroHeadline}
|
|
eyebrow={hero.eyebrow ?? t("nav.sections.aboutAndStrategy")}
|
|
lead={hero.subheading ?? copy.lead}
|
|
{...(hero.media ? { media: hero.media } : {})}
|
|
/>
|
|
|
|
{/* THE FOUR AREAS OF WORK */}
|
|
<PageSection
|
|
aria-labelledby="wwd-workstreams-heading"
|
|
className="pt-0 md:pt-0"
|
|
>
|
|
<div className="flex flex-col gap-2xl">
|
|
<SectionHeader
|
|
id="wwd-workstreams-heading"
|
|
title={copy.workstreamsHeading}
|
|
lead={intro ? undefined : copy.workstreamsIntro}
|
|
/>
|
|
|
|
{intro ? (
|
|
<Prose>
|
|
<RichText markdown={intro} />
|
|
</Prose>
|
|
) : null}
|
|
|
|
<ul className="grid list-none grid-cols-1 gap-lg md:grid-cols-2">
|
|
{workstreams.map((workstream, index) => (
|
|
<li key={workstream.title}>
|
|
<InfoPanel
|
|
title={workstream.title}
|
|
eyebrow={formatNumber(index + 1, locale, {
|
|
minimumIntegerDigits: 2,
|
|
})}
|
|
>
|
|
{workstream.description}
|
|
</InfoPanel>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
|
|
<ValueChips values={values} label={copy.valuesLabel} />
|
|
</div>
|
|
</PageSection>
|
|
|
|
{/* HOW WE WORK */}
|
|
<PageSection aria-labelledby="wwd-principles-heading">
|
|
<div className="flex flex-col gap-lg">
|
|
<SectionHeader
|
|
id="wwd-principles-heading"
|
|
title={copy.principlesHeading}
|
|
/>
|
|
|
|
{loaderData.principles.length > 0 ? (
|
|
<ul className="grid list-none grid-cols-1 gap-lg md:grid-cols-2">
|
|
{loaderData.principles.map((principle) => (
|
|
<li key={principle.title}>
|
|
<InfoPanel title={principle.title}>
|
|
{principle.description}
|
|
</InfoPanel>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<Prose>
|
|
<p className="body-md text-[var(--text-secondary)]">
|
|
{copy.principles}
|
|
</p>
|
|
</Prose>
|
|
)}
|
|
|
|
{richTextBlocks.map((block) => (
|
|
<Prose key={block.id}>
|
|
<RichText markdown={block.body} />
|
|
</Prose>
|
|
))}
|
|
</div>
|
|
</PageSection>
|
|
|
|
<PageSection>
|
|
<Card.CTA
|
|
headline={t("nav.sections.activities")}
|
|
body={copy.lead}
|
|
action={{
|
|
href: sectionPath("activities", locale),
|
|
label: t("actions.seeActivities"),
|
|
}}
|
|
/>
|
|
</PageSection>
|
|
</>
|
|
);
|
|
}
|