initial commit
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
/* ============================================================================
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user