/* ============================================================================ HOME - /en and /ar ---------------------------------------------------------------------------- Figma frame 253:4799 ("Home-en"), which is laid out as: Hero header (253:4800) headline + CTA group + wide picture Stats hidden in the frame, and every figure in the draft copy is marked TBD - so the band renders a localised "not yet confirmed" note and only shows numbers the CMS supplies Offical launch (361:10853) "The Journey of the SHARP-ER Launch", the launch story, the photo strip and the three captions under it Partners (393:14754) "Meet Our Partners" logo grid Footer owned by the app shell, not by this module The launch story is the approved copy from docs/SHARPER Home Page .docx in both languages; the pictures, the figures and the partner list come from Strapi and degrade to a localised empty state while it is empty. ========================================================================= */ import { Link } from "react-router"; import { ArrowIcon, Button } from "~/components/ui/Button"; import { Card } from "~/components/ui/Card"; import { createTranslator, getMessages, sectionPath } from "~/i18n"; import { buildMeta, buildOrganizationJsonLd, buildWebSiteJsonLd, } from "~/lib/seo"; import { safeHref } from "~/lib/safe-href"; import type { Route } from "./+types/home"; import { isImageGalleryBlock, type CmsHomePage, type CmsPartner, } from "./_page-kit/cms-types"; import { list, loadMany, loadSingle, toImage, toPartner, type ResolvedImage, } from "./_page-kit/cms.server"; import { pageCopy } from "./_page-kit/copy"; import { routeLocale } from "./_page-kit/locale"; import { EmptyState, PageHero, PageSection, SectionHeader, StatStrip, } from "./_page-kit/sections"; /** Partner logos and CSO tiles - a wide but finite grid. */ const PARTNER_PAGE_SIZE = 36; export async function loader({ params }: Route.LoaderArgs) { const locale = routeLocale(params); const page = await loadSingle("home-page", { locale, populate: { hero: { populate: { media: true, ctas: true } }, stats: true, // Strapi 5 populates a dynamic zone per component with `on`. blocks: { on: { "blocks.image-gallery": { populate: { images: true } } } }, partners: { populate: { logo: true, logoDark: true } }, seo: { populate: { ogImage: true } }, }, }); /* The curated relation wins; otherwise show every partner the CMS knows, so the grid fills in as soon as partners exist - before anyone remembers to curate the home page itself. */ const curated = list(page?.partners); const partners = curated.length > 0 ? curated : await loadMany("partners", { locale, sort: ["name:asc"], filters: { partnerRole: { $in: ["cso-partner", "institution"] } }, populate: { logo: true, logoDark: true }, pagination: { pageSize: PARTNER_PAGE_SIZE }, }); const gallery = list(page?.blocks) .filter(isImageGalleryBlock) .flatMap((block) => list(block.images)) .map((image) => toImage(image, "medium")) .filter((image): image is ResolvedImage => image !== undefined); 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, ctas: list(page?.hero?.ctas).map((cta) => ({ label: cta.label, href: cta.href, variant: cta.variant ?? "primary", })), }, stats: list(page?.stats).map((stat) => ({ value: stat.value, label: stat.label, })), gallery, partnersHeadline: page?.partnersHeadline ?? null, partners: partners.map(toPartner), 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 messages = getMessages(locale); return buildMeta({ locale, pathname: root.pathname, siteUrl: root.siteUrl, title: loaderData?.seo.title ?? messages.site.metaTitle, description: loaderData?.seo.description ?? messages.site.description, ...(loaderData?.seo.image ? { image: { url: loaderData.seo.image.src, alt: loaderData.seo.image.alt } } : {}), noIndex: loaderData?.seo.noIndex ?? false, // Emitted once for the whole site, from its home page. jsonLd: [ buildOrganizationJsonLd({ siteUrl: root.siteUrl, locale }), buildWebSiteJsonLd({ siteUrl: root.siteUrl, locale }), ], }); } export default function Home({ loaderData }: Route.ComponentProps) { const { locale, hero, stats, gallery, partners } = loaderData; const t = createTranslator(locale); const copy = pageCopy(locale).home; const headline = hero.headline ?? copy.heroHeadline; const lead = hero.subheading ?? t("site.description"); const partnersHeadline = loaderData.partnersHeadline ?? copy.partnersHeading; /* The three captions under the photo strip in Figma. Each one points at the section of the site that carries that strand of work forward, and every label is a catalogue string so /ar never inherits an English one. */ const mechanisms = [ { title: copy.mechanisms[0]!, href: sectionPath("whatWeDo", locale), actionLabel: t("actions.learnWhatWeDo"), }, { title: copy.mechanisms[1]!, href: sectionPath("activities", locale), actionLabel: t("actions.seeActivities"), }, { title: copy.mechanisms[2]!, href: sectionPath("applications", locale), actionLabel: t("actions.seeCurrentCalls"), }, ]; return ( <> 0 ? ( hero.ctas.map((cta) => ( )) ) : ( <> ) } /> {/* FIGURES - never invented here. The draft fact sheet marks them TBD. */}
{stats.length > 0 ? ( ) : ( )}
{/* THE LAUNCH STORY - approved copy, EN and AR. */}
{copy.launchParagraphs.map((paragraph) => (

{paragraph}

))}

{copy.fundingNote}

{/* Figma prints these three captions under the photo strip with no heading of their own; the heading exists for the outline only. */}

{copy.mechanismsHeading}

    {mechanisms.map((mechanism, index) => ( ))}
{/* PARTNERS */}
{partners.length > 0 ? (
    {partners.map((partner) => (
  • {partner.logo ? ( ) : null} {/* An editor-supplied URL: `safeHref` refuses anything that is not http/https/mailto/tel, and the plain-text branch below already renders the partner's name. */} {safeHref(partner.website) ? ( {partner.name} ) : ( {partner.name} )}
  • ))}
) : ( )}
); }